MoviePlayer.java revision 540e4698824654232f300992ed8c03b20c9d5946
1/* 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; 36import android.os.Bundle; 37import 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 53 private static final String KEY_VIDEO_POSITION = "video-position"; 54 private static final String KEY_RESUMEABLE_TIME = "resumeable-timeout"; 55 56 // 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 61 // 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 65 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; 73 private final MediaController mMediaController; 74 75 private long mResumeableTime = Long.MAX_VALUE; 76 private int mVideoPosition = 0; 77 private boolean mHasPaused = false; 78 79 private final Runnable mPlayingChecker = new Runnable() { 80 @Override 81 public void run() { 82 if (mVideoView.isPlaying()) { 83 mProgressView.setVisibility(View.GONE); 84 } else { 85 mHandler.postDelayed(mPlayingChecker, 250); 86 } 87 } 88 }; 89 90 public MoviePlayer(View rootView, final MovieActivity movieActivity, Uri videoUri, 91 Bundle savedInstance) { 92 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 112 mMediaController = new MediaController(movieActivity) { 113 @Override 114 public void show() { 115 super.show(); 116 mActionBar.show(); 117 } 118 119 @Override 120 public void hide() { 121 super.hide(); 122 mActionBar.hide(); 123 } 124 }; 125 mMediaController.setOnKeyListener(new View.OnKeyListener() { 126 @Override 127 public boolean onKey(View v, int keyCode, KeyEvent event) { 128 if (keyCode == KeyEvent.KEYCODE_BACK) { 129 if (event.getAction() == KeyEvent.ACTION_UP) { 130 movieActivity.onBackPressed(); 131 } 132 return true; 133 } 134 return false; 135 } 136 }); 137 mVideoView.setMediaController(mMediaController); 138 139 mAudioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver(); 140 mAudioBecomingNoisyReceiver.register(); 141 142 // make the video view handle keys for seeking and pausing 143 mVideoView.requestFocus(); 144 145 Intent i = new Intent(SERVICECMD); 146 i.putExtra(CMDNAME, CMDPAUSE); 147 movieActivity.sendBroadcast(i); 148 149 if (savedInstance != null) { // this is a resumed activity 150 mVideoPosition = savedInstance.getInt(KEY_VIDEO_POSITION, 0); 151 mResumeableTime = savedInstance.getLong(KEY_RESUMEABLE_TIME, Long.MAX_VALUE); 152 mVideoView.start(); 153 mVideoView.suspend(); 154 mHasPaused = true; 155 } else { 156 final Integer bookmark = mBookmarker.getBookmark(mUri); 157 if (bookmark != null) { 158 showResumeDialog(movieActivity, bookmark); 159 } else { 160 mVideoView.start(); 161 } 162 } 163 } 164 165 public void onSaveInstanceState(Bundle outState) { 166 outState.putInt(KEY_VIDEO_POSITION, mVideoPosition); 167 outState.putLong(KEY_RESUMEABLE_TIME, mResumeableTime); 168 } 169 170 private void showResumeDialog(Context context, final int bookmark) { 171 AlertDialog.Builder builder = new AlertDialog.Builder(context); 172 builder.setTitle(R.string.resume_playing_title); 173 builder.setMessage(String.format( 174 context.getString(R.string.resume_playing_message), 175 GalleryUtils.formatDuration(context, bookmark / 1000))); 176 builder.setOnCancelListener(new OnCancelListener() { 177 @Override 178 public void onCancel(DialogInterface dialog) { 179 onCompletion(); 180 } 181 }); 182 builder.setPositiveButton( 183 R.string.resume_playing_resume, new OnClickListener() { 184 @Override 185 public void onClick(DialogInterface dialog, int which) { 186 mVideoView.seekTo(bookmark); 187 mVideoView.start(); 188 } 189 }); 190 builder.setNegativeButton( 191 R.string.resume_playing_restart, new OnClickListener() { 192 @Override 193 public void onClick(DialogInterface dialog, int which) { 194 mVideoView.start(); 195 } 196 }); 197 builder.show(); 198 } 199 200 public void onPause() { 201 mHasPaused = true; 202 mHandler.removeCallbacksAndMessages(null); 203 mVideoPosition = mVideoView.getCurrentPosition(); 204 mBookmarker.setBookmark(mUri, mVideoPosition, mVideoView.getDuration()); 205 mVideoView.suspend(); 206 mResumeableTime = System.currentTimeMillis() + RESUMEABLE_TIMEOUT; 207 } 208 209 public void onResume() { 210 if (mHasPaused) { 211 mVideoView.seekTo(mVideoPosition); 212 mVideoView.resume(); 213 214 // If we have slept for too long, pause the play 215 if (System.currentTimeMillis() > mResumeableTime) { 216 mMediaController.show(); 217 mVideoView.pause(); 218 } 219 } 220 } 221 222 public void onDestroy() { 223 mVideoView.stopPlayback(); 224 mAudioBecomingNoisyReceiver.unregister(); 225 } 226 227 public boolean onError(MediaPlayer player, int arg1, int arg2) { 228 mHandler.removeCallbacksAndMessages(null); 229 mProgressView.setVisibility(View.GONE); 230 return false; 231 } 232 233 public void onCompletion(MediaPlayer mp) { 234 onCompletion(); 235 } 236 237 public void onCompletion() { 238 } 239 240 private class AudioBecomingNoisyReceiver extends BroadcastReceiver { 241 242 public void register() { 243 mContext.registerReceiver(this, 244 new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); 245 } 246 247 public void unregister() { 248 mContext.unregisterReceiver(this); 249 } 250 251 @Override 252 public void onReceive(Context context, Intent intent) { 253 if (mVideoView.isPlaying()) { 254 mVideoView.pause(); 255 } 256 } 257 } 258} 259 260class Bookmarker { 261 private static final String TAG = "Bookmarker"; 262 263 private static final String BOOKMARK_CACHE_FILE = "bookmark"; 264 private static final int BOOKMARK_CACHE_MAX_ENTRIES = 100; 265 private static final int BOOKMARK_CACHE_MAX_BYTES = 10 * 1024; 266 private static final int BOOKMARK_CACHE_VERSION = 1; 267 268 private static final int HALF_MINUTE = 30 * 1000; 269 private static final int TWO_MINUTES = 4 * HALF_MINUTE; 270 271 private final Context mContext; 272 273 public Bookmarker(Context context) { 274 mContext = context; 275 } 276 277 public void setBookmark(Uri uri, int bookmark, int duration) { 278 try { 279 BlobCache cache = CacheManager.getCache(mContext, 280 BOOKMARK_CACHE_FILE, BOOKMARK_CACHE_MAX_ENTRIES, 281 BOOKMARK_CACHE_MAX_BYTES, BOOKMARK_CACHE_VERSION); 282 283 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 284 DataOutputStream dos = new DataOutputStream(bos); 285 dos.writeUTF(uri.toString()); 286 dos.writeInt(bookmark); 287 dos.writeInt(duration); 288 dos.flush(); 289 cache.insert(uri.hashCode(), bos.toByteArray()); 290 } catch (Throwable t) { 291 Log.w(TAG, "setBookmark failed", t); 292 } 293 } 294 295 public Integer getBookmark(Uri uri) { 296 try { 297 BlobCache cache = CacheManager.getCache(mContext, 298 BOOKMARK_CACHE_FILE, BOOKMARK_CACHE_MAX_ENTRIES, 299 BOOKMARK_CACHE_MAX_BYTES, BOOKMARK_CACHE_VERSION); 300 301 byte[] data = cache.lookup(uri.hashCode()); 302 if (data == null) return null; 303 304 DataInputStream dis = new DataInputStream( 305 new ByteArrayInputStream(data)); 306 307 String uriString = dis.readUTF(dis); 308 int bookmark = dis.readInt(); 309 int duration = dis.readInt(); 310 311 if (!uriString.equals(uri.toString())) { 312 return null; 313 } 314 315 if ((bookmark < HALF_MINUTE) || (duration < TWO_MINUTES) 316 || (bookmark > (duration - HALF_MINUTE))) { 317 return null; 318 } 319 return Integer.valueOf(bookmark); 320 } catch (Throwable t) { 321 Log.w(TAG, "getBookmark failed", t); 322 } 323 return null; 324 } 325} 326