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