| /* |
| * 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); |
| } |
| } |
| } |