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