blob: 62b85b852bca04b197e68645eeed2a32f98dab8c [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
19import com.android.gallery3d.R;
20import com.android.gallery3d.common.BlobCache;
21import com.android.gallery3d.util.CacheManager;
22import com.android.gallery3d.util.GalleryUtils;
23
24import android.app.ActionBar;
25import android.app.AlertDialog;
26import android.content.BroadcastReceiver;
27import android.content.Context;
28import android.content.DialogInterface;
29import android.content.DialogInterface.OnCancelListener;
30import android.content.DialogInterface.OnClickListener;
31import android.content.Intent;
32import android.content.IntentFilter;
33import android.media.AudioManager;
34import android.media.MediaPlayer;
35import android.net.Uri;
Owen Lin540e4692011-09-14 19:49:03 +080036import android.os.Bundle;
Owen Linf9a0a432011-08-17 22:07:43 +080037import android.os.Handler;
38import android.view.KeyEvent;
39import android.view.View;
40import android.widget.MediaController;
41import android.widget.VideoView;
42
43import java.io.ByteArrayInputStream;
44import java.io.ByteArrayOutputStream;
45import java.io.DataInputStream;
46import java.io.DataOutputStream;
47
48public class MoviePlayer implements
49 MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener {
50 @SuppressWarnings("unused")
51 private static final String TAG = "MoviePlayer";
52
Owen Lin540e4692011-09-14 19:49:03 +080053 private static final String KEY_VIDEO_POSITION = "video-position";
54 private static final String KEY_RESUMEABLE_TIME = "resumeable-timeout";
55
Owen Linf9a0a432011-08-17 22:07:43 +080056 // Copied from MediaPlaybackService in the Music Player app.
57 private static final String SERVICECMD = "com.android.music.musicservicecommand";
58 private static final String CMDNAME = "command";
59 private static final String CMDPAUSE = "pause";
60
Owen Lin540e4692011-09-14 19:49:03 +080061 // If we resume the acitivty with in RESUMEABLE_TIMEOUT, we will keep playing.
62 // Otherwise, we pause the player.
63 private static final long RESUMEABLE_TIMEOUT = 3 * 60 * 1000; // 3 mins
64
Owen Linf9a0a432011-08-17 22:07:43 +080065 private Context mContext;
66 private final VideoView mVideoView;
67 private final View mProgressView;
68 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;
Owen Lin540e4692011-09-14 19:49:03 +080073 private final MediaController mMediaController;
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
79 private final Runnable mPlayingChecker = new Runnable() {
Owen Lin540e4692011-09-14 19:49:03 +080080 @Override
Owen Linf9a0a432011-08-17 22:07:43 +080081 public void run() {
82 if (mVideoView.isPlaying()) {
83 mProgressView.setVisibility(View.GONE);
84 } else {
85 mHandler.postDelayed(mPlayingChecker, 250);
86 }
87 }
88 };
89
Owen Lin540e4692011-09-14 19:49:03 +080090 public MoviePlayer(View rootView, final MovieActivity movieActivity, Uri videoUri,
91 Bundle savedInstance) {
Owen Linf9a0a432011-08-17 22:07:43 +080092 mContext = movieActivity.getApplicationContext();
93 mVideoView = (VideoView) rootView.findViewById(R.id.surface_view);
94 mProgressView = rootView.findViewById(R.id.progress_indicator);
95 mBookmarker = new Bookmarker(movieActivity);
96 mActionBar = movieActivity.getActionBar();
97 mUri = videoUri;
98
99 // For streams that we expect to be slow to start up, show a
100 // progress spinner until playback starts.
101 String scheme = mUri.getScheme();
102 if ("http".equalsIgnoreCase(scheme) || "rtsp".equalsIgnoreCase(scheme)) {
103 mHandler.postDelayed(mPlayingChecker, 250);
104 } else {
105 mProgressView.setVisibility(View.GONE);
106 }
107
108 mVideoView.setOnErrorListener(this);
109 mVideoView.setOnCompletionListener(this);
110 mVideoView.setVideoURI(mUri);
111
Owen Lin540e4692011-09-14 19:49:03 +0800112 mMediaController = new MediaController(movieActivity) {
Owen Linf9a0a432011-08-17 22:07:43 +0800113 @Override
114 public void show() {
Chih-Chung Chang81760122011-09-27 16:50:16 +0800115 showSystemUi(true);
Owen Linf9a0a432011-08-17 22:07:43 +0800116 mActionBar.show();
Chih-Chung Chang81760122011-09-27 16:50:16 +0800117 super.show();
Owen Linf9a0a432011-08-17 22:07:43 +0800118 }
119
120 @Override
121 public void hide() {
122 super.hide();
123 mActionBar.hide();
Chih-Chung Chang81760122011-09-27 16:50:16 +0800124 showSystemUi(false);
Owen Linf9a0a432011-08-17 22:07:43 +0800125 }
126 };
Chih-Chung Chang81760122011-09-27 16:50:16 +0800127
128 mMediaController.show();
129
130 // When the user touches the screen or uses some hard key, the framework
131 // will change system ui visibility from invisible to visible. We show
132 // the media control at this point.
133 mVideoView.setOnSystemUiVisibilityChangeListener(
134 new View.OnSystemUiVisibilityChangeListener() {
135 public void onSystemUiVisibilityChange(int visibility) {
136 if ((visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
137 mMediaController.show();
138 }
139 }
140 });
141
Owen Lin540e4692011-09-14 19:49:03 +0800142 mMediaController.setOnKeyListener(new View.OnKeyListener() {
143 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800144 public boolean onKey(View v, int keyCode, KeyEvent event) {
145 if (keyCode == KeyEvent.KEYCODE_BACK) {
146 if (event.getAction() == KeyEvent.ACTION_UP) {
147 movieActivity.onBackPressed();
148 }
149 return true;
150 }
151 return false;
152 }
153 });
Owen Lin540e4692011-09-14 19:49:03 +0800154 mVideoView.setMediaController(mMediaController);
Owen Linf9a0a432011-08-17 22:07:43 +0800155
156 mAudioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver();
157 mAudioBecomingNoisyReceiver.register();
158
159 // make the video view handle keys for seeking and pausing
160 mVideoView.requestFocus();
161
162 Intent i = new Intent(SERVICECMD);
163 i.putExtra(CMDNAME, CMDPAUSE);
164 movieActivity.sendBroadcast(i);
165
Owen Lin540e4692011-09-14 19:49:03 +0800166 if (savedInstance != null) { // this is a resumed activity
167 mVideoPosition = savedInstance.getInt(KEY_VIDEO_POSITION, 0);
168 mResumeableTime = savedInstance.getLong(KEY_RESUMEABLE_TIME, Long.MAX_VALUE);
Owen Linf9a0a432011-08-17 22:07:43 +0800169 mVideoView.start();
Owen Lin540e4692011-09-14 19:49:03 +0800170 mVideoView.suspend();
171 mHasPaused = true;
172 } else {
173 final Integer bookmark = mBookmarker.getBookmark(mUri);
174 if (bookmark != null) {
175 showResumeDialog(movieActivity, bookmark);
176 } else {
177 mVideoView.start();
178 }
Owen Linf9a0a432011-08-17 22:07:43 +0800179 }
180 }
181
Chih-Chung Chang81760122011-09-27 16:50:16 +0800182 private void showSystemUi(boolean visible) {
183 int flag = visible ? 0 : View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
184 mVideoView.setSystemUiVisibility(flag);
185 mMediaController.setSystemUiVisibility(flag);
186 }
187
Owen Lin540e4692011-09-14 19:49:03 +0800188 public void onSaveInstanceState(Bundle outState) {
189 outState.putInt(KEY_VIDEO_POSITION, mVideoPosition);
190 outState.putLong(KEY_RESUMEABLE_TIME, mResumeableTime);
191 }
192
Owen Linf9a0a432011-08-17 22:07:43 +0800193 private void showResumeDialog(Context context, final int bookmark) {
194 AlertDialog.Builder builder = new AlertDialog.Builder(context);
195 builder.setTitle(R.string.resume_playing_title);
196 builder.setMessage(String.format(
197 context.getString(R.string.resume_playing_message),
198 GalleryUtils.formatDuration(context, bookmark / 1000)));
199 builder.setOnCancelListener(new OnCancelListener() {
Owen Lin540e4692011-09-14 19:49:03 +0800200 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800201 public void onCancel(DialogInterface dialog) {
202 onCompletion();
203 }
204 });
205 builder.setPositiveButton(
206 R.string.resume_playing_resume, new OnClickListener() {
Owen Lin540e4692011-09-14 19:49:03 +0800207 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800208 public void onClick(DialogInterface dialog, int which) {
209 mVideoView.seekTo(bookmark);
210 mVideoView.start();
211 }
212 });
213 builder.setNegativeButton(
214 R.string.resume_playing_restart, new OnClickListener() {
Owen Lin540e4692011-09-14 19:49:03 +0800215 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800216 public void onClick(DialogInterface dialog, int which) {
217 mVideoView.start();
218 }
219 });
220 builder.show();
221 }
222
223 public void onPause() {
Owen Linf9a0a432011-08-17 22:07:43 +0800224 mHasPaused = true;
Owen Lin540e4692011-09-14 19:49:03 +0800225 mHandler.removeCallbacksAndMessages(null);
226 mVideoPosition = mVideoView.getCurrentPosition();
227 mBookmarker.setBookmark(mUri, mVideoPosition, mVideoView.getDuration());
228 mVideoView.suspend();
229 mResumeableTime = System.currentTimeMillis() + RESUMEABLE_TIMEOUT;
Owen Linf9a0a432011-08-17 22:07:43 +0800230 }
231
232 public void onResume() {
233 if (mHasPaused) {
Owen Lin540e4692011-09-14 19:49:03 +0800234 mVideoView.seekTo(mVideoPosition);
235 mVideoView.resume();
236
237 // If we have slept for too long, pause the play
238 if (System.currentTimeMillis() > mResumeableTime) {
239 mMediaController.show();
240 mVideoView.pause();
Owen Linf9a0a432011-08-17 22:07:43 +0800241 }
242 }
Owen Linf9a0a432011-08-17 22:07:43 +0800243 }
244
245 public void onDestroy() {
246 mVideoView.stopPlayback();
247 mAudioBecomingNoisyReceiver.unregister();
248 }
249
250 public boolean onError(MediaPlayer player, int arg1, int arg2) {
251 mHandler.removeCallbacksAndMessages(null);
252 mProgressView.setVisibility(View.GONE);
253 return false;
254 }
255
256 public void onCompletion(MediaPlayer mp) {
257 onCompletion();
258 }
259
260 public void onCompletion() {
261 }
262
263 private class AudioBecomingNoisyReceiver extends BroadcastReceiver {
264
265 public void register() {
266 mContext.registerReceiver(this,
267 new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
268 }
269
270 public void unregister() {
271 mContext.unregisterReceiver(this);
272 }
273
274 @Override
275 public void onReceive(Context context, Intent intent) {
276 if (mVideoView.isPlaying()) {
277 mVideoView.pause();
278 }
279 }
280 }
281}
282
283class Bookmarker {
284 private static final String TAG = "Bookmarker";
285
286 private static final String BOOKMARK_CACHE_FILE = "bookmark";
287 private static final int BOOKMARK_CACHE_MAX_ENTRIES = 100;
288 private static final int BOOKMARK_CACHE_MAX_BYTES = 10 * 1024;
289 private static final int BOOKMARK_CACHE_VERSION = 1;
290
291 private static final int HALF_MINUTE = 30 * 1000;
292 private static final int TWO_MINUTES = 4 * HALF_MINUTE;
293
294 private final Context mContext;
295
296 public Bookmarker(Context context) {
297 mContext = context;
298 }
299
300 public void setBookmark(Uri uri, int bookmark, int duration) {
301 try {
302 BlobCache cache = CacheManager.getCache(mContext,
303 BOOKMARK_CACHE_FILE, BOOKMARK_CACHE_MAX_ENTRIES,
304 BOOKMARK_CACHE_MAX_BYTES, BOOKMARK_CACHE_VERSION);
305
306 ByteArrayOutputStream bos = new ByteArrayOutputStream();
307 DataOutputStream dos = new DataOutputStream(bos);
308 dos.writeUTF(uri.toString());
309 dos.writeInt(bookmark);
310 dos.writeInt(duration);
311 dos.flush();
312 cache.insert(uri.hashCode(), bos.toByteArray());
313 } catch (Throwable t) {
314 Log.w(TAG, "setBookmark failed", t);
315 }
316 }
317
318 public Integer getBookmark(Uri uri) {
319 try {
320 BlobCache cache = CacheManager.getCache(mContext,
321 BOOKMARK_CACHE_FILE, BOOKMARK_CACHE_MAX_ENTRIES,
322 BOOKMARK_CACHE_MAX_BYTES, BOOKMARK_CACHE_VERSION);
323
324 byte[] data = cache.lookup(uri.hashCode());
325 if (data == null) return null;
326
327 DataInputStream dis = new DataInputStream(
328 new ByteArrayInputStream(data));
329
330 String uriString = dis.readUTF(dis);
331 int bookmark = dis.readInt();
332 int duration = dis.readInt();
333
334 if (!uriString.equals(uri.toString())) {
335 return null;
336 }
337
338 if ((bookmark < HALF_MINUTE) || (duration < TWO_MINUTES)
339 || (bookmark > (duration - HALF_MINUTE))) {
340 return null;
341 }
342 return Integer.valueOf(bookmark);
343 } catch (Throwable t) {
344 Log.w(TAG, "getBookmark failed", t);
345 }
346 return null;
347 }
348}