Home | History | Annotate | Download | only in players
      1 /*
      2  * Copyright 2017 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 
     17 package com.example.android.mediasession.service.players;
     18 
     19 import android.content.Context;
     20 import android.content.res.AssetFileDescriptor;
     21 import android.media.MediaPlayer;
     22 import android.os.SystemClock;
     23 import android.support.v4.media.MediaMetadataCompat;
     24 import android.support.v4.media.session.PlaybackStateCompat;
     25 import android.util.Log;
     26 
     27 import com.example.android.mediasession.service.PlaybackInfoListener;
     28 import com.example.android.mediasession.service.PlayerAdapter;
     29 import com.example.android.mediasession.service.contentcatalogs.MusicLibrary;
     30 import com.example.android.mediasession.ui.MainActivity;
     31 
     32 /**
     33  * Exposes the functionality of the {@link MediaPlayer} and implements the {@link PlayerAdapter}
     34  * so that {@link MainActivity} can control music playback.
     35  */
     36 public final class MediaPlayerAdapter extends PlayerAdapter {
     37 
     38     private final Context mContext;
     39     private MediaPlayer mMediaPlayer;
     40     private String mFilename;
     41     private PlaybackInfoListener mPlaybackInfoListener;
     42     private MediaMetadataCompat mCurrentMedia;
     43     private int mState;
     44     private boolean mCurrentMediaPlayedToCompletion;
     45 
     46     // Work-around for a MediaPlayer bug related to the behavior of MediaPlayer.seekTo()
     47     // while not playing.
     48     private int mSeekWhileNotPlaying = -1;
     49 
     50     public MediaPlayerAdapter(Context context, PlaybackInfoListener listener) {
     51         super(context);
     52         mContext = context.getApplicationContext();
     53         mPlaybackInfoListener = listener;
     54     }
     55 
     56     /**
     57      * Once the {@link MediaPlayer} is released, it can't be used again, and another one has to be
     58      * created. In the onStop() method of the {@link MainActivity} the {@link MediaPlayer} is
     59      * released. Then in the onStart() of the {@link MainActivity} a new {@link MediaPlayer}
     60      * object has to be created. That's why this method is private, and called by load(int) and
     61      * not the constructor.
     62      */
     63     private void initializeMediaPlayer() {
     64         if (mMediaPlayer == null) {
     65             mMediaPlayer = new MediaPlayer();
     66             mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
     67                 @Override
     68                 public void onCompletion(MediaPlayer mediaPlayer) {
     69                     mPlaybackInfoListener.onPlaybackCompleted();
     70 
     71                     // Set the state to "paused" because it most closely matches the state
     72                     // in MediaPlayer with regards to available state transitions compared
     73                     // to "stop".
     74                     // Paused allows: seekTo(), start(), pause(), stop()
     75                     // Stop allows: stop()
     76                     setNewState(PlaybackStateCompat.STATE_PAUSED);
     77                 }
     78             });
     79         }
     80     }
     81 
     82     // Implements PlaybackControl.
     83     @Override
     84     public void playFromMedia(MediaMetadataCompat metadata) {
     85         mCurrentMedia = metadata;
     86         final String mediaId = metadata.getDescription().getMediaId();
     87         playFile(MusicLibrary.getMusicFilename(mediaId));
     88     }
     89 
     90     @Override
     91     public MediaMetadataCompat getCurrentMedia() {
     92         return mCurrentMedia;
     93     }
     94 
     95     private void playFile(String filename) {
     96         boolean mediaChanged = (mFilename == null || !filename.equals(mFilename));
     97         if (mCurrentMediaPlayedToCompletion) {
     98             // Last audio file was played to completion, the resourceId hasn't changed, but the
     99             // player was released, so force a reload of the media file for playback.
    100             mediaChanged = true;
    101             mCurrentMediaPlayedToCompletion = false;
    102         }
    103         if (!mediaChanged) {
    104             if (!isPlaying()) {
    105                 play();
    106             }
    107             return;
    108         } else {
    109             release();
    110         }
    111 
    112         mFilename = filename;
    113 
    114         initializeMediaPlayer();
    115 
    116         try {
    117             AssetFileDescriptor assetFileDescriptor = mContext.getAssets().openFd(mFilename);
    118             mMediaPlayer.setDataSource(
    119                     assetFileDescriptor.getFileDescriptor(),
    120                     assetFileDescriptor.getStartOffset(),
    121                     assetFileDescriptor.getLength());
    122         } catch (Exception e) {
    123             throw new RuntimeException("Failed to open file: " + mFilename, e);
    124         }
    125 
    126         try {
    127             mMediaPlayer.prepare();
    128         } catch (Exception e) {
    129             throw new RuntimeException("Failed to open file: " + mFilename, e);
    130         }
    131 
    132         play();
    133     }
    134 
    135     @Override
    136     public void onStop() {
    137         // Regardless of whether or not the MediaPlayer has been created / started, the state must
    138         // be updated, so that MediaNotificationManager can take down the notification.
    139         setNewState(PlaybackStateCompat.STATE_STOPPED);
    140         release();
    141     }
    142 
    143     private void release() {
    144         if (mMediaPlayer != null) {
    145             mMediaPlayer.release();
    146             mMediaPlayer = null;
    147         }
    148     }
    149 
    150     @Override
    151     public boolean isPlaying() {
    152         return mMediaPlayer != null && mMediaPlayer.isPlaying();
    153     }
    154 
    155     @Override
    156     protected void onPlay() {
    157         if (mMediaPlayer != null && !mMediaPlayer.isPlaying()) {
    158             mMediaPlayer.start();
    159             setNewState(PlaybackStateCompat.STATE_PLAYING);
    160         }
    161     }
    162 
    163     @Override
    164     protected void onPause() {
    165         if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
    166             mMediaPlayer.pause();
    167             setNewState(PlaybackStateCompat.STATE_PAUSED);
    168         }
    169     }
    170 
    171     // This is the main reducer for the player state machine.
    172     private void setNewState(@PlaybackStateCompat.State int newPlayerState) {
    173         mState = newPlayerState;
    174 
    175         // Whether playback goes to completion, or whether it is stopped, the
    176         // mCurrentMediaPlayedToCompletion is set to true.
    177         if (mState == PlaybackStateCompat.STATE_STOPPED) {
    178             mCurrentMediaPlayedToCompletion = true;
    179         }
    180 
    181         // Work around for MediaPlayer.getCurrentPosition() when it changes while not playing.
    182         final long reportPosition;
    183         if (mSeekWhileNotPlaying >= 0) {
    184             reportPosition = mSeekWhileNotPlaying;
    185 
    186             if (mState == PlaybackStateCompat.STATE_PLAYING) {
    187                 mSeekWhileNotPlaying = -1;
    188             }
    189         } else {
    190             reportPosition = mMediaPlayer == null ? 0 : mMediaPlayer.getCurrentPosition();
    191         }
    192 
    193         final PlaybackStateCompat.Builder stateBuilder = new PlaybackStateCompat.Builder();
    194         stateBuilder.setActions(getAvailableActions());
    195         stateBuilder.setState(mState,
    196                               reportPosition,
    197                               1.0f,
    198                               SystemClock.elapsedRealtime());
    199         mPlaybackInfoListener.onPlaybackStateChange(stateBuilder.build());
    200     }
    201 
    202     /**
    203      * Set the current capabilities available on this session. Note: If a capability is not
    204      * listed in the bitmask of capabilities then the MediaSession will not handle it. For
    205      * example, if you don't want ACTION_STOP to be handled by the MediaSession, then don't
    206      * included it in the bitmask that's returned.
    207      */
    208     @PlaybackStateCompat.Actions
    209     private long getAvailableActions() {
    210         long actions = PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID
    211                        | PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH
    212                        | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
    213                        | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
    214         switch (mState) {
    215             case PlaybackStateCompat.STATE_STOPPED:
    216                 actions |= PlaybackStateCompat.ACTION_PLAY
    217                            | PlaybackStateCompat.ACTION_PAUSE;
    218                 break;
    219             case PlaybackStateCompat.STATE_PLAYING:
    220                 actions |= PlaybackStateCompat.ACTION_STOP
    221                            | PlaybackStateCompat.ACTION_PAUSE
    222                            | PlaybackStateCompat.ACTION_SEEK_TO;
    223                 break;
    224             case PlaybackStateCompat.STATE_PAUSED:
    225                 actions |= PlaybackStateCompat.ACTION_PLAY
    226                            | PlaybackStateCompat.ACTION_STOP;
    227                 break;
    228             default:
    229                 actions |= PlaybackStateCompat.ACTION_PLAY
    230                            | PlaybackStateCompat.ACTION_PLAY_PAUSE
    231                            | PlaybackStateCompat.ACTION_STOP
    232                            | PlaybackStateCompat.ACTION_PAUSE;
    233         }
    234         return actions;
    235     }
    236 
    237     @Override
    238     public void seekTo(long position) {
    239         if (mMediaPlayer != null) {
    240             if (!mMediaPlayer.isPlaying()) {
    241                 mSeekWhileNotPlaying = (int) position;
    242             }
    243             mMediaPlayer.seekTo((int) position);
    244 
    245             // Set the state (to the current state) because the position changed and should
    246             // be reported to clients.
    247             setNewState(mState);
    248         }
    249     }
    250 
    251     @Override
    252     public void setVolume(float volume) {
    253         if (mMediaPlayer != null) {
    254             mMediaPlayer.setVolume(volume, volume);
    255         }
    256     }
    257 }
    258