Home | History | Annotate | Download | only in media
      1 /*
      2  * Copyright (C) 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 androidx.leanback.media;
     18 
     19 import static androidx.leanback.media.PlaybackBaseControlGlue.ACTION_FAST_FORWARD;
     20 import static androidx.leanback.media.PlaybackBaseControlGlue.ACTION_PLAY_PAUSE;
     21 import static androidx.leanback.media.PlaybackBaseControlGlue.ACTION_REPEAT;
     22 import static androidx.leanback.media.PlaybackBaseControlGlue.ACTION_REWIND;
     23 import static androidx.leanback.media.PlaybackBaseControlGlue.ACTION_SHUFFLE;
     24 import static androidx.leanback.media.PlaybackBaseControlGlue.ACTION_SKIP_TO_NEXT;
     25 import static androidx.leanback.media.PlaybackBaseControlGlue.ACTION_SKIP_TO_PREVIOUS;
     26 
     27 import android.content.Context;
     28 import android.graphics.Bitmap;
     29 import android.graphics.drawable.BitmapDrawable;
     30 import android.graphics.drawable.Drawable;
     31 import android.os.Handler;
     32 import android.support.v4.media.MediaMetadataCompat;
     33 import android.support.v4.media.session.MediaControllerCompat;
     34 import android.support.v4.media.session.PlaybackStateCompat;
     35 import android.util.Log;
     36 
     37 import androidx.leanback.widget.PlaybackControlsRow;
     38 
     39 /**
     40  * A helper class for implementing a adapter layer for {@link MediaControllerCompat}.
     41  */
     42 public class MediaControllerAdapter extends PlayerAdapter {
     43 
     44     private static final String TAG = "MediaControllerAdapter";
     45     private static final boolean DEBUG = false;
     46 
     47     private MediaControllerCompat mController;
     48     private Handler mHandler = new Handler();
     49 
     50     // Runnable object to update current media's playing position.
     51     private final Runnable mPositionUpdaterRunnable = new Runnable() {
     52         @Override
     53         public void run() {
     54             getCallback().onCurrentPositionChanged(MediaControllerAdapter.this);
     55             mHandler.postDelayed(this, getUpdatePeriod());
     56         }
     57     };
     58 
     59     // Update period to post runnable.
     60     private int getUpdatePeriod() {
     61         return 16;
     62     }
     63 
     64     private boolean mIsBuffering = false;
     65 
     66     MediaControllerCompat.Callback mMediaControllerCallback =
     67             new MediaControllerCompat.Callback() {
     68                 @Override
     69                 public void onPlaybackStateChanged(PlaybackStateCompat state) {
     70                     if (mIsBuffering && state.getState() != PlaybackStateCompat.STATE_BUFFERING) {
     71                         getCallback().onBufferingStateChanged(MediaControllerAdapter.this, false);
     72                         getCallback().onBufferedPositionChanged(MediaControllerAdapter.this);
     73                         mIsBuffering = false;
     74                     }
     75                     if (state.getState() == PlaybackStateCompat.STATE_NONE) {
     76                         // The STATE_NONE playback state will only occurs when initialize the player
     77                         // at first time.
     78                         if (DEBUG) {
     79                             Log.d(TAG, "Playback state is none");
     80                         }
     81                     } else if (state.getState() == PlaybackStateCompat.STATE_STOPPED) {
     82                         // STATE_STOPPED is associated with onPlayCompleted() callback.
     83                         // STATE_STOPPED playback state will only occurs when the last item in
     84                         // play list is finished. And repeat mode is not enabled.
     85                         getCallback().onPlayCompleted(MediaControllerAdapter.this);
     86                     } else if (state.getState() == PlaybackStateCompat.STATE_PAUSED) {
     87                         getCallback().onPlayStateChanged(MediaControllerAdapter.this);
     88                         getCallback().onCurrentPositionChanged(MediaControllerAdapter.this);
     89                     } else if (state.getState() == PlaybackStateCompat.STATE_PLAYING) {
     90                         getCallback().onPlayStateChanged(MediaControllerAdapter.this);
     91                         getCallback().onCurrentPositionChanged(MediaControllerAdapter.this);
     92                     } else if (state.getState() == PlaybackStateCompat.STATE_BUFFERING) {
     93                         mIsBuffering = true;
     94                         getCallback().onBufferingStateChanged(MediaControllerAdapter.this, true);
     95                         getCallback().onBufferedPositionChanged(MediaControllerAdapter.this);
     96                     } else if (state.getState() == PlaybackStateCompat.STATE_ERROR) {
     97                         CharSequence errorMessage = state.getErrorMessage();
     98                         if (errorMessage == null) {
     99                             getCallback().onError(MediaControllerAdapter.this, state.getErrorCode(),
    100                                     "");
    101                         } else {
    102                             getCallback().onError(MediaControllerAdapter.this, state.getErrorCode(),
    103                                     state.getErrorMessage().toString());
    104                         }
    105                     } else if (state.getState() == PlaybackStateCompat.STATE_FAST_FORWARDING) {
    106                         getCallback().onPlayStateChanged(MediaControllerAdapter.this);
    107                         getCallback().onCurrentPositionChanged(MediaControllerAdapter.this);
    108                     } else if (state.getState() == PlaybackStateCompat.STATE_REWINDING) {
    109                         getCallback().onPlayStateChanged(MediaControllerAdapter.this);
    110                         getCallback().onCurrentPositionChanged(MediaControllerAdapter.this);
    111                     }
    112                 }
    113 
    114                 @Override
    115                 public void onMetadataChanged(MediaMetadataCompat metadata) {
    116                     getCallback().onMetadataChanged(MediaControllerAdapter.this);
    117                 }
    118             };
    119 
    120     /**
    121      * Constructor for the adapter using {@link MediaControllerCompat}.
    122      *
    123      * @param controller Object of MediaControllerCompat..
    124      */
    125     public MediaControllerAdapter(MediaControllerCompat controller) {
    126         if (controller == null) {
    127             throw new NullPointerException("Object of MediaControllerCompat is null");
    128         }
    129         mController = controller;
    130     }
    131 
    132     /**
    133      * Return the object of {@link MediaControllerCompat} from this class.
    134      *
    135      * @return Media Controller Compat object owned by this class.
    136      */
    137     public MediaControllerCompat getMediaController() {
    138         return mController;
    139     }
    140 
    141     @Override
    142     public void play() {
    143         mController.getTransportControls().play();
    144     }
    145 
    146     @Override
    147     public void pause() {
    148         mController.getTransportControls().pause();
    149     }
    150 
    151     @Override
    152     public void seekTo(long positionInMs) {
    153         mController.getTransportControls().seekTo(positionInMs);
    154     }
    155 
    156     @Override
    157     public void next() {
    158         mController.getTransportControls().skipToNext();
    159     }
    160 
    161     @Override
    162     public void previous() {
    163         mController.getTransportControls().skipToPrevious();
    164     }
    165 
    166     @Override
    167     public void fastForward() {
    168         mController.getTransportControls().fastForward();
    169     }
    170 
    171     @Override
    172     public void rewind() {
    173         mController.getTransportControls().rewind();
    174     }
    175 
    176     @Override
    177     public void setRepeatAction(int repeatActionIndex) {
    178         int repeatMode = mapRepeatActionToRepeatMode(repeatActionIndex);
    179         mController.getTransportControls().setRepeatMode(repeatMode);
    180     }
    181 
    182     @Override
    183     public void setShuffleAction(int shuffleActionIndex) {
    184         int shuffleMode = mapShuffleActionToShuffleMode(shuffleActionIndex);
    185         mController.getTransportControls().setShuffleMode(shuffleMode);
    186     }
    187 
    188     @Override
    189     public boolean isPlaying() {
    190         if (mController.getPlaybackState() == null) {
    191             return false;
    192         }
    193         return mController.getPlaybackState().getState()
    194                 == PlaybackStateCompat.STATE_PLAYING
    195                 || mController.getPlaybackState().getState()
    196                 == PlaybackStateCompat.STATE_FAST_FORWARDING
    197                 || mController.getPlaybackState().getState() == PlaybackStateCompat.STATE_REWINDING;
    198     }
    199 
    200     @Override
    201     public long getCurrentPosition() {
    202         if (mController.getPlaybackState() == null) {
    203             return 0;
    204         }
    205         return mController.getPlaybackState().getPosition();
    206     }
    207 
    208     @Override
    209     public long getBufferedPosition() {
    210         if (mController.getPlaybackState() == null) {
    211             return 0;
    212         }
    213         return mController.getPlaybackState().getBufferedPosition();
    214     }
    215 
    216     /**
    217      * Get current media's title.
    218      *
    219      * @return Title of current media.
    220      */
    221     public CharSequence getMediaTitle() {
    222         if (mController.getMetadata() == null) {
    223             return "";
    224         }
    225         return mController.getMetadata().getDescription().getTitle();
    226     }
    227 
    228     /**
    229      * Get current media's subtitle.
    230      *
    231      * @return Subtitle of current media.
    232      */
    233     public CharSequence getMediaSubtitle() {
    234         if (mController.getMetadata() == null) {
    235             return "";
    236         }
    237         return mController.getMetadata().getDescription().getSubtitle();
    238     }
    239 
    240     /**
    241      * Get current media's drawable art.
    242      *
    243      * @return Drawable art of current media.
    244      */
    245     public Drawable getMediaArt(Context context) {
    246         if (mController.getMetadata() == null) {
    247             return null;
    248         }
    249         Bitmap bitmap = mController.getMetadata().getDescription().getIconBitmap();
    250         return bitmap == null ? null : new BitmapDrawable(context.getResources(), bitmap);
    251     }
    252 
    253     @Override
    254     public long getDuration() {
    255         if (mController.getMetadata() == null) {
    256             return 0;
    257         }
    258         return (int) mController.getMetadata().getLong(
    259                 MediaMetadataCompat.METADATA_KEY_DURATION);
    260     }
    261 
    262     @Override
    263     public void onAttachedToHost(PlaybackGlueHost host) {
    264         mController.registerCallback(mMediaControllerCallback);
    265     }
    266 
    267     @Override
    268     public void onDetachedFromHost() {
    269         mController.unregisterCallback(mMediaControllerCallback);
    270     }
    271 
    272     @Override
    273     public void setProgressUpdatingEnabled(boolean enabled) {
    274         mHandler.removeCallbacks(mPositionUpdaterRunnable);
    275         if (!enabled) {
    276             return;
    277         }
    278         mHandler.postDelayed(mPositionUpdaterRunnable, getUpdatePeriod());
    279     }
    280 
    281     @Override
    282     public long getSupportedActions() {
    283         long supportedActions = 0;
    284         if (mController.getPlaybackState() == null) {
    285             return supportedActions;
    286         }
    287         long actionsFromController = mController.getPlaybackState().getActions();
    288         // Translation.
    289         if ((actionsFromController & PlaybackStateCompat.ACTION_PLAY_PAUSE) != 0) {
    290             supportedActions |= ACTION_PLAY_PAUSE;
    291         }
    292         if ((actionsFromController & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) {
    293             supportedActions |= ACTION_SKIP_TO_NEXT;
    294         }
    295         if ((actionsFromController & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) {
    296             supportedActions |= ACTION_SKIP_TO_PREVIOUS;
    297         }
    298         if ((actionsFromController & PlaybackStateCompat.ACTION_FAST_FORWARD) != 0) {
    299             supportedActions |= ACTION_FAST_FORWARD;
    300         }
    301         if ((actionsFromController & PlaybackStateCompat.ACTION_REWIND) != 0) {
    302             supportedActions |= ACTION_REWIND;
    303         }
    304         if ((actionsFromController & PlaybackStateCompat.ACTION_SET_REPEAT_MODE) != 0) {
    305             supportedActions |= ACTION_REPEAT;
    306         }
    307         if ((actionsFromController & PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE) != 0) {
    308             supportedActions |= ACTION_SHUFFLE;
    309         }
    310         return supportedActions;
    311     }
    312 
    313     /**
    314      * This function will translate the index of RepeatAction in PlaybackControlsRow to
    315      * the repeat mode which is defined by PlaybackStateCompat.
    316      *
    317      * @param repeatActionIndex Index of RepeatAction in PlaybackControlsRow.
    318      * @return Repeat Mode in playback state.
    319      */
    320     private int mapRepeatActionToRepeatMode(int repeatActionIndex) {
    321         switch (repeatActionIndex) {
    322             case PlaybackControlsRow.RepeatAction.INDEX_NONE:
    323                 return PlaybackStateCompat.REPEAT_MODE_NONE;
    324             case PlaybackControlsRow.RepeatAction.INDEX_ALL:
    325                 return PlaybackStateCompat.REPEAT_MODE_ALL;
    326             case PlaybackControlsRow.RepeatAction.INDEX_ONE:
    327                 return PlaybackStateCompat.REPEAT_MODE_ONE;
    328         }
    329         return -1;
    330     }
    331 
    332     /**
    333      * This function will translate the index of RepeatAction in PlaybackControlsRow to
    334      * the repeat mode which is defined by PlaybackStateCompat.
    335      *
    336      * @param shuffleActionIndex Index of RepeatAction in PlaybackControlsRow.
    337      * @return Repeat Mode in playback state.
    338      */
    339     private int mapShuffleActionToShuffleMode(int shuffleActionIndex) {
    340         switch (shuffleActionIndex) {
    341             case PlaybackControlsRow.ShuffleAction.INDEX_OFF:
    342                 return PlaybackStateCompat.SHUFFLE_MODE_NONE;
    343             case PlaybackControlsRow.ShuffleAction.INDEX_ON:
    344                 return PlaybackStateCompat.SHUFFLE_MODE_ALL;
    345         }
    346         return -1;
    347     }
    348 }
    349 
    350