blob: ce9183483b74ff06315f829a92a5afa2b62ebce5 [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 Lin7a5e1e72012-06-20 16:54:24 +080019import android.annotation.TargetApi;
Owen Linf9a0a432011-08-17 22:07:43 +080020import 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 Lindf4763c2012-06-28 17:12:20 +080031import android.os.Build;
Owen Lin540e4692011-09-14 19:49:03 +080032import android.os.Bundle;
Owen Linf9a0a432011-08-17 22:07:43 +080033import android.os.Handler;
Chih-Chung Changfc8c5032011-11-29 14:21:11 +080034import android.view.KeyEvent;
Chih-Chung Chang209a9162011-10-14 16:09:09 +080035import android.view.MotionEvent;
Owen Linf9a0a432011-08-17 22:07:43 +080036import android.view.View;
Chih-Chung Chang209a9162011-10-14 16:09:09 +080037import android.view.ViewGroup;
Owen Linf9a0a432011-08-17 22:07:43 +080038import android.widget.VideoView;
39
Owen Linaea077a2011-11-11 12:01:09 +080040import com.android.gallery3d.R;
Owen Lin7a5e1e72012-06-20 16:54:24 +080041import com.android.gallery3d.common.ApiHelper;
Owen Linaea077a2011-11-11 12:01:09 +080042import com.android.gallery3d.common.BlobCache;
43import com.android.gallery3d.util.CacheManager;
44import com.android.gallery3d.util.GalleryUtils;
45
Owen Linf9a0a432011-08-17 22:07:43 +080046import java.io.ByteArrayInputStream;
47import java.io.ByteArrayOutputStream;
48import java.io.DataInputStream;
49import java.io.DataOutputStream;
50
51public class MoviePlayer implements
Chih-Chung Chang209a9162011-10-14 16:09:09 +080052 MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener,
53 ControllerOverlay.Listener {
Owen Linf9a0a432011-08-17 22:07:43 +080054 @SuppressWarnings("unused")
55 private static final String TAG = "MoviePlayer";
56
Owen Lin540e4692011-09-14 19:49:03 +080057 private static final String KEY_VIDEO_POSITION = "video-position";
58 private static final String KEY_RESUMEABLE_TIME = "resumeable-timeout";
59
Chih-Chung Chang67721732012-07-03 12:49:45 +080060 // These are constants in KeyEvent, appearing on API level 11.
61 private static final int KEYCODE_MEDIA_PLAY = 126;
62 private static final int KEYCODE_MEDIA_PAUSE = 127;
63
Owen Linf9a0a432011-08-17 22:07:43 +080064 // Copied from MediaPlaybackService in the Music Player app.
65 private static final String SERVICECMD = "com.android.music.musicservicecommand";
66 private static final String CMDNAME = "command";
67 private static final String CMDPAUSE = "pause";
68
Owen Lin00bb8e42012-05-23 15:47:36 -070069 private static final long BLACK_TIMEOUT = 500;
70
Owen Lin540e4692011-09-14 19:49:03 +080071 // If we resume the acitivty with in RESUMEABLE_TIMEOUT, we will keep playing.
72 // Otherwise, we pause the player.
73 private static final long RESUMEABLE_TIMEOUT = 3 * 60 * 1000; // 3 mins
74
Owen Linf9a0a432011-08-17 22:07:43 +080075 private Context mContext;
76 private final VideoView mVideoView;
Owen Lin00bb8e42012-05-23 15:47:36 -070077 private final View mRootView;
Owen Linf9a0a432011-08-17 22:07:43 +080078 private final Bookmarker mBookmarker;
79 private final Uri mUri;
80 private final Handler mHandler = new Handler();
81 private final AudioBecomingNoisyReceiver mAudioBecomingNoisyReceiver;
Owen Lin00bb8e42012-05-23 15:47:36 -070082 private final MovieControllerOverlay mController;
Owen Linf9a0a432011-08-17 22:07:43 +080083
Owen Lin540e4692011-09-14 19:49:03 +080084 private long mResumeableTime = Long.MAX_VALUE;
85 private int mVideoPosition = 0;
86 private boolean mHasPaused = false;
Ray Chen24525c22012-05-22 17:34:29 +080087 private int mLastSystemUiVis = 0;
Owen Linf9a0a432011-08-17 22:07:43 +080088
Chih-Chung Chang209a9162011-10-14 16:09:09 +080089 // If the time bar is being dragged.
90 private boolean mDragging;
91
92 // If the time bar is visible.
93 private boolean mShowing;
94
Owen Linf9a0a432011-08-17 22:07:43 +080095 private final Runnable mPlayingChecker = new Runnable() {
Owen Lin540e4692011-09-14 19:49:03 +080096 @Override
Owen Linf9a0a432011-08-17 22:07:43 +080097 public void run() {
98 if (mVideoView.isPlaying()) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +080099 mController.showPlaying();
Owen Linf9a0a432011-08-17 22:07:43 +0800100 } else {
101 mHandler.postDelayed(mPlayingChecker, 250);
102 }
103 }
104 };
105
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800106 private final Runnable mProgressChecker = new Runnable() {
107 @Override
108 public void run() {
109 int pos = setProgress();
110 mHandler.postDelayed(mProgressChecker, 1000 - (pos % 1000));
111 }
112 };
113
Owen Lin00bb8e42012-05-23 15:47:36 -0700114 public MoviePlayer(View rootView, final MovieActivity movieActivity,
115 Uri videoUri, Bundle savedInstance, boolean canReplay) {
Owen Linf9a0a432011-08-17 22:07:43 +0800116 mContext = movieActivity.getApplicationContext();
Owen Lin00bb8e42012-05-23 15:47:36 -0700117 mRootView = rootView;
Owen Linf9a0a432011-08-17 22:07:43 +0800118 mVideoView = (VideoView) rootView.findViewById(R.id.surface_view);
Owen Linf9a0a432011-08-17 22:07:43 +0800119 mBookmarker = new Bookmarker(movieActivity);
Owen Linf9a0a432011-08-17 22:07:43 +0800120 mUri = videoUri;
121
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800122 mController = new MovieControllerOverlay(mContext);
123 ((ViewGroup)rootView).addView(mController.getView());
124 mController.setListener(this);
125 mController.setCanReplay(canReplay);
Owen Linf9a0a432011-08-17 22:07:43 +0800126
127 mVideoView.setOnErrorListener(this);
128 mVideoView.setOnCompletionListener(this);
129 mVideoView.setVideoURI(mUri);
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800130 mVideoView.setOnTouchListener(new View.OnTouchListener() {
Owen Lin00bb8e42012-05-23 15:47:36 -0700131 @Override
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800132 public boolean onTouch(View v, MotionEvent event) {
133 mController.show();
134 return true;
Owen Linf9a0a432011-08-17 22:07:43 +0800135 }
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800136 });
ztenghuie8b4b342013-04-17 14:41:05 -0700137 mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
138 @Override
139 public void onPrepared(MediaPlayer player) {
140 if (!mVideoView.canSeekForward() || !mVideoView.canSeekBackward()) {
141 mController.setSeekable(false);
142 } else {
143 mController.setSeekable(true);
144 }
145 setProgress();
146 }
147 });
Chih-Chung Chang81760122011-09-27 16:50:16 +0800148
Owen Lin00bb8e42012-05-23 15:47:36 -0700149 // The SurfaceView is transparent before drawing the first frame.
150 // This makes the UI flashing when open a video. (black -> old screen
151 // -> video) However, we have no way to know the timing of the first
152 // frame. So, we hide the VideoView for a while to make sure the
153 // video has been drawn on it.
154 mVideoView.postDelayed(new Runnable() {
155 @Override
156 public void run() {
157 mVideoView.setVisibility(View.VISIBLE);
158 }
159 }, BLACK_TIMEOUT);
160
Owen Lin7a5e1e72012-06-20 16:54:24 +0800161 setOnSystemUiVisibilityChangeListener();
162 // Hide system UI by default
163 showSystemUi(false);
164
165 mAudioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver();
166 mAudioBecomingNoisyReceiver.register();
167
168 Intent i = new Intent(SERVICECMD);
169 i.putExtra(CMDNAME, CMDPAUSE);
170 movieActivity.sendBroadcast(i);
171
172 if (savedInstance != null) { // this is a resumed activity
173 mVideoPosition = savedInstance.getInt(KEY_VIDEO_POSITION, 0);
174 mResumeableTime = savedInstance.getLong(KEY_RESUMEABLE_TIME, Long.MAX_VALUE);
175 mVideoView.start();
176 mVideoView.suspend();
177 mHasPaused = true;
178 } else {
179 final Integer bookmark = mBookmarker.getBookmark(mUri);
180 if (bookmark != null) {
181 showResumeDialog(movieActivity, bookmark);
182 } else {
183 startVideo();
184 }
185 }
186 }
187
Owen Lindf4763c2012-06-28 17:12:20 +0800188 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
Owen Lin7a5e1e72012-06-20 16:54:24 +0800189 private void setOnSystemUiVisibilityChangeListener() {
190 if (!ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_HIDE_NAVIGATION) return;
191
Chih-Chung Chang81760122011-09-27 16:50:16 +0800192 // When the user touches the screen or uses some hard key, the framework
193 // will change system ui visibility from invisible to visible. We show
Ray Chen392a2922012-05-14 17:19:56 +0800194 // the media control and enable system UI (e.g. ActionBar) to be visible at this point
Chih-Chung Chang81760122011-09-27 16:50:16 +0800195 mVideoView.setOnSystemUiVisibilityChangeListener(
196 new View.OnSystemUiVisibilityChangeListener() {
Owen Lin00bb8e42012-05-23 15:47:36 -0700197 @Override
Chih-Chung Chang81760122011-09-27 16:50:16 +0800198 public void onSystemUiVisibilityChange(int visibility) {
Ray Chen24525c22012-05-22 17:34:29 +0800199 int diff = mLastSystemUiVis ^ visibility;
200 mLastSystemUiVis = visibility;
201 if ((diff & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
202 && (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800203 mController.show();
Chih-Chung Chang81760122011-09-27 16:50:16 +0800204 }
205 }
206 });
Owen Linf9a0a432011-08-17 22:07:43 +0800207 }
208
Owen Lin28cb4162012-08-29 11:53:10 +0800209 @SuppressWarnings("deprecation")
Owen Lindf4763c2012-06-28 17:12:20 +0800210 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
Chih-Chung Chang81760122011-09-27 16:50:16 +0800211 private void showSystemUi(boolean visible) {
Owen Lin7a5e1e72012-06-20 16:54:24 +0800212 if (!ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) return;
213
Ray Chen24525c22012-05-22 17:34:29 +0800214 int flag = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
215 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
Owen Lin7a5e1e72012-06-20 16:54:24 +0800216 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
Ray Chen24525c22012-05-22 17:34:29 +0800217 if (!visible) {
Owen Lin28cb4162012-08-29 11:53:10 +0800218 // We used the deprecated "STATUS_BAR_HIDDEN" for unbundling
The Android Open Source Projectca7d9bf2012-06-29 08:19:39 -0700219 flag |= View.STATUS_BAR_HIDDEN | View.SYSTEM_UI_FLAG_FULLSCREEN
Ray Chen24525c22012-05-22 17:34:29 +0800220 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
Ray Chen392a2922012-05-14 17:19:56 +0800221 }
Ray Chen24525c22012-05-22 17:34:29 +0800222 mVideoView.setSystemUiVisibility(flag);
Chih-Chung Chang81760122011-09-27 16:50:16 +0800223 }
224
Owen Lin540e4692011-09-14 19:49:03 +0800225 public void onSaveInstanceState(Bundle outState) {
226 outState.putInt(KEY_VIDEO_POSITION, mVideoPosition);
227 outState.putLong(KEY_RESUMEABLE_TIME, mResumeableTime);
228 }
229
Owen Linf9a0a432011-08-17 22:07:43 +0800230 private void showResumeDialog(Context context, final int bookmark) {
231 AlertDialog.Builder builder = new AlertDialog.Builder(context);
232 builder.setTitle(R.string.resume_playing_title);
233 builder.setMessage(String.format(
234 context.getString(R.string.resume_playing_message),
235 GalleryUtils.formatDuration(context, bookmark / 1000)));
236 builder.setOnCancelListener(new OnCancelListener() {
Owen Lin540e4692011-09-14 19:49:03 +0800237 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800238 public void onCancel(DialogInterface dialog) {
239 onCompletion();
240 }
241 });
242 builder.setPositiveButton(
243 R.string.resume_playing_resume, new OnClickListener() {
Owen Lin540e4692011-09-14 19:49:03 +0800244 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800245 public void onClick(DialogInterface dialog, int which) {
246 mVideoView.seekTo(bookmark);
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800247 startVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800248 }
249 });
250 builder.setNegativeButton(
251 R.string.resume_playing_restart, new OnClickListener() {
Owen Lin540e4692011-09-14 19:49:03 +0800252 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800253 public void onClick(DialogInterface dialog, int which) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800254 startVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800255 }
256 });
257 builder.show();
258 }
259
260 public void onPause() {
Owen Linf9a0a432011-08-17 22:07:43 +0800261 mHasPaused = true;
Owen Lin540e4692011-09-14 19:49:03 +0800262 mHandler.removeCallbacksAndMessages(null);
263 mVideoPosition = mVideoView.getCurrentPosition();
264 mBookmarker.setBookmark(mUri, mVideoPosition, mVideoView.getDuration());
265 mVideoView.suspend();
266 mResumeableTime = System.currentTimeMillis() + RESUMEABLE_TIMEOUT;
Owen Linf9a0a432011-08-17 22:07:43 +0800267 }
268
269 public void onResume() {
270 if (mHasPaused) {
Owen Lin540e4692011-09-14 19:49:03 +0800271 mVideoView.seekTo(mVideoPosition);
272 mVideoView.resume();
273
274 // If we have slept for too long, pause the play
275 if (System.currentTimeMillis() > mResumeableTime) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800276 pauseVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800277 }
278 }
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800279 mHandler.post(mProgressChecker);
Owen Linf9a0a432011-08-17 22:07:43 +0800280 }
281
282 public void onDestroy() {
283 mVideoView.stopPlayback();
284 mAudioBecomingNoisyReceiver.unregister();
285 }
286
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800287 // This updates the time bar display (if necessary). It is called every
288 // second by mProgressChecker and also from places where the time bar needs
289 // to be updated immediately.
290 private int setProgress() {
291 if (mDragging || !mShowing) {
292 return 0;
293 }
294 int position = mVideoView.getCurrentPosition();
295 int duration = mVideoView.getDuration();
Teng-Hui Zhu3f1f1ba2012-08-24 14:50:37 -0700296 mController.setTimes(position, duration, 0, 0);
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800297 return position;
298 }
299
300 private void startVideo() {
301 // For streams that we expect to be slow to start up, show a
302 // progress spinner until playback starts.
303 String scheme = mUri.getScheme();
304 if ("http".equalsIgnoreCase(scheme) || "rtsp".equalsIgnoreCase(scheme)) {
305 mController.showLoading();
306 mHandler.removeCallbacks(mPlayingChecker);
307 mHandler.postDelayed(mPlayingChecker, 250);
308 } else {
309 mController.showPlaying();
Owen Lin00bb8e42012-05-23 15:47:36 -0700310 mController.hide();
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800311 }
312
313 mVideoView.start();
314 setProgress();
315 }
316
317 private void playVideo() {
318 mVideoView.start();
319 mController.showPlaying();
320 setProgress();
321 }
322
323 private void pauseVideo() {
324 mVideoView.pause();
325 mController.showPaused();
326 }
327
328 // Below are notifications from VideoView
329 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800330 public boolean onError(MediaPlayer player, int arg1, int arg2) {
331 mHandler.removeCallbacksAndMessages(null);
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800332 // VideoView will show an error dialog if we return false, so no need
333 // to show more message.
334 mController.showErrorMessage("");
Owen Linf9a0a432011-08-17 22:07:43 +0800335 return false;
336 }
337
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800338 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800339 public void onCompletion(MediaPlayer mp) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800340 mController.showEnded();
Owen Linf9a0a432011-08-17 22:07:43 +0800341 onCompletion();
342 }
343
344 public void onCompletion() {
345 }
346
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800347 // Below are notifications from ControllerOverlay
348 @Override
349 public void onPlayPause() {
350 if (mVideoView.isPlaying()) {
351 pauseVideo();
352 } else {
353 playVideo();
354 }
355 }
356
357 @Override
358 public void onSeekStart() {
359 mDragging = true;
360 }
361
362 @Override
363 public void onSeekMove(int time) {
364 mVideoView.seekTo(time);
365 }
366
367 @Override
Teng-Hui Zhu3f1f1ba2012-08-24 14:50:37 -0700368 public void onSeekEnd(int time, int start, int end) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800369 mDragging = false;
370 mVideoView.seekTo(time);
371 setProgress();
372 }
373
374 @Override
375 public void onShown() {
376 mShowing = true;
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800377 setProgress();
Owen Lin0b4e9092012-06-19 14:35:06 +0800378 showSystemUi(true);
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800379 }
380
381 @Override
382 public void onHidden() {
383 mShowing = false;
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800384 showSystemUi(false);
385 }
386
387 @Override
388 public void onReplay() {
389 startVideo();
390 }
391
Chih-Chung Changfc8c5032011-11-29 14:21:11 +0800392 // Below are key events passed from MovieActivity.
393 public boolean onKeyDown(int keyCode, KeyEvent event) {
394
395 // Some headsets will fire off 7-10 events on a single click
396 if (event.getRepeatCount() > 0) {
397 return isMediaKey(keyCode);
398 }
399
400 switch (keyCode) {
401 case KeyEvent.KEYCODE_HEADSETHOOK:
402 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
403 if (mVideoView.isPlaying()) {
404 pauseVideo();
405 } else {
406 playVideo();
407 }
408 return true;
Chih-Chung Chang67721732012-07-03 12:49:45 +0800409 case KEYCODE_MEDIA_PAUSE:
Chih-Chung Changfc8c5032011-11-29 14:21:11 +0800410 if (mVideoView.isPlaying()) {
411 pauseVideo();
412 }
413 return true;
Chih-Chung Chang67721732012-07-03 12:49:45 +0800414 case KEYCODE_MEDIA_PLAY:
Chih-Chung Changfc8c5032011-11-29 14:21:11 +0800415 if (!mVideoView.isPlaying()) {
416 playVideo();
417 }
418 return true;
419 case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
420 case KeyEvent.KEYCODE_MEDIA_NEXT:
421 // TODO: Handle next / previous accordingly, for now we're
422 // just consuming the events.
423 return true;
424 }
425 return false;
426 }
427
428 public boolean onKeyUp(int keyCode, KeyEvent event) {
429 return isMediaKey(keyCode);
430 }
431
432 private static boolean isMediaKey(int keyCode) {
433 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK
434 || keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS
435 || keyCode == KeyEvent.KEYCODE_MEDIA_NEXT
436 || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
437 || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY
438 || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE;
439 }
440
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800441 // We want to pause when the headset is unplugged.
Owen Linf9a0a432011-08-17 22:07:43 +0800442 private class AudioBecomingNoisyReceiver extends BroadcastReceiver {
443
444 public void register() {
445 mContext.registerReceiver(this,
446 new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
447 }
448
449 public void unregister() {
450 mContext.unregisterReceiver(this);
451 }
452
453 @Override
454 public void onReceive(Context context, Intent intent) {
Owen Linaea077a2011-11-11 12:01:09 +0800455 if (mVideoView.isPlaying()) pauseVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800456 }
457 }
458}
459
460class Bookmarker {
461 private static final String TAG = "Bookmarker";
462
463 private static final String BOOKMARK_CACHE_FILE = "bookmark";
464 private static final int BOOKMARK_CACHE_MAX_ENTRIES = 100;
465 private static final int BOOKMARK_CACHE_MAX_BYTES = 10 * 1024;
466 private static final int BOOKMARK_CACHE_VERSION = 1;
467
468 private static final int HALF_MINUTE = 30 * 1000;
469 private static final int TWO_MINUTES = 4 * HALF_MINUTE;
470
471 private final Context mContext;
472
473 public Bookmarker(Context context) {
474 mContext = context;
475 }
476
477 public void setBookmark(Uri uri, int bookmark, int duration) {
478 try {
479 BlobCache cache = CacheManager.getCache(mContext,
480 BOOKMARK_CACHE_FILE, BOOKMARK_CACHE_MAX_ENTRIES,
481 BOOKMARK_CACHE_MAX_BYTES, BOOKMARK_CACHE_VERSION);
482
483 ByteArrayOutputStream bos = new ByteArrayOutputStream();
484 DataOutputStream dos = new DataOutputStream(bos);
485 dos.writeUTF(uri.toString());
486 dos.writeInt(bookmark);
487 dos.writeInt(duration);
488 dos.flush();
489 cache.insert(uri.hashCode(), bos.toByteArray());
490 } catch (Throwable t) {
491 Log.w(TAG, "setBookmark failed", t);
492 }
493 }
494
495 public Integer getBookmark(Uri uri) {
496 try {
497 BlobCache cache = CacheManager.getCache(mContext,
498 BOOKMARK_CACHE_FILE, BOOKMARK_CACHE_MAX_ENTRIES,
499 BOOKMARK_CACHE_MAX_BYTES, BOOKMARK_CACHE_VERSION);
500
501 byte[] data = cache.lookup(uri.hashCode());
502 if (data == null) return null;
503
504 DataInputStream dis = new DataInputStream(
505 new ByteArrayInputStream(data));
506
Owen Lin7a5e1e72012-06-20 16:54:24 +0800507 String uriString = DataInputStream.readUTF(dis);
Owen Linf9a0a432011-08-17 22:07:43 +0800508 int bookmark = dis.readInt();
509 int duration = dis.readInt();
510
511 if (!uriString.equals(uri.toString())) {
512 return null;
513 }
514
515 if ((bookmark < HALF_MINUTE) || (duration < TWO_MINUTES)
516 || (bookmark > (duration - HALF_MINUTE))) {
517 return null;
518 }
519 return Integer.valueOf(bookmark);
520 } catch (Throwable t) {
521 Log.w(TAG, "getBookmark failed", t);
522 }
523 return null;
524 }
525}