Home | History | Annotate | Download | only in playback
      1 /*
      2  * Copyright (C) 2016 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.android.tv.dvr.ui.playback;
     18 
     19 import android.app.Activity;
     20 import android.content.Intent;
     21 import android.graphics.Bitmap;
     22 import android.graphics.BitmapFactory;
     23 import android.media.MediaMetadata;
     24 import android.media.session.MediaController;
     25 import android.media.session.MediaSession;
     26 import android.media.session.PlaybackState;
     27 import android.media.tv.TvContract;
     28 import android.os.AsyncTask;
     29 import android.support.annotation.Nullable;
     30 import android.text.TextUtils;
     31 import com.android.tv.R;
     32 import com.android.tv.TvSingletons;
     33 import com.android.tv.data.ChannelDataManager;
     34 import com.android.tv.data.api.Channel;
     35 import com.android.tv.dvr.DvrWatchedPositionManager;
     36 import com.android.tv.dvr.data.RecordedProgram;
     37 import com.android.tv.util.TimeShiftUtils;
     38 import com.android.tv.util.Utils;
     39 import com.android.tv.util.images.ImageLoader;
     40 
     41 class DvrPlaybackMediaSessionHelper {
     42     private static final String TAG = "DvrPlaybackMediaSessionHelper";
     43     private static final boolean DEBUG = false;
     44 
     45     private int mNowPlayingCardWidth;
     46     private int mNowPlayingCardHeight;
     47     private int mSpeedLevel;
     48     private long mProgramDurationMs;
     49 
     50     private Activity mActivity;
     51     private DvrPlayer mDvrPlayer;
     52     private MediaSession mMediaSession;
     53     private final DvrWatchedPositionManager mDvrWatchedPositionManager;
     54     private final ChannelDataManager mChannelDataManager;
     55 
     56     public DvrPlaybackMediaSessionHelper(
     57             Activity activity,
     58             String mediaSessionTag,
     59             DvrPlayer dvrPlayer,
     60             DvrPlaybackOverlayFragment overlayFragment) {
     61         mActivity = activity;
     62         mDvrPlayer = dvrPlayer;
     63         mDvrWatchedPositionManager =
     64                 TvSingletons.getSingletons(activity).getDvrWatchedPositionManager();
     65         mChannelDataManager = TvSingletons.getSingletons(activity).getChannelDataManager();
     66         mDvrPlayer.setCallback(
     67                 new DvrPlayer.DvrPlayerCallback() {
     68                     @Override
     69                     public void onPlaybackStateChanged(int playbackState, int playbackSpeed) {
     70                         updateMediaSessionPlaybackState();
     71                     }
     72 
     73                     @Override
     74                     public void onPlaybackPositionChanged(long positionMs) {
     75                         updateMediaSessionPlaybackState();
     76                         if (mDvrPlayer.isPlaybackPrepared()) {
     77                             mDvrWatchedPositionManager.setWatchedPosition(
     78                                     mDvrPlayer.getProgram().getId(), positionMs);
     79                         }
     80                     }
     81 
     82                     @Override
     83                     public void onPlaybackEnded() {
     84                         // TODO: Deal with watched over recordings in DVR library
     85                         RecordedProgram nextEpisode =
     86                                 overlayFragment.getNextEpisode(mDvrPlayer.getProgram());
     87                         if (nextEpisode == null) {
     88                             mDvrPlayer.reset();
     89                             mActivity.finish();
     90                         } else {
     91                             Intent intent = new Intent(activity, DvrPlaybackActivity.class);
     92                             intent.putExtra(
     93                                     Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, nextEpisode.getId());
     94                             mActivity.startActivity(intent);
     95                         }
     96                     }
     97                 });
     98         initializeMediaSession(mediaSessionTag);
     99     }
    100 
    101     /** Stops DVR player and release media session. */
    102     public void release() {
    103         if (mDvrPlayer != null) {
    104             mDvrPlayer.reset();
    105         }
    106         if (mMediaSession != null) {
    107             mMediaSession.release();
    108             mMediaSession = null;
    109         }
    110     }
    111 
    112     /** Updates media session's playback state and speed. */
    113     public void updateMediaSessionPlaybackState() {
    114         mMediaSession.setPlaybackState(
    115                 new PlaybackState.Builder()
    116                         .setState(
    117                                 mDvrPlayer.getPlaybackState(),
    118                                 mDvrPlayer.getPlaybackPosition(),
    119                                 mSpeedLevel)
    120                         .build());
    121     }
    122 
    123     /**
    124      * Sets the recorded program for playback.
    125      *
    126      * @param program The recorded program to play. {@code null} to reset the DVR player.
    127      */
    128     public void setupPlayback(RecordedProgram program, long seekPositionMs) {
    129         if (program != null) {
    130             mDvrPlayer.setProgram(program, seekPositionMs);
    131             setupMediaSession(program);
    132         } else {
    133             mDvrPlayer.reset();
    134             mMediaSession.setActive(false);
    135         }
    136     }
    137 
    138     /** Returns the recorded program now playing. */
    139     public RecordedProgram getProgram() {
    140         return mDvrPlayer.getProgram();
    141     }
    142 
    143     /** Checks if the recorded program is the same as now playing one. */
    144     public boolean isCurrentProgram(RecordedProgram program) {
    145         return program != null && program.equals(getProgram());
    146     }
    147 
    148     /** Returns playback state. */
    149     public int getPlaybackState() {
    150         return mDvrPlayer.getPlaybackState();
    151     }
    152 
    153     /** Returns the underlying DVR player. */
    154     public DvrPlayer getDvrPlayer() {
    155         return mDvrPlayer;
    156     }
    157 
    158     private void initializeMediaSession(String mediaSessionTag) {
    159         mMediaSession = new MediaSession(mActivity, mediaSessionTag);
    160         mMediaSession.setFlags(
    161                 MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
    162                         | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
    163         mNowPlayingCardWidth =
    164                 mActivity.getResources().getDimensionPixelSize(R.dimen.notif_card_img_max_width);
    165         mNowPlayingCardHeight =
    166                 mActivity.getResources().getDimensionPixelSize(R.dimen.notif_card_img_height);
    167         mMediaSession.setCallback(new MediaSessionCallback());
    168         mActivity.setMediaController(
    169                 new MediaController(mActivity, mMediaSession.getSessionToken()));
    170         updateMediaSessionPlaybackState();
    171     }
    172 
    173     private void setupMediaSession(RecordedProgram program) {
    174         mProgramDurationMs = program.getDurationMillis();
    175         String cardTitleText = program.getTitle();
    176         if (TextUtils.isEmpty(cardTitleText)) {
    177             Channel channel = mChannelDataManager.getChannel(program.getChannelId());
    178             cardTitleText =
    179                     (channel != null)
    180                             ? channel.getDisplayName()
    181                             : mActivity.getString(R.string.no_program_information);
    182         }
    183         final MediaMetadata currentMetadata =
    184                 updateMetadataTextInfo(
    185                         program.getId(),
    186                         cardTitleText,
    187                         program.getDescription(),
    188                         mProgramDurationMs);
    189         String posterArtUri = program.getPosterArtUri();
    190         if (posterArtUri == null) {
    191             posterArtUri = TvContract.buildChannelLogoUri(program.getChannelId()).toString();
    192         }
    193         updatePosterArt(program, currentMetadata, null, posterArtUri);
    194         mMediaSession.setActive(true);
    195     }
    196 
    197     private void updatePosterArt(
    198             RecordedProgram program,
    199             MediaMetadata currentMetadata,
    200             @Nullable Bitmap posterArt,
    201             @Nullable String posterArtUri) {
    202         if (posterArt != null) {
    203             updateMetadataImageInfo(program, currentMetadata, posterArt, 0);
    204         } else if (posterArtUri != null) {
    205             ImageLoader.loadBitmap(
    206                     mActivity,
    207                     posterArtUri,
    208                     mNowPlayingCardWidth,
    209                     mNowPlayingCardHeight,
    210                     new ProgramPosterArtCallback(mActivity, program, currentMetadata));
    211         } else {
    212             updateMetadataImageInfo(program, currentMetadata, null, R.drawable.default_now_card);
    213         }
    214     }
    215 
    216     private class ProgramPosterArtCallback extends ImageLoader.ImageLoaderCallback<Activity> {
    217         private final RecordedProgram mRecordedProgram;
    218         private final MediaMetadata mCurrentMetadata;
    219 
    220         public ProgramPosterArtCallback(
    221                 Activity activity, RecordedProgram program, MediaMetadata metadata) {
    222             super(activity);
    223             mRecordedProgram = program;
    224             mCurrentMetadata = metadata;
    225         }
    226 
    227         @Override
    228         public void onBitmapLoaded(Activity activity, @Nullable Bitmap posterArt) {
    229             if (isCurrentProgram(mRecordedProgram)) {
    230                 updatePosterArt(mRecordedProgram, mCurrentMetadata, posterArt, null);
    231             }
    232         }
    233     }
    234 
    235     private MediaMetadata updateMetadataTextInfo(
    236             final long programId, final String title, final String subtitle, final long duration) {
    237         MediaMetadata.Builder builder = new MediaMetadata.Builder();
    238         builder.putString(MediaMetadata.METADATA_KEY_MEDIA_ID, Long.toString(programId))
    239                 .putString(MediaMetadata.METADATA_KEY_TITLE, title)
    240                 .putLong(MediaMetadata.METADATA_KEY_DURATION, duration);
    241         if (subtitle != null) {
    242             builder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, subtitle);
    243         }
    244         MediaMetadata metadata = builder.build();
    245         mMediaSession.setMetadata(metadata);
    246         return metadata;
    247     }
    248 
    249     private void updateMetadataImageInfo(
    250             final RecordedProgram program,
    251             final MediaMetadata currentMetadata,
    252             final Bitmap posterArt,
    253             final int imageResId) {
    254         if (mMediaSession != null && (posterArt != null || imageResId != 0)) {
    255             MediaMetadata.Builder builder = new MediaMetadata.Builder(currentMetadata);
    256             if (posterArt != null) {
    257                 builder.putBitmap(MediaMetadata.METADATA_KEY_ART, posterArt);
    258                 mMediaSession.setMetadata(builder.build());
    259             } else {
    260                 new AsyncTask<Void, Void, Bitmap>() {
    261                     @Override
    262                     protected Bitmap doInBackground(Void... arg0) {
    263                         return BitmapFactory.decodeResource(mActivity.getResources(), imageResId);
    264                     }
    265 
    266                     @Override
    267                     protected void onPostExecute(Bitmap programPosterArt) {
    268                         if (mMediaSession != null
    269                                 && programPosterArt != null
    270                                 && isCurrentProgram(program)) {
    271                             builder.putBitmap(MediaMetadata.METADATA_KEY_ART, programPosterArt);
    272                             mMediaSession.setMetadata(builder.build());
    273                         }
    274                     }
    275                 }.execute();
    276             }
    277         }
    278     }
    279 
    280     // An event was triggered by MediaController.TransportControls and must be handled here.
    281     // Here we update the media itself to act on the event that was triggered.
    282     private class MediaSessionCallback extends MediaSession.Callback {
    283         @Override
    284         public void onPrepare() {
    285             if (!mDvrPlayer.isPlaybackPrepared()) {
    286                 mDvrPlayer.prepare(true);
    287             }
    288         }
    289 
    290         @Override
    291         public void onPlay() {
    292             if (mDvrPlayer.isPlaybackPrepared()) {
    293                 mDvrPlayer.play();
    294             }
    295         }
    296 
    297         @Override
    298         public void onPause() {
    299             if (mDvrPlayer.isPlaybackPrepared()) {
    300                 mDvrPlayer.pause();
    301             }
    302         }
    303 
    304         @Override
    305         public void onFastForward() {
    306             if (!mDvrPlayer.isPlaybackPrepared()) {
    307                 return;
    308             }
    309             if (mDvrPlayer.getPlaybackState() == PlaybackState.STATE_FAST_FORWARDING) {
    310                 if (mSpeedLevel < TimeShiftUtils.MAX_SPEED_LEVEL) {
    311                     mSpeedLevel++;
    312                 } else {
    313                     return;
    314                 }
    315             } else {
    316                 mSpeedLevel = 0;
    317             }
    318             mDvrPlayer.fastForward(
    319                     TimeShiftUtils.getPlaybackSpeed(mSpeedLevel, mProgramDurationMs));
    320         }
    321 
    322         @Override
    323         public void onRewind() {
    324             if (!mDvrPlayer.isPlaybackPrepared()) {
    325                 return;
    326             }
    327             if (mDvrPlayer.getPlaybackState() == PlaybackState.STATE_REWINDING) {
    328                 if (mSpeedLevel < TimeShiftUtils.MAX_SPEED_LEVEL) {
    329                     mSpeedLevel++;
    330                 } else {
    331                     return;
    332                 }
    333             } else {
    334                 mSpeedLevel = 0;
    335             }
    336             mDvrPlayer.rewind(TimeShiftUtils.getPlaybackSpeed(mSpeedLevel, mProgramDurationMs));
    337         }
    338 
    339         @Override
    340         public void onSeekTo(long positionMs) {
    341             if (mDvrPlayer.isPlaybackPrepared()) {
    342                 mDvrPlayer.seekTo(positionMs);
    343             }
    344         }
    345     }
    346 }
    347