/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.widget;

import android.annotation.NonNull;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.Cea708CaptionRenderer;
import android.media.ClosedCaptionRenderer;
import android.media.MediaFormat;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer.OnInfoListener;
import android.media.Metadata;
import android.media.SubtitleController;
import android.media.SubtitleTrack.RenderingWidget;
import android.media.TtmlRenderer;
import android.media.WebVttRenderer;
import android.net.Uri;
import android.os.Looper;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.MediaController.MediaPlayerControl;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Vector;

Displays a video file. The VideoView class can load images from various sources (such as resources or content providers), takes care of computing its measurement from the video so that it can be used in any layout manager, and provides various display options such as scaling and tinting.

Note: VideoView does not retain its full state when going into the background. In particular, it does not restore the current play state, play position, selected tracks, or any subtitle tracks added via addSubtitleSource(). Applications should save and restore these on their own in Activity.onSaveInstanceState and Activity.onRestoreInstanceState.

Also note that the audio session id (from getAudioSessionId) may change from its previously returned value when the VideoView is restored.

By default, VideoView requests audio focus with AudioManager.AUDIOFOCUS_GAIN. Use setAudioFocusRequest(int) to change this behavior.

The default AudioAttributes used during playback have a usage of AudioAttributes.USAGE_MEDIA and a content type of AudioAttributes.CONTENT_TYPE_MOVIE, use setAudioAttributes(AudioAttributes) to modify them.

/** * Displays a video file. The VideoView class * can load images from various sources (such as resources or content * providers), takes care of computing its measurement from the video so that * it can be used in any layout manager, and provides various display options * such as scaling and tinting.<p> * * <em>Note: VideoView does not retain its full state when going into the * background.</em> In particular, it does not restore the current play state, * play position, selected tracks, or any subtitle tracks added via * {@link #addSubtitleSource addSubtitleSource()}. Applications should * save and restore these on their own in * {@link android.app.Activity#onSaveInstanceState} and * {@link android.app.Activity#onRestoreInstanceState}.<p> * Also note that the audio session id (from {@link #getAudioSessionId}) may * change from its previously returned value when the VideoView is restored. * <p> * By default, VideoView requests audio focus with {@link AudioManager#AUDIOFOCUS_GAIN}. Use * {@link #setAudioFocusRequest(int)} to change this behavior. * <p> * The default {@link AudioAttributes} used during playback have a usage of * {@link AudioAttributes#USAGE_MEDIA} and a content type of * {@link AudioAttributes#CONTENT_TYPE_MOVIE}, use {@link #setAudioAttributes(AudioAttributes)} to * modify them. */
public class VideoView extends SurfaceView implements MediaPlayerControl, SubtitleController.Anchor { private static final String TAG = "VideoView"; // all possible internal states private static final int STATE_ERROR = -1; private static final int STATE_IDLE = 0; private static final int STATE_PREPARING = 1; private static final int STATE_PREPARED = 2; private static final int STATE_PLAYING = 3; private static final int STATE_PAUSED = 4; private static final int STATE_PLAYBACK_COMPLETED = 5; private final Vector<Pair<InputStream, MediaFormat>> mPendingSubtitleTracks = new Vector<>(); // settable by the client private Uri mUri; private Map<String, String> mHeaders; // mCurrentState is a VideoView object's current state. // mTargetState is the state that a method caller intends to reach. // For instance, regardless the VideoView object's current state, // calling pause() intends to bring the object to a target state // of STATE_PAUSED. private int mCurrentState = STATE_IDLE; private int mTargetState = STATE_IDLE; // All the stuff we need for playing and showing a video private SurfaceHolder mSurfaceHolder = null; private MediaPlayer mMediaPlayer = null; private int mAudioSession; private int mVideoWidth; private int mVideoHeight; private int mSurfaceWidth; private int mSurfaceHeight; private MediaController mMediaController; private OnCompletionListener mOnCompletionListener; private MediaPlayer.OnPreparedListener mOnPreparedListener; private int mCurrentBufferPercentage; private OnErrorListener mOnErrorListener; private OnInfoListener mOnInfoListener; private int mSeekWhenPrepared; // recording the seek position while preparing private boolean mCanPause; private boolean mCanSeekBack; private boolean mCanSeekForward; private AudioManager mAudioManager; private int mAudioFocusType = AudioManager.AUDIOFOCUS_GAIN; // legacy focus gain private AudioAttributes mAudioAttributes;
Subtitle rendering widget overlaid on top of the video.
/** Subtitle rendering widget overlaid on top of the video. */
private RenderingWidget mSubtitleWidget;
Listener for changes to subtitle data, used to redraw when needed.
/** Listener for changes to subtitle data, used to redraw when needed. */
private RenderingWidget.OnChangedListener mSubtitlesChangedListener; public VideoView(Context context) { this(context, null); } public VideoView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public VideoView(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public VideoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); mVideoWidth = 0; mVideoHeight = 0; mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); mAudioAttributes = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA) .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE).build(); getHolder().addCallback(mSHCallback); getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); setFocusable(true); setFocusableInTouchMode(true); requestFocus(); mCurrentState = STATE_IDLE; mTargetState = STATE_IDLE; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //Log.i("@@@@", "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", " // + MeasureSpec.toString(heightMeasureSpec) + ")"); int width = getDefaultSize(mVideoWidth, widthMeasureSpec); int height = getDefaultSize(mVideoHeight, heightMeasureSpec); if (mVideoWidth > 0 && mVideoHeight > 0) { int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) { // the size is fixed width = widthSpecSize; height = heightSpecSize; // for compatibility, we adjust size based on aspect ratio if ( mVideoWidth * height < width * mVideoHeight ) { //Log.i("@@@", "image too wide, correcting"); width = height * mVideoWidth / mVideoHeight; } else if ( mVideoWidth * height > width * mVideoHeight ) { //Log.i("@@@", "image too tall, correcting"); height = width * mVideoHeight / mVideoWidth; } } else if (widthSpecMode == MeasureSpec.EXACTLY) { // only the width is fixed, adjust the height to match aspect ratio if possible width = widthSpecSize; height = width * mVideoHeight / mVideoWidth; if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) { // couldn't match aspect ratio within the constraints height = heightSpecSize; } } else if (heightSpecMode == MeasureSpec.EXACTLY) { // only the height is fixed, adjust the width to match aspect ratio if possible height = heightSpecSize; width = height * mVideoWidth / mVideoHeight; if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) { // couldn't match aspect ratio within the constraints width = widthSpecSize; } } else { // neither the width nor the height are fixed, try to use actual video size width = mVideoWidth; height = mVideoHeight; if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) { // too tall, decrease both width and height height = heightSpecSize; width = height * mVideoWidth / mVideoHeight; } if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) { // too wide, decrease both width and height width = widthSpecSize; height = width * mVideoHeight / mVideoWidth; } } } else { // no size yet, just adopt the given spec sizes } setMeasuredDimension(width, height); } @Override public CharSequence getAccessibilityClassName() { return VideoView.class.getName(); } public int resolveAdjustedSize(int desiredSize, int measureSpec) { return getDefaultSize(desiredSize, measureSpec); }
Sets video path.
Params:
  • path – the path of the video.
/** * Sets video path. * * @param path the path of the video. */
public void setVideoPath(String path) { setVideoURI(Uri.parse(path)); }
Sets video URI.
Params:
  • uri – the URI of the video.
/** * Sets video URI. * * @param uri the URI of the video. */
public void setVideoURI(Uri uri) { setVideoURI(uri, null); }
Sets video URI using specific headers.
Params:
  • uri – the URI of the video.
  • headers – the headers for the URI request. Note that the cross domain redirection is allowed by default, but that can be changed with key/value pairs through the headers parameter with "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to disallow or allow cross domain redirection.
/** * Sets video URI using specific headers. * * @param uri the URI of the video. * @param headers the headers for the URI request. * Note that the cross domain redirection is allowed by default, but that can be * changed with key/value pairs through the headers parameter with * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value * to disallow or allow cross domain redirection. */
public void setVideoURI(Uri uri, Map<String, String> headers) { mUri = uri; mHeaders = headers; mSeekWhenPrepared = 0; openVideo(); requestLayout(); invalidate(); }
Sets which type of audio focus will be requested during the playback, or configures playback to not request audio focus. Valid values for focus requests are AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, and AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE. Or use AudioManager.AUDIOFOCUS_NONE to express that audio focus should not be requested when playback starts. You can for instance use this when playing a silent animation through this class, and you don't want to affect other audio applications playing in the background.
Params:
  • focusGain – the type of audio focus gain that will be requested, or AudioManager.AUDIOFOCUS_NONE to disable the use audio focus during playback.
/** * Sets which type of audio focus will be requested during the playback, or configures playback * to not request audio focus. Valid values for focus requests are * {@link AudioManager#AUDIOFOCUS_GAIN}, {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT}, * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}, and * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}. Or use * {@link AudioManager#AUDIOFOCUS_NONE} to express that audio focus should not be * requested when playback starts. You can for instance use this when playing a silent animation * through this class, and you don't want to affect other audio applications playing in the * background. * @param focusGain the type of audio focus gain that will be requested, or * {@link AudioManager#AUDIOFOCUS_NONE} to disable the use audio focus during playback. */
public void setAudioFocusRequest(int focusGain) { if (focusGain != AudioManager.AUDIOFOCUS_NONE && focusGain != AudioManager.AUDIOFOCUS_GAIN && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) { throw new IllegalArgumentException("Illegal audio focus type " + focusGain); } mAudioFocusType = focusGain; }
Sets the AudioAttributes to be used during the playback of the video.
Params:
  • attributes – non-null AudioAttributes.
/** * Sets the {@link AudioAttributes} to be used during the playback of the video. * @param attributes non-null <code>AudioAttributes</code>. */
public void setAudioAttributes(@NonNull AudioAttributes attributes) { if (attributes == null) { throw new IllegalArgumentException("Illegal null AudioAttributes"); } mAudioAttributes = attributes; }
Adds an external subtitle source file (from the provided input stream.) Note that a single external subtitle source may contain multiple or no supported tracks in it. If the source contained at least one track in it, one will receive an MediaPlayer.MEDIA_INFO_METADATA_UPDATE info message. Otherwise, if reading the source takes excessive time, one will receive a MediaPlayer.MEDIA_INFO_SUBTITLE_TIMED_OUT message. If the source contained no supported track (including an empty source file or null input stream), one will receive a MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE message. One can find the total number of available tracks using MediaPlayer.getTrackInfo() to see what additional tracks become available after this method call.
Params:
  • is – input stream containing the subtitle data. It will be closed by the media framework.
  • format – the format of the subtitle track(s). Must contain at least the mime type (MediaFormat.KEY_MIME) and the language (MediaFormat.KEY_LANGUAGE) of the file. If the file itself contains the language information, specify "und" for the language.
/** * Adds an external subtitle source file (from the provided input stream.) * * Note that a single external subtitle source may contain multiple or no * supported tracks in it. If the source contained at least one track in * it, one will receive an {@link MediaPlayer#MEDIA_INFO_METADATA_UPDATE} * info message. Otherwise, if reading the source takes excessive time, * one will receive a {@link MediaPlayer#MEDIA_INFO_SUBTITLE_TIMED_OUT} * message. If the source contained no supported track (including an empty * source file or null input stream), one will receive a {@link * MediaPlayer#MEDIA_INFO_UNSUPPORTED_SUBTITLE} message. One can find the * total number of available tracks using {@link MediaPlayer#getTrackInfo()} * to see what additional tracks become available after this method call. * * @param is input stream containing the subtitle data. It will be * closed by the media framework. * @param format the format of the subtitle track(s). Must contain at least * the mime type ({@link MediaFormat#KEY_MIME}) and the * language ({@link MediaFormat#KEY_LANGUAGE}) of the file. * If the file itself contains the language information, * specify "und" for the language. */
public void addSubtitleSource(InputStream is, MediaFormat format) { if (mMediaPlayer == null) { mPendingSubtitleTracks.add(Pair.create(is, format)); } else { try { mMediaPlayer.addSubtitleSource(is, format); } catch (IllegalStateException e) { mInfoListener.onInfo( mMediaPlayer, MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, 0); } } } public void stopPlayback() { if (mMediaPlayer != null) { mMediaPlayer.stop(); mMediaPlayer.release(); mMediaPlayer = null; mCurrentState = STATE_IDLE; mTargetState = STATE_IDLE; mAudioManager.abandonAudioFocus(null); } } private void openVideo() { if (mUri == null || mSurfaceHolder == null) { // not ready for playback just yet, will try again later return; } // we shouldn't clear the target state, because somebody might have // called start() previously release(false); if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) { // TODO this should have a focus listener mAudioManager.requestAudioFocus(null, mAudioAttributes, mAudioFocusType, 0 /*flags*/); } try { mMediaPlayer = new MediaPlayer(); // TODO: create SubtitleController in MediaPlayer, but we need // a context for the subtitle renderers final Context context = getContext(); final SubtitleController controller = new SubtitleController( context, mMediaPlayer.getMediaTimeProvider(), mMediaPlayer); controller.registerRenderer(new WebVttRenderer(context)); controller.registerRenderer(new TtmlRenderer(context)); controller.registerRenderer(new Cea708CaptionRenderer(context)); controller.registerRenderer(new ClosedCaptionRenderer(context)); mMediaPlayer.setSubtitleAnchor(controller, this); if (mAudioSession != 0) { mMediaPlayer.setAudioSessionId(mAudioSession); } else { mAudioSession = mMediaPlayer.getAudioSessionId(); } mMediaPlayer.setOnPreparedListener(mPreparedListener); mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener); mMediaPlayer.setOnCompletionListener(mCompletionListener); mMediaPlayer.setOnErrorListener(mErrorListener); mMediaPlayer.setOnInfoListener(mInfoListener); mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener); mCurrentBufferPercentage = 0; mMediaPlayer.setDataSource(mContext, mUri, mHeaders); mMediaPlayer.setDisplay(mSurfaceHolder); mMediaPlayer.setAudioAttributes(mAudioAttributes); mMediaPlayer.setScreenOnWhilePlaying(true); mMediaPlayer.prepareAsync(); for (Pair<InputStream, MediaFormat> pending: mPendingSubtitleTracks) { try { mMediaPlayer.addSubtitleSource(pending.first, pending.second); } catch (IllegalStateException e) { mInfoListener.onInfo( mMediaPlayer, MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, 0); } } // we don't set the target state here either, but preserve the // target state that was there before. mCurrentState = STATE_PREPARING; attachMediaController(); } catch (IOException ex) { Log.w(TAG, "Unable to open content: " + mUri, ex); mCurrentState = STATE_ERROR; mTargetState = STATE_ERROR; mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); return; } catch (IllegalArgumentException ex) { Log.w(TAG, "Unable to open content: " + mUri, ex); mCurrentState = STATE_ERROR; mTargetState = STATE_ERROR; mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); return; } finally { mPendingSubtitleTracks.clear(); } } public void setMediaController(MediaController controller) { if (mMediaController != null) { mMediaController.hide(); } mMediaController = controller; attachMediaController(); } private void attachMediaController() { if (mMediaPlayer != null && mMediaController != null) { mMediaController.setMediaPlayer(this); View anchorView = this.getParent() instanceof View ? (View)this.getParent() : this; mMediaController.setAnchorView(anchorView); mMediaController.setEnabled(isInPlaybackState()); } } MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener = new MediaPlayer.OnVideoSizeChangedListener() { public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { mVideoWidth = mp.getVideoWidth(); mVideoHeight = mp.getVideoHeight(); if (mVideoWidth != 0 && mVideoHeight != 0) { getHolder().setFixedSize(mVideoWidth, mVideoHeight); requestLayout(); } } }; MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() { public void onPrepared(MediaPlayer mp) { mCurrentState = STATE_PREPARED; // Get the capabilities of the player for this stream Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL, MediaPlayer.BYPASS_METADATA_FILTER); if (data != null) { mCanPause = !data.has(Metadata.PAUSE_AVAILABLE) || data.getBoolean(Metadata.PAUSE_AVAILABLE); mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE) || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE); mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE) || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE); } else { mCanPause = mCanSeekBack = mCanSeekForward = true; } if (mOnPreparedListener != null) { mOnPreparedListener.onPrepared(mMediaPlayer); } if (mMediaController != null) { mMediaController.setEnabled(true); } mVideoWidth = mp.getVideoWidth(); mVideoHeight = mp.getVideoHeight(); int seekToPosition = mSeekWhenPrepared; // mSeekWhenPrepared may be changed after seekTo() call if (seekToPosition != 0) { seekTo(seekToPosition); } if (mVideoWidth != 0 && mVideoHeight != 0) { //Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight); getHolder().setFixedSize(mVideoWidth, mVideoHeight); if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) { // We didn't actually change the size (it was already at the size // we need), so we won't get a "surface changed" callback, so // start the video here instead of in the callback. if (mTargetState == STATE_PLAYING) { start(); if (mMediaController != null) { mMediaController.show(); } } else if (!isPlaying() && (seekToPosition != 0 || getCurrentPosition() > 0)) { if (mMediaController != null) { // Show the media controls when we're paused into a video and make 'em stick. mMediaController.show(0); } } } } else { // We don't know the video size yet, but should start anyway. // The video size might be reported to us later. if (mTargetState == STATE_PLAYING) { start(); } } } }; private MediaPlayer.OnCompletionListener mCompletionListener = new MediaPlayer.OnCompletionListener() { public void onCompletion(MediaPlayer mp) { mCurrentState = STATE_PLAYBACK_COMPLETED; mTargetState = STATE_PLAYBACK_COMPLETED; if (mMediaController != null) { mMediaController.hide(); } if (mOnCompletionListener != null) { mOnCompletionListener.onCompletion(mMediaPlayer); } if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) { mAudioManager.abandonAudioFocus(null); } } }; private MediaPlayer.OnInfoListener mInfoListener = new MediaPlayer.OnInfoListener() { public boolean onInfo(MediaPlayer mp, int arg1, int arg2) { if (mOnInfoListener != null) { mOnInfoListener.onInfo(mp, arg1, arg2); } return true; } }; private MediaPlayer.OnErrorListener mErrorListener = new MediaPlayer.OnErrorListener() { public boolean onError(MediaPlayer mp, int framework_err, int impl_err) { Log.d(TAG, "Error: " + framework_err + "," + impl_err); mCurrentState = STATE_ERROR; mTargetState = STATE_ERROR; if (mMediaController != null) { mMediaController.hide(); } /* If an error handler has been supplied, use it and finish. */ if (mOnErrorListener != null) { if (mOnErrorListener.onError(mMediaPlayer, framework_err, impl_err)) { return true; } } /* Otherwise, pop up an error dialog so the user knows that * something bad has happened. Only try and pop up the dialog * if we're attached to a window. When we're going away and no * longer have a window, don't bother showing the user an error. */ if (getWindowToken() != null) { Resources r = mContext.getResources(); int messageId; if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) { messageId = com.android.internal.R.string.VideoView_error_text_invalid_progressive_playback; } else { messageId = com.android.internal.R.string.VideoView_error_text_unknown; } new AlertDialog.Builder(mContext) .setMessage(messageId) .setPositiveButton(com.android.internal.R.string.VideoView_error_button, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { /* If we get here, there is no onError listener, so * at least inform them that the video is over. */ if (mOnCompletionListener != null) { mOnCompletionListener.onCompletion(mMediaPlayer); } } }) .setCancelable(false) .show(); } return true; } }; private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() { public void onBufferingUpdate(MediaPlayer mp, int percent) { mCurrentBufferPercentage = percent; } };
Register a callback to be invoked when the media file is loaded and ready to go.
Params:
  • l – The callback that will be run
/** * Register a callback to be invoked when the media file * is loaded and ready to go. * * @param l The callback that will be run */
public void setOnPreparedListener(MediaPlayer.OnPreparedListener l) { mOnPreparedListener = l; }
Register a callback to be invoked when the end of a media file has been reached during playback.
Params:
  • l – The callback that will be run
/** * Register a callback to be invoked when the end of a media file * has been reached during playback. * * @param l The callback that will be run */
public void setOnCompletionListener(OnCompletionListener l) { mOnCompletionListener = l; }
Register a callback to be invoked when an error occurs during playback or setup. If no listener is specified, or if the listener returned false, VideoView will inform the user of any errors.
Params:
  • l – The callback that will be run
/** * Register a callback to be invoked when an error occurs * during playback or setup. If no listener is specified, * or if the listener returned false, VideoView will inform * the user of any errors. * * @param l The callback that will be run */
public void setOnErrorListener(OnErrorListener l) { mOnErrorListener = l; }
Register a callback to be invoked when an informational event occurs during playback or setup.
Params:
  • l – The callback that will be run
/** * Register a callback to be invoked when an informational event * occurs during playback or setup. * * @param l The callback that will be run */
public void setOnInfoListener(OnInfoListener l) { mOnInfoListener = l; } SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback() { public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { mSurfaceWidth = w; mSurfaceHeight = h; boolean isValidState = (mTargetState == STATE_PLAYING); boolean hasValidSize = (mVideoWidth == w && mVideoHeight == h); if (mMediaPlayer != null && isValidState && hasValidSize) { if (mSeekWhenPrepared != 0) { seekTo(mSeekWhenPrepared); } start(); } } public void surfaceCreated(SurfaceHolder holder) { mSurfaceHolder = holder; openVideo(); } public void surfaceDestroyed(SurfaceHolder holder) { // after we return from this we can't use the surface any more mSurfaceHolder = null; if (mMediaController != null) mMediaController.hide(); release(true); } }; /* * release the media player in any state */ private void release(boolean cleartargetstate) { if (mMediaPlayer != null) { mMediaPlayer.reset(); mMediaPlayer.release(); mMediaPlayer = null; mPendingSubtitleTracks.clear(); mCurrentState = STATE_IDLE; if (cleartargetstate) { mTargetState = STATE_IDLE; } if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) { mAudioManager.abandonAudioFocus(null); } } } @Override public boolean onTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN && isInPlaybackState() && mMediaController != null) { toggleMediaControlsVisiblity(); } return super.onTouchEvent(ev); } @Override public boolean onTrackballEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN && isInPlaybackState() && mMediaController != null) { toggleMediaControlsVisiblity(); } return super.onTrackballEvent(ev); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK && keyCode != KeyEvent.KEYCODE_VOLUME_UP && keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && keyCode != KeyEvent.KEYCODE_VOLUME_MUTE && keyCode != KeyEvent.KEYCODE_MENU && keyCode != KeyEvent.KEYCODE_CALL && keyCode != KeyEvent.KEYCODE_ENDCALL; if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) { if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) { if (mMediaPlayer.isPlaying()) { pause(); mMediaController.show(); } else { start(); mMediaController.hide(); } return true; } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) { if (!mMediaPlayer.isPlaying()) { start(); mMediaController.hide(); } return true; } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) { if (mMediaPlayer.isPlaying()) { pause(); mMediaController.show(); } return true; } else { toggleMediaControlsVisiblity(); } } return super.onKeyDown(keyCode, event); } private void toggleMediaControlsVisiblity() { if (mMediaController.isShowing()) { mMediaController.hide(); } else { mMediaController.show(); } } @Override public void start() { if (isInPlaybackState()) { mMediaPlayer.start(); mCurrentState = STATE_PLAYING; } mTargetState = STATE_PLAYING; } @Override public void pause() { if (isInPlaybackState()) { if (mMediaPlayer.isPlaying()) { mMediaPlayer.pause(); mCurrentState = STATE_PAUSED; } } mTargetState = STATE_PAUSED; } public void suspend() { release(false); } public void resume() { openVideo(); } @Override public int getDuration() { if (isInPlaybackState()) { return mMediaPlayer.getDuration(); } return -1; } @Override public int getCurrentPosition() { if (isInPlaybackState()) { return mMediaPlayer.getCurrentPosition(); } return 0; } @Override public void seekTo(int msec) { if (isInPlaybackState()) { mMediaPlayer.seekTo(msec); mSeekWhenPrepared = 0; } else { mSeekWhenPrepared = msec; } } @Override public boolean isPlaying() { return isInPlaybackState() && mMediaPlayer.isPlaying(); } @Override public int getBufferPercentage() { if (mMediaPlayer != null) { return mCurrentBufferPercentage; } return 0; } private boolean isInPlaybackState() { return (mMediaPlayer != null && mCurrentState != STATE_ERROR && mCurrentState != STATE_IDLE && mCurrentState != STATE_PREPARING); } @Override public boolean canPause() { return mCanPause; } @Override public boolean canSeekBackward() { return mCanSeekBack; } @Override public boolean canSeekForward() { return mCanSeekForward; } @Override public int getAudioSessionId() { if (mAudioSession == 0) { MediaPlayer foo = new MediaPlayer(); mAudioSession = foo.getAudioSessionId(); foo.release(); } return mAudioSession; } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (mSubtitleWidget != null) { mSubtitleWidget.onAttachedToWindow(); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (mSubtitleWidget != null) { mSubtitleWidget.onDetachedFromWindow(); } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (mSubtitleWidget != null) { measureAndLayoutSubtitleWidget(); } } @Override public void draw(Canvas canvas) { super.draw(canvas); if (mSubtitleWidget != null) { final int saveCount = canvas.save(); canvas.translate(getPaddingLeft(), getPaddingTop()); mSubtitleWidget.draw(canvas); canvas.restoreToCount(saveCount); } }
Forces a measurement and layout pass for all overlaid views.
See Also:
  • setSubtitleWidget(RenderingWidget)
/** * Forces a measurement and layout pass for all overlaid views. * * @see #setSubtitleWidget(RenderingWidget) */
private void measureAndLayoutSubtitleWidget() { final int width = getWidth() - getPaddingLeft() - getPaddingRight(); final int height = getHeight() - getPaddingTop() - getPaddingBottom(); mSubtitleWidget.setSize(width, height); }
@hide
/** @hide */
@Override public void setSubtitleWidget(RenderingWidget subtitleWidget) { if (mSubtitleWidget == subtitleWidget) { return; } final boolean attachedToWindow = isAttachedToWindow(); if (mSubtitleWidget != null) { if (attachedToWindow) { mSubtitleWidget.onDetachedFromWindow(); } mSubtitleWidget.setOnChangedListener(null); } mSubtitleWidget = subtitleWidget; if (subtitleWidget != null) { if (mSubtitlesChangedListener == null) { mSubtitlesChangedListener = new RenderingWidget.OnChangedListener() { @Override public void onChanged(RenderingWidget renderingWidget) { invalidate(); } }; } setWillNotDraw(false); subtitleWidget.setOnChangedListener(mSubtitlesChangedListener); if (attachedToWindow) { subtitleWidget.onAttachedToWindow(); requestLayout(); } } else { setWillNotDraw(true); } invalidate(); }
@hide
/** @hide */
@Override public Looper getSubtitleLooper() { return Looper.getMainLooper(); } }