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