VideoView源码分析
VideoView源码分析
VideoView
基于Android4.4源码进行分析
简介
/*** 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.*/
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
关系
public class VideoView extends SurfaceViewimplements MediaPlayerControl
- 1
- 2
成员
播放器所有的状态
// 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;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
记录播放器状态
// 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;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
主要功能部分
private SurfaceHolder mSurfaceHolder = null;// 显示图像 private MediaPlayer mMediaPlayer = null; // 声音、播放 private MediaController mMediaController; // 播放控制
- 1
- 2
- 3
其他
private int mVideoWidth; // 视频宽度 在onVideoSizeChanged() 和 onPrepared() 中可以得到具体大小 private int mVideoHeight; //视频高度 private int mSurfaceWidth; // Surface宽度 在SurfaceHolder.Callback.surfaceChanged() 中可以得到具体大小 private int mSurfaceHeight; // Surface高度 private int mSeekWhenPrepared; // recording the seek position while preparing
- 1
- 2
- 3
- 4
- 5
具体实现
构造方法
public VideoView(Context context) {super(context);initVideoView(); }public VideoView(Context context, AttributeSet attrs) {this(context, attrs, 0);initVideoView(); }public VideoView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);initVideoView(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
// 进行一些必要信息的初始化设置 private void initVideoView() {mVideoWidth = 0;mVideoHeight = 0;// 通过SurfaceHolder去控制SurfaceViewgetHolder().addCallback(mSHCallback);// Deprecated. this is ignored, this value is set automatically when needed.Android3.0以上会自动设置,但是为了兼容还需设置getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);setFocusable(true);setFocusableInTouchMode(true);requestFocus();// 字幕相关,用不到mPendingSubtitleTracks = new Vector<Pair<InputStream, MediaFormat>>();mCurrentState = STATE_IDLE;mTargetState = STATE_IDLE; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
SurfaceHolder.Callback源码
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);}// 如果当前已经是播放状态的话就调用mediaplaer.start() 方法,并且把当前状态以及目标状态进行改变start();}}public void surfaceCreated(SurfaceHolder holder){mSurfaceHolder = holder;// Surface创建后就开始调用播放openVideo();}public void surfaceDestroyed(SurfaceHolder holder){// after we return from this we can't use the surface any moremSurfaceHolder = null;if (mMediaController != null) mMediaController.hide();release(true);} };
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
重写onMeasure()方法
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int width = getDefaultSize(mVideoWidth, widthMeasureSpec);int height = getDefaultSize(mVideoHeight, heightMeasureSpec);// ....根据视频的宽高比进行处理, 为了更好的宽展,提供一些用户能自己选择的模式,一般会另外提供方法, 这部分代码可以先不看 startint 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 fixedwidth = widthSpecSize;height = heightSpecSize;// for compatibility, we adjust size based on aspect ratioif ( 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 possiblewidth = widthSpecSize;height = width * mVideoHeight / mVideoWidth;if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {// couldn't match aspect ratio within the constraintsheight = heightSpecSize;}} else if (heightSpecMode == MeasureSpec.EXACTLY) {// only the height is fixed, adjust the width to match aspect ratio if possibleheight = heightSpecSize;width = height * mVideoWidth / mVideoHeight;if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {// couldn't match aspect ratio within the constraintswidth = widthSpecSize;}} else {// neither the width nor the height are fixed, try to use actual video sizewidth = mVideoWidth;height = mVideoHeight;if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {// too tall, decrease both width and heightheight = heightSpecSize;width = height * mVideoWidth / mVideoHeight;}if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {// too wide, decrease both width and heightwidth = widthSpecSize;height = width * mVideoHeight / mVideoWidth;}}} else {// no size yet, just adopt the given spec sizes}// endsetMeasuredDimension(width, height); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
附上getDefaultSize()源码
/*** Utility to return a default size. Uses the supplied size if the* MeasureSpec imposed no constraints. Will get larger if allowed* by the MeasureSpec.** @param size Default size for this view* @param measureSpec Constraints imposed by the parent* @return The size this view should be.*/ public static int getDefaultSize(int size, int measureSpec) {int result = size;int specMode = MeasureSpec.getMode(measureSpec);int specSize = MeasureSpec.getSize(measureSpec);switch (specMode) {case MeasureSpec.UNSPECIFIED:result = size;break;case MeasureSpec.AT_MOST:case MeasureSpec.EXACTLY:result = specSize;break;}return result; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
外部进行播放调用
public void setVideoPath(String path) {setVideoURI(Uri.parse(path)); }public void setVideoURI(Uri uri) {setVideoURI(uri, null); }/*** @hide*/ public void setVideoURI(Uri uri, Map<String, String> headers) {mUri = uri;mHeaders = headers;mSeekWhenPrepared = 0;openVideo();requestLayout();invalidate(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
openVide() 源码
private void openVideo() {if (mUri == null || mSurfaceHolder == null) {// not ready for playback just yet, will try again laterreturn;}// Tell the music playback service to pause// TODO: these constants need to be published somewhere in the framework.Intent i = new Intent("com.android.music.musicservicecommand");i.putExtra("command", "pause");mContext.sendBroadcast(i);// we shouldn't clear the target state, because somebody might have// called start() previously // 先把已经存在的MediaPlayer释放掉,然后重新创建一个, 不一定只在SetVideoPath() 中调用,在其他地方也会调用release(false);try {// 创建一个MediaPlayermMediaPlayer = new MediaPlayer();// TODO: create SubtitleController in MediaPlayer, but we need// a context for the subtitle renderersfinal Context context = getContext();final SubtitleController controller = new SubtitleController(context, mMediaPlayer.getMediaTimeProvider(), mMediaPlayer);controller.registerRenderer(new WebVttRenderer(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;// 让MediaPlayer进行播放mMediaPlayer.setDataSource(mContext, mUri, mHeaders);// 让SurfaceView进行画面显示mMediaPlayer.setDisplay(mSurfaceHolder);// 设置音频类型mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);// 播放时屏幕常亮mMediaPlayer.setScreenOnWhilePlaying(true);// Prepares the player for playback, asynchronously. After setting the datasource and the display surface, you need to either call prepare() or prepareAsync(). For streams, you should call prepareAsync(), // which returns immediately, rather than blocking until enough data has been buffered.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;// 如果已经调用过SetMediaController() 方法,这里会直接显示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();} }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
release() 方法,在开始播放一个视频的时候会先调用该方法,然后重新创建一个,在SurfaceView销毁的时候也会调用该方法
/** 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;}} }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
外部停止播放调用
public void stopPlayback() {if (mMediaPlayer != null) {mMediaPlayer.stop();mMediaPlayer.release();mMediaPlayer = null;mCurrentState = STATE_IDLE;mTargetState = STATE_IDLE;} }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
外部设置控制栏部分
public void setMediaController(MediaController controller) {if (mMediaController != null) {mMediaController.hide();}mMediaController = controller;attachMediaController(); }private void attachMediaController() {if (mMediaPlayer != null && mMediaController != null) {// setMediaPlayer(MediaPlayerControl player), 让MediaPlayer相应的控制部分调用本类中的实现方法mMediaController.setMediaPlayer(this);View anchorView = this.getParent() instanceof View ?(View)this.getParent() : this;// 创建Controller并且依据AnchorView的位置进行显示mMediaController.setAnchorView(anchorView);mMediaController.setEnabled(isInPlaybackState());} }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
MediaPlayer必要监听
OnVideoSizeChangedListener
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) {// 这个方法是设置Surface分辨率,而不是设置视频播放窗口的大小,视频播放窗口大小是由SurfaceView的布局控制,要分清Surface与SurfaceView的区别,Surface是Window中整个的一个控件(句柄),// 而SurfaceView是一个包含Surface的View,SurfaceView覆盖到Surface上(可以这样理解),我们只能通过SurfaceView来看Surface中的内容,至于在SurfaceView显示之外的Surface我们是不可见的.getHolder().setFixedSize(mVideoWidth, mVideoHeight);requestLayout();}} };
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
OnPreparedListener
MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {public void onPrepared(MediaPlayer mp) {mCurrentState = STATE_PREPARED;// Get the capabilities of the player for this streamMetadata 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() callif (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();}}} };
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
OnCompletionListener
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);}} };
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
Touch以及Key的监听
@Override public boolean onTouchEvent(MotionEvent ev) {if (isInPlaybackState() && mMediaController != null) {// 控制MediaController的显示与隐藏toggleMediaControlsVisiblity();}return false; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
toggleMediaControlsVisiblity
private void toggleMediaControlsVisiblity() {if (mMediaController.isShowing()) {mMediaController.hide();} else {mMediaController.show();} }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
Key
@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); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
华丽丽的分割线 上源码
/** 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.*//*** 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.*/
public class VideoView extends SurfaceViewimplements MediaPlayerControl, SubtitleController.Anchor {private String TAG = "VideoView";// settable by the clientprivate Uri mUri;private Map<String, String> mHeaders;// all possible internal statesprivate 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;// 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 videoprivate 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 preparingprivate boolean mCanPause;private boolean mCanSeekBack;private boolean mCanSeekForward;/** Subtitle rendering widget overlaid on top of the video. */private RenderingWidget mSubtitleWidget;/** Listener for changes to subtitle data, used to redraw when needed. */private RenderingWidget.OnChangedListener mSubtitlesChangedListener;public VideoView(Context context) {super(context);initVideoView();}public VideoView(Context context, AttributeSet attrs) {this(context, attrs, 0);initVideoView();}public VideoView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);initVideoView();}@Overrideprotected 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 fixedwidth = widthSpecSize;height = heightSpecSize;// for compatibility, we adjust size based on aspect ratioif ( 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 possiblewidth = widthSpecSize;height = width * mVideoHeight / mVideoWidth;if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {// couldn't match aspect ratio within the constraintsheight = heightSpecSize;}} else if (heightSpecMode == MeasureSpec.EXACTLY) {// only the height is fixed, adjust the width to match aspect ratio if possibleheight = heightSpecSize;width = height * mVideoWidth / mVideoHeight;if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {// couldn't match aspect ratio within the constraintswidth = widthSpecSize;}} else {// neither the width nor the height are fixed, try to use actual video sizewidth = mVideoWidth;height = mVideoHeight;if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {// too tall, decrease both width and heightheight = heightSpecSize;width = height * mVideoWidth / mVideoHeight;}if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {// too wide, decrease both width and heightwidth = widthSpecSize;height = width * mVideoHeight / mVideoWidth;}}} else {// no size yet, just adopt the given spec sizes}setMeasuredDimension(width, height);}@Overridepublic void onInitializeAccessibilityEvent(AccessibilityEvent event) {super.onInitializeAccessibilityEvent(event);event.setClassName(VideoView.class.getName());}@Overridepublic void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {super.onInitializeAccessibilityNodeInfo(info);info.setClassName(VideoView.class.getName());}public int resolveAdjustedSize(int desiredSize, int measureSpec) {return getDefaultSize(desiredSize, measureSpec);}private void initVideoView() {mVideoWidth = 0;mVideoHeight = 0;getHolder().addCallback(mSHCallback);getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);setFocusable(true);setFocusableInTouchMode(true);requestFocus();mPendingSubtitleTracks = new Vector<Pair<InputStream, MediaFormat>>();mCurrentState = STATE_IDLE;mTargetState = STATE_IDLE;}public void setVideoPath(String path) {setVideoURI(Uri.parse(path));}public void setVideoURI(Uri uri) {setVideoURI(uri, null);}/*** @hide*/public void setVideoURI(Uri uri, Map<String, String> headers) {mUri = uri;mHeaders = headers;mSeekWhenPrepared = 0;openVideo();requestLayout();invalidate();}/*** 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);}}}private Vector<Pair<InputStream, MediaFormat>> mPendingSubtitleTracks;public void stopPlayback() {if (mMediaPlayer != null) {mMediaPlayer.stop();mMediaPlayer.release();mMediaPlayer = null;mCurrentState = STATE_IDLE;mTargetState = STATE_IDLE;}}private void openVideo() {if (mUri == null || mSurfaceHolder == null) {// not ready for playback just yet, will try again laterreturn;}// Tell the music playback service to pause// TODO: these constants need to be published somewhere in the framework.Intent i = new Intent("com.android.music.musicservicecommand");i.putExtra("command", "pause");mContext.sendBroadcast(i);// we shouldn't clear the target state, because somebody might have// called start() previouslyrelease(false);try {mMediaPlayer = new MediaPlayer();// TODO: create SubtitleController in MediaPlayer, but we need// a context for the subtitle renderersfinal Context context = getContext();final SubtitleController controller = new SubtitleController(context, mMediaPlayer.getMediaTimeProvider(), mMediaPlayer);controller.registerRenderer(new WebVttRenderer(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.setAudioStreamType(AudioManager.STREAM_MUSIC);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 streamMetadata 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() callif (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);}}};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.** @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.** @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.** @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.** @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 moremSurfaceHolder = 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;}}}@Overridepublic boolean onTouchEvent(MotionEvent ev) {if (isInPlaybackState() && mMediaController != null) {toggleMediaControlsVisiblity();}return false;}@Overridepublic boolean onTrackballEvent(MotionEvent ev) {if (isInPlaybackState() && mMediaController != null) {toggleMediaControlsVisiblity();}return false;}@Overridepublic 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();}}@Overridepublic void start() {if (isInPlaybackState()) {mMediaPlayer.start();mCurrentState = STATE_PLAYING;}mTargetState = STATE_PLAYING;}@Overridepublic void pause() {if (isInPlaybackState()) {if (mMediaPlayer.isPlaying()) {mMediaPlayer.pause();mCurrentState = STATE_PAUSED;}}mTargetState = STATE_PAUSED;}public void suspend() {release(false);}public void resume() {openVideo();}@Overridepublic int getDuration() {if (isInPlaybackState()) {return mMediaPlayer.getDuration();}return -1;}@Overridepublic int getCurrentPosition() {if (isInPlaybackState()) {return mMediaPlayer.getCurrentPosition();}return 0;}@Overridepublic void seekTo(int msec) {if (isInPlaybackState()) {mMediaPlayer.seekTo(msec);mSeekWhenPrepared = 0;} else {mSeekWhenPrepared = msec;}}@Overridepublic boolean isPlaying() {return isInPlaybackState() && mMediaPlayer.isPlaying();}@Overridepublic int getBufferPercentage() {if (mMediaPlayer != null) {return mCurrentBufferPercentage;}return 0;}private boolean isInPlaybackState() {return (mMediaPlayer != null &&mCurrentState != STATE_ERROR &&mCurrentState != STATE_IDLE &&mCurrentState != STATE_PREPARING);}@Overridepublic boolean canPause() {return mCanPause;}@Overridepublic boolean canSeekBackward() {return mCanSeekBack;}@Overridepublic boolean canSeekForward() {return mCanSeekForward;}@Overridepublic int getAudioSessionId() {if (mAudioSession == 0) {MediaPlayer foo = new MediaPlayer();mAudioSession = foo.getAudioSessionId();foo.release();}return mAudioSession;}@Overrideprotected void onAttachedToWindow() {super.onAttachedToWindow();if (mSubtitleWidget != null) {mSubtitleWidget.onAttachedToWindow();}}@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();if (mSubtitleWidget != null) {mSubtitleWidget.onDetachedFromWindow();}}@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {super.onLayout(changed, left, top, right, bottom);if (mSubtitleWidget != null) {measureAndLayoutSubtitleWidget();}}@Overridepublic 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 #setSubtitleWidget(RenderingWidget)*/private void measureAndLayoutSubtitleWidget() {final int width = getWidth() - getPaddingLeft() - getPaddingRight();final int height = getHeight() - getPaddingTop() - getPaddingBottom();mSubtitleWidget.setSize(width, height);}/** @hide */@Overridepublic 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() {@Overridepublic void onChanged(RenderingWidget renderingWidget) {invalidate();}};}setWillNotDraw(false);subtitleWidget.setOnChangedListener(mSubtitlesChangedListener);if (attachedToWindow) {subtitleWidget.onAttachedToWindow();requestLayout();}} else {setWillNotDraw(true);}invalidate();}/** @hide */@Overridepublic Looper getSubtitleLooper() {return Looper.getMainLooper();}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 368
- 369
- 370
- 371
- 372
- 373
- 374
- 375
- 376
- 377
- 378
- 379
- 380
- 381
- 382
- 383
- 384
- 385
- 386
- 387
- 388
- 389
- 390
- 391
- 392
- 393
- 394
- 395
- 396
- 397
- 398
- 399
- 400
- 401
- 402
- 403
- 404
- 405
- 406
- 407
- 408
- 409
- 410
- 411
- 412
- 413
- 414
- 415
- 416
- 417
- 418
- 419
- 420
- 421
- 422
- 423
- 424
- 425
- 426
- 427
- 428
- 429
- 430
- 431
- 432
- 433
- 434
- 435
- 436
- 437
- 438
- 439
- 440
- 441
- 442
- 443
- 444
- 445
- 446
- 447
- 448
- 449
- 450
- 451
- 452
- 453
- 454
- 455
- 456
- 457
- 458
- 459
- 460
- 461
- 462
- 463
- 464
- 465
- 466
- 467
- 468
- 469
- 470
- 471
- 472
- 473
- 474
- 475
- 476
- 477
- 478
- 479
- 480
- 481
- 482
- 483
- 484
- 485
- 486
- 487
- 488
- 489
- 490
- 491
- 492
- 493
- 494
- 495
- 496
- 497
- 498
- 499
- 500
- 501
- 502
- 503
- 504
- 505
- 506
- 507
- 508
- 509
- 510
- 511
- 512
- 513
- 514
- 515
- 516
- 517
- 518
- 519
- 520
- 521
- 522
- 523
- 524
- 525
- 526
- 527
- 528
- 529
- 530
- 531
- 532
- 533
- 534
- 535
- 536
- 537
- 538
- 539
- 540
- 541
- 542
- 543
- 544
- 545
- 546
- 547
- 548
- 549
- 550
- 551
- 552
- 553
- 554
- 555
- 556
- 557
- 558
- 559
- 560
- 561
- 562
- 563
- 564
- 565
- 566
- 567
- 568
- 569
- 570
- 571
- 572
- 573
- 574
- 575
- 576
- 577
- 578
- 579
- 580
- 581
- 582
- 583
- 584
- 585
- 586
- 587
- 588
- 589
- 590
- 591
- 592
- 593
- 594
- 595
- 596
- 597
- 598
- 599
- 600
- 601
- 602
- 603
- 604
- 605
- 606
- 607
- 608
- 609
- 610
- 611
- 612
- 613
- 614
- 615
- 616
- 617
- 618
- 619
- 620
- 621
- 622
- 623
- 624
- 625
- 626
- 627
- 628
- 629
- 630
- 631
- 632
- 633
- 634
- 635
- 636
- 637
- 638
- 639
- 640
- 641
- 642
- 643
- 644
- 645
- 646
- 647
- 648
- 649
- 650
- 651
- 652
- 653
- 654
- 655
- 656
- 657
- 658
- 659
- 660
- 661
- 662
- 663
- 664
- 665
- 666
- 667
- 668
- 669
- 670
- 671
- 672
- 673
- 674
- 675
- 676
- 677
- 678
- 679
- 680
- 681
- 682
- 683
- 684
- 685
- 686
- 687
- 688
- 689
- 690
- 691
- 692
- 693
- 694
- 695
- 696
- 697
- 698
- 699
- 700
- 701
- 702
- 703
- 704
- 705
- 706
- 707
- 708
- 709
- 710
- 711
- 712
- 713
- 714
- 715
- 716
- 717
- 718
- 719
- 720
- 721
- 722
- 723
- 724
- 725
- 726
- 727
- 728
- 729
- 730
- 731
- 732
- 733
- 734
- 735
- 736
- 737
- 738
- 739
- 740
- 741
- 742
- 743
- 744
- 745
- 746
- 747
- 748
- 749
- 750
- 751
- 752
- 753
- 754
- 755
- 756
- 757
- 758
- 759
- 760
- 761
- 762
- 763
- 764
- 765
- 766
- 767
- 768
- 769
- 770
- 771
- 772
- 773
- 774
- 775
- 776
- 777
- 778
- 779
- 780
- 781
- 782
- 783
- 784
- 785
- 786
- 787
- 788
- 789
- 790
- 791
- 792
- 793
- 794
- 795
- 796
- 797
- 798
- 799
- 800
- 801
- 802
- 803
- 804
- 805
- 806
- 807
- 808
- 809
- 810
- 811
- 812
- 813
- 814
- 815
- 816
- 817
- 818
- 819
- 820
- 821
- 822
- 823
- 824
- 825
- 826
- 827
- 828
- 829
- 830
- 831
- 832
- 833
- 834
- 835
- 836
- 837
- 838
- 839
- 840
- 841
- 842
- 843
- 844
- 845
- 846
- 847
- 848
- 849
- 850
- 851
- 852
- 853
- 854
- 855
- 856
- 857
- 858
- 859
- 860
- 861
- 862
MediaPlayerControl
通过该接口来打通MediaController以及VideoView
“`java
public interface MediaPlayerControl {
void start();
void pause();
int getDuration();
int getCurrentPosition();
void seekTo(int pos);
boolean isPlaying();
int getBufferPercentage();
boolean canPause();
boolean canSeekBackward();
boolean canSeekForward();
/*** Get the audio session id for the player used by this VideoView. This can be used to* apply audio effects to the audio track of a video.* @return The audio session, or 0 if there was an error.*/
int getAudioSessionId();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
}
“`
MediaController
简介
/** * A view containing controls for a MediaPlayer. Typically contains the * buttons like "Play/Pause", "Rewind", "Fast Forward" and a progress * slider. It takes care of synchronizing the controls with the state * of the MediaPlayer. * <p> * The way to use this class is to instantiate it programatically. * The MediaController will create a default set of controls * and put them in a window floating above your application. Specifically, * the controls will float above the view specified with setAnchorView(). * The window will disappear if left idle for three seconds and reappear * when the user touches the anchor view. * <p> * Functions like show() and hide() have no effect when MediaController * is created in an xml layout. * * MediaController will hide and * show the buttons according to these rules: * <ul> * <li> The "previous" and "next" buttons are hidden until setPrevNextListeners() * has been called * <li> The "previous" and "next" buttons are visible but disabled if * setPrevNextListeners() was called with null listeners * <li> The "rewind" and "fastforward" buttons are shown unless requested * otherwise by using the MediaController(Context, boolean) constructor * with the boolean set to false * </ul> */
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
关系
public class MediaController extends FrameLayout
- 1
成员
// 一些控制功能的接口 private MediaPlayerControl mPlayer; private Context mContext; // VideoView中调用setAnchorView()设置进来的View,MediaController显示的时候会感觉该AnchorView的位置进行显示 private View mAnchor; // MediaController最外层的根布局 private View mRoot;// 通过Window的方式来显示MediaController,MediaController是一个填充屏幕的布局,但是背景是透明的 private WindowManager mWindowManager; private Window mWindow; private View mDecor; // 理解为当前整个MediaController的布局 private WindowManager.LayoutParams mDecorLayoutParams; private ProgressBar mProgress; private TextView mEndTime, mCurrentTime; private boolean mShowing; private boolean mDragging; // 默认自动消失的时间 private static final int sDefaultTimeout = 3000; private static final int FADE_OUT = 1; private static final int SHOW_PROGRESS = 2; private boolean mUseFastForward; private boolean mFromXml; private boolean mListenersSet; private View.OnClickListener mNextListener, mPrevListener; StringBuilder mFormatBuilder; Formatter mFormatter; private ImageButton mPauseButton; private ImageButton mFfwdButton; private ImageButton mRewButton; private ImageButton mNextButton; private ImageButton mPrevButton;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
构造方法
public MediaController(Context context, AttributeSet attrs) {super(context, attrs);mRoot = this;mContext = context;mUseFastForward = true;mFromXml = true; }@Override public void onFinishInflate() {if (mRoot != null)initControllerView(mRoot); }public MediaController(Context context, boolean useFastForward) {super(context);mContext = context;mUseFastForward = useFastForward;// 创建该MediaController的布局initFloatingWindowLayout();initFloatingWindow(); }public MediaController(Context context) {this(context, true); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
initFloatingWindowLayout
// Allocate and initialize the static parts of mDecorLayoutParams. Must // also call updateFloatingWindowLayout() to fill in the dynamic parts // (y and width) before mDecorLayoutParams can be used. private void initFloatingWindowLayout() {mDecorLayoutParams = new WindowManager.LayoutParams();WindowManager.LayoutParams p = mDecorLayoutParams;p.gravity = Gravity.TOP | Gravity.LEFT;p.height = LayoutParams.WRAP_CONTENT;p.x = 0;p.format = PixelFormat.TRANSLUCENT;p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;p.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;p.token = null;p.windowAnimations = 0; // android.R.style.DropDownAnimationDown; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
initFloatingWindow
private void initFloatingWindow() {// Android内核剖析 中有介绍mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);mWindow = PolicyManager.makeNewWindow(mContext);mWindow.setWindowManager(mWindowManager, null, null);mWindow.requestFeature(Window.FEATURE_NO_TITLE);// 通过WindowManager去add该Decor以及remove来实现MediaController的显示与隐藏mDecor = mWindow.getDecorView();mDecor.setOnTouchListener(mTouchListener);mWindow.setContentView(this);mWindow.setBackgroundDrawableResource(android.R.color.transparent);// While the media controller is up, the volume control keys should// affect the media stream typemWindow.setVolumeControlStream(AudioManager.STREAM_MUSIC);setFocusable(true);setFocusableInTouchMode(true);setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);requestFocus(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
mTouchListener
private OnTouchListener mTouchListener = new OnTouchListener() {public boolean onTouch(View v, MotionEvent event) {if (event.getAction() == MotionEvent.ACTION_DOWN) {if (mShowing) {hide();}}return false;} };
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
setMediaPlayer
VideoView调用setMediaController的时候会调用到该方法public void setMediaPlayer(MediaPlayerControl player) {mPlayer = player;updatePausePlay(); }
- 1
- 2
- 3
- 4
setAnchorView
VideoView调用setMediaController的时候会调用到该方法/*** Set the view that acts as the anchor for the control view.* This can for example be a VideoView, or your Activity's main view.* When VideoView calls this method, it will use the VideoView's parent* as the anchor.* @param view The view to which to anchor the controller when it is visible.*/ public void setAnchorView(View view) {if (mAnchor != null) {mAnchor.removeOnLayoutChangeListener(mLayoutChangeListener);}mAnchor = view;if (mAnchor != null) {mAnchor.addOnLayoutChangeListener(mLayoutChangeListener);}FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);removeAllViews();View v = makeControllerView();addView(v, frameParams); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
mLayoutChangeListener
// This is called whenever mAnchor's layout bound changes private OnLayoutChangeListener mLayoutChangeListener =new OnLayoutChangeListener() {public void onLayoutChange(View v, int left, int top, int right,int bottom, int oldLeft, int oldTop, int oldRight,int oldBottom) {// 更新布局updateFloatingWindowLayout();if (mShowing) {mWindowManager.updateViewLayout(mDecor, mDecorLayoutParams);}} };
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
updateFloatingWindowLayout
// Update the dynamic parts of mDecorLayoutParams // Must be called with mAnchor != NULL. private void updateFloatingWindowLayout() {int [] anchorPos = new int[2];mAnchor.getLocationOnScreen(anchorPos);// we need to know the size of the controller so we can properly position it// within its spacemDecor.measure(MeasureSpec.makeMeasureSpec(mAnchor.getWidth(), MeasureSpec.AT_MOST),MeasureSpec.makeMeasureSpec(mAnchor.getHeight(), MeasureSpec.AT_MOST));WindowManager.LayoutParams p = mDecorLayoutParams;p.width = mAnchor.getWidth();p.x = anchorPos[0] + (mAnchor.getWidth() - p.width) / 2;p.y = anchorPos[1] + mAnchor.getHeight() - mDecor.getMeasuredHeight(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
makeControllerView
/*** Create the view that holds the widgets that control playback.* Derived classes can override this to create their own.* @return The controller view.* @hide This doesn't work as advertised*/ protected View makeControllerView() {LayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);mRoot = inflate.inflate(com.android.internal.R.layout.media_controller, null);// 对Controller中的一些按钮、功能进行事件设置initControllerView(mRoot);return mRoot; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
touch事件处理
@Override public boolean onTouchEvent(MotionEvent event) {show(sDefaultTimeout);return true; }
- 1
- 2
- 3
- 4
- 5
进度的处理
seekBar的处理
// There are two scenarios that can trigger the seekbar listener to trigger: // // The first is the user using the touchpad to adjust the posititon of the // seekbar's thumb. In this case onStartTrackingTouch is called followed by // a number of onProgressChanged notifications, concluded by onStopTrackingTouch. // We're setting the field "mDragging" to true for the duration of the dragging // session to avoid jumps in the position in case of ongoing playback. // // The second scenario involves the user operating the scroll ball, in this // case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications, // we will simply apply the updated position without suspending regular updates. private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {public void onStartTrackingTouch(SeekBar bar) {show(3600000);mDragging = true;// By removing these pending progress messages we make sure// that a) we won't update the progress while the user adjusts// the seekbar and b) once the user is done dragging the thumb// we will post one of these messages to the queue again and// this ensures that there will be exactly one message queued up.mHandler.removeMessages(SHOW_PROGRESS);}public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {if (!fromuser) {// We're not interested in programmatically generated changes to// the progress bar's position.return;}long duration = mPlayer.getDuration();long newposition = (duration * progress) / 1000L;mPlayer.seekTo( (int) newposition);if (mCurrentTime != null)mCurrentTime.setText(stringForTime( (int) newposition));}public void onStopTrackingTouch(SeekBar bar) {mDragging = false;setProgress();updatePausePlay();show(sDefaultTimeout);// Ensure that progress is properly updated in the future,// the call to show() does not guarantee this because it is a// no-op if we are already showing.mHandler.sendEmptyMessage(SHOW_PROGRESS);} };
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
SetProgress
private int setProgress() {if (mPlayer == null || mDragging) {return 0;}int position = mPlayer.getCurrentPosition();int duration = mPlayer.getDuration();if (mProgress != null) {if (duration > 0) {// use long to avoid overflowlong pos = 1000L * position / duration;mProgress.setProgress( (int) pos);}int percent = mPlayer.getBufferPercentage();mProgress.setSecondaryProgress(percent * 10);}if (mEndTime != null)mEndTime.setText(stringForTime(duration));if (mCurrentTime != null)mCurrentTime.setText(stringForTime(position));return position; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
show
/*** Show the controller on screen. It will go away* automatically after 'timeout' milliseconds of inactivity.* @param timeout The timeout in milliseconds. Use 0 to show* the controller until hide() is called.*/ public void show(int timeout) {if (!mShowing && mAnchor != null) {// 先去设置一下进度setProgress();if (mPauseButton != null) {mPauseButton.requestFocus();}disableUnsupportedButtons();updateFloatingWindowLayout();mWindowManager.addView(mDecor, mDecorLayoutParams);mShowing = true;}updatePausePlay();// cause the progress bar to be updated even if mShowing// was already true. This happens, for example, if we're// paused with the progress bar showing the user hits play.// 发送定期更新进度的消息mHandler.sendEmptyMessage(SHOW_PROGRESS);Message msg = mHandler.obtainMessage(FADE_OUT);if (timeout != 0) {mHandler.removeMessages(FADE_OUT);mHandler.sendMessageDelayed(msg, timeout);} }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
hide
/*** Remove the controller from the screen.*/ public void hide() {if (mAnchor == null)return;if (mShowing) {try {// 移除定期更新消息mHandler.removeMessages(SHOW_PROGRESS);mWindowManager.removeView(mDecor);} catch (IllegalArgumentException ex) {Log.w("MediaController", "already removed");}mShowing = false;} }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
上源码
/** 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.content.Context;
import android.graphics.PixelFormat;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.SeekBar.OnSeekBarChangeListener;import com.android.internal.policy.PolicyManager;import java.util.Formatter;
import java.util.Locale;/*** A view containing controls for a MediaPlayer. Typically contains the* buttons like "Play/Pause", "Rewind", "Fast Forward" and a progress* slider. It takes care of synchronizing the controls with the state* of the MediaPlayer.* <p>* The way to use this class is to instantiate it programatically.* The MediaController will create a default set of controls* and put them in a window floating above your application. Specifically,* the controls will float above the view specified with setAnchorView().* The window will disappear if left idle for three seconds and reappear* when the user touches the anchor view.* <p>* Functions like show() and hide() have no effect when MediaController* is created in an xml layout.* * MediaController will hide and* show the buttons according to these rules:* <ul>* <li> The "previous" and "next" buttons are hidden until setPrevNextListeners()* has been called* <li> The "previous" and "next" buttons are visible but disabled if* setPrevNextListeners() was called with null listeners* <li> The "rewind" and "fastforward" buttons are shown unless requested* otherwise by using the MediaController(Context, boolean) constructor* with the boolean set to false* </ul>*/
public class MediaController extends FrameLayout {private MediaPlayerControl mPlayer;private Context mContext;private View mAnchor;private View mRoot;private WindowManager mWindowManager;private Window mWindow;private View mDecor;private WindowManager.LayoutParams mDecorLayoutParams;private ProgressBar mProgress;private TextView mEndTime, mCurrentTime;private boolean mShowing;private boolean mDragging;private static final int sDefaultTimeout = 3000;private static final int FADE_OUT = 1;private static final int SHOW_PROGRESS = 2;private boolean mUseFastForward;private boolean mFromXml;private boolean mListenersSet;private View.OnClickListener mNextListener, mPrevListener;StringBuilder mFormatBuilder;Formatter mFormatter;private ImageButton mPauseButton;private ImageButton mFfwdButton;private ImageButton mRewButton;private ImageButton mNextButton;private ImageButton mPrevButton;public MediaController(Context context, AttributeSet attrs) {super(context, attrs);mRoot = this;mContext = context;mUseFastForward = true;mFromXml = true;}@Overridepublic void onFinishInflate() {if (mRoot != null)initControllerView(mRoot);}public MediaController(Context context, boolean useFastForward) {super(context);mContext = context;mUseFastForward = useFastForward;initFloatingWindowLayout();initFloatingWindow();}public MediaController(Context context) {this(context, true);}private void initFloatingWindow() {mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);mWindow = PolicyManager.makeNewWindow(mContext);mWindow.setWindowManager(mWindowManager, null, null);mWindow.requestFeature(Window.FEATURE_NO_TITLE);mDecor = mWindow.getDecorView();mDecor.setOnTouchListener(mTouchListener);mWindow.setContentView(this);mWindow.setBackgroundDrawableResource(android.R.color.transparent);// While the media controller is up, the volume control keys should// affect the media stream typemWindow.setVolumeControlStream(AudioManager.STREAM_MUSIC);setFocusable(true);setFocusableInTouchMode(true);setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);requestFocus();}// Allocate and initialize the static parts of mDecorLayoutParams. Must// also call updateFloatingWindowLayout() to fill in the dynamic parts// (y and width) before mDecorLayoutParams can be used.private void initFloatingWindowLayout() {mDecorLayoutParams = new WindowManager.LayoutParams();WindowManager.LayoutParams p = mDecorLayoutParams;p.gravity = Gravity.TOP | Gravity.LEFT;p.height = LayoutParams.WRAP_CONTENT;p.x = 0;p.format = PixelFormat.TRANSLUCENT;p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;p.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;p.token = null;p.windowAnimations = 0; // android.R.style.DropDownAnimationDown;}// Update the dynamic parts of mDecorLayoutParams// Must be called with mAnchor != NULL.private void updateFloatingWindowLayout() {int [] anchorPos = new int[2];mAnchor.getLocationOnScreen(anchorPos);// we need to know the size of the controller so we can properly position it// within its spacemDecor.measure(MeasureSpec.makeMeasureSpec(mAnchor.getWidth(), MeasureSpec.AT_MOST),MeasureSpec.makeMeasureSpec(mAnchor.getHeight(), MeasureSpec.AT_MOST));WindowManager.LayoutParams p = mDecorLayoutParams;p.width = mAnchor.getWidth();p.x = anchorPos[0] + (mAnchor.getWidth() - p.width) / 2;p.y = anchorPos[1] + mAnchor.getHeight() - mDecor.getMeasuredHeight();}// This is called whenever mAnchor's layout bound changesprivate OnLayoutChangeListener mLayoutChangeListener =new OnLayoutChangeListener() {public void onLayoutChange(View v, int left, int top, int right,int bottom, int oldLeft, int oldTop, int oldRight,int oldBottom) {updateFloatingWindowLayout();if (mShowing) {mWindowManager.updateViewLayout(mDecor, mDecorLayoutParams);}}};private OnTouchListener mTouchListener = new OnTouchListener() {public boolean onTouch(View v, MotionEvent event) {if (event.getAction() == MotionEvent.ACTION_DOWN) {if (mShowing) {hide();}}return false;}};public void setMediaPlayer(MediaPlayerControl player) {mPlayer = player;updatePausePlay();}/*** Set the view that acts as the anchor for the control view.* This can for example be a VideoView, or your Activity's main view.* When VideoView calls this method, it will use the VideoView's parent* as the anchor.* @param view The view to which to anchor the controller when it is visible.*/public void setAnchorView(View view) {if (mAnchor != null) {mAnchor.removeOnLayoutChangeListener(mLayoutChangeListener);}mAnchor = view;if (mAnchor != null) {mAnchor.addOnLayoutChangeListener(mLayoutChangeListener);}FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);removeAllViews();View v = makeControllerView();addView(v, frameParams);}/*** Create the view that holds the widgets that control playback.* Derived classes can override this to create their own.* @return The controller view.* @hide This doesn't work as advertised*/protected View makeControllerView() {LayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);mRoot = inflate.inflate(com.android.internal.R.layout.media_controller, null);initControllerView(mRoot);return mRoot;}private void initControllerView(View v) {mPauseButton = (ImageButton) v.findViewById(com.android.internal.R.id.pause);if (mPauseButton != null) {mPauseButton.requestFocus();mPauseButton.setOnClickListener(mPauseListener);}mFfwdButton = (ImageButton) v.findViewById(com.android.internal.R.id.ffwd);if (mFfwdButton != null) {mFfwdButton.setOnClickListener(mFfwdListener);if (!mFromXml) {mFfwdButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);}}mRewButton = (ImageButton) v.findViewById(com.android.internal.R.id.rew);if (mRewButton != null) {mRewButton.setOnClickListener(mRewListener);if (!mFromXml) {mRewButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);}}// By default these are hidden. They will be enabled when setPrevNextListeners() is called mNextButton = (ImageButton) v.findViewById(com.android.internal.R.id.next);if (mNextButton != null && !mFromXml && !mListenersSet) {mNextButton.setVisibility(View.GONE);}mPrevButton = (ImageButton) v.findViewById(com.android.internal.R.id.prev);if (mPrevButton != null && !mFromXml && !mListenersSet) {mPrevButton.setVisibility(View.GONE);}mProgress = (ProgressBar) v.findViewById(com.android.internal.R.id.mediacontroller_progress);if (mProgress != null) {if (mProgress instanceof SeekBar) {SeekBar seeker = (SeekBar) mProgress;seeker.setOnSeekBarChangeListener(mSeekListener);}mProgress.setMax(1000);}mEndTime = (TextView) v.findViewById(com.android.internal.R.id.time);mCurrentTime = (TextView) v.findViewById(com.android.internal.R.id.time_current);mFormatBuilder = new StringBuilder();mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());installPrevNextListeners();}/*** Show the controller on screen. It will go away* automatically after 3 seconds of inactivity.*/public void show() {show(sDefaultTimeout);}/*** Disable pause or seek buttons if the stream cannot be paused or seeked.* This requires the control interface to be a MediaPlayerControlExt*/private void disableUnsupportedButtons() {try {if (mPauseButton != null && !mPlayer.canPause()) {mPauseButton.setEnabled(false);}if (mRewButton != null && !mPlayer.canSeekBackward()) {mRewButton.setEnabled(false);}if (mFfwdButton != null && !mPlayer.canSeekForward()) {mFfwdButton.setEnabled(false);}} catch (IncompatibleClassChangeError ex) {// We were given an old version of the interface, that doesn't have// the canPause/canSeekXYZ methods. This is OK, it just means we// assume the media can be paused and seeked, and so we don't disable// the buttons.}}/*** Show the controller on screen. It will go away* automatically after 'timeout' milliseconds of inactivity.* @param timeout The timeout in milliseconds. Use 0 to show* the controller until hide() is called.*/public void show(int timeout) {if (!mShowing && mAnchor != null) {setProgress();if (mPauseButton != null) {mPauseButton.requestFocus();}disableUnsupportedButtons();updateFloatingWindowLayout();mWindowManager.addView(mDecor, mDecorLayoutParams);mShowing = true;}updatePausePlay();// cause the progress bar to be updated even if mShowing// was already true. This happens, for example, if we're// paused with the progress bar showing the user hits play.mHandler.sendEmptyMessage(SHOW_PROGRESS);Message msg = mHandler.obtainMessage(FADE_OUT);if (timeout != 0) {mHandler.removeMessages(FADE_OUT);mHandler.sendMessageDelayed(msg, timeout);}}public boolean isShowing() {return mShowing;}/*** Remove the controller from the screen.*/public void hide() {if (mAnchor == null)return;if (mShowing) {try {mHandler.removeMessages(SHOW_PROGRESS);mWindowManager.removeView(mDecor);} catch (IllegalArgumentException ex) {Log.w("MediaController", "already removed");}mShowing = false;}}private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {int pos;switch (msg.what) {case FADE_OUT:hide();break;case SHOW_PROGRESS:pos = setProgress();if (!mDragging && mShowing && mPlayer.isPlaying()) {msg = obtainMessage(SHOW_PROGRESS);sendMessageDelayed(msg, 1000 - (pos % 1000));}break;}}};private String stringForTime(int timeMs) {int totalSeconds = timeMs / 1000;int seconds = totalSeconds % 60;int minutes = (totalSeconds / 60) % 60;int hours = totalSeconds / 3600;mFormatBuilder.setLength(0);if (hours > 0) {return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();} else {return mFormatter.format("%02d:%02d", minutes, seconds).toString();}}private int setProgress() {if (mPlayer == null || mDragging) {return 0;}int position = mPlayer.getCurrentPosition();int duration = mPlayer.getDuration();if (mProgress != null) {if (duration > 0) {// use long to avoid overflowlong pos = 1000L * position / duration;mProgress.setProgress( (int) pos);}int percent = mPlayer.getBufferPercentage();mProgress.setSecondaryProgress(percent * 10);}if (mEndTime != null)mEndTime.setText(stringForTime(duration));if (mCurrentTime != null)mCurrentTime.setText(stringForTime(position));return position;}@Overridepublic boolean onTouchEvent(MotionEvent event) {show(sDefaultTimeout);return true;}@Overridepublic boolean onTrackballEvent(MotionEvent ev) {show(sDefaultTimeout);return false;}@Overridepublic boolean dispatchKeyEvent(KeyEvent event) {int keyCode = event.getKeyCode();final boolean uniqueDown = event.getRepeatCount() == 0&& event.getAction() == KeyEvent.ACTION_DOWN;if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK|| keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE|| keyCode == KeyEvent.KEYCODE_SPACE) {if (uniqueDown) {doPauseResume();show(sDefaultTimeout);if (mPauseButton != null) {mPauseButton.requestFocus();}}return true;} else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {if (uniqueDown && !mPlayer.isPlaying()) {mPlayer.start();updatePausePlay();show(sDefaultTimeout);}return true;} else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP|| keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {if (uniqueDown && mPlayer.isPlaying()) {mPlayer.pause();updatePausePlay();show(sDefaultTimeout);}return true;} else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN|| keyCode == KeyEvent.KEYCODE_VOLUME_UP|| keyCode == KeyEvent.KEYCODE_VOLUME_MUTE|| keyCode == KeyEvent.KEYCODE_CAMERA) {// don't show the controls for volume adjustmentreturn super.dispatchKeyEvent(event);} else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {if (uniqueDown) {hide();}return true;}show(sDefaultTimeout);return super.dispatchKeyEvent(event);}private View.OnClickListener mPauseListener = new View.OnClickListener() {public void onClick(View v) {doPauseResume();show(sDefaultTimeout);}};private void updatePausePlay() {if (mRoot == null || mPauseButton == null)return;if (mPlayer.isPlaying()) {mPauseButton.setImageResource(com.android.internal.R.drawable.ic_media_pause);} else {mPauseButton.setImageResource(com.android.internal.R.drawable.ic_media_play);}}private void doPauseResume() {if (mPlayer.isPlaying()) {mPlayer.pause();} else {mPlayer.start();}updatePausePlay();}// There are two scenarios that can trigger the seekbar listener to trigger://// The first is the user using the touchpad to adjust the posititon of the// seekbar's thumb. In this case onStartTrackingTouch is called followed by// a number of onProgressChanged notifications, concluded by onStopTrackingTouch.// We're setting the field "mDragging" to true for the duration of the dragging// session to avoid jumps in the position in case of ongoing playback.//// The second scenario involves the user operating the scroll ball, in this// case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications,// we will simply apply the updated position without suspending regular updates.private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {public void onStartTrackingTouch(SeekBar bar) {show(3600000);mDragging = true;// By removing these pending progress messages we make sure// that a) we won't update the progress while the user adjusts// the seekbar and b) once the user is done dragging the thumb// we will post one of these messages to the queue again and// this ensures that there will be exactly one message queued up.mHandler.removeMessages(SHOW_PROGRESS);}public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {if (!fromuser) {// We're not interested in programmatically generated changes to// the progress bar's position.return;}long duration = mPlayer.getDuration();long newposition = (duration * progress) / 1000L;mPlayer.seekTo( (int) newposition);if (mCurrentTime != null)mCurrentTime.setText(stringForTime( (int) newposition));}public void onStopTrackingTouch(SeekBar bar) {mDragging = false;setProgress();updatePausePlay();show(sDefaultTimeout);// Ensure that progress is properly updated in the future,// the call to show() does not guarantee this because it is a// no-op if we are already showing.mHandler.sendEmptyMessage(SHOW_PROGRESS);}};@Overridepublic void setEnabled(boolean enabled) {if (mPauseButton != null) {mPauseButton.setEnabled(enabled);}if (mFfwdButton != null) {mFfwdButton.setEnabled(enabled);}if (mRewButton != null) {mRewButton.setEnabled(enabled);}if (mNextButton != null) {mNextButton.setEnabled(enabled && mNextListener != null);}if (mPrevButton != null) {mPrevButton.setEnabled(enabled && mPrevListener != null);}if (mProgress != null) {mProgress.setEnabled(enabled);}disableUnsupportedButtons();super.setEnabled(enabled);}@Overridepublic void onInitializeAccessibilityEvent(AccessibilityEvent event) {super.onInitializeAccessibilityEvent(event);event.setClassName(MediaController.class.getName());}@Overridepublic void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {super.onInitializeAccessibilityNodeInfo(info);info.setClassName(MediaController.class.getName());}private View.OnClickListener mRewListener = new View.OnClickListener() {public void onClick(View v) {int pos = mPlayer.getCurrentPosition();pos -= 5000; // millisecondsmPlayer.seekTo(pos);setProgress();show(sDefaultTimeout);}};private View.OnClickListener mFfwdListener = new View.OnClickListener() {public void onClick(View v) {int pos = mPlayer.getCurrentPosition();pos += 15000; // millisecondsmPlayer.seekTo(pos);setProgress();show(sDefaultTimeout);}};private void installPrevNextListeners() {if (mNextButton != null) {mNextButton.setOnClickListener(mNextListener);mNextButton.setEnabled(mNextListener != null);}if (mPrevButton != null) {mPrevButton.setOnClickListener(mPrevListener);mPrevButton.setEnabled(mPrevListener != null);}}public void setPrevNextListeners(View.OnClickListener next, View.OnClickListener prev) {mNextListener = next;mPrevListener = prev;mListenersSet = true;if (mRoot != null) {installPrevNextListeners();if (mNextButton != null && !mFromXml) {mNextButton.setVisibility(View.VISIBLE);}if (mPrevButton != null && !mFromXml) {mPrevButton.setVisibility(View.VISIBLE);}}}public interface MediaPlayerControl {void start();void pause();int getDuration();int getCurrentPosition();void seekTo(int pos);boolean isPlaying();int getBufferPercentage();boolean canPause();boolean canSeekBackward();boolean canSeekForward();/*** Get the audio session id for the player used by this VideoView. This can be used to* apply audio effects to the audio track of a video.* @return The audio session, or 0 if there was an error.*/int getAudioSessionId();}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 368
- 369
- 370
- 371
- 372
- 373
- 374
- 375
- 376
- 377
- 378
- 379
- 380
- 381
- 382
- 383
- 384
- 385
- 386
- 387
- 388
- 389
- 390
- 391
- 392
- 393
- 394
- 395
- 396
- 397
- 398
- 399
- 400
- 401
- 402
- 403
- 404
- 405
- 406
- 407
- 408
- 409
- 410
- 411
- 412
- 413
- 414
- 415
- 416
- 417
- 418
- 419
- 420
- 421
- 422
- 423
- 424
- 425
- 426
- 427
- 428
- 429
- 430
- 431
- 432
- 433
- 434
- 435
- 436
- 437
- 438
- 439
- 440
- 441
- 442
- 443
- 444
- 445
- 446
- 447
- 448
- 449
- 450
- 451
- 452
- 453
- 454
- 455
- 456
- 457
- 458
- 459
- 460
- 461
- 462
- 463
- 464
- 465
- 466
- 467
- 468
- 469
- 470
- 471
- 472
- 473
- 474
- 475
- 476
- 477
- 478
- 479
- 480
- 481
- 482
- 483
- 484
- 485
- 486
- 487
- 488
- 489
- 490
- 491
- 492
- 493
- 494
- 495
- 496
- 497
- 498
- 499
- 500
- 501
- 502
- 503
- 504
- 505
- 506
- 507
- 508
- 509
- 510
- 511
- 512
- 513
- 514
- 515
- 516
- 517
- 518
- 519
- 520
- 521
- 522
- 523
- 524
- 525
- 526
- 527
- 528
- 529
- 530
- 531
- 532
- 533
- 534
- 535
- 536
- 537
- 538
- 539
- 540
- 541
- 542
- 543
- 544
- 545
- 546
- 547
- 548
- 549
- 550
- 551
- 552
- 553
- 554
- 555
- 556
- 557
- 558
- 559
- 560
- 561
- 562
- 563
- 564
- 565
- 566
- 567
- 568
- 569
- 570
- 571
- 572
- 573
- 574
- 575
- 576
- 577
- 578
- 579
- 580
- 581
- 582
- 583
- 584
- 585
- 586
- 587
- 588
- 589
- 590
- 591
- 592
- 593
- 594
- 595
- 596
- 597
- 598
- 599
- 600
- 601
- 602
- 603
- 604
- 605
- 606
- 607
- 608
- 609
- 610
- 611
- 612
- 613
- 614
- 615
- 616
- 617
- 618
- 619
- 620
- 621
- 622
- 623
- 624
- 625
- 626
- 627
- 628
- 629
- 630
- 631
- 632
- 633
- 634
- 635
- 636
- 637
- 638
- 639
- 640
- 641
- 642
- 643
- 644
- 645
- 646
- 647
- 648
- 649
- 650
- 651
- 652
- 653
- 654
- 655
- 656
- 657
- 658
- 659
- 660
- 661
- 662
- 663
- 664
- 665
- 666
- 667
- 668
- 669
- 670
- 671
- 672
- 673
- 674
- 675
- 676
- 677
- 678
- 679
- 680
- 681
- 682
- 683
- 684
- 685
- 686
- 687
- 688
- 邮箱 :charon.chui@gmail.com
- Good Luck!
VideoView源码分析相关推荐
- android源码分析
01_Android系统概述 02_Android系统的开发综述 03_Android的Linux内核与驱动程序 04_Android的底层库和程序 05_Android的JAVA虚拟机和JAVA环境 ...
- Android上百实例源码分析以及开源分析集合打包
感谢网友banketree的收集,压缩包的内容如下: 1.360新版特性界面源代码 实现了360新版特性界面的效果,主要涉及到Qt的一些事件处理与自定义控件.但源码好像是c++. 2.aidl跨进程调 ...
- 【Golang源码分析】Go Web常用程序包gorilla/mux的使用与源码简析
目录[阅读时间:约10分钟] 一.概述 二.对比: gorilla/mux与net/http DefaultServeMux 三.简单使用 四.源码简析 1.NewRouter函数 2.HandleF ...
- SpringBoot-web开发(四): SpringMVC的拓展、接管(源码分析)
[SpringBoot-web系列]前文: SpringBoot-web开发(一): 静态资源的导入(源码分析) SpringBoot-web开发(二): 页面和图标定制(源码分析) SpringBo ...
- SpringBoot-web开发(二): 页面和图标定制(源码分析)
[SpringBoot-web系列]前文: SpringBoot-web开发(一): 静态资源的导入(源码分析) 目录 一.首页 1. 源码分析 2. 访问首页测试 二.动态页面 1. 动态资源目录t ...
- SpringBoot-web开发(一): 静态资源的导入(源码分析)
目录 方式一:通过WebJars 1. 什么是webjars? 2. webjars的使用 3. webjars结构 4. 解析源码 5. 测试访问 方式二:放入静态资源目录 1. 源码分析 2. 测 ...
- Yolov3Yolov4网络结构与源码分析
Yolov3&Yolov4网络结构与源码分析 从2018年Yolov3年提出的两年后,在原作者声名放弃更新Yolo算法后,俄罗斯的Alexey大神扛起了Yolov4的大旗. 文章目录 论文汇总 ...
- ViewGroup的Touch事件分发(源码分析)
Android中Touch事件的分发又分为View和ViewGroup的事件分发,View的touch事件分发相对比较简单,可参考 View的Touch事件分发(一.初步了解) View的Touch事 ...
- View的Touch事件分发(二.源码分析)
Android中Touch事件的分发又分为View和ViewGroup的事件分发,先来看简单的View的touch事件分发. 主要分析View的dispatchTouchEvent()方法和onTou ...
- MyBatis原理分析之四:一次SQL查询的源码分析
上回我们讲到Mybatis加载相关的配置文件进行初始化,这回我们讲一下一次SQL查询怎么进行的. 准备工作 Mybatis完成一次SQL查询需要使用的代码如下: Java代码 String res ...
最新文章
- Loadrunner安装使用入门
- Asp.net下实现隐藏真实下载地址(可以实现简单的防盗链)
- 阿里一面,给了几条SQL,问需要执行几次树搜索操作?
- Java8 Stream 使用
- python读取连接数据库文件_python 读取配置文件 pandas连接数据库
- 算法题——Cantor表
- CSS画基本图形——圆
- HDU 1247(Hat’s Words )
- php 文字水印如何居中,php文字水印和php图片水印实现代码(二种加水印方法)
- 拜托!程序员的工作不能用时间来衡量
- 微信小程序怎么弄成链接_自己怎么弄微信小程序?
- 从0开始html前端页面开发_CSS设置图像边框阴影
- 数据库学习----MySQL(一)
- PAL文件(Palette,调色板)及格式
- 详细介绍如何在华为云调用SDK的Python代码(以文字识别OCR技术实现身份证识别为例)
- mybatis PageHelper.startPage出现limit错误
- MSDN 我告诉你 window实用软件下载地址
- 最新影牛视频双端影视视频APP网站源码+详细视频教程
- win10安装oracle数据库失败
- pacman下载时经常出现Operation too slow. Less than 1 bytes/sec transferred the last 10 seconds