• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
DvrPlaybackMediaSessionHelper( Activity activity, String mediaSessionTag, DvrPlayer dvrPlayer, DvrPlaybackOverlayFragment overlayFragment)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. */
release()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. */
updateMediaSessionPlaybackState()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      */
setupPlayback(RecordedProgram program, long seekPositionMs)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. */
getProgram()139     public RecordedProgram getProgram() {
140         return mDvrPlayer.getProgram();
141     }
142 
143     /** Checks if the recorded program is the same as now playing one. */
isCurrentProgram(RecordedProgram program)144     public boolean isCurrentProgram(RecordedProgram program) {
145         return program != null && program.equals(getProgram());
146     }
147 
148     /** Returns playback state. */
getPlaybackState()149     public int getPlaybackState() {
150         return mDvrPlayer.getPlaybackState();
151     }
152 
153     /** Returns the underlying DVR player. */
getDvrPlayer()154     public DvrPlayer getDvrPlayer() {
155         return mDvrPlayer;
156     }
157 
initializeMediaSession(String mediaSessionTag)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 
setupMediaSession(RecordedProgram program)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 
updatePosterArt( RecordedProgram program, MediaMetadata currentMetadata, @Nullable Bitmap posterArt, @Nullable String posterArtUri)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 
ProgramPosterArtCallback( Activity activity, RecordedProgram program, MediaMetadata metadata)220         public ProgramPosterArtCallback(
221                 Activity activity, RecordedProgram program, MediaMetadata metadata) {
222             super(activity);
223             mRecordedProgram = program;
224             mCurrentMetadata = metadata;
225         }
226 
227         @Override
onBitmapLoaded(Activity activity, @Nullable Bitmap posterArt)228         public void onBitmapLoaded(Activity activity, @Nullable Bitmap posterArt) {
229             if (isCurrentProgram(mRecordedProgram)) {
230                 updatePosterArt(mRecordedProgram, mCurrentMetadata, posterArt, null);
231             }
232         }
233     }
234 
updateMetadataTextInfo( final long programId, final String title, final String subtitle, final long duration)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 
updateMetadataImageInfo( final RecordedProgram program, final MediaMetadata currentMetadata, final Bitmap posterArt, final int imageResId)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
onPrepare()284         public void onPrepare() {
285             if (!mDvrPlayer.isPlaybackPrepared()) {
286                 mDvrPlayer.prepare(true);
287             }
288         }
289 
290         @Override
onPlay()291         public void onPlay() {
292             if (mDvrPlayer.isPlaybackPrepared()) {
293                 mDvrPlayer.play();
294             }
295         }
296 
297         @Override
onPause()298         public void onPause() {
299             if (mDvrPlayer.isPlaybackPrepared()) {
300                 mDvrPlayer.pause();
301             }
302         }
303 
304         @Override
onFastForward()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
onRewind()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
onSeekTo(long positionMs)340         public void onSeekTo(long positionMs) {
341             if (mDvrPlayer.isPlaybackPrepared()) {
342                 mDvrPlayer.seekTo(positionMs);
343             }
344         }
345     }
346 }
347