1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v4.media;
18
19import android.os.SystemClock;
20import android.view.KeyEvent;
21
22/**
23 * Implemented by the playback side of the media system, to respond to
24 * requests to perform actions and to retrieve its current state.  These
25 * requests may either come from key events dispatched directly to your UI, or
26 * events sent over a media button event receiver that this class keeps active
27 * while your window is in focus.
28 */
29public abstract class TransportPerformer {
30    /**
31     * Request to start playback on the media, resuming from whatever current state
32     * (position etc) it is in.
33     */
34    public abstract void onStart();
35
36    /**
37     * Request to pause playback of the media, staying at the current playback position
38     * and other state so a later call to {@link #onStart()} will resume at the same place.
39     */
40    public abstract void onPause();
41
42    /**
43     * Request to completely stop playback of the media, clearing whatever state the
44     * player thinks is appropriate.
45     */
46    public abstract void onStop();
47
48    /**
49     * Request to return the duration of the current media, in milliseconds.
50     */
51    public abstract long onGetDuration();
52
53    /**
54     * Request to return the current playback position, in milliseconds.
55     */
56    public abstract long onGetCurrentPosition();
57
58    /**
59     * Request to move the current playback position.
60     * @param pos New position to move to, in milliseconds.
61     */
62    public abstract void onSeekTo(long pos);
63
64    /**
65     * Request to find out whether the player is currently playing its media.
66     */
67    public abstract boolean onIsPlaying();
68
69    /**
70     * Request to find out how much of the media has been buffered on the local device.
71     * @return Return a percentage (0-100) indicating how much of the total data
72     * has been buffered.  The default implementation returns 100, meaning the content
73     * is always on the local device.
74     */
75    public int onGetBufferPercentage() {
76        return 100;
77    }
78
79    /**
80     * Retrieves the flags for the media transport control buttons that this transport supports.
81     * Result is a combination of the following flags:
82     *      {@link TransportMediator#FLAG_KEY_MEDIA_PREVIOUS},
83     *      {@link TransportMediator#FLAG_KEY_MEDIA_REWIND},
84     *      {@link TransportMediator#FLAG_KEY_MEDIA_PLAY},
85     *      {@link TransportMediator#FLAG_KEY_MEDIA_PLAY_PAUSE},
86     *      {@link TransportMediator#FLAG_KEY_MEDIA_PAUSE},
87     *      {@link TransportMediator#FLAG_KEY_MEDIA_STOP},
88     *      {@link TransportMediator#FLAG_KEY_MEDIA_FAST_FORWARD},
89     *      {@link TransportMediator#FLAG_KEY_MEDIA_NEXT}
90     *
91     * <p>The default implementation returns:
92     *      {@link TransportMediator#FLAG_KEY_MEDIA_PLAY},
93     *      {@link TransportMediator#FLAG_KEY_MEDIA_PLAY_PAUSE},
94     *      {@link TransportMediator#FLAG_KEY_MEDIA_PAUSE}, and
95     *      {@link TransportMediator#FLAG_KEY_MEDIA_STOP}</p>
96     */
97    public int onGetTransportControlFlags() {
98        return TransportMediator.FLAG_KEY_MEDIA_PLAY
99                | TransportMediator.FLAG_KEY_MEDIA_PLAY_PAUSE
100                | TransportMediator.FLAG_KEY_MEDIA_PAUSE
101                | TransportMediator.FLAG_KEY_MEDIA_STOP;
102    }
103
104    /**
105     * Report that a media button has been pressed.  This is like
106     * {@link android.view.KeyEvent.Callback#onKeyDown(int, android.view.KeyEvent)} but
107     * will only deliver media keys.  The default implementation handles these keys:
108     * <ul>
109     *     <li>KEYCODE_MEDIA_PLAY: call {@link #onStart}</li>
110     *     <li>KEYCODE_MEDIA_PAUSE: call {@link #onPause}</li>
111     *     <li>KEYCODE_MEDIA_STOP: call {@link #onStop}</li>
112     *     <li>KEYCODE_MEDIA_PLAY_PAUSE and KEYCODE_HEADSETHOOK: call {@link #onPause}
113     *          if {@link #onIsPlaying()} returns true, otherwise call {@link #onStart}</li>
114     * </ul>
115     * @param keyCode The code of the media key.
116     * @param event The full key event.
117     * @return Indicate whether the key has been consumed.  The default
118     * implementation always returns true.  This only matters for keys
119     * being dispatched here from
120     * {@link TransportMediator#dispatchKeyEvent(android.view.KeyEvent)
121     * TransportController.dispatchKeyEvent}, and determines whether the key
122     * continues on to its default key handling (which for media keys means
123     * being delivered to the current media remote control, which should
124     * be us).
125     */
126    public boolean onMediaButtonDown(int keyCode, KeyEvent event) {
127        switch (keyCode) {
128            case TransportMediator.KEYCODE_MEDIA_PLAY:
129                onStart();
130                return true;
131            case TransportMediator.KEYCODE_MEDIA_PAUSE:
132                onPause();
133                return true;
134            case KeyEvent.KEYCODE_MEDIA_STOP:
135                onStop();
136                return true;
137            case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
138            case KeyEvent.KEYCODE_HEADSETHOOK:
139                if (onIsPlaying()) {
140                    onPause();
141                } else {
142                    onStart();
143                }
144        }
145        return true;
146    }
147
148    /**
149     * Report that a media button has been released.  This is like
150     * {@link KeyEvent.Callback#onKeyUp(int, android.view.KeyEvent)} but
151     * will only deliver media keys.  The default implementation does nothing.
152     * @param keyCode The code of the media key.
153     * @param event The full key event.
154     * @return Indicate whether the key has been consumed.  The default
155     * implementation always returns true.  This only matters for keys
156     * being dispatched here from
157     * {@link TransportMediator#dispatchKeyEvent(android.view.KeyEvent)
158     * TransportController.dispatchKeyEvent}, and determines whether the key
159     * continues on to its default key handling (which for media keys means
160     * being delivered to the current media remote control, which should
161     * be us).
162     */
163    public boolean onMediaButtonUp(int keyCode, KeyEvent event) {
164        return true;
165    }
166
167    // Copy constants from framework since we can't link to them.
168    static final int AUDIOFOCUS_GAIN = 1;
169    static final int AUDIOFOCUS_GAIN_TRANSIENT = 2;
170    static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3;
171    static final int AUDIOFOCUS_LOSS = -1 * AUDIOFOCUS_GAIN;
172    static final int AUDIOFOCUS_LOSS_TRANSIENT = -1 * AUDIOFOCUS_GAIN_TRANSIENT;
173    static final int AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK =
174            -1 * AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
175
176    /**
177     * Report that audio focus has changed on the app.  This only happens if
178     * you have indicated you have started playing with
179     * {@link TransportMediator#startPlaying TransportController.startPlaying},
180     * which takes audio focus for you.
181     * @param focusChange The type of focus change, as per
182     * {@link android.media.AudioManager.OnAudioFocusChangeListener#onAudioFocusChange(int)
183     * OnAudioFocusChangeListener.onAudioFocusChange}.  The default implementation will
184     * deliver a {@link KeyEvent#KEYCODE_MEDIA_STOP}
185     * when receiving {@link android.media.AudioManager#AUDIOFOCUS_LOSS}.
186     */
187    public void onAudioFocusChange(int focusChange) {
188        int keyCode = 0;
189        switch (focusChange) {
190            case AUDIOFOCUS_LOSS:
191                // This will cause us to stop playback, which means we drop audio focus
192                // so we will not get any further audio focus gain.
193                keyCode = TransportMediator.KEYCODE_MEDIA_PAUSE;
194                break;
195        }
196        if (keyCode != 0) {
197            final long now = SystemClock.uptimeMillis();
198            onMediaButtonDown(keyCode, new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0));
199            onMediaButtonUp(keyCode, new KeyEvent(now, now, KeyEvent.ACTION_UP, keyCode, 0));
200        }
201    }
202}
203