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 DvrPlaybackMediaSessionHelper(Activity activity, String mediaSessionTag, DvrPlayer dvrPlayer, DvrPlaybackOverlayFragment overlayFragment)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 */ release()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 */ updateMediaSessionPlaybackState()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 */ setupPlayback(RecordedProgram program, long seekPositionMs)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 */ getProgram()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 */ isCurrentProgram(RecordedProgram program)142 public boolean isCurrentProgram(RecordedProgram program) { 143 return program != null && program.equals(getProgram()); 144 } 145 146 /** 147 * Returns playback state. 148 */ getPlaybackState()149 public int getPlaybackState() { 150 return mDvrPlayer.getPlaybackState(); 151 } 152 153 /** 154 * Returns the underlying DVR player. 155 */ getDvrPlayer()156 public DvrPlayer getDvrPlayer() { 157 return mDvrPlayer; 158 } 159 initializeMediaSession(String mediaSessionTag)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 setupMediaSession(RecordedProgram program)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 updatePosterArt(RecordedProgram program, String cardTitleText, String cardSubtitleText, long duration, @Nullable Bitmap posterArt, @Nullable String posterArtUri)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 ProgramPosterArtCallback(Activity activity, RecordedProgram program, String cardTitleText, String cardSubtitleText, long duration)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 onBitmapLoaded(Activity activity, @Nullable Bitmap posterArt)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 updateMediaMetadata(final long programId, final String title, final String subtitle, final long duration, final Bitmap posterArt, final int imageResId)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 onPrepare()265 public void onPrepare() { 266 if (!mDvrPlayer.isPlaybackPrepared()) { 267 mDvrPlayer.prepare(true); 268 } 269 } 270 271 @Override onPlay()272 public void onPlay() { 273 if (mDvrPlayer.isPlaybackPrepared()) { 274 mDvrPlayer.play(); 275 } 276 } 277 278 @Override onPause()279 public void onPause() { 280 if (mDvrPlayer.isPlaybackPrepared()) { 281 mDvrPlayer.pause(); 282 } 283 } 284 285 @Override onFastForward()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 onRewind()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 onSeekTo(long positionMs)321 public void onSeekTo(long positionMs) { 322 if (mDvrPlayer.isPlaybackPrepared()) { 323 mDvrPlayer.seekTo(positionMs); 324 } 325 } 326 } 327 } 328