Skip to content

Most visited

Recently visited

navigation
MediaBrowserService / src / com.example.android.mediabrowserservice /

Playback.java

1
/*
2
 * Copyright (C) 2014 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
package com.example.android.mediabrowserservice;
17
 
18
import android.content.BroadcastReceiver;
19
import android.content.Context;
20
import android.content.Intent;
21
import android.content.IntentFilter;
22
import android.media.AudioManager;
23
import android.media.MediaMetadata;
24
import android.media.MediaPlayer;
25
import android.media.session.PlaybackState;
26
import android.net.wifi.WifiManager;
27
import android.os.PowerManager;
28
import android.text.TextUtils;
29
 
30
import com.example.android.mediabrowserservice.model.MusicProvider;
31
import com.example.android.mediabrowserservice.utils.LogHelper;
32
import com.example.android.mediabrowserservice.utils.MediaIDHelper;
33
 
34
import java.io.IOException;
35
 
36
import static android.media.MediaPlayer.OnCompletionListener;
37
import static android.media.MediaPlayer.OnErrorListener;
38
import static android.media.MediaPlayer.OnPreparedListener;
39
import static android.media.MediaPlayer.OnSeekCompleteListener;
40
import static android.media.session.MediaSession.QueueItem;
41
 
42
/**
43
 * A class that implements local media playback using {@link android.media.MediaPlayer}
44
 */
45
public class Playback implements AudioManager.OnAudioFocusChangeListener,
46
        OnCompletionListener, OnErrorListener, OnPreparedListener, OnSeekCompleteListener {
47
 
48
    private static final String TAG = LogHelper.makeLogTag(Playback.class);
49
 
50
    // The volume we set the media player to when we lose audio focus, but are
51
    // allowed to reduce the volume instead of stopping playback.
52
    public static final float VOLUME_DUCK = 0.2f;
53
    // The volume we set the media player when we have audio focus.
54
    public static final float VOLUME_NORMAL = 1.0f;
55
 
56
    // we don't have audio focus, and can't duck (play at a low volume)
57
    private static final int AUDIO_NO_FOCUS_NO_DUCK = 0;
58
    // we don't have focus, but can duck (play at a low volume)
59
    private static final int AUDIO_NO_FOCUS_CAN_DUCK = 1;
60
    // we have full audio focus
61
    private static final int AUDIO_FOCUSED  = 2;
62
 
63
    private final MusicService mService;
64
    private final WifiManager.WifiLock mWifiLock;
65
    private int mState;
66
    private boolean mPlayOnFocusGain;
67
    private Callback mCallback;
68
    private MusicProvider mMusicProvider;
69
    private volatile boolean mAudioNoisyReceiverRegistered;
70
    private volatile int mCurrentPosition;
71
    private volatile String mCurrentMediaId;
72
 
73
    // Type of audio focus we have:
74
    private int mAudioFocus = AUDIO_NO_FOCUS_NO_DUCK;
75
    private AudioManager mAudioManager;
76
    private MediaPlayer mMediaPlayer;
77
 
78
    private IntentFilter mAudioNoisyIntentFilter =
79
            new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
80
 
81
    private BroadcastReceiver mAudioNoisyReceiver = new BroadcastReceiver() {
82
        @Override
83
        public void onReceive(Context context, Intent intent) {
84
            if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
85
                LogHelper.d(TAG, "Headphones disconnected.");
86
                if (isPlaying()) {
87
                    Intent i = new Intent(context, MusicService.class);
88
                    i.setAction(MusicService.ACTION_CMD);
89
                    i.putExtra(MusicService.CMD_NAME, MusicService.CMD_PAUSE);
90
                    mService.startService(i);
91
                }
92
            }
93
        }
94
    };
95
 
96
    public Playback(MusicService service, MusicProvider musicProvider) {
97
        this.mService = service;
98
        this.mMusicProvider = musicProvider;
99
        this.mAudioManager = (AudioManager) service.getSystemService(Context.AUDIO_SERVICE);
100
        // Create the Wifi lock (this does not acquire the lock, this just creates it)
101
        this.mWifiLock = ((WifiManager) service.getSystemService(Context.WIFI_SERVICE))
102
                .createWifiLock(WifiManager.WIFI_MODE_FULL, "sample_lock");
103
    }
104
 
105
    public void start() {
106
    }
107
 
108
    public void stop(boolean notifyListeners) {
109
        mState = PlaybackState.STATE_STOPPED;
110
        if (notifyListeners && mCallback != null) {
111
            mCallback.onPlaybackStatusChanged(mState);
112
        }
113
        mCurrentPosition = getCurrentStreamPosition();
114
        // Give up Audio focus
115
        giveUpAudioFocus();
116
        unregisterAudioNoisyReceiver();
117
        // Relax all resources
118
        relaxResources(true);
119
        if (mWifiLock.isHeld()) {
120
            mWifiLock.release();
121
        }
122
    }
123
 
124
    public void setState(int state) {
125
        this.mState = state;
126
    }
127
 
128
    public int getState() {
129
        return mState;
130
    }
131
 
132
    public boolean isConnected() {
133
        return true;
134
    }
135
 
136
    public boolean isPlaying() {
137
        return mPlayOnFocusGain || (mMediaPlayer != null && mMediaPlayer.isPlaying());
138
    }
139
 
140
    public int getCurrentStreamPosition() {
141
        return mMediaPlayer != null ?
142
                mMediaPlayer.getCurrentPosition() : mCurrentPosition;
143
    }
144
 
145
    public void play(QueueItem item) {
146
        mPlayOnFocusGain = true;
147
        tryToGetAudioFocus();
148
        registerAudioNoisyReceiver();
149
        String mediaId = item.getDescription().getMediaId();
150
        boolean mediaHasChanged = !TextUtils.equals(mediaId, mCurrentMediaId);
151
        if (mediaHasChanged) {
152
            mCurrentPosition = 0;
153
            mCurrentMediaId = mediaId;
154
        }
155
 
156
        if (mState == PlaybackState.STATE_PAUSED && !mediaHasChanged && mMediaPlayer != null) {
157
            configMediaPlayerState();
158
        } else {
159
            mState = PlaybackState.STATE_STOPPED;
160
            relaxResources(false); // release everything except MediaPlayer
161
            MediaMetadata track = mMusicProvider.getMusic(
162
                    MediaIDHelper.extractMusicIDFromMediaID(item.getDescription().getMediaId()));
163
 
164
            String source = track.getString(MusicProvider.CUSTOM_METADATA_TRACK_SOURCE);
165
 
166
            try {
167
                createMediaPlayerIfNeeded();
168
 
169
                mState = PlaybackState.STATE_BUFFERING;
170
 
171
                mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
172
                mMediaPlayer.setDataSource(source);
173
 
174
                // Starts preparing the media player in the background. When
175
                // it's done, it will call our OnPreparedListener (that is,
176
                // the onPrepared() method on this class, since we set the
177
                // listener to 'this'). Until the media player is prepared,
178
                // we *cannot* call start() on it!
179
                mMediaPlayer.prepareAsync();
180
 
181
                // If we are streaming from the internet, we want to hold a
182
                // Wifi lock, which prevents the Wifi radio from going to
183
                // sleep while the song is playing.
184
                mWifiLock.acquire();
185
 
186
                if (mCallback != null) {
187
                    mCallback.onPlaybackStatusChanged(mState);
188
                }
189
 
190
            } catch (IOException ex) {
191
                LogHelper.e(TAG, ex, "Exception playing song");
192
                if (mCallback != null) {
193
                    mCallback.onError(ex.getMessage());
194
                }
195
            }
196
        }
197
    }
198
 
199
    public void pause() {
200
        if (mState == PlaybackState.STATE_PLAYING) {
201
            // Pause media player and cancel the 'foreground service' state.
202
            if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
203
                mMediaPlayer.pause();
204
                mCurrentPosition = mMediaPlayer.getCurrentPosition();
205
            }
206
            // while paused, retain the MediaPlayer but give up audio focus
207
            relaxResources(false);
208
            giveUpAudioFocus();
209
        }
210
        mState = PlaybackState.STATE_PAUSED;
211
        if (mCallback != null) {
212
            mCallback.onPlaybackStatusChanged(mState);
213
        }
214
        unregisterAudioNoisyReceiver();
215
    }
216
 
217
    public void seekTo(int position) {
218
        LogHelper.d(TAG, "seekTo called with ", position);
219
 
220
        if (mMediaPlayer == null) {
221
            // If we do not have a current media player, simply update the current position
222
            mCurrentPosition = position;
223
        } else {
224
            if (mMediaPlayer.isPlaying()) {
225
                mState = PlaybackState.STATE_BUFFERING;
226
            }
227
            mMediaPlayer.seekTo(position);
228
            if (mCallback != null) {
229
                mCallback.onPlaybackStatusChanged(mState);
230
            }
231
        }
232
    }
233
 
234
    public void setCallback(Callback callback) {
235
        this.mCallback = callback;
236
    }
237
 
238
    /**
239
     * Try to get the system audio focus.
240
     */
241
    private void tryToGetAudioFocus() {
242
        LogHelper.d(TAG, "tryToGetAudioFocus");
243
        if (mAudioFocus != AUDIO_FOCUSED) {
244
            int result = mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
245
                    AudioManager.AUDIOFOCUS_GAIN);
246
            if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
247
                mAudioFocus = AUDIO_FOCUSED;
248
            }
249
        }
250
    }
251
 
252
    /**
253
     * Give up the audio focus.
254
     */
255
    private void giveUpAudioFocus() {
256
        LogHelper.d(TAG, "giveUpAudioFocus");
257
        if (mAudioFocus == AUDIO_FOCUSED) {
258
            if (mAudioManager.abandonAudioFocus(this) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
259
                mAudioFocus = AUDIO_NO_FOCUS_NO_DUCK;
260
            }
261
        }
262
    }
263
 
264
    /**
265
     * Reconfigures MediaPlayer according to audio focus settings and
266
     * starts/restarts it. This method starts/restarts the MediaPlayer
267
     * respecting the current audio focus state. So if we have focus, it will
268
     * play normally; if we don't have focus, it will either leave the
269
     * MediaPlayer paused or set it to a low volume, depending on what is
270
     * allowed by the current focus settings. This method assumes mPlayer !=
271
     * null, so if you are calling it, you have to do so from a context where
272
     * you are sure this is the case.
273
     */
274
    private void configMediaPlayerState() {
275
        LogHelper.d(TAG, "configMediaPlayerState. mAudioFocus=", mAudioFocus);
276
        if (mAudioFocus == AUDIO_NO_FOCUS_NO_DUCK) {
277
            // If we don't have audio focus and can't duck, we have to pause,
278
            if (mState == PlaybackState.STATE_PLAYING) {
279
                pause();
280
            }
281
        } else {  // we have audio focus:
282
            if (mAudioFocus == AUDIO_NO_FOCUS_CAN_DUCK) {
283
                mMediaPlayer.setVolume(VOLUME_DUCK, VOLUME_DUCK); // we'll be relatively quiet
284
            } else {
285
                if (mMediaPlayer != null) {
286
                    mMediaPlayer.setVolume(VOLUME_NORMAL, VOLUME_NORMAL); // we can be loud again
287
                } // else do something for remote client.
288
            }
289
            // If we were playing when we lost focus, we need to resume playing.
290
            if (mPlayOnFocusGain) {
291
                if (mMediaPlayer != null && !mMediaPlayer.isPlaying()) {
292
                    LogHelper.d(TAG,"configMediaPlayerState startMediaPlayer. seeking to ",
293
                        mCurrentPosition);
294
                    if (mCurrentPosition == mMediaPlayer.getCurrentPosition()) {
295
                        mMediaPlayer.start();
296
                        mState = PlaybackState.STATE_PLAYING;
297
                    } else {
298
                        mMediaPlayer.seekTo(mCurrentPosition);
299
                        mState = PlaybackState.STATE_BUFFERING;
300
                    }
301
                }
302
                mPlayOnFocusGain = false;
303
            }
304
        }
305
        if (mCallback != null) {
306
            mCallback.onPlaybackStatusChanged(mState);
307
        }
308
    }
309
 
310
    /**
311
     * Called by AudioManager on audio focus changes.
312
     * Implementation of {@link android.media.AudioManager.OnAudioFocusChangeListener}
313
     */
314
    @Override
315
    public void onAudioFocusChange(int focusChange) {
316
        LogHelper.d(TAG, "onAudioFocusChange. focusChange=", focusChange);
317
        if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
318
            // We have gained focus:
319
            mAudio