1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 package com.example.android.tvleanback.ui;
15 
16 import android.app.Activity;
17 import android.content.Intent;
18 import android.graphics.Bitmap;
19 import android.media.MediaMetadata;
20 import android.media.MediaPlayer;
21 import android.media.session.MediaSession;
22 import android.media.session.PlaybackState;
23 import android.net.Uri;
24 import android.os.Bundle;
25 import android.util.DisplayMetrics;
26 import android.util.Log;
27 import android.widget.FrameLayout;
28 import android.widget.VideoView;
29 
30 import com.bumptech.glide.Glide;
31 import com.bumptech.glide.request.animation.GlideAnimation;
32 import com.bumptech.glide.request.target.SimpleTarget;
33 import com.example.android.tvleanback.R;
34 import com.example.android.tvleanback.model.Movie;
35 
36 /**
37  * PlaybackOverlayActivity for video playback that loads PlaybackOverlayFragment
38  */
39 public class PlaybackOverlayActivity extends Activity implements
40         PlaybackOverlayFragment.OnPlayPauseClickedListener {
41     private static final String TAG = "PlaybackOverlayActivity";
42 
43     private static final double MEDIA_HEIGHT = 0.95;
44     private static final double MEDIA_WIDTH = 0.95;
45     private static final double MEDIA_TOP_MARGIN = 0.025;
46     private static final double MEDIA_RIGHT_MARGIN = 0.025;
47     private static final double MEDIA_BOTTOM_MARGIN = 0.025;
48     private static final double MEDIA_LEFT_MARGIN = 0.025;
49     private VideoView mVideoView;
50     private LeanbackPlaybackState mPlaybackState = LeanbackPlaybackState.IDLE;
51     private MediaSession mSession;
52 
53     /**
54      * Called when the activity is first created.
55      */
56     @Override
onCreate(Bundle savedInstanceState)57     public void onCreate(Bundle savedInstanceState) {
58         super.onCreate(savedInstanceState);
59         setContentView(R.layout.playback_controls);
60         loadViews();
61         //Example for handling resizing view for overscan
62         //overScan();
63 
64         mSession = new MediaSession (this, "LeanbackSampleApp");
65         mSession.setCallback(new MediaSessionCallback());
66         mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
67                 MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
68 
69         mSession.setActive(true);
70 
71     }
72 
73     @Override
onDestroy()74     public void onDestroy() {
75         super.onDestroy();
76         mVideoView.suspend();
77     }
78 
79     /**
80      * Implementation of OnPlayPauseClickedListener
81      */
onFragmentPlayPause(Movie movie, int position, Boolean playPause)82     public void onFragmentPlayPause(Movie movie, int position, Boolean playPause) {
83         mVideoView.setVideoPath(movie.getVideoUrl());
84 
85         if (position == 0 || mPlaybackState == LeanbackPlaybackState.IDLE) {
86             setupCallbacks();
87             mPlaybackState = LeanbackPlaybackState.IDLE;
88         }
89 
90         if (playPause && mPlaybackState != LeanbackPlaybackState.PLAYING) {
91             mPlaybackState = LeanbackPlaybackState.PLAYING;
92             if (position > 0) {
93                 mVideoView.seekTo(position);
94                 mVideoView.start();
95             }
96         } else {
97             mPlaybackState = LeanbackPlaybackState.PAUSED;
98             mVideoView.pause();
99         }
100         updatePlaybackState(position);
101         updateMetadata(movie);
102     }
103 
104     /**
105      * Implementation of OnPlayPauseClickedListener
106      */
onFragmentFfwRwd(Movie movie, int position)107     public void onFragmentFfwRwd(Movie movie, int position) {
108         mVideoView.setVideoPath(movie.getVideoUrl());
109 
110         Log.d(TAG, "seek current time: " + position);
111         if (mPlaybackState == LeanbackPlaybackState.PLAYING) {
112             if (position > 0) {
113                 mVideoView.seekTo(position);
114                 mVideoView.start();
115             }
116         }
117     }
118 
updatePlaybackState(int position)119     private void updatePlaybackState(int position) {
120         PlaybackState.Builder stateBuilder = new PlaybackState.Builder()
121                 .setActions(getAvailableActions());
122         int state = PlaybackState.STATE_PLAYING;
123         if (mPlaybackState == LeanbackPlaybackState.PAUSED) {
124             state = PlaybackState.STATE_PAUSED;
125         }
126         stateBuilder.setState(state, position, 1.0f);
127         mSession.setPlaybackState(stateBuilder.build());
128     }
129 
getAvailableActions()130     private long getAvailableActions() {
131         long actions = PlaybackState.ACTION_PLAY |
132                 PlaybackState.ACTION_PLAY_FROM_MEDIA_ID |
133                 PlaybackState.ACTION_PLAY_FROM_SEARCH;
134 
135         if (mPlaybackState == LeanbackPlaybackState.PLAYING) {
136             actions |= PlaybackState.ACTION_PAUSE;
137         }
138 
139         return actions;
140     }
141 
updateMetadata(final Movie movie)142     private void updateMetadata(final Movie movie) {
143         final MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder();
144 
145         String title = movie.getTitle().replace("_", " -");
146 
147         metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, title);
148         metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE,
149                 movie.getDescription());
150         metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI,
151                 movie.getCardImageUrl());
152 
153         // And at minimum the title and artist for legacy support
154         metadataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE, title);
155         metadataBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST, movie.getStudio());
156 
157         Glide.with(this)
158             .load(Uri.parse(movie.getCardImageUrl()))
159             .asBitmap()
160             .into(new SimpleTarget<Bitmap>(500, 500) {
161                 @Override
162                 public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
163                     metadataBuilder.putBitmap(MediaMetadata.METADATA_KEY_ART, bitmap);
164                     mSession.setMetadata(metadataBuilder.build());
165                 }
166             });
167     }
168 
loadViews()169     private void loadViews() {
170         mVideoView = (VideoView) findViewById(R.id.videoView);
171     }
172 
173     /**
174      * Example for handling resizing content for overscan.  Typically you won't need to resize which
175      * is why overScan(); is commented out.
176      */
overScan()177     private void overScan() {
178         DisplayMetrics metrics = new DisplayMetrics();
179         getWindowManager().getDefaultDisplay().getMetrics(metrics);
180         int w = (int) (metrics.widthPixels * MEDIA_WIDTH);
181         int h = (int) (metrics.heightPixels * MEDIA_HEIGHT);
182         int marginLeft = (int) (metrics.widthPixels * MEDIA_LEFT_MARGIN);
183         int marginTop = (int) (metrics.heightPixels * MEDIA_TOP_MARGIN);
184         int marginRight = (int) (metrics.widthPixels * MEDIA_RIGHT_MARGIN);
185         int marginBottom = (int) (metrics.heightPixels * MEDIA_BOTTOM_MARGIN);
186         FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(w, h);
187         lp.setMargins(marginLeft, marginTop, marginRight, marginBottom);
188         mVideoView.setLayoutParams(lp);
189     }
190 
setupCallbacks()191     private void setupCallbacks() {
192 
193         mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
194 
195             @Override
196             public boolean onError(MediaPlayer mp, int what, int extra) {
197                 String msg = "";
198                 if (extra == MediaPlayer.MEDIA_ERROR_TIMED_OUT) {
199                     msg = getString(R.string.video_error_media_load_timeout);
200                 } else if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
201                     msg = getString(R.string.video_error_server_inaccessible);
202                 } else {
203                     msg = getString(R.string.video_error_unknown_error);
204                 }
205                 mVideoView.stopPlayback();
206                 mPlaybackState = LeanbackPlaybackState.IDLE;
207                 return false;
208             }
209         });
210 
211 
212         mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
213             @Override
214             public void onPrepared(MediaPlayer mp) {
215                 if (mPlaybackState == LeanbackPlaybackState.PLAYING) {
216                     mVideoView.start();
217                 }
218             }
219         });
220 
221 
222         mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
223             @Override
224             public void onCompletion(MediaPlayer mp) {
225                 mPlaybackState = LeanbackPlaybackState.IDLE;
226             }
227         });
228 
229     }
230 
231     @Override
onResume()232     public void onResume() {
233         super.onResume();
234     }
235 
236     @Override
onPause()237     public void onPause() {
238         super.onPause();
239         if (mVideoView.isPlaying()) {
240             if (!requestVisibleBehind(true)) {
241                 // Try to play behind launcher, but if it fails, stop playback.
242                 stopPlayback();
243             }
244         } else {
245             requestVisibleBehind(false);
246         }
247     }
248 
249     @Override
onStop()250     protected void onStop() {
251         super.onStop();
252         mSession.release();
253     }
254 
255     @Override
onVisibleBehindCanceled()256     public void onVisibleBehindCanceled() {
257         super.onVisibleBehindCanceled();
258         stopPlayback();
259     }
260 
stopPlayback()261     private void stopPlayback() {
262         if (mVideoView != null) {
263             mVideoView.stopPlayback();
264         }
265     }
266 
267     @Override
onSearchRequested()268     public boolean onSearchRequested() {
269         startActivity(new Intent(this, SearchActivity.class));
270         return true;
271     }
272 
273     /*
274      * List of various states that we can be in
275      */
276     public static enum LeanbackPlaybackState {
277         PLAYING, PAUSED, BUFFERING, IDLE;
278     }
279 
280     private class MediaSessionCallback extends MediaSession.Callback {
281     }
282 }
283