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