blob: 58a2b0f00877de059fcb558cd831ed62618e078a [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 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 android.widget;
18
Jean-Michel Trivi0f49f822017-02-16 14:36:43 -080019import android.annotation.NonNull;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.app.AlertDialog;
21import android.content.Context;
22import android.content.DialogInterface;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.content.res.Resources;
Alan Viverettede213f72013-09-03 16:17:56 -070024import android.graphics.Canvas;
Jean-Michel Trivi0f49f822017-02-16 14:36:43 -080025import android.media.AudioAttributes;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.media.AudioManager;
Jaesung Chung978bf5e2015-12-07 21:46:40 +090027import android.media.Cea708CaptionRenderer;
Chong Zhangbdfd9102014-06-11 15:10:23 -070028import android.media.ClosedCaptionRenderer;
Lajos Molnaraf3098242013-08-15 20:56:53 -070029import android.media.MediaFormat;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.media.MediaPlayer;
31import android.media.MediaPlayer.OnCompletionListener;
32import android.media.MediaPlayer.OnErrorListener;
James Donga0ba7942012-07-27 17:30:38 -070033import android.media.MediaPlayer.OnInfoListener;
Lajos Molnaraf3098242013-08-15 20:56:53 -070034import android.media.Metadata;
Lajos Molnar484ff7a92013-08-15 11:37:47 -070035import android.media.SubtitleController;
Alan Viveretted43daf32013-09-05 16:34:30 -070036import android.media.SubtitleTrack.RenderingWidget;
Sungsoo Limba3699b2014-03-17 10:24:32 +090037import android.media.TtmlRenderer;
Lajos Molnar484ff7a92013-08-15 11:37:47 -070038import android.media.WebVttRenderer;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import android.net.Uri;
Lajos Molnar29f51832013-09-20 08:45:31 -070040import android.os.Looper;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041import android.util.AttributeSet;
42import android.util.Log;
Lajos Molnar484ff7a92013-08-15 11:37:47 -070043import android.util.Pair;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044import android.view.KeyEvent;
45import android.view.MotionEvent;
46import android.view.SurfaceHolder;
47import android.view.SurfaceView;
48import android.view.View;
Owen Lina8381df2010-11-09 19:32:30 +080049import android.widget.MediaController.MediaPlayerControl;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050
51import java.io.IOException;
Lajos Molnaraf3098242013-08-15 20:56:53 -070052import java.io.InputStream;
Andreas Huber25643002010-01-28 11:19:57 -080053import java.util.Map;
Lajos Molnaraf3098242013-08-15 20:56:53 -070054import java.util.Vector;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055
56/**
57 * Displays a video file. The VideoView class
58 * can load images from various sources (such as resources or content
59 * providers), takes care of computing its measurement from the video so that
60 * it can be used in any layout manager, and provides various display options
Lajos Molnar7e11b162013-10-30 14:04:10 -070061 * such as scaling and tinting.<p>
62 *
63 * <em>Note: VideoView does not retain its full state when going into the
64 * background.</em> In particular, it does not restore the current play state,
65 * play position, selected tracks, or any subtitle tracks added via
66 * {@link #addSubtitleSource addSubtitleSource()}. Applications should
67 * save and restore these on their own in
68 * {@link android.app.Activity#onSaveInstanceState} and
69 * {@link android.app.Activity#onRestoreInstanceState}.<p>
70 * Also note that the audio session id (from {@link #getAudioSessionId}) may
71 * change from its previously returned value when the VideoView is restored.
Jean-Michel Trivi0f49f822017-02-16 14:36:43 -080072 * <p>
73 * By default, VideoView requests audio focus with {@link AudioManager#AUDIOFOCUS_GAIN}. Use
74 * {@link #setAudioFocusRequest(int)} to change this behavior.
75 * <p>
76 * The default {@link AudioAttributes} used during playback have a usage of
77 * {@link AudioAttributes#USAGE_MEDIA} and a content type of
78 * {@link AudioAttributes#CONTENT_TYPE_MOVIE}, use {@link #setAudioAttributes(AudioAttributes)} to
79 * modify them.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080080 */
Lajos Molnar484ff7a92013-08-15 11:37:47 -070081public class VideoView extends SurfaceView
82 implements MediaPlayerControl, SubtitleController.Anchor {
Alan Viverette768ca7d2016-08-04 09:54:14 -040083 private static final String TAG = "VideoView";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080084
James Dongfdf3ac62009-07-06 12:37:45 -070085 // all possible internal states
Alan Viverette768ca7d2016-08-04 09:54:14 -040086 private static final int STATE_ERROR = -1;
87 private static final int STATE_IDLE = 0;
88 private static final int STATE_PREPARING = 1;
89 private static final int STATE_PREPARED = 2;
90 private static final int STATE_PLAYING = 3;
91 private static final int STATE_PAUSED = 4;
James Dongfdf3ac62009-07-06 12:37:45 -070092 private static final int STATE_PLAYBACK_COMPLETED = 5;
93
Alan Viverette768ca7d2016-08-04 09:54:14 -040094 private final Vector<Pair<InputStream, MediaFormat>> mPendingSubtitleTracks = new Vector<>();
95
96 // settable by the client
97 private Uri mUri;
98 private Map<String, String> mHeaders;
99
James Dongfdf3ac62009-07-06 12:37:45 -0700100 // mCurrentState is a VideoView object's current state.
101 // mTargetState is the state that a method caller intends to reach.
102 // For instance, regardless the VideoView object's current state,
103 // calling pause() intends to bring the object to a target state
104 // of STATE_PAUSED.
105 private int mCurrentState = STATE_IDLE;
Alan Viverette768ca7d2016-08-04 09:54:14 -0400106 private int mTargetState = STATE_IDLE;
James Dongfdf3ac62009-07-06 12:37:45 -0700107
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800108 // All the stuff we need for playing and showing a video
109 private SurfaceHolder mSurfaceHolder = null;
110 private MediaPlayer mMediaPlayer = null;
Alan Viverette768ca7d2016-08-04 09:54:14 -0400111 private int mAudioSession;
112 private int mVideoWidth;
113 private int mVideoHeight;
114 private int mSurfaceWidth;
115 private int mSurfaceHeight;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116 private MediaController mMediaController;
117 private OnCompletionListener mOnCompletionListener;
118 private MediaPlayer.OnPreparedListener mOnPreparedListener;
Alan Viverette768ca7d2016-08-04 09:54:14 -0400119 private int mCurrentBufferPercentage;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800120 private OnErrorListener mOnErrorListener;
Alan Viverette768ca7d2016-08-04 09:54:14 -0400121 private OnInfoListener mOnInfoListener;
122 private int mSeekWhenPrepared; // recording the seek position while preparing
123 private boolean mCanPause;
124 private boolean mCanSeekBack;
125 private boolean mCanSeekForward;
Jean-Michel Trivi0f49f822017-02-16 14:36:43 -0800126 private AudioManager mAudioManager;
127 private int mAudioFocusType = AudioManager.AUDIOFOCUS_GAIN; // legacy focus gain
128 private AudioAttributes mAudioAttributes;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800129
Alan Viveretted43daf32013-09-05 16:34:30 -0700130 /** Subtitle rendering widget overlaid on top of the video. */
131 private RenderingWidget mSubtitleWidget;
Alan Viverettede213f72013-09-03 16:17:56 -0700132
Alan Viveretted43daf32013-09-05 16:34:30 -0700133 /** Listener for changes to subtitle data, used to redraw when needed. */
134 private RenderingWidget.OnChangedListener mSubtitlesChangedListener;
Alan Viverettede213f72013-09-03 16:17:56 -0700135
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800136 public VideoView(Context context) {
Alan Viverette768ca7d2016-08-04 09:54:14 -0400137 this(context, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800138 }
Andreas Huber25643002010-01-28 11:19:57 -0800139
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140 public VideoView(Context context, AttributeSet attrs) {
141 this(context, attrs, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800142 }
Andreas Huber25643002010-01-28 11:19:57 -0800143
Alan Viverette617feb92013-09-09 18:09:13 -0700144 public VideoView(Context context, AttributeSet attrs, int defStyleAttr) {
145 this(context, attrs, defStyleAttr, 0);
146 }
147
148 public VideoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
149 super(context, attrs, defStyleAttr, defStyleRes);
Alan Viverette768ca7d2016-08-04 09:54:14 -0400150
151 mVideoWidth = 0;
152 mVideoHeight = 0;
153
Jean-Michel Trivi0f49f822017-02-16 14:36:43 -0800154 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
155 mAudioAttributes = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA)
156 .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE).build();
157
Alan Viverette768ca7d2016-08-04 09:54:14 -0400158 getHolder().addCallback(mSHCallback);
159 getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
160
161 setFocusable(true);
162 setFocusableInTouchMode(true);
163 requestFocus();
164
165 mCurrentState = STATE_IDLE;
166 mTargetState = STATE_IDLE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800167 }
168
169 @Override
170 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Marco Nelissen0b2d8722012-11-12 15:20:57 -0800171 //Log.i("@@@@", "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", "
172 // + MeasureSpec.toString(heightMeasureSpec) + ")");
173
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800174 int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
175 int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
176 if (mVideoWidth > 0 && mVideoHeight > 0) {
Marco Nelissen0b2d8722012-11-12 15:20:57 -0800177
178 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
179 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
180 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
181 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
182
183 if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
184 // the size is fixed
185 width = widthSpecSize;
186 height = heightSpecSize;
187
188 // for compatibility, we adjust size based on aspect ratio
189 if ( mVideoWidth * height < width * mVideoHeight ) {
190 //Log.i("@@@", "image too wide, correcting");
191 width = height * mVideoWidth / mVideoHeight;
192 } else if ( mVideoWidth * height > width * mVideoHeight ) {
193 //Log.i("@@@", "image too tall, correcting");
194 height = width * mVideoHeight / mVideoWidth;
195 }
196 } else if (widthSpecMode == MeasureSpec.EXACTLY) {
197 // only the width is fixed, adjust the height to match aspect ratio if possible
198 width = widthSpecSize;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800199 height = width * mVideoHeight / mVideoWidth;
Marco Nelissen0b2d8722012-11-12 15:20:57 -0800200 if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
201 // couldn't match aspect ratio within the constraints
202 height = heightSpecSize;
203 }
204 } else if (heightSpecMode == MeasureSpec.EXACTLY) {
205 // only the height is fixed, adjust the width to match aspect ratio if possible
206 height = heightSpecSize;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800207 width = height * mVideoWidth / mVideoHeight;
Marco Nelissen0b2d8722012-11-12 15:20:57 -0800208 if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
209 // couldn't match aspect ratio within the constraints
210 width = widthSpecSize;
211 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800212 } else {
Marco Nelissen0b2d8722012-11-12 15:20:57 -0800213 // neither the width nor the height are fixed, try to use actual video size
214 width = mVideoWidth;
215 height = mVideoHeight;
216 if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
217 // too tall, decrease both width and height
218 height = heightSpecSize;
219 width = height * mVideoWidth / mVideoHeight;
220 }
221 if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
222 // too wide, decrease both width and height
223 width = widthSpecSize;
224 height = width * mVideoHeight / mVideoWidth;
225 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800226 }
Marco Nelissen0b2d8722012-11-12 15:20:57 -0800227 } else {
228 // no size yet, just adopt the given spec sizes
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800229 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800230 setMeasuredDimension(width, height);
231 }
Andreas Huber25643002010-01-28 11:19:57 -0800232
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800233 @Override
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -0800234 public CharSequence getAccessibilityClassName() {
235 return VideoView.class.getName();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800236 }
237
Marco Nelissenc1374e82012-11-13 10:51:50 -0800238 public int resolveAdjustedSize(int desiredSize, int measureSpec) {
239 return getDefaultSize(desiredSize, measureSpec);
240 }
241
Ronghua Wu0e9e3b22014-08-20 16:24:52 -0700242 /**
243 * Sets video path.
244 *
245 * @param path the path of the video.
246 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800247 public void setVideoPath(String path) {
248 setVideoURI(Uri.parse(path));
249 }
250
Ronghua Wu0e9e3b22014-08-20 16:24:52 -0700251 /**
252 * Sets video URI.
253 *
254 * @param uri the URI of the video.
255 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800256 public void setVideoURI(Uri uri) {
Andreas Huber25643002010-01-28 11:19:57 -0800257 setVideoURI(uri, null);
258 }
259
260 /**
Ronghua Wu0e9e3b22014-08-20 16:24:52 -0700261 * Sets video URI using specific headers.
262 *
263 * @param uri the URI of the video.
264 * @param headers the headers for the URI request.
Ronghua Wu48db7832014-08-25 14:47:58 -0700265 * Note that the cross domain redirection is allowed by default, but that can be
266 * changed with key/value pairs through the headers parameter with
267 * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value
268 * to disallow or allow cross domain redirection.
Andreas Huber25643002010-01-28 11:19:57 -0800269 */
270 public void setVideoURI(Uri uri, Map<String, String> headers) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800271 mUri = uri;
Andreas Huber25643002010-01-28 11:19:57 -0800272 mHeaders = headers;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800273 mSeekWhenPrepared = 0;
274 openVideo();
275 requestLayout();
276 invalidate();
277 }
Andreas Huber25643002010-01-28 11:19:57 -0800278
Lajos Molnaraf3098242013-08-15 20:56:53 -0700279 /**
Jean-Michel Trivi0f49f822017-02-16 14:36:43 -0800280 * Sets which type of audio focus will be requested during the playback, or configures playback
281 * to not request audio focus. Valid values for focus requests are
282 * {@link AudioManager#AUDIOFOCUS_GAIN}, {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT},
283 * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}, and
284 * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}. Or use
285 * {@link AudioManager#AUDIOFOCUS_NONE} to express that audio focus should not be
286 * requested when playback starts. You can for instance use this when playing a silent animation
287 * through this class, and you don't want to affect other audio applications playing in the
288 * background.
289 * @param focusGain the type of audio focus gain that will be requested, or
290 * {@link AudioManager#AUDIOFOCUS_NONE} to disable the use audio focus during playback.
291 */
292 public void setAudioFocusRequest(int focusGain) {
293 if (focusGain != AudioManager.AUDIOFOCUS_NONE
294 && focusGain != AudioManager.AUDIOFOCUS_GAIN
295 && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
296 && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
297 && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) {
298 throw new IllegalArgumentException("Illegal audio focus type " + focusGain);
299 }
300 mAudioFocusType = focusGain;
301 }
302
303 /**
304 * Sets the {@link AudioAttributes} to be used during the playback of the video.
305 * @param attributes non-null <code>AudioAttributes</code>.
306 */
307 public void setAudioAttributes(@NonNull AudioAttributes attributes) {
308 if (attributes == null) {
309 throw new IllegalArgumentException("Illegal null AudioAttributes");
310 }
311 mAudioAttributes = attributes;
312 }
313
314 /**
Lajos Molnaraf3098242013-08-15 20:56:53 -0700315 * Adds an external subtitle source file (from the provided input stream.)
316 *
317 * Note that a single external subtitle source may contain multiple or no
318 * supported tracks in it. If the source contained at least one track in
319 * it, one will receive an {@link MediaPlayer#MEDIA_INFO_METADATA_UPDATE}
320 * info message. Otherwise, if reading the source takes excessive time,
321 * one will receive a {@link MediaPlayer#MEDIA_INFO_SUBTITLE_TIMED_OUT}
322 * message. If the source contained no supported track (including an empty
323 * source file or null input stream), one will receive a {@link
324 * MediaPlayer#MEDIA_INFO_UNSUPPORTED_SUBTITLE} message. One can find the
325 * total number of available tracks using {@link MediaPlayer#getTrackInfo()}
326 * to see what additional tracks become available after this method call.
327 *
328 * @param is input stream containing the subtitle data. It will be
329 * closed by the media framework.
330 * @param format the format of the subtitle track(s). Must contain at least
331 * the mime type ({@link MediaFormat#KEY_MIME}) and the
332 * language ({@link MediaFormat#KEY_LANGUAGE}) of the file.
333 * If the file itself contains the language information,
334 * specify "und" for the language.
335 */
336 public void addSubtitleSource(InputStream is, MediaFormat format) {
Lajos Molnaraf3098242013-08-15 20:56:53 -0700337 if (mMediaPlayer == null) {
Lajos Molnar484ff7a92013-08-15 11:37:47 -0700338 mPendingSubtitleTracks.add(Pair.create(is, format));
Lajos Molnaraf3098242013-08-15 20:56:53 -0700339 } else {
Lajos Molnar484ff7a92013-08-15 11:37:47 -0700340 try {
341 mMediaPlayer.addSubtitleSource(is, format);
342 } catch (IllegalStateException e) {
343 mInfoListener.onInfo(
344 mMediaPlayer, MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, 0);
345 }
Lajos Molnaraf3098242013-08-15 20:56:53 -0700346 }
347 }
348
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800349 public void stopPlayback() {
350 if (mMediaPlayer != null) {
351 mMediaPlayer.stop();
352 mMediaPlayer.release();
353 mMediaPlayer = null;
James Dongfdf3ac62009-07-06 12:37:45 -0700354 mCurrentState = STATE_IDLE;
355 mTargetState = STATE_IDLE;
Jean-Michel Trivi0f49f822017-02-16 14:36:43 -0800356 mAudioManager.abandonAudioFocus(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800357 }
358 }
359
360 private void openVideo() {
361 if (mUri == null || mSurfaceHolder == null) {
362 // not ready for playback just yet, will try again later
363 return;
364 }
Marco Nelissendddeee62009-07-10 14:14:52 -0700365 // we shouldn't clear the target state, because somebody might have
366 // called start() previously
367 release(false);
Marco Nelissen926ebb82015-03-11 09:59:49 -0700368
Jean-Michel Trivi0f49f822017-02-16 14:36:43 -0800369 if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
370 // TODO this should have a focus listener
371 mAudioManager.requestAudioFocus(null, mAudioAttributes, mAudioFocusType, 0 /*flags*/);
372 }
Marco Nelissen926ebb82015-03-11 09:59:49 -0700373
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800374 try {
375 mMediaPlayer = new MediaPlayer();
Lajos Molnar484ff7a92013-08-15 11:37:47 -0700376 // TODO: create SubtitleController in MediaPlayer, but we need
377 // a context for the subtitle renderers
Alan Viveretted43daf32013-09-05 16:34:30 -0700378 final Context context = getContext();
379 final SubtitleController controller = new SubtitleController(
380 context, mMediaPlayer.getMediaTimeProvider(), mMediaPlayer);
381 controller.registerRenderer(new WebVttRenderer(context));
Sungsoo Limba3699b2014-03-17 10:24:32 +0900382 controller.registerRenderer(new TtmlRenderer(context));
Jaesung Chung978bf5e2015-12-07 21:46:40 +0900383 controller.registerRenderer(new Cea708CaptionRenderer(context));
Chong Zhangbdfd9102014-06-11 15:10:23 -0700384 controller.registerRenderer(new ClosedCaptionRenderer(context));
Lajos Molnar484ff7a92013-08-15 11:37:47 -0700385 mMediaPlayer.setSubtitleAnchor(controller, this);
386
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700387 if (mAudioSession != 0) {
388 mMediaPlayer.setAudioSessionId(mAudioSession);
389 } else {
390 mAudioSession = mMediaPlayer.getAudioSessionId();
391 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800392 mMediaPlayer.setOnPreparedListener(mPreparedListener);
393 mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800394 mMediaPlayer.setOnCompletionListener(mCompletionListener);
395 mMediaPlayer.setOnErrorListener(mErrorListener);
Lajos Molnaraf3098242013-08-15 20:56:53 -0700396 mMediaPlayer.setOnInfoListener(mInfoListener);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800397 mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
398 mCurrentBufferPercentage = 0;
Andreas Huber25643002010-01-28 11:19:57 -0800399 mMediaPlayer.setDataSource(mContext, mUri, mHeaders);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800400 mMediaPlayer.setDisplay(mSurfaceHolder);
Jean-Michel Trivi0f49f822017-02-16 14:36:43 -0800401 mMediaPlayer.setAudioAttributes(mAudioAttributes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800402 mMediaPlayer.setScreenOnWhilePlaying(true);
403 mMediaPlayer.prepareAsync();
Lajos Molnaraf3098242013-08-15 20:56:53 -0700404
Lajos Molnar484ff7a92013-08-15 11:37:47 -0700405 for (Pair<InputStream, MediaFormat> pending: mPendingSubtitleTracks) {
406 try {
407 mMediaPlayer.addSubtitleSource(pending.first, pending.second);
408 } catch (IllegalStateException e) {
409 mInfoListener.onInfo(
410 mMediaPlayer, MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, 0);
411 }
Lajos Molnaraf3098242013-08-15 20:56:53 -0700412 }
413
Marco Nelissendddeee62009-07-10 14:14:52 -0700414 // we don't set the target state here either, but preserve the
415 // target state that was there before.
James Dongfdf3ac62009-07-06 12:37:45 -0700416 mCurrentState = STATE_PREPARING;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800417 attachMediaController();
418 } catch (IOException ex) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700419 Log.w(TAG, "Unable to open content: " + mUri, ex);
James Dongfdf3ac62009-07-06 12:37:45 -0700420 mCurrentState = STATE_ERROR;
421 mTargetState = STATE_ERROR;
Andrei Popescu020d2e32009-09-30 14:54:55 +0100422 mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800423 return;
424 } catch (IllegalArgumentException ex) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700425 Log.w(TAG, "Unable to open content: " + mUri, ex);
James Dongfdf3ac62009-07-06 12:37:45 -0700426 mCurrentState = STATE_ERROR;
427 mTargetState = STATE_ERROR;
Andrei Popescu020d2e32009-09-30 14:54:55 +0100428 mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800429 return;
Lajos Molnaraf3098242013-08-15 20:56:53 -0700430 } finally {
Lajos Molnar484ff7a92013-08-15 11:37:47 -0700431 mPendingSubtitleTracks.clear();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800432 }
433 }
Andreas Huber25643002010-01-28 11:19:57 -0800434
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800435 public void setMediaController(MediaController controller) {
436 if (mMediaController != null) {
437 mMediaController.hide();
438 }
439 mMediaController = controller;
440 attachMediaController();
441 }
442
443 private void attachMediaController() {
444 if (mMediaPlayer != null && mMediaController != null) {
445 mMediaController.setMediaPlayer(this);
446 View anchorView = this.getParent() instanceof View ?
447 (View)this.getParent() : this;
448 mMediaController.setAnchorView(anchorView);
James Dongfdf3ac62009-07-06 12:37:45 -0700449 mMediaController.setEnabled(isInPlaybackState());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800450 }
451 }
Andreas Huber25643002010-01-28 11:19:57 -0800452
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800453 MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
454 new MediaPlayer.OnVideoSizeChangedListener() {
455 public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
456 mVideoWidth = mp.getVideoWidth();
457 mVideoHeight = mp.getVideoHeight();
458 if (mVideoWidth != 0 && mVideoHeight != 0) {
459 getHolder().setFixedSize(mVideoWidth, mVideoHeight);
Teng-Hui Zhuf5d102e2012-08-23 15:24:29 -0700460 requestLayout();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800461 }
462 }
463 };
Andreas Huber25643002010-01-28 11:19:57 -0800464
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800465 MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
466 public void onPrepared(MediaPlayer mp) {
James Dongfdf3ac62009-07-06 12:37:45 -0700467 mCurrentState = STATE_PREPARED;
Marco Nelissenc818b142009-08-19 08:32:21 -0700468
469 // Get the capabilities of the player for this stream
470 Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL,
471 MediaPlayer.BYPASS_METADATA_FILTER);
Andreas Huberd44d33b2009-08-20 11:12:27 -0700472
473 if (data != null) {
474 mCanPause = !data.has(Metadata.PAUSE_AVAILABLE)
475 || data.getBoolean(Metadata.PAUSE_AVAILABLE);
476 mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE)
477 || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE);
478 mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE)
479 || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE);
480 } else {
Andreas Huber2869e952010-03-09 09:18:01 -0800481 mCanPause = mCanSeekBack = mCanSeekForward = true;
Andreas Huberd44d33b2009-08-20 11:12:27 -0700482 }
Marco Nelissenc818b142009-08-19 08:32:21 -0700483
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800484 if (mOnPreparedListener != null) {
485 mOnPreparedListener.onPrepared(mMediaPlayer);
486 }
487 if (mMediaController != null) {
488 mMediaController.setEnabled(true);
489 }
490 mVideoWidth = mp.getVideoWidth();
491 mVideoHeight = mp.getVideoHeight();
Marco Nelissenc818b142009-08-19 08:32:21 -0700492
James Dongfdf3ac62009-07-06 12:37:45 -0700493 int seekToPosition = mSeekWhenPrepared; // mSeekWhenPrepared may be changed after seekTo() call
494 if (seekToPosition != 0) {
495 seekTo(seekToPosition);
496 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800497 if (mVideoWidth != 0 && mVideoHeight != 0) {
498 //Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight);
499 getHolder().setFixedSize(mVideoWidth, mVideoHeight);
500 if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {
501 // We didn't actually change the size (it was already at the size
502 // we need), so we won't get a "surface changed" callback, so
503 // start the video here instead of in the callback.
James Dongfdf3ac62009-07-06 12:37:45 -0700504 if (mTargetState == STATE_PLAYING) {
James Dongaa00e392009-07-02 16:26:39 -0700505 start();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800506 if (mMediaController != null) {
507 mMediaController.show();
508 }
509 } else if (!isPlaying() &&
James Dongfdf3ac62009-07-06 12:37:45 -0700510 (seekToPosition != 0 || getCurrentPosition() > 0)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800511 if (mMediaController != null) {
512 // Show the media controls when we're paused into a video and make 'em stick.
513 mMediaController.show(0);
514 }
515 }
516 }
517 } else {
518 // We don't know the video size yet, but should start anyway.
519 // The video size might be reported to us later.
James Dongfdf3ac62009-07-06 12:37:45 -0700520 if (mTargetState == STATE_PLAYING) {
James Dongaa00e392009-07-02 16:26:39 -0700521 start();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800522 }
523 }
524 }
525 };
526
527 private MediaPlayer.OnCompletionListener mCompletionListener =
528 new MediaPlayer.OnCompletionListener() {
529 public void onCompletion(MediaPlayer mp) {
James Dongfdf3ac62009-07-06 12:37:45 -0700530 mCurrentState = STATE_PLAYBACK_COMPLETED;
531 mTargetState = STATE_PLAYBACK_COMPLETED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800532 if (mMediaController != null) {
533 mMediaController.hide();
534 }
535 if (mOnCompletionListener != null) {
536 mOnCompletionListener.onCompletion(mMediaPlayer);
537 }
Jean-Michel Trivi0f49f822017-02-16 14:36:43 -0800538 if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
539 mAudioManager.abandonAudioFocus(null);
540 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800541 }
542 };
543
Lajos Molnaraf3098242013-08-15 20:56:53 -0700544 private MediaPlayer.OnInfoListener mInfoListener =
545 new MediaPlayer.OnInfoListener() {
546 public boolean onInfo(MediaPlayer mp, int arg1, int arg2) {
547 if (mOnInfoListener != null) {
548 mOnInfoListener.onInfo(mp, arg1, arg2);
549 }
550 return true;
551 }
552 };
553
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800554 private MediaPlayer.OnErrorListener mErrorListener =
555 new MediaPlayer.OnErrorListener() {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700556 public boolean onError(MediaPlayer mp, int framework_err, int impl_err) {
557 Log.d(TAG, "Error: " + framework_err + "," + impl_err);
James Dongfdf3ac62009-07-06 12:37:45 -0700558 mCurrentState = STATE_ERROR;
559 mTargetState = STATE_ERROR;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800560 if (mMediaController != null) {
561 mMediaController.hide();
562 }
563
564 /* If an error handler has been supplied, use it and finish. */
565 if (mOnErrorListener != null) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700566 if (mOnErrorListener.onError(mMediaPlayer, framework_err, impl_err)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800567 return true;
568 }
569 }
570
571 /* Otherwise, pop up an error dialog so the user knows that
572 * something bad has happened. Only try and pop up the dialog
573 * if we're attached to a window. When we're going away and no
574 * longer have a window, don't bother showing the user an error.
575 */
576 if (getWindowToken() != null) {
577 Resources r = mContext.getResources();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700578 int messageId;
579
580 if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
581 messageId = com.android.internal.R.string.VideoView_error_text_invalid_progressive_playback;
582 } else {
583 messageId = com.android.internal.R.string.VideoView_error_text_unknown;
584 }
585
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800586 new AlertDialog.Builder(mContext)
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700587 .setMessage(messageId)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800588 .setPositiveButton(com.android.internal.R.string.VideoView_error_button,
589 new DialogInterface.OnClickListener() {
590 public void onClick(DialogInterface dialog, int whichButton) {
591 /* If we get here, there is no onError listener, so
592 * at least inform them that the video is over.
593 */
594 if (mOnCompletionListener != null) {
595 mOnCompletionListener.onCompletion(mMediaPlayer);
596 }
597 }
598 })
599 .setCancelable(false)
600 .show();
601 }
602 return true;
603 }
604 };
605
606 private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =
607 new MediaPlayer.OnBufferingUpdateListener() {
608 public void onBufferingUpdate(MediaPlayer mp, int percent) {
609 mCurrentBufferPercentage = percent;
610 }
611 };
612
613 /**
614 * Register a callback to be invoked when the media file
615 * is loaded and ready to go.
616 *
617 * @param l The callback that will be run
618 */
619 public void setOnPreparedListener(MediaPlayer.OnPreparedListener l)
620 {
621 mOnPreparedListener = l;
622 }
623
624 /**
625 * Register a callback to be invoked when the end of a media file
626 * has been reached during playback.
627 *
628 * @param l The callback that will be run
629 */
630 public void setOnCompletionListener(OnCompletionListener l)
631 {
632 mOnCompletionListener = l;
633 }
634
635 /**
636 * Register a callback to be invoked when an error occurs
637 * during playback or setup. If no listener is specified,
638 * or if the listener returned false, VideoView will inform
639 * the user of any errors.
640 *
641 * @param l The callback that will be run
642 */
643 public void setOnErrorListener(OnErrorListener l)
644 {
645 mOnErrorListener = l;
646 }
647
James Donga0ba7942012-07-27 17:30:38 -0700648 /**
649 * Register a callback to be invoked when an informational event
650 * occurs during playback or setup.
651 *
652 * @param l The callback that will be run
653 */
654 public void setOnInfoListener(OnInfoListener l) {
655 mOnInfoListener = l;
656 }
657
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800658 SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()
659 {
660 public void surfaceChanged(SurfaceHolder holder, int format,
661 int w, int h)
662 {
663 mSurfaceWidth = w;
664 mSurfaceHeight = h;
James Dongfdf3ac62009-07-06 12:37:45 -0700665 boolean isValidState = (mTargetState == STATE_PLAYING);
666 boolean hasValidSize = (mVideoWidth == w && mVideoHeight == h);
667 if (mMediaPlayer != null && isValidState && hasValidSize) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800668 if (mSeekWhenPrepared != 0) {
James Dongfdf3ac62009-07-06 12:37:45 -0700669 seekTo(mSeekWhenPrepared);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800670 }
James Dongfdf3ac62009-07-06 12:37:45 -0700671 start();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800672 }
673 }
674
675 public void surfaceCreated(SurfaceHolder holder)
676 {
677 mSurfaceHolder = holder;
Andreas Huber69b8d692010-10-29 12:00:20 -0700678 openVideo();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800679 }
680
681 public void surfaceDestroyed(SurfaceHolder holder)
682 {
683 // after we return from this we can't use the surface any more
684 mSurfaceHolder = null;
685 if (mMediaController != null) mMediaController.hide();
Andreas Huber69b8d692010-10-29 12:00:20 -0700686 release(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800687 }
688 };
689
James Dongfdf3ac62009-07-06 12:37:45 -0700690 /*
691 * release the media player in any state
692 */
Marco Nelissendddeee62009-07-10 14:14:52 -0700693 private void release(boolean cleartargetstate) {
James Dongfdf3ac62009-07-06 12:37:45 -0700694 if (mMediaPlayer != null) {
695 mMediaPlayer.reset();
696 mMediaPlayer.release();
697 mMediaPlayer = null;
Lajos Molnar484ff7a92013-08-15 11:37:47 -0700698 mPendingSubtitleTracks.clear();
James Dongfdf3ac62009-07-06 12:37:45 -0700699 mCurrentState = STATE_IDLE;
Marco Nelissendddeee62009-07-10 14:14:52 -0700700 if (cleartargetstate) {
701 mTargetState = STATE_IDLE;
702 }
Jean-Michel Trivi0f49f822017-02-16 14:36:43 -0800703 if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
704 mAudioManager.abandonAudioFocus(null);
705 }
James Dongfdf3ac62009-07-06 12:37:45 -0700706 }
707 }
708
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800709 @Override
710 public boolean onTouchEvent(MotionEvent ev) {
Vladislav Kaznacheevfd48ed62017-05-02 09:39:06 -0700711 if (ev.getAction() == MotionEvent.ACTION_DOWN
712 && isInPlaybackState() && mMediaController != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800713 toggleMediaControlsVisiblity();
714 }
Vladislav Kaznacheevfd48ed62017-05-02 09:39:06 -0700715 return super.onTouchEvent(ev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800716 }
Andreas Huber25643002010-01-28 11:19:57 -0800717
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800718 @Override
719 public boolean onTrackballEvent(MotionEvent ev) {
Vladislav Kaznacheevfd48ed62017-05-02 09:39:06 -0700720 if (ev.getAction() == MotionEvent.ACTION_DOWN
721 && isInPlaybackState() && mMediaController != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800722 toggleMediaControlsVisiblity();
723 }
Vladislav Kaznacheevfd48ed62017-05-02 09:39:06 -0700724 return super.onTrackballEvent(ev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800725 }
Andreas Huber25643002010-01-28 11:19:57 -0800726
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800727 @Override
728 public boolean onKeyDown(int keyCode, KeyEvent event)
729 {
James Dongfdf3ac62009-07-06 12:37:45 -0700730 boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK &&
731 keyCode != KeyEvent.KEYCODE_VOLUME_UP &&
732 keyCode != KeyEvent.KEYCODE_VOLUME_DOWN &&
Jeff Brownb0418da2010-11-01 15:24:01 -0700733 keyCode != KeyEvent.KEYCODE_VOLUME_MUTE &&
James Dongfdf3ac62009-07-06 12:37:45 -0700734 keyCode != KeyEvent.KEYCODE_MENU &&
735 keyCode != KeyEvent.KEYCODE_CALL &&
736 keyCode != KeyEvent.KEYCODE_ENDCALL;
737 if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800738 if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
Andy Stadlerf8a7cea2009-04-10 16:24:47 -0700739 keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800740 if (mMediaPlayer.isPlaying()) {
741 pause();
742 mMediaController.show();
743 } else {
744 start();
745 mMediaController.hide();
746 }
747 return true;
Jeff Brown4d396052010-10-29 21:50:21 -0700748 } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
Chih-Chung Changd7db7012011-02-10 18:11:27 +0800749 if (!mMediaPlayer.isPlaying()) {
Jeff Brown4d396052010-10-29 21:50:21 -0700750 start();
751 mMediaController.hide();
752 }
753 return true;
Andreas Huber25643002010-01-28 11:19:57 -0800754 } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
Jeff Brown4d396052010-10-29 21:50:21 -0700755 || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
Chih-Chung Changd7db7012011-02-10 18:11:27 +0800756 if (mMediaPlayer.isPlaying()) {
Jeff Brown4d396052010-10-29 21:50:21 -0700757 pause();
758 mMediaController.show();
759 }
760 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800761 } else {
762 toggleMediaControlsVisiblity();
763 }
764 }
765
766 return super.onKeyDown(keyCode, event);
767 }
768
769 private void toggleMediaControlsVisiblity() {
Andreas Huber25643002010-01-28 11:19:57 -0800770 if (mMediaController.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800771 mMediaController.hide();
772 } else {
773 mMediaController.show();
774 }
775 }
Andreas Huber25643002010-01-28 11:19:57 -0800776
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700777 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800778 public void start() {
James Dongfdf3ac62009-07-06 12:37:45 -0700779 if (isInPlaybackState()) {
780 mMediaPlayer.start();
781 mCurrentState = STATE_PLAYING;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800782 }
James Dongfdf3ac62009-07-06 12:37:45 -0700783 mTargetState = STATE_PLAYING;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800784 }
Andreas Huber25643002010-01-28 11:19:57 -0800785
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700786 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800787 public void pause() {
James Dongfdf3ac62009-07-06 12:37:45 -0700788 if (isInPlaybackState()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800789 if (mMediaPlayer.isPlaying()) {
790 mMediaPlayer.pause();
James Dongfdf3ac62009-07-06 12:37:45 -0700791 mCurrentState = STATE_PAUSED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800792 }
793 }
James Dongfdf3ac62009-07-06 12:37:45 -0700794 mTargetState = STATE_PAUSED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800795 }
Andreas Huber25643002010-01-28 11:19:57 -0800796
Gloria Wang2e1818a2010-02-22 14:49:13 -0800797 public void suspend() {
Andreas Huber69b8d692010-10-29 12:00:20 -0700798 release(false);
Gloria Wang2e1818a2010-02-22 14:49:13 -0800799 }
800
801 public void resume() {
Andreas Huber69b8d692010-10-29 12:00:20 -0700802 openVideo();
Gloria Wang2e1818a2010-02-22 14:49:13 -0800803 }
804
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700805 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800806 public int getDuration() {
James Dongfdf3ac62009-07-06 12:37:45 -0700807 if (isInPlaybackState()) {
Andreas Huber585c07e2012-11-27 15:06:56 -0800808 return mMediaPlayer.getDuration();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800809 }
Andreas Huber585c07e2012-11-27 15:06:56 -0800810
811 return -1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800812 }
Andreas Huber25643002010-01-28 11:19:57 -0800813
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700814 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800815 public int getCurrentPosition() {
James Dongfdf3ac62009-07-06 12:37:45 -0700816 if (isInPlaybackState()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800817 return mMediaPlayer.getCurrentPosition();
818 }
819 return 0;
820 }
Andreas Huber25643002010-01-28 11:19:57 -0800821
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700822 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800823 public void seekTo(int msec) {
James Dongfdf3ac62009-07-06 12:37:45 -0700824 if (isInPlaybackState()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800825 mMediaPlayer.seekTo(msec);
James Dongfdf3ac62009-07-06 12:37:45 -0700826 mSeekWhenPrepared = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800827 } else {
828 mSeekWhenPrepared = msec;
829 }
Andreas Huber25643002010-01-28 11:19:57 -0800830 }
831
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700832 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800833 public boolean isPlaying() {
James Dongfdf3ac62009-07-06 12:37:45 -0700834 return isInPlaybackState() && mMediaPlayer.isPlaying();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800835 }
Andreas Huber25643002010-01-28 11:19:57 -0800836
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700837 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800838 public int getBufferPercentage() {
839 if (mMediaPlayer != null) {
840 return mCurrentBufferPercentage;
841 }
842 return 0;
843 }
James Dongfdf3ac62009-07-06 12:37:45 -0700844
845 private boolean isInPlaybackState() {
846 return (mMediaPlayer != null &&
847 mCurrentState != STATE_ERROR &&
848 mCurrentState != STATE_IDLE &&
849 mCurrentState != STATE_PREPARING);
850 }
Marco Nelissenc818b142009-08-19 08:32:21 -0700851
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700852 @Override
Marco Nelissenc818b142009-08-19 08:32:21 -0700853 public boolean canPause() {
854 return mCanPause;
855 }
856
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700857 @Override
Marco Nelissenc818b142009-08-19 08:32:21 -0700858 public boolean canSeekBackward() {
859 return mCanSeekBack;
860 }
861
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700862 @Override
Marco Nelissenc818b142009-08-19 08:32:21 -0700863 public boolean canSeekForward() {
864 return mCanSeekForward;
865 }
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700866
867 @Override
868 public int getAudioSessionId() {
869 if (mAudioSession == 0) {
870 MediaPlayer foo = new MediaPlayer();
871 mAudioSession = foo.getAudioSessionId();
872 foo.release();
873 }
874 return mAudioSession;
875 }
Alan Viverettede213f72013-09-03 16:17:56 -0700876
877 @Override
Alan Viveretted43daf32013-09-05 16:34:30 -0700878 protected void onAttachedToWindow() {
879 super.onAttachedToWindow();
880
881 if (mSubtitleWidget != null) {
882 mSubtitleWidget.onAttachedToWindow();
883 }
884 }
885
886 @Override
887 protected void onDetachedFromWindow() {
888 super.onDetachedFromWindow();
889
890 if (mSubtitleWidget != null) {
891 mSubtitleWidget.onDetachedFromWindow();
892 }
893 }
894
895 @Override
Alan Viverettede213f72013-09-03 16:17:56 -0700896 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
897 super.onLayout(changed, left, top, right, bottom);
898
Alan Viveretted43daf32013-09-05 16:34:30 -0700899 if (mSubtitleWidget != null) {
900 measureAndLayoutSubtitleWidget();
Alan Viverettede213f72013-09-03 16:17:56 -0700901 }
902 }
903
904 @Override
905 public void draw(Canvas canvas) {
906 super.draw(canvas);
907
Alan Viveretted43daf32013-09-05 16:34:30 -0700908 if (mSubtitleWidget != null) {
909 final int saveCount = canvas.save();
910 canvas.translate(getPaddingLeft(), getPaddingTop());
911 mSubtitleWidget.draw(canvas);
912 canvas.restoreToCount(saveCount);
Alan Viverettede213f72013-09-03 16:17:56 -0700913 }
914 }
915
916 /**
Alan Viverettede213f72013-09-03 16:17:56 -0700917 * Forces a measurement and layout pass for all overlaid views.
918 *
Alan Viveretted43daf32013-09-05 16:34:30 -0700919 * @see #setSubtitleWidget(RenderingWidget)
Alan Viverettede213f72013-09-03 16:17:56 -0700920 */
Alan Viveretted43daf32013-09-05 16:34:30 -0700921 private void measureAndLayoutSubtitleWidget() {
922 final int width = getWidth() - getPaddingLeft() - getPaddingRight();
923 final int height = getHeight() - getPaddingTop() - getPaddingBottom();
Alan Viverettede213f72013-09-03 16:17:56 -0700924
Alan Viveretted43daf32013-09-05 16:34:30 -0700925 mSubtitleWidget.setSize(width, height);
Alan Viverettede213f72013-09-03 16:17:56 -0700926 }
Lajos Molnar484ff7a92013-08-15 11:37:47 -0700927
928 /** @hide */
929 @Override
Alan Viveretted43daf32013-09-05 16:34:30 -0700930 public void setSubtitleWidget(RenderingWidget subtitleWidget) {
931 if (mSubtitleWidget == subtitleWidget) {
Lajos Molnar484ff7a92013-08-15 11:37:47 -0700932 return;
933 }
934
Alan Viveretted43daf32013-09-05 16:34:30 -0700935 final boolean attachedToWindow = isAttachedToWindow();
936 if (mSubtitleWidget != null) {
937 if (attachedToWindow) {
938 mSubtitleWidget.onDetachedFromWindow();
939 }
Lajos Molnar484ff7a92013-08-15 11:37:47 -0700940
Alan Viveretted43daf32013-09-05 16:34:30 -0700941 mSubtitleWidget.setOnChangedListener(null);
942 }
943
944 mSubtitleWidget = subtitleWidget;
945
946 if (subtitleWidget != null) {
947 if (mSubtitlesChangedListener == null) {
948 mSubtitlesChangedListener = new RenderingWidget.OnChangedListener() {
949 @Override
950 public void onChanged(RenderingWidget renderingWidget) {
951 invalidate();
952 }
953 };
954 }
955
956 setWillNotDraw(false);
957 subtitleWidget.setOnChangedListener(mSubtitlesChangedListener);
958
959 if (attachedToWindow) {
960 subtitleWidget.onAttachedToWindow();
961 requestLayout();
962 }
963 } else {
964 setWillNotDraw(true);
965 }
966
967 invalidate();
968 }
Lajos Molnar29f51832013-09-20 08:45:31 -0700969
970 /** @hide */
971 @Override
972 public Looper getSubtitleLooper() {
973 return Looper.getMainLooper();
974 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800975}