blob: d24f44f8bd8b89ffdcab3560fbe5b4431ea4c940 [file] [log] [blame]
/*
* Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.mediasession.service.players;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.media.MediaPlayer;
import android.os.SystemClock;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.util.Log;
import com.example.android.mediasession.service.PlaybackInfoListener;
import com.example.android.mediasession.service.PlayerAdapter;
import com.example.android.mediasession.service.contentcatalogs.MusicLibrary;
import com.example.android.mediasession.ui.MainActivity;
/**
* Exposes the functionality of the {@link MediaPlayer} and implements the {@link PlayerAdapter}
* so that {@link MainActivity} can control music playback.
*/
public final class MediaPlayerAdapter extends PlayerAdapter {
private final Context mContext;
private MediaPlayer mMediaPlayer;
private String mFilename;
private PlaybackInfoListener mPlaybackInfoListener;
private MediaMetadataCompat mCurrentMedia;
private int mState;
private boolean mCurrentMediaPlayedToCompletion;
// Work-around for a MediaPlayer bug related to the behavior of MediaPlayer.seekTo()
// while not playing.
private int mSeekWhileNotPlaying = -1;
public MediaPlayerAdapter(Context context, PlaybackInfoListener listener) {
super(context);
mContext = context.getApplicationContext();
mPlaybackInfoListener = listener;
}
/**
* Once the {@link MediaPlayer} is released, it can't be used again, and another one has to be
* created. In the onStop() method of the {@link MainActivity} the {@link MediaPlayer} is
* released. Then in the onStart() of the {@link MainActivity} a new {@link MediaPlayer}
* object has to be created. That's why this method is private, and called by load(int) and
* not the constructor.
*/
private void initializeMediaPlayer() {
if (mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
mPlaybackInfoListener.onPlaybackCompleted();
// Set the state to "paused" because it most closely matches the state
// in MediaPlayer with regards to available state transitions compared
// to "stop".
// Paused allows: seekTo(), start(), pause(), stop()
// Stop allows: stop()
setNewState(PlaybackStateCompat.STATE_PAUSED);
}
});
}
}
// Implements PlaybackControl.
@Override
public void playFromMedia(MediaMetadataCompat metadata) {
mCurrentMedia = metadata;
final String mediaId = metadata.getDescription().getMediaId();
playFile(MusicLibrary.getMusicFilename(mediaId));
}
@Override
public MediaMetadataCompat getCurrentMedia() {
return mCurrentMedia;
}
private void playFile(String filename) {
boolean mediaChanged = (mFilename == null || !filename.equals(mFilename));
if (mCurrentMediaPlayedToCompletion) {
// Last audio file was played to completion, the resourceId hasn't changed, but the
// player was released, so force a reload of the media file for playback.
mediaChanged = true;
mCurrentMediaPlayedToCompletion = false;
}
if (!mediaChanged) {
if (!isPlaying()) {
play();
}
return;
} else {
release();
}
mFilename = filename;
initializeMediaPlayer();
try {
AssetFileDescriptor assetFileDescriptor = mContext.getAssets().openFd(mFilename);
mMediaPlayer.setDataSource(
assetFileDescriptor.getFileDescriptor(),
assetFileDescriptor.getStartOffset(),
assetFileDescriptor.getLength());
} catch (Exception e) {
throw new RuntimeException("Failed to open file: " + mFilename, e);
}
try {
mMediaPlayer.prepare();
} catch (Exception e) {
throw new RuntimeException("Failed to open file: " + mFilename, e);
}
play();
}
@Override
public void onStop() {
// Regardless of whether or not the MediaPlayer has been created / started, the state must
// be updated, so that MediaNotificationManager can take down the notification.
setNewState(PlaybackStateCompat.STATE_STOPPED);
release();
}
private void release() {
if (mMediaPlayer != null) {
mMediaPlayer.release();
mMediaPlayer = null;
}
}
@Override
public boolean isPlaying() {
return mMediaPlayer != null && mMediaPlayer.isPlaying();
}
@Override
protected void onPlay() {
if (mMediaPlayer != null && !mMediaPlayer.isPlaying()) {
mMediaPlayer.start();
setNewState(PlaybackStateCompat.STATE_PLAYING);
}
}
@Override
protected void onPause() {
if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
mMediaPlayer.pause();
setNewState(PlaybackStateCompat.STATE_PAUSED);
}
}
// This is the main reducer for the player state machine.
private void setNewState(@PlaybackStateCompat.State int newPlayerState) {
mState = newPlayerState;
// Whether playback goes to completion, or whether it is stopped, the
// mCurrentMediaPlayedToCompletion is set to true.
if (mState == PlaybackStateCompat.STATE_STOPPED) {
mCurrentMediaPlayedToCompletion = true;
}
// Work around for MediaPlayer.getCurrentPosition() when it changes while not playing.
final long reportPosition;
if (mSeekWhileNotPlaying >= 0) {
reportPosition = mSeekWhileNotPlaying;
if (mState == PlaybackStateCompat.STATE_PLAYING) {
mSeekWhileNotPlaying = -1;
}
} else {
reportPosition = mMediaPlayer == null ? 0 : mMediaPlayer.getCurrentPosition();
}
final PlaybackStateCompat.Builder stateBuilder = new PlaybackStateCompat.Builder();
stateBuilder.setActions(getAvailableActions());
stateBuilder.setState(mState,
reportPosition,
1.0f,
SystemClock.elapsedRealtime());
mPlaybackInfoListener.onPlaybackStateChange(stateBuilder.build());
}
/**
* Set the current capabilities available on this session. Note: If a capability is not
* listed in the bitmask of capabilities then the MediaSession will not handle it. For
* example, if you don't want ACTION_STOP to be handled by the MediaSession, then don't
* included it in the bitmask that's returned.
*/
@PlaybackStateCompat.Actions
private long getAvailableActions() {
long actions = PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID
| PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH
| PlaybackStateCompat.ACTION_SKIP_TO_NEXT
| PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
switch (mState) {
case PlaybackStateCompat.STATE_STOPPED:
actions |= PlaybackStateCompat.ACTION_PLAY
| PlaybackStateCompat.ACTION_PAUSE;
break;
case PlaybackStateCompat.STATE_PLAYING:
actions |= PlaybackStateCompat.ACTION_STOP
| PlaybackStateCompat.ACTION_PAUSE
| PlaybackStateCompat.ACTION_SEEK_TO;
break;
case PlaybackStateCompat.STATE_PAUSED:
actions |= PlaybackStateCompat.ACTION_PLAY
| PlaybackStateCompat.ACTION_STOP;
break;
default:
actions |= PlaybackStateCompat.ACTION_PLAY
| PlaybackStateCompat.ACTION_PLAY_PAUSE
| PlaybackStateCompat.ACTION_STOP
| PlaybackStateCompat.ACTION_PAUSE;
}
return actions;
}
@Override
public void seekTo(long position) {
if (mMediaPlayer != null) {
if (!mMediaPlayer.isPlaying()) {
mSeekWhileNotPlaying = (int) position;
}
mMediaPlayer.seekTo((int) position);
// Set the state (to the current state) because the position changed and should
// be reported to clients.
setNewState(mState);
}
}
@Override
public void setVolume(float volume) {
if (mMediaPlayer != null) {
mMediaPlayer.setVolume(volume, volume);
}
}
}