1 /*
2  * Copyright (C) 2013 Google Inc. All Rights Reserved.
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.example.android.leanback;
18 
19 import android.app.Activity;
20 import android.content.Intent;
21 import android.media.MediaPlayer;
22 import android.media.MediaPlayer.OnCompletionListener;
23 import android.media.MediaPlayer.OnErrorListener;
24 import android.media.MediaPlayer.OnPreparedListener;
25 import android.os.Bundle;
26 import android.os.Handler;
27 import android.util.DisplayMetrics;
28 import android.util.Log;
29 import android.view.KeyEvent;
30 import android.view.MenuItem;
31 import android.view.View;
32 import android.widget.ImageView;
33 import android.widget.ProgressBar;
34 import android.widget.RelativeLayout.LayoutParams;
35 import android.widget.SeekBar;
36 import android.widget.TextView;
37 import android.widget.VideoView;
38 
39 import java.util.Locale;
40 import java.util.Timer;
41 import java.util.TimerTask;
42 import java.util.concurrent.TimeUnit;
43 
44 /*
45  * PlayerActivity handles video playback
46  */
47 public class PlayerActivity extends Activity {
48 
49     private static final String TAG = "PlayerActivity";
50 
51     private static final int HIDE_CONTROLLER_TIME = 5000;
52     private static final int SEEKBAR_DELAY_TIME = 100;
53     private static final int SEEKBAR_INTERVAL_TIME = 1000;
54     private static final int MIN_SCRUB_TIME = 3000;
55     private static final int SCRUB_SEGMENT_DIVISOR = 30;
56     private static final double MEDIA_BAR_TOP_MARGIN = 0.8;
57     private static final double MEDIA_BAR_RIGHT_MARGIN = 0.2;
58     private static final double MEDIA_BAR_BOTTOM_MARGIN = 0.0;
59     private static final double MEDIA_BAR_LEFT_MARGIN = 0.2;
60     private static final double MEDIA_BAR_HEIGHT = 0.1;
61     private static final double MEDIA_BAR_WIDTH = 0.9;
62 
63     private VideoView mVideoView;
64     private TextView mStartText;
65     private TextView mEndText;
66     private SeekBar mSeekbar;
67     private ImageView mPlayPause;
68     private ProgressBar mLoading;
69     private View mControllers;
70     private View mContainer;
71     private Timer mSeekbarTimer;
72     private Timer mControllersTimer;
73     private PlaybackState mPlaybackState;
74     private final Handler mHandler = new Handler();
75     private Movie mSelectedMovie;
76     private boolean mShouldStartPlayback;
77     private boolean mControllersVisible;
78     private int mDuration;
79     private DisplayMetrics mMetrics;
80 
81     /*
82      * List of various states that we can be in
83      */
84     public static enum PlaybackState {
85         PLAYING, PAUSED, BUFFERING, IDLE;
86     }
87 
88     @Override
onCreate(Bundle savedInstanceState)89     public void onCreate(Bundle savedInstanceState) {
90         super.onCreate(savedInstanceState);
91         setContentView(R.layout.player_activity);
92 
93         mMetrics = new DisplayMetrics();
94         getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
95 
96         loadViews();
97         setupController();
98         setupControlsCallbacks();
99         startVideoPlayer();
100         updateMetadata(true);
101     }
102 
startVideoPlayer()103     private void startVideoPlayer() {
104         Bundle b = getIntent().getExtras();
105         mSelectedMovie = (Movie) getIntent().getSerializableExtra(
106                 getResources().getString(R.string.movie));
107         if (null != b) {
108             mShouldStartPlayback = b.getBoolean(getResources().getString(R.string.should_start));
109             int startPosition = b.getInt(getResources().getString(R.string.start_position), 0);
110             mVideoView.setVideoPath(mSelectedMovie.getVideoUrl());
111             if (mShouldStartPlayback) {
112                 mPlaybackState = PlaybackState.PLAYING;
113                 updatePlayButton(mPlaybackState);
114                 if (startPosition > 0) {
115                     mVideoView.seekTo(startPosition);
116                 }
117                 mVideoView.start();
118                 mPlayPause.requestFocus();
119                 startControllersTimer();
120             } else {
121                 updatePlaybackLocation();
122                 mPlaybackState = PlaybackState.PAUSED;
123                 updatePlayButton(mPlaybackState);
124             }
125         }
126     }
127 
updatePlaybackLocation()128     private void updatePlaybackLocation() {
129         if (mPlaybackState == PlaybackState.PLAYING ||
130                 mPlaybackState == PlaybackState.BUFFERING) {
131             startControllersTimer();
132         } else {
133             stopControllersTimer();
134         }
135     }
136 
play(int position)137     private void play(int position) {
138         startControllersTimer();
139         mVideoView.seekTo(position);
140         mVideoView.start();
141         restartSeekBarTimer();
142     }
143 
stopSeekBarTimer()144     private void stopSeekBarTimer() {
145         if (null != mSeekbarTimer) {
146             mSeekbarTimer.cancel();
147         }
148     }
149 
restartSeekBarTimer()150     private void restartSeekBarTimer() {
151         stopSeekBarTimer();
152         mSeekbarTimer = new Timer();
153         mSeekbarTimer.scheduleAtFixedRate(new UpdateSeekbarTask(), SEEKBAR_DELAY_TIME,
154                 SEEKBAR_INTERVAL_TIME);
155     }
156 
stopControllersTimer()157     private void stopControllersTimer() {
158         if (null != mControllersTimer) {
159             mControllersTimer.cancel();
160         }
161     }
162 
startControllersTimer()163     private void startControllersTimer() {
164         if (null != mControllersTimer) {
165             mControllersTimer.cancel();
166         }
167         mControllersTimer = new Timer();
168         mControllersTimer.schedule(new HideControllersTask(), HIDE_CONTROLLER_TIME);
169     }
170 
updateControllersVisibility(boolean show)171     private void updateControllersVisibility(boolean show) {
172         if (show) {
173             mControllers.setVisibility(View.VISIBLE);
174         } else {
175             mControllers.setVisibility(View.INVISIBLE);
176         }
177     }
178 
179     @Override
onPause()180     protected void onPause() {
181         super.onPause();
182         Log.d(TAG, "onPause() was called");
183         if (null != mSeekbarTimer) {
184             mSeekbarTimer.cancel();
185             mSeekbarTimer = null;
186         }
187         if (null != mControllersTimer) {
188             mControllersTimer.cancel();
189         }
190         mVideoView.pause();
191         mPlaybackState = PlaybackState.PAUSED;
192         updatePlayButton(PlaybackState.PAUSED);
193     }
194 
195     @Override
onStop()196     protected void onStop() {
197         Log.d(TAG, "onStop() was called");
198         super.onStop();
199     }
200 
201     @Override
onDestroy()202     protected void onDestroy() {
203         Log.d(TAG, "onDestroy() is called");
204         stopControllersTimer();
205         stopSeekBarTimer();
206         super.onDestroy();
207     }
208 
209     @Override
onStart()210     protected void onStart() {
211         Log.d(TAG, "onStart() was called");
212         super.onStart();
213     }
214 
215     @Override
onResume()216     protected void onResume() {
217         Log.d(TAG, "onResume() was called");
218         super.onResume();
219     }
220 
221     private class HideControllersTask extends TimerTask {
222         @Override
run()223         public void run() {
224             mHandler.post(new Runnable() {
225                 @Override
226                 public void run() {
227                     updateControllersVisibility(false);
228                     mControllersVisible = false;
229                 }
230             });
231 
232         }
233     }
234 
235     private class UpdateSeekbarTask extends TimerTask {
236 
237         @Override
run()238         public void run() {
239             mHandler.post(new Runnable() {
240 
241                 @Override
242                 public void run() {
243                     int currentPos = 0;
244                     currentPos = mVideoView.getCurrentPosition();
245                     updateSeekbar(currentPos, mDuration);
246                 }
247             });
248         }
249     }
250 
251     private class BackToDetailTask extends TimerTask {
252 
253         @Override
run()254         public void run() {
255             mHandler.post(new Runnable() {
256                 @Override
257                 public void run() {
258                     Intent intent = new Intent(PlayerActivity.this, DetailsActivity.class);
259                     intent.putExtra(getResources().getString(R.string.movie), mSelectedMovie);
260                     startActivity(intent);
261                 }
262             });
263 
264         }
265     }
266 
setupController()267     private void setupController() {
268 
269         int w = (int) (mMetrics.widthPixels * MEDIA_BAR_WIDTH);
270         int h = (int) (mMetrics.heightPixels * MEDIA_BAR_HEIGHT);
271         int marginLeft = (int) (mMetrics.widthPixels * MEDIA_BAR_LEFT_MARGIN);
272         int marginTop = (int) (mMetrics.heightPixels * MEDIA_BAR_TOP_MARGIN);
273         int marginRight = (int) (mMetrics.widthPixels * MEDIA_BAR_RIGHT_MARGIN);
274         int marginBottom = (int) (mMetrics.heightPixels * MEDIA_BAR_BOTTOM_MARGIN);
275         LayoutParams lp = new LayoutParams(w, h);
276         lp.setMargins(marginLeft, marginTop, marginRight, marginBottom);
277         mControllers.setLayoutParams(lp);
278         mStartText.setText(getResources().getString(R.string.init_text));
279         mEndText.setText(getResources().getString(R.string.init_text));
280     }
281 
setupControlsCallbacks()282     private void setupControlsCallbacks() {
283 
284         mVideoView.setOnErrorListener(new OnErrorListener() {
285 
286             @Override
287             public boolean onError(MediaPlayer mp, int what, int extra) {
288                 String msg = "";
289                 if (extra == MediaPlayer.MEDIA_ERROR_TIMED_OUT) {
290                     msg = getString(R.string.video_error_media_load_timeout);
291                 } else if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
292                     msg = getString(R.string.video_error_server_unaccessible);
293                 } else {
294                     msg = getString(R.string.video_error_unknown_error);
295                 }
296                 Utils.showErrorDialog(PlayerActivity.this, msg);
297                 mVideoView.stopPlayback();
298                 mPlaybackState = PlaybackState.IDLE;
299                 return false;
300             }
301         });
302 
303         mVideoView.setOnPreparedListener(new OnPreparedListener() {
304 
305             @Override
306             public void onPrepared(MediaPlayer mp) {
307                 Log.d(TAG, "onPrepared is reached");
308                 mDuration = mp.getDuration();
309                 mEndText.setText(formatTimeSignature(mDuration));
310                 mSeekbar.setMax(mDuration);
311                 restartSeekBarTimer();
312             }
313         });
314 
315         mVideoView.setOnCompletionListener(new OnCompletionListener() {
316 
317             @Override
318             public void onCompletion(MediaPlayer mp) {
319                 stopSeekBarTimer();
320                 mPlaybackState = PlaybackState.IDLE;
321                 updatePlayButton(PlaybackState.IDLE);
322                 mControllersTimer = new Timer();
323                 mControllersTimer.schedule(new BackToDetailTask(), HIDE_CONTROLLER_TIME);
324             }
325         });
326     }
327 
328     /*
329      * @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return
330      * super.onKeyDown(keyCode, event); }
331      */
332 
333     @Override
onKeyDown(int keyCode, KeyEvent event)334     public boolean onKeyDown(int keyCode, KeyEvent event) {
335         int currentPos = 0;
336         int delta = (int) (mDuration / SCRUB_SEGMENT_DIVISOR);
337         if (delta < MIN_SCRUB_TIME)
338             delta = MIN_SCRUB_TIME;
339 
340         Log.v("keycode", "duration " + mDuration + " delta:" + delta);
341         if (!mControllersVisible) {
342             updateControllersVisibility(true);
343         }
344         switch (keyCode) {
345             case KeyEvent.KEYCODE_DPAD_CENTER:
346                 return true;
347             case KeyEvent.KEYCODE_DPAD_DOWN:
348                 return true;
349             case KeyEvent.KEYCODE_DPAD_LEFT:
350                 currentPos = mVideoView.getCurrentPosition();
351                 currentPos -= delta;
352                 if (currentPos > 0)
353                     play(currentPos);
354                 return true;
355             case KeyEvent.KEYCODE_DPAD_RIGHT:
356                 currentPos = mVideoView.getCurrentPosition();
357                 currentPos += delta;
358                 if (currentPos < mDuration)
359                     play(currentPos);
360                 return true;
361             case KeyEvent.KEYCODE_DPAD_UP:
362                 return true;
363         }
364 
365         return super.onKeyDown(keyCode, event);
366     }
367 
updateSeekbar(int position, int duration)368     private void updateSeekbar(int position, int duration) {
369         mSeekbar.setProgress(position);
370         mSeekbar.setMax(duration);
371         mStartText.setText(formatTimeSignature(mDuration));
372     }
373 
updatePlayButton(PlaybackState state)374     private void updatePlayButton(PlaybackState state) {
375         switch (state) {
376             case PLAYING:
377                 mLoading.setVisibility(View.INVISIBLE);
378                 mPlayPause.setVisibility(View.VISIBLE);
379                 mPlayPause.setImageDrawable(
380                         getResources().getDrawable(R.drawable.ic_pause_playcontrol_normal));
381                 break;
382             case PAUSED:
383             case IDLE:
384                 mLoading.setVisibility(View.INVISIBLE);
385                 mPlayPause.setVisibility(View.VISIBLE);
386                 mPlayPause.setImageDrawable(
387                         getResources().getDrawable(R.drawable.ic_play_playcontrol_normal));
388                 break;
389             case BUFFERING:
390                 mPlayPause.setVisibility(View.INVISIBLE);
391                 mLoading.setVisibility(View.VISIBLE);
392                 break;
393             default:
394                 break;
395         }
396     }
397 
updateMetadata(boolean visible)398     private void updateMetadata(boolean visible) {
399         mVideoView.invalidate();
400     }
401 
402     @Override
onOptionsItemSelected(MenuItem item)403     public boolean onOptionsItemSelected(MenuItem item) {
404         return true;
405     }
406 
loadViews()407     private void loadViews() {
408         mVideoView = (VideoView) findViewById(R.id.videoView);
409         mStartText = (TextView) findViewById(R.id.startText);
410         mEndText = (TextView) findViewById(R.id.endText);
411         mSeekbar = (SeekBar) findViewById(R.id.seekBar);
412         mPlayPause = (ImageView) findViewById(R.id.playpause);
413         mLoading = (ProgressBar) findViewById(R.id.progressBar);
414         mControllers = findViewById(R.id.controllers);
415         mContainer = findViewById(R.id.container);
416 
417         mVideoView.setOnClickListener(mPlayPauseHandler);
418     }
419 
420     View.OnClickListener mPlayPauseHandler = new View.OnClickListener() {
421         public void onClick(View v) {
422             Log.d(TAG, "clicked play pause button");
423 
424             if (!mControllersVisible) {
425                 updateControllersVisibility(true);
426             }
427 
428             if (mPlaybackState == PlaybackState.PAUSED) {
429                 mPlaybackState = PlaybackState.PLAYING;
430                 updatePlayButton(mPlaybackState);
431                 mVideoView.start();
432                 startControllersTimer();
433             } else {
434                 mVideoView.pause();
435                 mPlaybackState = PlaybackState.PAUSED;
436                 updatePlayButton(PlaybackState.PAUSED);
437                 stopControllersTimer();
438             }
439         }
440     };
441 
formatTimeSignature(int timeSignature)442     private String formatTimeSignature(int timeSignature) {
443         return String.format(Locale.US,
444                 "%02d:%02d",
445                 TimeUnit.MILLISECONDS.toMinutes(timeSignature),
446                 TimeUnit.MILLISECONDS.toSeconds(timeSignature)
447                         -
448                         TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS
449                                 .toMinutes(timeSignature)));
450     }
451 }
452