blob: 9c81c79c24dbb01701b2badf5481081cf1e2588d [file] [log] [blame]
Owen Linf9a0a432011-08-17 22:07:43 +08001/*
2 * Copyright (C) 2009 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
17package com.android.gallery3d.app;
18
Owen Linf9a0a432011-08-17 22:07:43 +080019import android.app.ActionBar;
20import android.app.AlertDialog;
21import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.DialogInterface;
24import android.content.DialogInterface.OnCancelListener;
25import android.content.DialogInterface.OnClickListener;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.media.AudioManager;
29import android.media.MediaPlayer;
30import android.net.Uri;
Owen Lin540e4692011-09-14 19:49:03 +080031import android.os.Bundle;
Owen Linf9a0a432011-08-17 22:07:43 +080032import android.os.Handler;
Chih-Chung Chang209a9162011-10-14 16:09:09 +080033import android.view.MotionEvent;
Owen Linf9a0a432011-08-17 22:07:43 +080034import android.view.View;
Chih-Chung Chang209a9162011-10-14 16:09:09 +080035import android.view.ViewGroup;
Owen Linf9a0a432011-08-17 22:07:43 +080036import android.widget.VideoView;
37
Owen Linaea077a2011-11-11 12:01:09 +080038import com.android.gallery3d.R;
39import com.android.gallery3d.common.BlobCache;
40import com.android.gallery3d.util.CacheManager;
41import com.android.gallery3d.util.GalleryUtils;
42
Owen Linf9a0a432011-08-17 22:07:43 +080043import java.io.ByteArrayInputStream;
44import java.io.ByteArrayOutputStream;
45import java.io.DataInputStream;
46import java.io.DataOutputStream;
47
48public class MoviePlayer implements
Chih-Chung Chang209a9162011-10-14 16:09:09 +080049 MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener,
50 ControllerOverlay.Listener {
Owen Linf9a0a432011-08-17 22:07:43 +080051 @SuppressWarnings("unused")
52 private static final String TAG = "MoviePlayer";
53
Owen Lin540e4692011-09-14 19:49:03 +080054 private static final String KEY_VIDEO_POSITION = "video-position";
55 private static final String KEY_RESUMEABLE_TIME = "resumeable-timeout";
56
Owen Linf9a0a432011-08-17 22:07:43 +080057 // Copied from MediaPlaybackService in the Music Player app.
58 private static final String SERVICECMD = "com.android.music.musicservicecommand";
59 private static final String CMDNAME = "command";
60 private static final String CMDPAUSE = "pause";
61
Owen Lin540e4692011-09-14 19:49:03 +080062 // If we resume the acitivty with in RESUMEABLE_TIMEOUT, we will keep playing.
63 // Otherwise, we pause the player.
64 private static final long RESUMEABLE_TIMEOUT = 3 * 60 * 1000; // 3 mins
65
Owen Linf9a0a432011-08-17 22:07:43 +080066 private Context mContext;
67 private final VideoView mVideoView;
Owen Linf9a0a432011-08-17 22:07:43 +080068 private final Bookmarker mBookmarker;
69 private final Uri mUri;
70 private final Handler mHandler = new Handler();
71 private final AudioBecomingNoisyReceiver mAudioBecomingNoisyReceiver;
72 private final ActionBar mActionBar;
Chih-Chung Chang209a9162011-10-14 16:09:09 +080073 private final ControllerOverlay mController;
Owen Linf9a0a432011-08-17 22:07:43 +080074
Owen Lin540e4692011-09-14 19:49:03 +080075 private long mResumeableTime = Long.MAX_VALUE;
76 private int mVideoPosition = 0;
77 private boolean mHasPaused = false;
Owen Linf9a0a432011-08-17 22:07:43 +080078
Chih-Chung Chang209a9162011-10-14 16:09:09 +080079 // If the time bar is being dragged.
80 private boolean mDragging;
81
82 // If the time bar is visible.
83 private boolean mShowing;
84
Owen Linf9a0a432011-08-17 22:07:43 +080085 private final Runnable mPlayingChecker = new Runnable() {
Owen Lin540e4692011-09-14 19:49:03 +080086 @Override
Owen Linf9a0a432011-08-17 22:07:43 +080087 public void run() {
88 if (mVideoView.isPlaying()) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +080089 mController.showPlaying();
Owen Linf9a0a432011-08-17 22:07:43 +080090 } else {
91 mHandler.postDelayed(mPlayingChecker, 250);
92 }
93 }
94 };
95
Chih-Chung Chang209a9162011-10-14 16:09:09 +080096 private final Runnable mProgressChecker = new Runnable() {
97 @Override
98 public void run() {
99 int pos = setProgress();
100 mHandler.postDelayed(mProgressChecker, 1000 - (pos % 1000));
101 }
102 };
103
Owen Lin540e4692011-09-14 19:49:03 +0800104 public MoviePlayer(View rootView, final MovieActivity movieActivity, Uri videoUri,
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800105 Bundle savedInstance, boolean canReplay) {
Owen Linf9a0a432011-08-17 22:07:43 +0800106 mContext = movieActivity.getApplicationContext();
107 mVideoView = (VideoView) rootView.findViewById(R.id.surface_view);
Owen Linf9a0a432011-08-17 22:07:43 +0800108 mBookmarker = new Bookmarker(movieActivity);
109 mActionBar = movieActivity.getActionBar();
110 mUri = videoUri;
111
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800112 mController = new MovieControllerOverlay(mContext);
113 ((ViewGroup)rootView).addView(mController.getView());
114 mController.setListener(this);
115 mController.setCanReplay(canReplay);
Owen Linf9a0a432011-08-17 22:07:43 +0800116
117 mVideoView.setOnErrorListener(this);
118 mVideoView.setOnCompletionListener(this);
119 mVideoView.setVideoURI(mUri);
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800120 mVideoView.setOnTouchListener(new View.OnTouchListener() {
121 public boolean onTouch(View v, MotionEvent event) {
122 mController.show();
123 return true;
Owen Linf9a0a432011-08-17 22:07:43 +0800124 }
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800125 });
Chih-Chung Chang81760122011-09-27 16:50:16 +0800126
127 // When the user touches the screen or uses some hard key, the framework
128 // will change system ui visibility from invisible to visible. We show
129 // the media control at this point.
130 mVideoView.setOnSystemUiVisibilityChangeListener(
131 new View.OnSystemUiVisibilityChangeListener() {
132 public void onSystemUiVisibilityChange(int visibility) {
133 if ((visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800134 mController.show();
Chih-Chung Chang81760122011-09-27 16:50:16 +0800135 }
136 }
137 });
138
Owen Linf9a0a432011-08-17 22:07:43 +0800139 mAudioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver();
140 mAudioBecomingNoisyReceiver.register();
141
Owen Linf9a0a432011-08-17 22:07:43 +0800142 Intent i = new Intent(SERVICECMD);
143 i.putExtra(CMDNAME, CMDPAUSE);
144 movieActivity.sendBroadcast(i);
145
Owen Lin540e4692011-09-14 19:49:03 +0800146 if (savedInstance != null) { // this is a resumed activity
147 mVideoPosition = savedInstance.getInt(KEY_VIDEO_POSITION, 0);
148 mResumeableTime = savedInstance.getLong(KEY_RESUMEABLE_TIME, Long.MAX_VALUE);
Owen Linf9a0a432011-08-17 22:07:43 +0800149 mVideoView.start();
Owen Lin540e4692011-09-14 19:49:03 +0800150 mVideoView.suspend();
151 mHasPaused = true;
152 } else {
153 final Integer bookmark = mBookmarker.getBookmark(mUri);
154 if (bookmark != null) {
155 showResumeDialog(movieActivity, bookmark);
156 } else {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800157 startVideo();
Owen Lin540e4692011-09-14 19:49:03 +0800158 }
Owen Linf9a0a432011-08-17 22:07:43 +0800159 }
160 }
161
Chih-Chung Chang81760122011-09-27 16:50:16 +0800162 private void showSystemUi(boolean visible) {
Chih-Chung Chang5383c572011-11-09 20:57:53 +0800163 int flag = visible ? 0 : View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
164 View.SYSTEM_UI_FLAG_LOW_PROFILE;
Chih-Chung Chang81760122011-09-27 16:50:16 +0800165 mVideoView.setSystemUiVisibility(flag);
Chih-Chung Chang81760122011-09-27 16:50:16 +0800166 }
167
Owen Lin540e4692011-09-14 19:49:03 +0800168 public void onSaveInstanceState(Bundle outState) {
169 outState.putInt(KEY_VIDEO_POSITION, mVideoPosition);
170 outState.putLong(KEY_RESUMEABLE_TIME, mResumeableTime);
171 }
172
Owen Linf9a0a432011-08-17 22:07:43 +0800173 private void showResumeDialog(Context context, final int bookmark) {
174 AlertDialog.Builder builder = new AlertDialog.Builder(context);
175 builder.setTitle(R.string.resume_playing_title);
176 builder.setMessage(String.format(
177 context.getString(R.string.resume_playing_message),
178 GalleryUtils.formatDuration(context, bookmark / 1000)));
179 builder.setOnCancelListener(new OnCancelListener() {
Owen Lin540e4692011-09-14 19:49:03 +0800180 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800181 public void onCancel(DialogInterface dialog) {
182 onCompletion();
183 }
184 });
185 builder.setPositiveButton(
186 R.string.resume_playing_resume, new OnClickListener() {
Owen Lin540e4692011-09-14 19:49:03 +0800187 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800188 public void onClick(DialogInterface dialog, int which) {
189 mVideoView.seekTo(bookmark);
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800190 startVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800191 }
192 });
193 builder.setNegativeButton(
194 R.string.resume_playing_restart, new OnClickListener() {
Owen Lin540e4692011-09-14 19:49:03 +0800195 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800196 public void onClick(DialogInterface dialog, int which) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800197 startVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800198 }
199 });
200 builder.show();
201 }
202
203 public void onPause() {
Owen Linf9a0a432011-08-17 22:07:43 +0800204 mHasPaused = true;
Owen Lin540e4692011-09-14 19:49:03 +0800205 mHandler.removeCallbacksAndMessages(null);
206 mVideoPosition = mVideoView.getCurrentPosition();
207 mBookmarker.setBookmark(mUri, mVideoPosition, mVideoView.getDuration());
208 mVideoView.suspend();
209 mResumeableTime = System.currentTimeMillis() + RESUMEABLE_TIMEOUT;
Owen Linf9a0a432011-08-17 22:07:43 +0800210 }
211
212 public void onResume() {
213 if (mHasPaused) {
Owen Lin540e4692011-09-14 19:49:03 +0800214 mVideoView.seekTo(mVideoPosition);
215 mVideoView.resume();
216
217 // If we have slept for too long, pause the play
218 if (System.currentTimeMillis() > mResumeableTime) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800219 pauseVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800220 }
221 }
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800222 mHandler.post(mProgressChecker);
Owen Linf9a0a432011-08-17 22:07:43 +0800223 }
224
225 public void onDestroy() {
226 mVideoView.stopPlayback();
227 mAudioBecomingNoisyReceiver.unregister();
228 }
229
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800230 // This updates the time bar display (if necessary). It is called every
231 // second by mProgressChecker and also from places where the time bar needs
232 // to be updated immediately.
233 private int setProgress() {
234 if (mDragging || !mShowing) {
235 return 0;
236 }
237 int position = mVideoView.getCurrentPosition();
238 int duration = mVideoView.getDuration();
239 mController.setTimes(position, duration);
240 return position;
241 }
242
243 private void startVideo() {
244 // For streams that we expect to be slow to start up, show a
245 // progress spinner until playback starts.
246 String scheme = mUri.getScheme();
247 if ("http".equalsIgnoreCase(scheme) || "rtsp".equalsIgnoreCase(scheme)) {
248 mController.showLoading();
249 mHandler.removeCallbacks(mPlayingChecker);
250 mHandler.postDelayed(mPlayingChecker, 250);
251 } else {
252 mController.showPlaying();
253 }
254
255 mVideoView.start();
256 setProgress();
257 }
258
259 private void playVideo() {
260 mVideoView.start();
261 mController.showPlaying();
262 setProgress();
263 }
264
265 private void pauseVideo() {
266 mVideoView.pause();
267 mController.showPaused();
268 }
269
270 // Below are notifications from VideoView
271 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800272 public boolean onError(MediaPlayer player, int arg1, int arg2) {
273 mHandler.removeCallbacksAndMessages(null);
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800274 // VideoView will show an error dialog if we return false, so no need
275 // to show more message.
276 mController.showErrorMessage("");
Owen Linf9a0a432011-08-17 22:07:43 +0800277 return false;
278 }
279
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800280 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800281 public void onCompletion(MediaPlayer mp) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800282 mController.showEnded();
Owen Linf9a0a432011-08-17 22:07:43 +0800283 onCompletion();
284 }
285
286 public void onCompletion() {
287 }
288
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800289 // Below are notifications from ControllerOverlay
290 @Override
291 public void onPlayPause() {
292 if (mVideoView.isPlaying()) {
293 pauseVideo();
294 } else {
295 playVideo();
296 }
297 }
298
299 @Override
300 public void onSeekStart() {
301 mDragging = true;
302 }
303
304 @Override
305 public void onSeekMove(int time) {
306 mVideoView.seekTo(time);
307 }
308
309 @Override
310 public void onSeekEnd(int time) {
311 mDragging = false;
312 mVideoView.seekTo(time);
313 setProgress();
314 }
315
316 @Override
317 public void onShown() {
318 mShowing = true;
319 mActionBar.show();
320 showSystemUi(true);
321 setProgress();
322 }
323
324 @Override
325 public void onHidden() {
326 mShowing = false;
327 mActionBar.hide();
328 showSystemUi(false);
329 }
330
331 @Override
332 public void onReplay() {
333 startVideo();
334 }
335
336 // We want to pause when the headset is unplugged.
Owen Linf9a0a432011-08-17 22:07:43 +0800337 private class AudioBecomingNoisyReceiver extends BroadcastReceiver {
338
339 public void register() {
340 mContext.registerReceiver(this,
341 new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
342 }
343
344 public void unregister() {
345 mContext.unregisterReceiver(this);
346 }
347
348 @Override
349 public void onReceive(Context context, Intent intent) {
Owen Linaea077a2011-11-11 12:01:09 +0800350 if (mVideoView.isPlaying()) pauseVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800351 }
352 }
353}
354
355class Bookmarker {
356 private static final String TAG = "Bookmarker";
357
358 private static final String BOOKMARK_CACHE_FILE = "bookmark";
359 private static final int BOOKMARK_CACHE_MAX_ENTRIES = 100;
360 private static final int BOOKMARK_CACHE_MAX_BYTES = 10 * 1024;
361 private static final int BOOKMARK_CACHE_VERSION = 1;
362
363 private static final int HALF_MINUTE = 30 * 1000;
364 private static final int TWO_MINUTES = 4 * HALF_MINUTE;
365
366 private final Context mContext;
367
368 public Bookmarker(Context context) {
369 mContext = context;
370 }
371
372 public void setBookmark(Uri uri, int bookmark, int duration) {
373 try {
374 BlobCache cache = CacheManager.getCache(mContext,
375 BOOKMARK_CACHE_FILE, BOOKMARK_CACHE_MAX_ENTRIES,
376 BOOKMARK_CACHE_MAX_BYTES, BOOKMARK_CACHE_VERSION);
377
378 ByteArrayOutputStream bos = new ByteArrayOutputStream();
379 DataOutputStream dos = new DataOutputStream(bos);
380 dos.writeUTF(uri.toString());
381 dos.writeInt(bookmark);
382 dos.writeInt(duration);
383 dos.flush();
384 cache.insert(uri.hashCode(), bos.toByteArray());
385 } catch (Throwable t) {
386 Log.w(TAG, "setBookmark failed", t);
387 }
388 }
389
390 public Integer getBookmark(Uri uri) {
391 try {
392 BlobCache cache = CacheManager.getCache(mContext,
393 BOOKMARK_CACHE_FILE, BOOKMARK_CACHE_MAX_ENTRIES,
394 BOOKMARK_CACHE_MAX_BYTES, BOOKMARK_CACHE_VERSION);
395
396 byte[] data = cache.lookup(uri.hashCode());
397 if (data == null) return null;
398
399 DataInputStream dis = new DataInputStream(
400 new ByteArrayInputStream(data));
401
402 String uriString = dis.readUTF(dis);
403 int bookmark = dis.readInt();
404 int duration = dis.readInt();
405
406 if (!uriString.equals(uri.toString())) {
407 return null;
408 }
409
410 if ((bookmark < HALF_MINUTE) || (duration < TWO_MINUTES)
411 || (bookmark > (duration - HALF_MINUTE))) {
412 return null;
413 }
414 return Integer.valueOf(bookmark);
415 } catch (Throwable t) {
416 Log.w(TAG, "getBookmark failed", t);
417 }
418 return null;
419 }
420}