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