blob: 36dafd5c0a05259eed879b6d28df0cb82a67549b [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;
Artur Satayeved5a6ae2019-12-10 17:47:54 +000021import android.compat.annotation.UnsupportedAppUsage;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.content.Context;
23import android.content.DialogInterface;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.content.res.Resources;
Alan Viverettede213f72013-09-03 16:17:56 -070025import android.graphics.Canvas;
Jean-Michel Trivi0f49f822017-02-16 14:36:43 -080026import android.media.AudioAttributes;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.media.AudioManager;
Jaesung Chung978bf5e2015-12-07 21:46:40 +090028import android.media.Cea708CaptionRenderer;
Chong Zhangbdfd9102014-06-11 15:10:23 -070029import android.media.ClosedCaptionRenderer;
Lajos Molnaraf3098242013-08-15 20:56:53 -070030import android.media.MediaFormat;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.media.MediaPlayer;
32import android.media.MediaPlayer.OnCompletionListener;
33import android.media.MediaPlayer.OnErrorListener;
James Donga0ba7942012-07-27 17:30:38 -070034import android.media.MediaPlayer.OnInfoListener;
Lajos Molnaraf3098242013-08-15 20:56:53 -070035import android.media.Metadata;
Lajos Molnar484ff7a92013-08-15 11:37:47 -070036import android.media.SubtitleController;
Alan Viveretted43daf32013-09-05 16:34:30 -070037import android.media.SubtitleTrack.RenderingWidget;
Sungsoo Limba3699b2014-03-17 10:24:32 +090038import android.media.TtmlRenderer;
Lajos Molnar484ff7a92013-08-15 11:37:47 -070039import android.media.WebVttRenderer;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040import android.net.Uri;
Mathew Inwood8c854f82018-09-14 12:35:36 +010041import android.os.Build;
Lajos Molnar29f51832013-09-20 08:45:31 -070042import android.os.Looper;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043import android.util.AttributeSet;
44import android.util.Log;
Lajos Molnar484ff7a92013-08-15 11:37:47 -070045import android.util.Pair;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046import android.view.KeyEvent;
47import android.view.MotionEvent;
48import android.view.SurfaceHolder;
49import android.view.SurfaceView;
50import android.view.View;
Owen Lina8381df2010-11-09 19:32:30 +080051import android.widget.MediaController.MediaPlayerControl;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052
53import java.io.IOException;
Lajos Molnaraf3098242013-08-15 20:56:53 -070054import java.io.InputStream;
Andreas Huber25643002010-01-28 11:19:57 -080055import java.util.Map;
Lajos Molnaraf3098242013-08-15 20:56:53 -070056import java.util.Vector;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080057
58/**
59 * Displays a video file. The VideoView class
60 * can load images from various sources (such as resources or content
61 * providers), takes care of computing its measurement from the video so that
62 * it can be used in any layout manager, and provides various display options
Lajos Molnar7e11b162013-10-30 14:04:10 -070063 * such as scaling and tinting.<p>
64 *
65 * <em>Note: VideoView does not retain its full state when going into the
66 * background.</em> In particular, it does not restore the current play state,
67 * play position, selected tracks, or any subtitle tracks added via
68 * {@link #addSubtitleSource addSubtitleSource()}. Applications should
69 * save and restore these on their own in
70 * {@link android.app.Activity#onSaveInstanceState} and
71 * {@link android.app.Activity#onRestoreInstanceState}.<p>
72 * Also note that the audio session id (from {@link #getAudioSessionId}) may
73 * change from its previously returned value when the VideoView is restored.
Jean-Michel Trivi0f49f822017-02-16 14:36:43 -080074 * <p>
75 * By default, VideoView requests audio focus with {@link AudioManager#AUDIOFOCUS_GAIN}. Use
76 * {@link #setAudioFocusRequest(int)} to change this behavior.
77 * <p>
78 * The default {@link AudioAttributes} used during playback have a usage of
79 * {@link AudioAttributes#USAGE_MEDIA} and a content type of
80 * {@link AudioAttributes#CONTENT_TYPE_MOVIE}, use {@link #setAudioAttributes(AudioAttributes)} to
81 * modify them.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080082 */
Lajos Molnar484ff7a92013-08-15 11:37:47 -070083public class VideoView extends SurfaceView
84 implements MediaPlayerControl, SubtitleController.Anchor {
Alan Viverette768ca7d2016-08-04 09:54:14 -040085 private static final String TAG = "VideoView";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080086
James Dongfdf3ac62009-07-06 12:37:45 -070087 // all possible internal states
Alan Viverette768ca7d2016-08-04 09:54:14 -040088 private static final int STATE_ERROR = -1;
Mathew Inwood8c854f82018-09-14 12:35:36 +010089 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Alan Viverette768ca7d2016-08-04 09:54:14 -040090 private static final int STATE_IDLE = 0;
91 private static final int STATE_PREPARING = 1;
92 private static final int STATE_PREPARED = 2;
93 private static final int STATE_PLAYING = 3;
94 private static final int STATE_PAUSED = 4;
James Dongfdf3ac62009-07-06 12:37:45 -070095 private static final int STATE_PLAYBACK_COMPLETED = 5;
96
Alan Viverette768ca7d2016-08-04 09:54:14 -040097 private final Vector<Pair<InputStream, MediaFormat>> mPendingSubtitleTracks = new Vector<>();
98
99 // settable by the client
Mathew Inwood978c6e22018-08-21 15:58:55 +0100100 @UnsupportedAppUsage
Alan Viverette768ca7d2016-08-04 09:54:14 -0400101 private Uri mUri;
Mathew Inwood978c6e22018-08-21 15:58:55 +0100102 @UnsupportedAppUsage
Alan Viverette768ca7d2016-08-04 09:54:14 -0400103 private Map<String, String> mHeaders;
104
James Dongfdf3ac62009-07-06 12:37:45 -0700105 // mCurrentState is a VideoView object's current state.
106 // mTargetState is the state that a method caller intends to reach.
107 // For instance, regardless the VideoView object's current state,
108 // calling pause() intends to bring the object to a target state
109 // of STATE_PAUSED.
Mathew Inwood978c6e22018-08-21 15:58:55 +0100110 @UnsupportedAppUsage
James Dongfdf3ac62009-07-06 12:37:45 -0700111 private int mCurrentState = STATE_IDLE;
Mathew Inwood978c6e22018-08-21 15:58:55 +0100112 @UnsupportedAppUsage
Alan Viverette768ca7d2016-08-04 09:54:14 -0400113 private int mTargetState = STATE_IDLE;
James Dongfdf3ac62009-07-06 12:37:45 -0700114
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800115 // All the stuff we need for playing and showing a video
Mathew Inwood31755f92018-12-20 13:53:36 +0000116 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800117 private SurfaceHolder mSurfaceHolder = null;
Mathew Inwood978c6e22018-08-21 15:58:55 +0100118 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800119 private MediaPlayer mMediaPlayer = null;
Alan Viverette768ca7d2016-08-04 09:54:14 -0400120 private int mAudioSession;
Mathew Inwood978c6e22018-08-21 15:58:55 +0100121 @UnsupportedAppUsage
Alan Viverette768ca7d2016-08-04 09:54:14 -0400122 private int mVideoWidth;
Mathew Inwood978c6e22018-08-21 15:58:55 +0100123 @UnsupportedAppUsage
Alan Viverette768ca7d2016-08-04 09:54:14 -0400124 private int mVideoHeight;
125 private int mSurfaceWidth;
126 private int mSurfaceHeight;
Mathew Inwood978c6e22018-08-21 15:58:55 +0100127 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800128 private MediaController mMediaController;
129 private OnCompletionListener mOnCompletionListener;
130 private MediaPlayer.OnPreparedListener mOnPreparedListener;
Mathew Inwood978c6e22018-08-21 15:58:55 +0100131 @UnsupportedAppUsage
Alan Viverette768ca7d2016-08-04 09:54:14 -0400132 private int mCurrentBufferPercentage;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800133 private OnErrorListener mOnErrorListener;
Alan Viverette768ca7d2016-08-04 09:54:14 -0400134 private OnInfoListener mOnInfoListener;
135 private int mSeekWhenPrepared; // recording the seek position while preparing
136 private boolean mCanPause;
137 private boolean mCanSeekBack;
138 private boolean mCanSeekForward;
Jean-Michel Trivi0f49f822017-02-16 14:36:43 -0800139 private AudioManager mAudioManager;
140 private int mAudioFocusType = AudioManager.AUDIOFOCUS_GAIN; // legacy focus gain
141 private AudioAttributes mAudioAttributes;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800142
Alan Viveretted43daf32013-09-05 16:34:30 -0700143 /** Subtitle rendering widget overlaid on top of the video. */
144 private RenderingWidget mSubtitleWidget;
Alan Viverettede213f72013-09-03 16:17:56 -0700145
Alan Viveretted43daf32013-09-05 16:34:30 -0700146 /** Listener for changes to subtitle data, used to redraw when needed. */
147 private RenderingWidget.OnChangedListener mSubtitlesChangedListener;
Alan Viverettede213f72013-09-03 16:17:56 -0700148
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800149 public VideoView(Context context) {
Alan Viverette768ca7d2016-08-04 09:54:14 -0400150 this(context, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800151 }
Andreas Huber25643002010-01-28 11:19:57 -0800152
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800153 public VideoView(Context context, AttributeSet attrs) {
154 this(context, attrs, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800155 }
Andreas Huber25643002010-01-28 11:19:57 -0800156
Alan Viverette617feb92013-09-09 18:09:13 -0700157 public VideoView(Context context, AttributeSet attrs, int defStyleAttr) {
158 this(context, attrs, defStyleAttr, 0);
159 }
160
161 public VideoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
162 super(context, attrs, defStyleAttr, defStyleRes);
Alan Viverette768ca7d2016-08-04 09:54:14 -0400163
164 mVideoWidth = 0;
165 mVideoHeight = 0;
166
Jean-Michel Trivi0f49f822017-02-16 14:36:43 -0800167 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
168 mAudioAttributes = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA)
169 .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE).build();
170
Alan Viverette768ca7d2016-08-04 09:54:14 -0400171 getHolder().addCallback(mSHCallback);
172 getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
173
174 setFocusable(true);
175 setFocusableInTouchMode(true);
176 requestFocus();
177
178 mCurrentState = STATE_IDLE;
179 mTargetState = STATE_IDLE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800180 }
181
182 @Override
183 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Marco Nelissen0b2d8722012-11-12 15:20:57 -0800184 //Log.i("@@@@", "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", "
185 // + MeasureSpec.toString(heightMeasureSpec) + ")");
186
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800187 int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
188 int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
189 if (mVideoWidth > 0 && mVideoHeight > 0) {
Marco Nelissen0b2d8722012-11-12 15:20:57 -0800190
191 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
192 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
193 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
194 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
195
196 if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
197 // the size is fixed
198 width = widthSpecSize;
199 height = heightSpecSize;
200
201 // for compatibility, we adjust size based on aspect ratio
202 if ( mVideoWidth * height < width * mVideoHeight ) {
203 //Log.i("@@@", "image too wide, correcting");
204 width = height * mVideoWidth / mVideoHeight;
205 } else if ( mVideoWidth * height > width * mVideoHeight ) {
206 //Log.i("@@@", "image too tall, correcting");
207 height = width * mVideoHeight / mVideoWidth;
208 }
209 } else if (widthSpecMode == MeasureSpec.EXACTLY) {
210 // only the width is fixed, adjust the height to match aspect ratio if possible
211 width = widthSpecSize;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800212 height = width * mVideoHeight / mVideoWidth;
Marco Nelissen0b2d8722012-11-12 15:20:57 -0800213 if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
214 // couldn't match aspect ratio within the constraints
215 height = heightSpecSize;
216 }
217 } else if (heightSpecMode == MeasureSpec.EXACTLY) {
218 // only the height is fixed, adjust the width to match aspect ratio if possible
219 height = heightSpecSize;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800220 width = height * mVideoWidth / mVideoHeight;
Marco Nelissen0b2d8722012-11-12 15:20:57 -0800221 if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
222 // couldn't match aspect ratio within the constraints
223 width = widthSpecSize;
224 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800225 } else {
Marco Nelissen0b2d8722012-11-12 15:20:57 -0800226 // neither the width nor the height are fixed, try to use actual video size
227 width = mVideoWidth;
228 height = mVideoHeight;
229 if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
230 // too tall, decrease both width and height
231 height = heightSpecSize;
232 width = height * mVideoWidth / mVideoHeight;
233 }
234 if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
235 // too wide, decrease both width and height
236 width = widthSpecSize;
237 height = width * mVideoHeight / mVideoWidth;
238 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800239 }
Marco Nelissen0b2d8722012-11-12 15:20:57 -0800240 } else {
241 // no size yet, just adopt the given spec sizes
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800242 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800243 setMeasuredDimension(width, height);
244 }
Andreas Huber25643002010-01-28 11:19:57 -0800245
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800246 @Override
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -0800247 public CharSequence getAccessibilityClassName() {
248 return VideoView.class.getName();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800249 }
250
Marco Nelissenc1374e82012-11-13 10:51:50 -0800251 public int resolveAdjustedSize(int desiredSize, int measureSpec) {
252 return getDefaultSize(desiredSize, measureSpec);
253 }
254
Ronghua Wu0e9e3b22014-08-20 16:24:52 -0700255 /**
256 * Sets video path.
257 *
258 * @param path the path of the video.
259 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800260 public void setVideoPath(String path) {
261 setVideoURI(Uri.parse(path));
262 }
263
Ronghua Wu0e9e3b22014-08-20 16:24:52 -0700264 /**
265 * Sets video URI.
266 *
267 * @param uri the URI of the video.
268 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800269 public void setVideoURI(Uri uri) {
Andreas Huber25643002010-01-28 11:19:57 -0800270 setVideoURI(uri, null);
271 }
272
273 /**
Ronghua Wu0e9e3b22014-08-20 16:24:52 -0700274 * Sets video URI using specific headers.
275 *
276 * @param uri the URI of the video.
277 * @param headers the headers for the URI request.
Ronghua Wu48db7832014-08-25 14:47:58 -0700278 * Note that the cross domain redirection is allowed by default, but that can be
279 * changed with key/value pairs through the headers parameter with
280 * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value
281 * to disallow or allow cross domain redirection.
Andreas Huber25643002010-01-28 11:19:57 -0800282 */
283 public void setVideoURI(Uri uri, Map<String, String> headers) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800284 mUri = uri;
Andreas Huber25643002010-01-28 11:19:57 -0800285 mHeaders = headers;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800286 mSeekWhenPrepared = 0;
287 openVideo();
288 requestLayout();
289 invalidate();
290 }
Andreas Huber25643002010-01-28 11:19:57 -0800291
Lajos Molnaraf3098242013-08-15 20:56:53 -0700292 /**
Jean-Michel Trivi0f49f822017-02-16 14:36:43 -0800293 * Sets which type of audio focus will be requested during the playback, or configures playback
294 * to not request audio focus. Valid values for focus requests are
295 * {@link AudioManager#AUDIOFOCUS_GAIN}, {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT},
296 * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}, and
297 * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}. Or use
298 * {@link AudioManager#AUDIOFOCUS_NONE} to express that audio focus should not be
299 * requested when playback starts. You can for instance use this when playing a silent animation
300 * through this class, and you don't want to affect other audio applications playing in the
301 * background.
302 * @param focusGain the type of audio focus gain that will be requested, or
303 * {@link AudioManager#AUDIOFOCUS_NONE} to disable the use audio focus during playback.
304 */
305 public void setAudioFocusRequest(int focusGain) {
306 if (focusGain != AudioManager.AUDIOFOCUS_NONE
307 && focusGain != AudioManager.AUDIOFOCUS_GAIN
308 && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
309 && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
310 && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) {
311 throw new IllegalArgumentException("Illegal audio focus type " + focusGain);
312 }
313 mAudioFocusType = focusGain;
314 }
315
316 /**
317 * Sets the {@link AudioAttributes} to be used during the playback of the video.
318 * @param attributes non-null <code>AudioAttributes</code>.
319 */
320 public void setAudioAttributes(@NonNull AudioAttributes attributes) {
321 if (attributes == null) {
322 throw new IllegalArgumentException("Illegal null AudioAttributes");
323 }
324 mAudioAttributes = attributes;
325 }
326
327 /**
Lajos Molnaraf3098242013-08-15 20:56:53 -0700328 * Adds an external subtitle source file (from the provided input stream.)
329 *
330 * Note that a single external subtitle source may contain multiple or no
331 * supported tracks in it. If the source contained at least one track in
332 * it, one will receive an {@link MediaPlayer#MEDIA_INFO_METADATA_UPDATE}
333 * info message. Otherwise, if reading the source takes excessive time,
334 * one will receive a {@link MediaPlayer#MEDIA_INFO_SUBTITLE_TIMED_OUT}
335 * message. If the source contained no supported track (including an empty
336 * source file or null input stream), one will receive a {@link
337 * MediaPlayer#MEDIA_INFO_UNSUPPORTED_SUBTITLE} message. One can find the
338 * total number of available tracks using {@link MediaPlayer#getTrackInfo()}
339 * to see what additional tracks become available after this method call.
340 *
341 * @param is input stream containing the subtitle data. It will be
342 * closed by the media framework.
343 * @param format the format of the subtitle track(s). Must contain at least
344 * the mime type ({@link MediaFormat#KEY_MIME}) and the
345 * language ({@link MediaFormat#KEY_LANGUAGE}) of the file.
346 * If the file itself contains the language information,
347 * specify "und" for the language.
348 */
349 public void addSubtitleSource(InputStream is, MediaFormat format) {
Lajos Molnaraf3098242013-08-15 20:56:53 -0700350 if (mMediaPlayer == null) {
Lajos Molnar484ff7a92013-08-15 11:37:47 -0700351 mPendingSubtitleTracks.add(Pair.create(is, format));
Lajos Molnaraf3098242013-08-15 20:56:53 -0700352 } else {
Lajos Molnar484ff7a92013-08-15 11:37:47 -0700353 try {
354 mMediaPlayer.addSubtitleSource(is, format);
355 } catch (IllegalStateException e) {
356 mInfoListener.onInfo(
357 mMediaPlayer, MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, 0);
358 }
Lajos Molnaraf3098242013-08-15 20:56:53 -0700359 }
360 }
361
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800362 public void stopPlayback() {
363 if (mMediaPlayer != null) {
364 mMediaPlayer.stop();
365 mMediaPlayer.release();
366 mMediaPlayer = null;
James Dongfdf3ac62009-07-06 12:37:45 -0700367 mCurrentState = STATE_IDLE;
368 mTargetState = STATE_IDLE;
Jean-Michel Trivi0f49f822017-02-16 14:36:43 -0800369 mAudioManager.abandonAudioFocus(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800370 }
371 }
372
373 private void openVideo() {
374 if (mUri == null || mSurfaceHolder == null) {
375 // not ready for playback just yet, will try again later
376 return;
377 }
Marco Nelissendddeee62009-07-10 14:14:52 -0700378 // we shouldn't clear the target state, because somebody might have
379 // called start() previously
380 release(false);
Marco Nelissen926ebb82015-03-11 09:59:49 -0700381
Jean-Michel Trivi0f49f822017-02-16 14:36:43 -0800382 if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
383 // TODO this should have a focus listener
384 mAudioManager.requestAudioFocus(null, mAudioAttributes, mAudioFocusType, 0 /*flags*/);
385 }
Marco Nelissen926ebb82015-03-11 09:59:49 -0700386
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800387 try {
388 mMediaPlayer = new MediaPlayer();
Lajos Molnar484ff7a92013-08-15 11:37:47 -0700389 // TODO: create SubtitleController in MediaPlayer, but we need
390 // a context for the subtitle renderers
Alan Viveretted43daf32013-09-05 16:34:30 -0700391 final Context context = getContext();
392 final SubtitleController controller = new SubtitleController(
393 context, mMediaPlayer.getMediaTimeProvider(), mMediaPlayer);
394 controller.registerRenderer(new WebVttRenderer(context));
Sungsoo Limba3699b2014-03-17 10:24:32 +0900395 controller.registerRenderer(new TtmlRenderer(context));
Jaesung Chung978bf5e2015-12-07 21:46:40 +0900396 controller.registerRenderer(new Cea708CaptionRenderer(context));
Chong Zhangbdfd9102014-06-11 15:10:23 -0700397 controller.registerRenderer(new ClosedCaptionRenderer(context));
Lajos Molnar484ff7a92013-08-15 11:37:47 -0700398 mMediaPlayer.setSubtitleAnchor(controller, this);
399
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700400 if (mAudioSession != 0) {
401 mMediaPlayer.setAudioSessionId(mAudioSession);
402 } else {
403 mAudioSession = mMediaPlayer.getAudioSessionId();
404 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800405 mMediaPlayer.setOnPreparedListener(mPreparedListener);
406 mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800407 mMediaPlayer.setOnCompletionListener(mCompletionListener);
408 mMediaPlayer.setOnErrorListener(mErrorListener);
Lajos Molnaraf3098242013-08-15 20:56:53 -0700409 mMediaPlayer.setOnInfoListener(mInfoListener);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800410 mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
411 mCurrentBufferPercentage = 0;
Andreas Huber25643002010-01-28 11:19:57 -0800412 mMediaPlayer.setDataSource(mContext, mUri, mHeaders);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800413 mMediaPlayer.setDisplay(mSurfaceHolder);
Jean-Michel Trivi0f49f822017-02-16 14:36:43 -0800414 mMediaPlayer.setAudioAttributes(mAudioAttributes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800415 mMediaPlayer.setScreenOnWhilePlaying(true);
416 mMediaPlayer.prepareAsync();
Lajos Molnaraf3098242013-08-15 20:56:53 -0700417
Lajos Molnar484ff7a92013-08-15 11:37:47 -0700418 for (Pair<InputStream, MediaFormat> pending: mPendingSubtitleTracks) {
419 try {
420 mMediaPlayer.addSubtitleSource(pending.first, pending.second);
421 } catch (IllegalStateException e) {
422 mInfoListener.onInfo(
423 mMediaPlayer, MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, 0);
424 }
Lajos Molnaraf3098242013-08-15 20:56:53 -0700425 }
426
Marco Nelissendddeee62009-07-10 14:14:52 -0700427 // we don't set the target state here either, but preserve the
428 // target state that was there before.
James Dongfdf3ac62009-07-06 12:37:45 -0700429 mCurrentState = STATE_PREPARING;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800430 attachMediaController();
431 } catch (IOException ex) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700432 Log.w(TAG, "Unable to open content: " + mUri, ex);
James Dongfdf3ac62009-07-06 12:37:45 -0700433 mCurrentState = STATE_ERROR;
434 mTargetState = STATE_ERROR;
Andrei Popescu020d2e32009-09-30 14:54:55 +0100435 mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800436 return;
437 } catch (IllegalArgumentException ex) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700438 Log.w(TAG, "Unable to open content: " + mUri, ex);
James Dongfdf3ac62009-07-06 12:37:45 -0700439 mCurrentState = STATE_ERROR;
440 mTargetState = STATE_ERROR;
Andrei Popescu020d2e32009-09-30 14:54:55 +0100441 mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800442 return;
Lajos Molnaraf3098242013-08-15 20:56:53 -0700443 } finally {
Lajos Molnar484ff7a92013-08-15 11:37:47 -0700444 mPendingSubtitleTracks.clear();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800445 }
446 }
Andreas Huber25643002010-01-28 11:19:57 -0800447
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800448 public void setMediaController(MediaController controller) {
449 if (mMediaController != null) {
450 mMediaController.hide();
451 }
452 mMediaController = controller;
453 attachMediaController();
454 }
455
456 private void attachMediaController() {
457 if (mMediaPlayer != null && mMediaController != null) {
458 mMediaController.setMediaPlayer(this);
459 View anchorView = this.getParent() instanceof View ?
460 (View)this.getParent() : this;
461 mMediaController.setAnchorView(anchorView);
James Dongfdf3ac62009-07-06 12:37:45 -0700462 mMediaController.setEnabled(isInPlaybackState());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800463 }
464 }
Andreas Huber25643002010-01-28 11:19:57 -0800465
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800466 MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
467 new MediaPlayer.OnVideoSizeChangedListener() {
468 public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
469 mVideoWidth = mp.getVideoWidth();
470 mVideoHeight = mp.getVideoHeight();
471 if (mVideoWidth != 0 && mVideoHeight != 0) {
472 getHolder().setFixedSize(mVideoWidth, mVideoHeight);
Teng-Hui Zhuf5d102e2012-08-23 15:24:29 -0700473 requestLayout();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800474 }
475 }
476 };
Andreas Huber25643002010-01-28 11:19:57 -0800477
Mathew Inwood978c6e22018-08-21 15:58:55 +0100478 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800479 MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
480 public void onPrepared(MediaPlayer mp) {
James Dongfdf3ac62009-07-06 12:37:45 -0700481 mCurrentState = STATE_PREPARED;
Marco Nelissenc818b142009-08-19 08:32:21 -0700482
483 // Get the capabilities of the player for this stream
484 Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL,
485 MediaPlayer.BYPASS_METADATA_FILTER);
Andreas Huberd44d33b2009-08-20 11:12:27 -0700486
487 if (data != null) {
488 mCanPause = !data.has(Metadata.PAUSE_AVAILABLE)
489 || data.getBoolean(Metadata.PAUSE_AVAILABLE);
490 mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE)
491 || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE);
492 mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE)
493 || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE);
494 } else {
Andreas Huber2869e952010-03-09 09:18:01 -0800495 mCanPause = mCanSeekBack = mCanSeekForward = true;
Andreas Huberd44d33b2009-08-20 11:12:27 -0700496 }
Marco Nelissenc818b142009-08-19 08:32:21 -0700497
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800498 if (mOnPreparedListener != null) {
499 mOnPreparedListener.onPrepared(mMediaPlayer);
500 }
501 if (mMediaController != null) {
502 mMediaController.setEnabled(true);
503 }
504 mVideoWidth = mp.getVideoWidth();
505 mVideoHeight = mp.getVideoHeight();
Marco Nelissenc818b142009-08-19 08:32:21 -0700506
James Dongfdf3ac62009-07-06 12:37:45 -0700507 int seekToPosition = mSeekWhenPrepared; // mSeekWhenPrepared may be changed after seekTo() call
508 if (seekToPosition != 0) {
509 seekTo(seekToPosition);
510 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800511 if (mVideoWidth != 0 && mVideoHeight != 0) {
512 //Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight);
513 getHolder().setFixedSize(mVideoWidth, mVideoHeight);
514 if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {
515 // We didn't actually change the size (it was already at the size
516 // we need), so we won't get a "surface changed" callback, so
517 // start the video here instead of in the callback.
James Dongfdf3ac62009-07-06 12:37:45 -0700518 if (mTargetState == STATE_PLAYING) {
James Dongaa00e392009-07-02 16:26:39 -0700519 start();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800520 if (mMediaController != null) {
521 mMediaController.show();
522 }
523 } else if (!isPlaying() &&
James Dongfdf3ac62009-07-06 12:37:45 -0700524 (seekToPosition != 0 || getCurrentPosition() > 0)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800525 if (mMediaController != null) {
526 // Show the media controls when we're paused into a video and make 'em stick.
527 mMediaController.show(0);
528 }
529 }
530 }
531 } else {
532 // We don't know the video size yet, but should start anyway.
533 // The video size might be reported to us later.
James Dongfdf3ac62009-07-06 12:37:45 -0700534 if (mTargetState == STATE_PLAYING) {
James Dongaa00e392009-07-02 16:26:39 -0700535 start();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800536 }
537 }
538 }
539 };
540
541 private MediaPlayer.OnCompletionListener mCompletionListener =
542 new MediaPlayer.OnCompletionListener() {
543 public void onCompletion(MediaPlayer mp) {
James Dongfdf3ac62009-07-06 12:37:45 -0700544 mCurrentState = STATE_PLAYBACK_COMPLETED;
545 mTargetState = STATE_PLAYBACK_COMPLETED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800546 if (mMediaController != null) {
547 mMediaController.hide();
548 }
549 if (mOnCompletionListener != null) {
550 mOnCompletionListener.onCompletion(mMediaPlayer);
551 }
Jean-Michel Trivi0f49f822017-02-16 14:36:43 -0800552 if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
553 mAudioManager.abandonAudioFocus(null);
554 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800555 }
556 };
557
Lajos Molnaraf3098242013-08-15 20:56:53 -0700558 private MediaPlayer.OnInfoListener mInfoListener =
559 new MediaPlayer.OnInfoListener() {
560 public boolean onInfo(MediaPlayer mp, int arg1, int arg2) {
561 if (mOnInfoListener != null) {
562 mOnInfoListener.onInfo(mp, arg1, arg2);
563 }
564 return true;
565 }
566 };
567
Mathew Inwood31755f92018-12-20 13:53:36 +0000568 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800569 private MediaPlayer.OnErrorListener mErrorListener =
570 new MediaPlayer.OnErrorListener() {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700571 public boolean onError(MediaPlayer mp, int framework_err, int impl_err) {
572 Log.d(TAG, "Error: " + framework_err + "," + impl_err);
James Dongfdf3ac62009-07-06 12:37:45 -0700573 mCurrentState = STATE_ERROR;
574 mTargetState = STATE_ERROR;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800575 if (mMediaController != null) {
576 mMediaController.hide();
577 }
578
579 /* If an error handler has been supplied, use it and finish. */
580 if (mOnErrorListener != null) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700581 if (mOnErrorListener.onError(mMediaPlayer, framework_err, impl_err)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800582 return true;
583 }
584 }
585
586 /* Otherwise, pop up an error dialog so the user knows that
587 * something bad has happened. Only try and pop up the dialog
588 * if we're attached to a window. When we're going away and no
589 * longer have a window, don't bother showing the user an error.
590 */
591 if (getWindowToken() != null) {
592 Resources r = mContext.getResources();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700593 int messageId;
594
595 if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
596 messageId = com.android.internal.R.string.VideoView_error_text_invalid_progressive_playback;
597 } else {
598 messageId = com.android.internal.R.string.VideoView_error_text_unknown;
599 }
600
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800601 new AlertDialog.Builder(mContext)
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700602 .setMessage(messageId)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800603 .setPositiveButton(com.android.internal.R.string.VideoView_error_button,
604 new DialogInterface.OnClickListener() {
605 public void onClick(DialogInterface dialog, int whichButton) {
606 /* If we get here, there is no onError listener, so
607 * at least inform them that the video is over.
608 */
609 if (mOnCompletionListener != null) {
610 mOnCompletionListener.onCompletion(mMediaPlayer);
611 }
612 }
613 })
614 .setCancelable(false)
615 .show();
616 }
617 return true;
618 }
619 };
620
621 private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =
622 new MediaPlayer.OnBufferingUpdateListener() {
623 public void onBufferingUpdate(MediaPlayer mp, int percent) {
624 mCurrentBufferPercentage = percent;
625 }
626 };
627
628 /**
629 * Register a callback to be invoked when the media file
630 * is loaded and ready to go.
631 *
632 * @param l The callback that will be run
633 */
634 public void setOnPreparedListener(MediaPlayer.OnPreparedListener l)
635 {
636 mOnPreparedListener = l;
637 }
638
639 /**
640 * Register a callback to be invoked when the end of a media file
641 * has been reached during playback.
642 *
643 * @param l The callback that will be run
644 */
645 public void setOnCompletionListener(OnCompletionListener l)
646 {
647 mOnCompletionListener = l;
648 }
649
650 /**
651 * Register a callback to be invoked when an error occurs
652 * during playback or setup. If no listener is specified,
653 * or if the listener returned false, VideoView will inform
654 * the user of any errors.
655 *
656 * @param l The callback that will be run
657 */
658 public void setOnErrorListener(OnErrorListener l)
659 {
660 mOnErrorListener = l;
661 }
662
James Donga0ba7942012-07-27 17:30:38 -0700663 /**
664 * Register a callback to be invoked when an informational event
665 * occurs during playback or setup.
666 *
667 * @param l The callback that will be run
668 */
669 public void setOnInfoListener(OnInfoListener l) {
670 mOnInfoListener = l;
671 }
672
Mathew Inwood978c6e22018-08-21 15:58:55 +0100673 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800674 SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()
675 {
676 public void surfaceChanged(SurfaceHolder holder, int format,
677 int w, int h)
678 {
679 mSurfaceWidth = w;
680 mSurfaceHeight = h;
James Dongfdf3ac62009-07-06 12:37:45 -0700681 boolean isValidState = (mTargetState == STATE_PLAYING);
682 boolean hasValidSize = (mVideoWidth == w && mVideoHeight == h);
683 if (mMediaPlayer != null && isValidState && hasValidSize) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800684 if (mSeekWhenPrepared != 0) {
James Dongfdf3ac62009-07-06 12:37:45 -0700685 seekTo(mSeekWhenPrepared);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800686 }
James Dongfdf3ac62009-07-06 12:37:45 -0700687 start();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800688 }
689 }
690
691 public void surfaceCreated(SurfaceHolder holder)
692 {
693 mSurfaceHolder = holder;
Andreas Huber69b8d692010-10-29 12:00:20 -0700694 openVideo();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800695 }
696
697 public void surfaceDestroyed(SurfaceHolder holder)
698 {
699 // after we return from this we can't use the surface any more
700 mSurfaceHolder = null;
701 if (mMediaController != null) mMediaController.hide();
Andreas Huber69b8d692010-10-29 12:00:20 -0700702 release(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800703 }
704 };
705
James Dongfdf3ac62009-07-06 12:37:45 -0700706 /*
707 * release the media player in any state
708 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100709 @UnsupportedAppUsage
Marco Nelissendddeee62009-07-10 14:14:52 -0700710 private void release(boolean cleartargetstate) {
James Dongfdf3ac62009-07-06 12:37:45 -0700711 if (mMediaPlayer != null) {
712 mMediaPlayer.reset();
713 mMediaPlayer.release();
714 mMediaPlayer = null;
Lajos Molnar484ff7a92013-08-15 11:37:47 -0700715 mPendingSubtitleTracks.clear();
James Dongfdf3ac62009-07-06 12:37:45 -0700716 mCurrentState = STATE_IDLE;
Marco Nelissendddeee62009-07-10 14:14:52 -0700717 if (cleartargetstate) {
718 mTargetState = STATE_IDLE;
719 }
Jean-Michel Trivi0f49f822017-02-16 14:36:43 -0800720 if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
721 mAudioManager.abandonAudioFocus(null);
722 }
James Dongfdf3ac62009-07-06 12:37:45 -0700723 }
724 }
725
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800726 @Override
727 public boolean onTouchEvent(MotionEvent ev) {
Vladislav Kaznacheevfd48ed62017-05-02 09:39:06 -0700728 if (ev.getAction() == MotionEvent.ACTION_DOWN
729 && isInPlaybackState() && mMediaController != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800730 toggleMediaControlsVisiblity();
731 }
Vladislav Kaznacheevfd48ed62017-05-02 09:39:06 -0700732 return super.onTouchEvent(ev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800733 }
Andreas Huber25643002010-01-28 11:19:57 -0800734
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800735 @Override
736 public boolean onTrackballEvent(MotionEvent ev) {
Vladislav Kaznacheevfd48ed62017-05-02 09:39:06 -0700737 if (ev.getAction() == MotionEvent.ACTION_DOWN
738 && isInPlaybackState() && mMediaController != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800739 toggleMediaControlsVisiblity();
740 }
Vladislav Kaznacheevfd48ed62017-05-02 09:39:06 -0700741 return super.onTrackballEvent(ev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800742 }
Andreas Huber25643002010-01-28 11:19:57 -0800743
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800744 @Override
745 public boolean onKeyDown(int keyCode, KeyEvent event)
746 {
James Dongfdf3ac62009-07-06 12:37:45 -0700747 boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK &&
748 keyCode != KeyEvent.KEYCODE_VOLUME_UP &&
749 keyCode != KeyEvent.KEYCODE_VOLUME_DOWN &&
Jeff Brownb0418da2010-11-01 15:24:01 -0700750 keyCode != KeyEvent.KEYCODE_VOLUME_MUTE &&
James Dongfdf3ac62009-07-06 12:37:45 -0700751 keyCode != KeyEvent.KEYCODE_MENU &&
752 keyCode != KeyEvent.KEYCODE_CALL &&
753 keyCode != KeyEvent.KEYCODE_ENDCALL;
754 if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800755 if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
Andy Stadlerf8a7cea2009-04-10 16:24:47 -0700756 keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800757 if (mMediaPlayer.isPlaying()) {
758 pause();
759 mMediaController.show();
760 } else {
761 start();
762 mMediaController.hide();
763 }
764 return true;
Jeff Brown4d396052010-10-29 21:50:21 -0700765 } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
Chih-Chung Changd7db7012011-02-10 18:11:27 +0800766 if (!mMediaPlayer.isPlaying()) {
Jeff Brown4d396052010-10-29 21:50:21 -0700767 start();
768 mMediaController.hide();
769 }
770 return true;
Andreas Huber25643002010-01-28 11:19:57 -0800771 } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
Jeff Brown4d396052010-10-29 21:50:21 -0700772 || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
Chih-Chung Changd7db7012011-02-10 18:11:27 +0800773 if (mMediaPlayer.isPlaying()) {
Jeff Brown4d396052010-10-29 21:50:21 -0700774 pause();
775 mMediaController.show();
776 }
777 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800778 } else {
779 toggleMediaControlsVisiblity();
780 }
781 }
782
783 return super.onKeyDown(keyCode, event);
784 }
785
786 private void toggleMediaControlsVisiblity() {
Andreas Huber25643002010-01-28 11:19:57 -0800787 if (mMediaController.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800788 mMediaController.hide();
789 } else {
790 mMediaController.show();
791 }
792 }
Andreas Huber25643002010-01-28 11:19:57 -0800793
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700794 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800795 public void start() {
James Dongfdf3ac62009-07-06 12:37:45 -0700796 if (isInPlaybackState()) {
797 mMediaPlayer.start();
798 mCurrentState = STATE_PLAYING;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800799 }
James Dongfdf3ac62009-07-06 12:37:45 -0700800 mTargetState = STATE_PLAYING;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800801 }
Andreas Huber25643002010-01-28 11:19:57 -0800802
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700803 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800804 public void pause() {
James Dongfdf3ac62009-07-06 12:37:45 -0700805 if (isInPlaybackState()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800806 if (mMediaPlayer.isPlaying()) {
807 mMediaPlayer.pause();
James Dongfdf3ac62009-07-06 12:37:45 -0700808 mCurrentState = STATE_PAUSED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800809 }
810 }
James Dongfdf3ac62009-07-06 12:37:45 -0700811 mTargetState = STATE_PAUSED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800812 }
Andreas Huber25643002010-01-28 11:19:57 -0800813
Gloria Wang2e1818a2010-02-22 14:49:13 -0800814 public void suspend() {
Andreas Huber69b8d692010-10-29 12:00:20 -0700815 release(false);
Gloria Wang2e1818a2010-02-22 14:49:13 -0800816 }
817
818 public void resume() {
Andreas Huber69b8d692010-10-29 12:00:20 -0700819 openVideo();
Gloria Wang2e1818a2010-02-22 14:49:13 -0800820 }
821
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700822 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800823 public int getDuration() {
James Dongfdf3ac62009-07-06 12:37:45 -0700824 if (isInPlaybackState()) {
Andreas Huber585c07e2012-11-27 15:06:56 -0800825 return mMediaPlayer.getDuration();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800826 }
Andreas Huber585c07e2012-11-27 15:06:56 -0800827
828 return -1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800829 }
Andreas Huber25643002010-01-28 11:19:57 -0800830
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700831 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800832 public int getCurrentPosition() {
James Dongfdf3ac62009-07-06 12:37:45 -0700833 if (isInPlaybackState()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800834 return mMediaPlayer.getCurrentPosition();
835 }
836 return 0;
837 }
Andreas Huber25643002010-01-28 11:19:57 -0800838
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700839 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800840 public void seekTo(int msec) {
James Dongfdf3ac62009-07-06 12:37:45 -0700841 if (isInPlaybackState()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800842 mMediaPlayer.seekTo(msec);
James Dongfdf3ac62009-07-06 12:37:45 -0700843 mSeekWhenPrepared = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800844 } else {
845 mSeekWhenPrepared = msec;
846 }
Andreas Huber25643002010-01-28 11:19:57 -0800847 }
848
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700849 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800850 public boolean isPlaying() {
James Dongfdf3ac62009-07-06 12:37:45 -0700851 return isInPlaybackState() && mMediaPlayer.isPlaying();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800852 }
Andreas Huber25643002010-01-28 11:19:57 -0800853
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700854 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800855 public int getBufferPercentage() {
856 if (mMediaPlayer != null) {
857 return mCurrentBufferPercentage;
858 }
859 return 0;
860 }
James Dongfdf3ac62009-07-06 12:37:45 -0700861
862 private boolean isInPlaybackState() {
863 return (mMediaPlayer != null &&
864 mCurrentState != STATE_ERROR &&
865 mCurrentState != STATE_IDLE &&
866 mCurrentState != STATE_PREPARING);
867 }
Marco Nelissenc818b142009-08-19 08:32:21 -0700868
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700869 @Override
Marco Nelissenc818b142009-08-19 08:32:21 -0700870 public boolean canPause() {
871 return mCanPause;
872 }
873
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700874 @Override
Marco Nelissenc818b142009-08-19 08:32:21 -0700875 public boolean canSeekBackward() {
876 return mCanSeekBack;
877 }
878
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700879 @Override
Marco Nelissenc818b142009-08-19 08:32:21 -0700880 public boolean canSeekForward() {
881 return mCanSeekForward;
882 }
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700883
884 @Override
885 public int getAudioSessionId() {
886 if (mAudioSession == 0) {
887 MediaPlayer foo = new MediaPlayer();
888 mAudioSession = foo.getAudioSessionId();
889 foo.release();
890 }
891 return mAudioSession;
892 }
Alan Viverettede213f72013-09-03 16:17:56 -0700893
894 @Override
Alan Viveretted43daf32013-09-05 16:34:30 -0700895 protected void onAttachedToWindow() {
896 super.onAttachedToWindow();
897
898 if (mSubtitleWidget != null) {
899 mSubtitleWidget.onAttachedToWindow();
900 }
901 }
902
903 @Override
904 protected void onDetachedFromWindow() {
905 super.onDetachedFromWindow();
906
907 if (mSubtitleWidget != null) {
908 mSubtitleWidget.onDetachedFromWindow();
909 }
910 }
911
912 @Override
Alan Viverettede213f72013-09-03 16:17:56 -0700913 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
914 super.onLayout(changed, left, top, right, bottom);
915
Alan Viveretted43daf32013-09-05 16:34:30 -0700916 if (mSubtitleWidget != null) {
917 measureAndLayoutSubtitleWidget();
Alan Viverettede213f72013-09-03 16:17:56 -0700918 }
919 }
920
921 @Override
922 public void draw(Canvas canvas) {
923 super.draw(canvas);
924
Alan Viveretted43daf32013-09-05 16:34:30 -0700925 if (mSubtitleWidget != null) {
926 final int saveCount = canvas.save();
927 canvas.translate(getPaddingLeft(), getPaddingTop());
928 mSubtitleWidget.draw(canvas);
929 canvas.restoreToCount(saveCount);
Alan Viverettede213f72013-09-03 16:17:56 -0700930 }
931 }
932
933 /**
Alan Viverettede213f72013-09-03 16:17:56 -0700934 * Forces a measurement and layout pass for all overlaid views.
935 *
Alan Viveretted43daf32013-09-05 16:34:30 -0700936 * @see #setSubtitleWidget(RenderingWidget)
Alan Viverettede213f72013-09-03 16:17:56 -0700937 */
Alan Viveretted43daf32013-09-05 16:34:30 -0700938 private void measureAndLayoutSubtitleWidget() {
939 final int width = getWidth() - getPaddingLeft() - getPaddingRight();
940 final int height = getHeight() - getPaddingTop() - getPaddingBottom();
Alan Viverettede213f72013-09-03 16:17:56 -0700941
Alan Viveretted43daf32013-09-05 16:34:30 -0700942 mSubtitleWidget.setSize(width, height);
Alan Viverettede213f72013-09-03 16:17:56 -0700943 }
Lajos Molnar484ff7a92013-08-15 11:37:47 -0700944
945 /** @hide */
946 @Override
Alan Viveretted43daf32013-09-05 16:34:30 -0700947 public void setSubtitleWidget(RenderingWidget subtitleWidget) {
948 if (mSubtitleWidget == subtitleWidget) {
Lajos Molnar484ff7a92013-08-15 11:37:47 -0700949 return;
950 }
951
Alan Viveretted43daf32013-09-05 16:34:30 -0700952 final boolean attachedToWindow = isAttachedToWindow();
953 if (mSubtitleWidget != null) {
954 if (attachedToWindow) {
955 mSubtitleWidget.onDetachedFromWindow();
956 }
Lajos Molnar484ff7a92013-08-15 11:37:47 -0700957
Alan Viveretted43daf32013-09-05 16:34:30 -0700958 mSubtitleWidget.setOnChangedListener(null);
959 }
960
961 mSubtitleWidget = subtitleWidget;
962
963 if (subtitleWidget != null) {
964 if (mSubtitlesChangedListener == null) {
965 mSubtitlesChangedListener = new RenderingWidget.OnChangedListener() {
966 @Override
967 public void onChanged(RenderingWidget renderingWidget) {
968 invalidate();
969 }
970 };
971 }
972
973 setWillNotDraw(false);
974 subtitleWidget.setOnChangedListener(mSubtitlesChangedListener);
975
976 if (attachedToWindow) {
977 subtitleWidget.onAttachedToWindow();
978 requestLayout();
979 }
980 } else {
981 setWillNotDraw(true);
982 }
983
984 invalidate();
985 }
Lajos Molnar29f51832013-09-20 08:45:31 -0700986
987 /** @hide */
988 @Override
989 public Looper getSubtitleLooper() {
990 return Looper.getMainLooper();
991 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800992}