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