MovieViewControl.java revision 1bb0c42b2a62f580eea4764d6a4434ffecfbf353
1package com.cooliris.media;
2
3/*
4 * Copyright (C) 2009 The Android Open Source Project
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *      http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19import android.app.AlertDialog;
20import android.content.ContentResolver;
21import android.content.ContentValues;
22import android.content.Context;
23import android.content.DialogInterface;
24import android.content.Intent;
25import android.content.DialogInterface.OnCancelListener;
26import android.content.DialogInterface.OnClickListener;
27import android.database.Cursor;
28import android.database.sqlite.SQLiteException;
29import android.media.MediaPlayer;
30import android.net.Uri;
31import android.os.Handler;
32import android.provider.MediaStore;
33import android.provider.MediaStore.Video;
34import android.view.View;
35import android.widget.MediaController;
36import android.widget.VideoView;
37
38public class MovieViewControl implements MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener {
39
40    @SuppressWarnings("unused")
41    private static final String TAG = "MovieViewControl";
42
43    private static final int ONE_MINUTE = 60 * 1000;
44    private static final int TWO_MINUTES = 2 * ONE_MINUTE;
45    private static final int FIVE_MINUTES = 5 * ONE_MINUTE;
46
47    // Copied from MediaPlaybackService in the Music Player app. Should be
48    // public, but isn't.
49    private static final String SERVICECMD = "com.android.music.musicservicecommand";
50    private static final String CMDNAME = "command";
51    private static final String CMDPAUSE = "pause";
52
53    private final VideoView mVideoView;
54    private final View mProgressView;
55    private final Uri mUri;
56    private final ContentResolver mContentResolver;
57
58    // State maintained for proper onPause/OnResume behaviour.
59    private int mPositionWhenPaused = -1;
60    private boolean mWasPlayingWhenPaused = false;
61
62    Handler mHandler = new Handler();
63
64    Runnable mPlayingChecker = new Runnable() {
65        public void run() {
66            if (mVideoView.isPlaying()) {
67                mProgressView.setVisibility(View.GONE);
68            } else {
69                mHandler.postDelayed(mPlayingChecker, 250);
70            }
71        }
72    };
73
74    public static String formatDuration(final Context context, int durationMs) {
75        int duration = durationMs / 1000;
76        int h = duration / 3600;
77        int m = (duration - h * 3600) / 60;
78        int s = duration - (h * 3600 + m * 60);
79        String durationValue;
80        if (h == 0) {
81            durationValue = String.format(context.getString(R.string.details_ms), m, s);
82        } else {
83            durationValue = String.format(context.getString(R.string.details_hms), h, m, s);
84        }
85        return durationValue;
86    }
87
88    public MovieViewControl(View rootView, Context context, Uri videoUri) {
89        mContentResolver = context.getContentResolver();
90        mVideoView = (VideoView) rootView.findViewById(R.id.surface_view);
91        mProgressView = rootView.findViewById(R.id.progress_indicator);
92
93        mUri = videoUri;
94
95        // For streams that we expect to be slow to start up, show a
96        // progress spinner until playback starts.
97        String scheme = mUri.getScheme();
98        if ("http".equalsIgnoreCase(scheme) || "rtsp".equalsIgnoreCase(scheme)) {
99            mHandler.postDelayed(mPlayingChecker, 250);
100        } else {
101            mProgressView.setVisibility(View.GONE);
102        }
103
104        mVideoView.setOnErrorListener(this);
105        mVideoView.setOnCompletionListener(this);
106        mVideoView.setVideoURI(mUri);
107        mVideoView.setMediaController(new MediaController(context));
108
109        // make the video view handle keys for seeking and pausing
110        mVideoView.requestFocus();
111
112        Intent i = new Intent(SERVICECMD);
113        i.putExtra(CMDNAME, CMDPAUSE);
114        context.sendBroadcast(i);
115
116        final Integer bookmark = getBookmark();
117        if (bookmark != null) {
118            AlertDialog.Builder builder = new AlertDialog.Builder(context);
119            builder.setTitle(R.string.resume_playing_title);
120            builder
121                    .setMessage(String
122                            .format(context.getString(R.string.resume_playing_message), formatDuration(context, bookmark)));
123            builder.setOnCancelListener(new OnCancelListener() {
124                public void onCancel(DialogInterface dialog) {
125                    onCompletion();
126                }
127            });
128            builder.setPositiveButton(R.string.resume_playing_resume, new OnClickListener() {
129                public void onClick(DialogInterface dialog, int which) {
130                    mVideoView.seekTo(bookmark);
131                    mVideoView.start();
132                }
133            });
134            builder.setNegativeButton(R.string.resume_playing_restart, new OnClickListener() {
135                public void onClick(DialogInterface dialog, int which) {
136                    mVideoView.start();
137                }
138            });
139            builder.show();
140        } else {
141            mVideoView.start();
142        }
143    }
144
145    private static boolean uriSupportsBookmarks(Uri uri) {
146        String scheme = uri.getScheme();
147        String authority = uri.getAuthority();
148        return ("content".equalsIgnoreCase(scheme) && MediaStore.AUTHORITY.equalsIgnoreCase(authority));
149    }
150
151    private Integer getBookmark() {
152        if (!uriSupportsBookmarks(mUri)) {
153            return null;
154        }
155
156        String[] projection = new String[] { Video.VideoColumns.DURATION, Video.VideoColumns.BOOKMARK };
157
158        try {
159            Cursor cursor = mContentResolver.query(mUri, projection, null, null, null);
160            if (cursor != null) {
161                try {
162                    if (cursor.moveToFirst()) {
163                        int duration = getCursorInteger(cursor, 0);
164                        int bookmark = getCursorInteger(cursor, 1);
165                        if ((bookmark < TWO_MINUTES) || (duration < FIVE_MINUTES) || (bookmark > (duration - ONE_MINUTE))) {
166                            return null;
167                        }
168                        return Integer.valueOf(bookmark);
169                    }
170                } finally {
171                    cursor.close();
172                }
173            }
174        } catch (SQLiteException e) {
175            // ignore
176        }
177
178        return null;
179    }
180
181    private static int getCursorInteger(Cursor cursor, int index) {
182        try {
183            return cursor.getInt(index);
184        } catch (SQLiteException e) {
185            return 0;
186        } catch (NumberFormatException e) {
187            return 0;
188        }
189
190    }
191
192    private void setBookmark(int bookmark) {
193        if (!uriSupportsBookmarks(mUri)) {
194            return;
195        }
196
197        ContentValues values = new ContentValues();
198        values.put(Video.VideoColumns.BOOKMARK, Integer.toString(bookmark));
199        try {
200            mContentResolver.update(mUri, values, null, null);
201        } catch (SecurityException ex) {
202            // Ignore, can happen if we try to set the bookmark on a read-only
203            // resource such as a video attached to GMail.
204        } catch (SQLiteException e) {
205            // ignore. can happen if the content doesn't support a bookmark
206            // column.
207        } catch (UnsupportedOperationException e) {
208            // ignore. can happen if the external volume is already detached.
209        }
210    }
211
212    public void onPause() {
213        mHandler.removeCallbacksAndMessages(null);
214        setBookmark(mVideoView.getCurrentPosition());
215
216        mPositionWhenPaused = mVideoView.getCurrentPosition();
217        mWasPlayingWhenPaused = mVideoView.isPlaying();
218        mVideoView.stopPlayback();
219    }
220
221    public void onResume() {
222        if (mPositionWhenPaused >= 0) {
223            mVideoView.setVideoURI(mUri);
224            mVideoView.seekTo(mPositionWhenPaused);
225            if (mWasPlayingWhenPaused) {
226                mVideoView.start();
227            }
228        }
229    }
230
231    public boolean onError(MediaPlayer player, int arg1, int arg2) {
232        mHandler.removeCallbacksAndMessages(null);
233        mProgressView.setVisibility(View.GONE);
234        return false;
235    }
236
237    public void onCompletion(MediaPlayer mp) {
238        onCompletion();
239    }
240
241    public void onCompletion() {
242    }
243}
244