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