赞
踩
最近需要使用到播放器,自带的播放器真的烂,网上其实资料根本不多,虽然有几个开源的大项目,但是都没有什么教程,说的非常模糊,找到的博客也同样说的很模糊,很难自己二次开发啊。
先说收ijkplayer的使用。引入项目大概需要以下四步
在build.gradle
中添加依赖,根据自己需求添加
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:26.1.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' androidTestCompile('com.android.support:support-annotations:26.1.0') { force = true } ...... //哔站的ijkplayer compile 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8' compile 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.8' compile 'tv.danmaku.ijk.media:ijkplayer-armv5:0.8.8' compile 'tv.danmaku.ijk.media:ijkplayer-arm64:0.8.8' compile 'tv.danmaku.ijk.media:ijkplayer-x86:0.8.8' compile 'tv.danmaku.ijk.media:ijkplayer-x86_64:0.8.8' // compile 'tv.danmaku.ijk.media:ijkplayer-exo:0.8.8' // compile 'tv.danmaku.ijk.media:ijkplayer-java:0.6.2' }
这里使用了14个配置文件,真的多,分别说说吧
/* * Copyright (C) 2015 Bilibili * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com> * * 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 com.example.k.bilibiliapi.video; import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.app.ActionBar; import android.util.AttributeSet; import android.view.View; import android.widget.MediaController; import java.util.ArrayList; public class AndroidMediaController extends MediaController implements IMediaController { private ActionBar mActionBar; public AndroidMediaController(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public AndroidMediaController(Context context, boolean useFastForward) { super(context, useFastForward); initView(context); } public AndroidMediaController(Context context) { super(context); initView(context); } private void initView(Context context) { } public void setSupportActionBar(@Nullable ActionBar actionBar) { mActionBar = actionBar; if (isShowing()) { actionBar.show(); } else { actionBar.hide(); } } @Override public void show() { super.show(); if (mActionBar != null) mActionBar.show(); } @Override public void hide() { super.hide(); if (mActionBar != null) mActionBar.hide(); for (View view : mShowOnceArray) view.setVisibility(View.GONE); mShowOnceArray.clear(); } //---------- // Extends //---------- private ArrayList<View> mShowOnceArray = new ArrayList<View>(); public void showOnce(@NonNull View view) { mShowOnceArray.add(view); view.setVisibility(View.VISIBLE); show(); } }
/* * Copyright (C) 2015 Bilibili * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com> * * 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 com.example.k.bilibiliapi.video; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import tv.danmaku.ijk.media.player.misc.IMediaDataSource; public class FileMediaDataSource implements IMediaDataSource { private RandomAccessFile mFile; private long mFileSize; public FileMediaDataSource(File file) throws IOException { mFile = new RandomAccessFile(file, "r"); mFileSize = mFile.length(); } @Override public int readAt(long position, byte[] buffer, int offset, int size) throws IOException { if (mFile.getFilePointer() != position) mFile.seek(position); if (size == 0) return 0; return mFile.read(buffer, 0, size); } @Override public long getSize() throws IOException { return mFileSize; } @Override public void close() throws IOException { mFileSize = 0; mFile.close(); mFile = null; } }
/* * Copyright (C) 2015 Bilibili * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com> * * 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 com.example.k.bilibiliapi.video; import android.annotation.TargetApi; import android.content.Context; import android.content.DialogInterface; import android.content.res.Resources; import android.media.AudioManager; import android.media.MediaPlayer; import android.net.Uri; import android.os.Build; import android.support.annotation.NonNull; import android.support.v7.app.AlertDialog; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; import android.widget.MediaController; import android.widget.TableLayout; import android.widget.TextView; import com.example.k.bilibiliapi.R; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; import tv.danmaku.ijk.media.exo.IjkExoMediaPlayer; import tv.danmaku.ijk.media.player.AndroidMediaPlayer; import tv.danmaku.ijk.media.player.IMediaPlayer; import tv.danmaku.ijk.media.player.IjkMediaPlayer; import tv.danmaku.ijk.media.player.IjkTimedText; import tv.danmaku.ijk.media.player.TextureMediaPlayer; import tv.danmaku.ijk.media.player.misc.IMediaDataSource; import tv.danmaku.ijk.media.player.misc.IMediaFormat; import tv.danmaku.ijk.media.player.misc.ITrackInfo; import tv.danmaku.ijk.media.player.misc.IjkMediaFormat; public class IjkVideoView extends FrameLayout implements MediaController.MediaPlayerControl { private String TAG = "IjkVideoView"; // settable by the client private Uri mUri; private Map<String, String> mHeaders; // 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; // 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 IRenderView.ISurfaceHolder mSurfaceHolder = null; private IMediaPlayer mMediaPlayer = null; // private int mAudioSession; private int mVideoWidth; private int mVideoHeight; private int mSurfaceWidth; private int mSurfaceHeight; private int mVideoRotationDegree; private IMediaController mMediaController; private IMediaPlayer.OnCompletionListener mOnCompletionListener; private IMediaPlayer.OnPreparedListener mOnPreparedListener; private int mCurrentBufferPercentage; private IMediaPlayer.OnErrorListener mOnErrorListener; private IMediaPlayer.OnInfoListener mOnInfoListener; private int mSeekWhenPrepared; // recording the seek position while preparing private boolean mCanPause = true; private boolean mCanSeekBack = true; private boolean mCanSeekForward = true; /** 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; private Context mAppContext; private Settings mSettings; private IRenderView mRenderView; private int mVideoSarNum; private int mVideoSarDen; private InfoHudViewHolder mHudViewHolder; private long mPrepareStartTime = 0; private long mPrepareEndTime = 0; private long mSeekStartTime = 0; private long mSeekEndTime = 0; private TextView subtitleDisplay; public IjkVideoView(Context context) { super(context); initVideoView(context); } public IjkVideoView(Context context, AttributeSet attrs) { super(context, attrs); initVideoView(context); } public IjkVideoView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initVideoView(context); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public IjkVideoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initVideoView(context); } // REMOVED: onMeasure // REMOVED: onInitializeAccessibilityEvent // REMOVED: onInitializeAccessibilityNodeInfo // REMOVED: resolveAdjustedSize private void initVideoView(Context context) { mAppContext = context.getApplicationContext(); mSettings = new Settings(mAppContext); initBackground(); initRenders(); mVideoWidth = 0; mVideoHeight = 0; // REMOVED: getHolder().addCallback(mSHCallback); // REMOVED: getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); setFocusable(true); setFocusableInTouchMode(true); requestFocus(); // REMOVED: mPendingSubtitleTracks = new Vector<Pair<InputStream, MediaFormat>>(); mCurrentState = STATE_IDLE; mTargetState = STATE_IDLE; subtitleDisplay = new TextView(context); subtitleDisplay.setTextSize(24); subtitleDisplay.setGravity(Gravity.CENTER); LayoutParams layoutParams_txt = new LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, Gravity.BOTTOM); addView(subtitleDisplay, layoutParams_txt); } public void setRenderView(IRenderView renderView) { if (mRenderView != null) { if (mMediaPlayer != null) mMediaPlayer.setDisplay(null); View renderUIView = mRenderView.getView(); mRenderView.removeRenderCallback(mSHCallback); mRenderView = null; removeView(renderUIView); } if (renderView == null) return; mRenderView = renderView; renderView.setAspectRatio(mCurrentAspectRatio); if (mVideoWidth > 0 && mVideoHeight > 0) renderView.setVideoSize(mVideoWidth, mVideoHeight); if (mVideoSarNum > 0 && mVideoSarDen > 0) renderView.setVideoSampleAspectRatio(mVideoSarNum, mVideoSarDen); View renderUIView = mRenderView.getView(); LayoutParams lp = new LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, Gravity.CENTER); renderUIView.setLayoutParams(lp); addView(renderUIView); mRenderView.addRenderCallback(mSHCallback); mRenderView.setVideoRotation(mVideoRotationDegree); } public void setAspectRatio(int arAspectFitParent){ if (mRenderView != null) { mRenderView.setAspectRatio(arAspectFitParent); } } public void setRender(int render) { switch (render) { case RENDER_NONE: setRenderView(null); break; case RENDER_SURFACE_VIEW: case RENDER_TEXTURE_VIEW: { TextureRenderView renderView = new TextureRenderView(getContext()); if (mMediaPlayer != null) { renderView.getSurfaceHolder().bindToMediaPlayer(mMediaPlayer); renderView.setVideoSize(mMediaPlayer.getVideoWidth(), mMediaPlayer.getVideoHeight()); renderView.setVideoSampleAspectRatio(mMediaPlayer.getVideoSarNum(), mMediaPlayer.getVideoSarDen()); renderView.setAspectRatio(mCurrentAspectRatio); } setRenderView(renderView); break; } default: Log.e(TAG, String.format(Locale.getDefault(), "invalid render %d\n", render)); break; } } public void setHudView(TableLayout tableLayout) { mHudViewHolder = new InfoHudViewHolder(getContext(), tableLayout); } /** * Sets video path. * * @param path the path of the video. */ public void setVideoPath(String path) { setVideoURI(Uri.parse(path)); } /** * Sets video URI. * * @param uri the URI of the video. */ public void setVideoURI(Uri uri) { setVideoURI(uri, null); } /** * 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. */ private void setVideoURI(Uri uri, Map<String, String> headers) { mUri = uri; mHeaders = headers; mSeekWhenPrepared = 0; openVideo(); requestLayout(); invalidate(); } // REMOVED: addSubtitleSource // REMOVED: mPendingSubtitleTracks public void stopPlayback() { if (mMediaPlayer != null) { mMediaPlayer.stop(); mMediaPlayer.release(); mMediaPlayer = null; if (mHudViewHolder != null) mHudViewHolder.setMediaPlayer(null); mCurrentState = STATE_IDLE; mTargetState = STATE_IDLE; AudioManager am = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE); am.abandonAudioFocus(null); } } @TargetApi(Build.VERSION_CODES.M) 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); AudioManager am = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE); am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); try { mMediaPlayer = createPlayer(mSettings.getPlayer()); // TODO: create SubtitleController in MediaPlayer, but we need // a context for the subtitle renderers final Context context = getContext(); // REMOVED: SubtitleController // REMOVED: mAudioSession mMediaPlayer.setOnPreparedListener(mPreparedListener); mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener); mMediaPlayer.setOnCompletionListener(mCompletionListener); mMediaPlayer.setOnErrorListener(mErrorListener); mMediaPlayer.setOnInfoListener(mInfoListener); mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener); mMediaPlayer.setOnSeekCompleteListener(mSeekCompleteListener); mMediaPlayer.setOnTimedTextListener(mOnTimedTextListener); mCurrentBufferPercentage = 0; String scheme = mUri.getScheme(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && mSettings.getUsingMediaDataSource() && (TextUtils.isEmpty(scheme) || scheme.equalsIgnoreCase("file"))) { IMediaDataSource dataSource = new FileMediaDataSource(new File(mUri.toString())); mMediaPlayer.setDataSource(dataSource); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { mMediaPlayer.setDataSource(mAppContext, mUri, mHeaders); } else { mMediaPlayer.setDataSource(mUri.toString()); } bindSurfaceHolder(mMediaPlayer, mSurfaceHolder); mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mMediaPlayer.setScreenOnWhilePlaying(true); mPrepareStartTime = System.currentTimeMillis(); mMediaPlayer.prepareAsync(); if (mHudViewHolder != null) mHudViewHolder.setMediaPlayer(mMediaPlayer); // REMOVED: mPendingSubtitleTracks // 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); } 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); } finally { // REMOVED: mPendingSubtitleTracks.clear(); } } public void setMediaController(IMediaController 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()); } } IMediaPlayer.OnVideoSizeChangedListener mSizeChangedListener = new IMediaPlayer.OnVideoSizeChangedListener() { public void onVideoSizeChanged(IMediaPlayer mp, int width, int height, int sarNum, int sarDen) { mVideoWidth = mp.getVideoWidth(); mVideoHeight = mp.getVideoHeight(); mVideoSarNum = mp.getVideoSarNum(); mVideoSarDen = mp.getVideoSarDen(); if (mVideoWidth != 0 && mVideoHeight != 0) { if (mRenderView != null) { mRenderView.setVideoSize(mVideoWidth, mVideoHeight); mRenderView.setVideoSampleAspectRatio(mVideoSarNum, mVideoSarDen); } // REMOVED: getHolder().setFixedSize(mVideoWidth, mVideoHeight); requestLayout(); } } }; IMediaPlayer.OnPreparedListener mPreparedListener = new IMediaPlayer.OnPreparedListener() { public void onPrepared(IMediaPlayer mp) { mPrepareEndTime = System.currentTimeMillis(); if (mHudViewHolder != null) { mHudViewHolder.updateLoadCost(mPrepareEndTime - mPrepareStartTime); } mCurrentState = STATE_PREPARED; // Get the capabilities of the player for this stream // REMOVED: Metadata 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); // REMOVED: getHolder().setFixedSize(mVideoWidth, mVideoHeight); if (mRenderView != null) { mRenderView.setVideoSize(mVideoWidth, mVideoHeight); mRenderView.setVideoSampleAspectRatio(mVideoSarNum, mVideoSarDen); if (!mRenderView.shouldWaitForResize() || 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 IMediaPlayer.OnCompletionListener mCompletionListener = new IMediaPlayer.OnCompletionListener() { public void onCompletion(IMediaPlayer mp) { mCurrentState = STATE_PLAYBACK_COMPLETED; mTargetState = STATE_PLAYBACK_COMPLETED; if (mMediaController != null) { mMediaController.hide(); } if (mOnCompletionListener != null) { mOnCompletionListener.onCompletion(mMediaPlayer); } } }; private IMediaPlayer.OnInfoListener mInfoListener = new IMediaPlayer.OnInfoListener() { public boolean onInfo(IMediaPlayer mp, int arg1, int arg2) { if (mOnInfoListener != null) { mOnInfoListener.onInfo(mp, arg1, arg2); } switch (arg1) { case IMediaPlayer.MEDIA_INFO_VIDEO_TRACK_LAGGING: Log.d(TAG, "MEDIA_INFO_VIDEO_TRACK_LAGGING:"); break; case IMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START: Log.d(TAG, "MEDIA_INFO_VIDEO_RENDERING_START:"); break; case IMediaPlayer.MEDIA_INFO_BUFFERING_START: Log.d(TAG, "MEDIA_INFO_BUFFERING_START:"); break; case IMediaPlayer.MEDIA_INFO_BUFFERING_END: Log.d(TAG, "MEDIA_INFO_BUFFERING_END:"); break; case IMediaPlayer.MEDIA_INFO_NETWORK_BANDWIDTH: Log.d(TAG, "MEDIA_INFO_NETWORK_BANDWIDTH: " + arg2); break; case IMediaPlayer.MEDIA_INFO_BAD_INTERLEAVING: Log.d(TAG, "MEDIA_INFO_BAD_INTERLEAVING:"); break; case IMediaPlayer.MEDIA_INFO_NOT_SEEKABLE: Log.d(TAG, "MEDIA_INFO_NOT_SEEKABLE:"); break; case IMediaPlayer.MEDIA_INFO_METADATA_UPDATE: Log.d(TAG, "MEDIA_INFO_METADATA_UPDATE:"); break; case IMediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE: Log.d(TAG, "MEDIA_INFO_UNSUPPORTED_SUBTITLE:"); break; case IMediaPlayer.MEDIA_INFO_SUBTITLE_TIMED_OUT: Log.d(TAG, "MEDIA_INFO_SUBTITLE_TIMED_OUT:"); break; case IMediaPlayer.MEDIA_INFO_VIDEO_ROTATION_CHANGED: mVideoRotationDegree = arg2; Log.d(TAG, "MEDIA_INFO_VIDEO_ROTATION_CHANGED: " + arg2); if (mRenderView != null) mRenderView.setVideoRotation(arg2); break; case IMediaPlayer.MEDIA_INFO_AUDIO_RENDERING_START: Log.d(TAG, "MEDIA_INFO_AUDIO_RENDERING_START:"); break; } return true; } }; private IMediaPlayer.OnErrorListener mErrorListener = new IMediaPlayer.OnErrorListener() { public boolean onError(IMediaPlayer 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 = mAppContext.getResources(); int messageId; if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) { messageId = R.string.VideoView_error_text_invalid_progressive_playback; } else { messageId = R.string.VideoView_error_text_unknown; } new AlertDialog.Builder(getContext()) .setMessage(messageId) .setPositiveButton(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 IMediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener = new IMediaPlayer.OnBufferingUpdateListener() { public void onBufferingUpdate(IMediaPlayer mp, int percent) { mCurrentBufferPercentage = percent; } }; private IMediaPlayer.OnSeekCompleteListener mSeekCompleteListener = new IMediaPlayer.OnSeekCompleteListener() { @Override public void onSeekComplete(IMediaPlayer mp) { mSeekEndTime = System.currentTimeMillis(); mHudViewHolder.updateSeekCost(mSeekEndTime - mSeekStartTime); } }; private IMediaPlayer.OnTimedTextListener mOnTimedTextListener = new IMediaPlayer.OnTimedTextListener() { @Override public void onTimedText(IMediaPlayer mp, IjkTimedText text) { if (text != null) { subtitleDisplay.setText(text.getText()); } } }; /** * 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(IMediaPlayer.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(IMediaPlayer.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(IMediaPlayer.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(IMediaPlayer.OnInfoListener l) { mOnInfoListener = l; } // REMOVED: mSHCallback private void bindSurfaceHolder(IMediaPlayer mp, IRenderView.ISurfaceHolder holder) { if (mp == null) return; if (holder == null) { mp.setDisplay(null); return; } holder.bindToMediaPlayer(mp); } IRenderView.IRenderCallback mSHCallback = new IRenderView.IRenderCallback() { @Override public void onSurfaceChanged(@NonNull IRenderView.ISurfaceHolder holder, int format, int w, int h) { if (holder.getRenderView() != mRenderView) { Log.e(TAG, "onSurfaceChanged: unmatched render callback\n"); return; } mSurfaceWidth = w; mSurfaceHeight = h; boolean isValidState = (mTargetState == STATE_PLAYING); boolean hasValidSize = !mRenderView.shouldWaitForResize() || (mVideoWidth == w && mVideoHeight == h); if (mMediaPlayer != null && isValidState && hasValidSize) { if (mSeekWhenPrepared != 0) { seekTo(mSeekWhenPrepared); } start(); } } @Override public void onSurfaceCreated(@NonNull IRenderView.ISurfaceHolder holder, int width, int height) { if (holder.getRenderView() != mRenderView) { Log.e(TAG, "onSurfaceCreated: unmatched render callback\n"); return; } mSurfaceHolder = holder; if (mMediaPlayer != null) bindSurfaceHolder(mMediaPlayer, holder); else openVideo(); } @Override public void onSurfaceDestroyed(@NonNull IRenderView.ISurfaceHolder holder) { if (holder.getRenderView() != mRenderView) { Log.e(TAG, "onSurfaceDestroyed: unmatched render callback\n"); return; } // after we return from this we can't use the surface any more mSurfaceHolder = null; // REMOVED: if (mMediaController != null) mMediaController.hide(); // REMOVED: release(true); releaseWithoutStop(); } }; public void releaseWithoutStop() { if (mMediaPlayer != null) mMediaPlayer.setDisplay(null); } /* * release the media player in any state */ public void release(boolean cleartargetstate) { if (mMediaPlayer != null) { mMediaPlayer.reset(); mMediaPlayer.release(); mMediaPlayer = null; // REMOVED: mPendingSubtitleTracks.clear(); mCurrentState = STATE_IDLE; if (cleartargetstate) { mTargetState = STATE_IDLE; } AudioManager am = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE); am.abandonAudioFocus(null); } } @Override public boolean onTouchEvent(MotionEvent ev) { if (isInPlaybackState() && mMediaController != null) { toggleMediaControlsVisiblity(); } return false; } @Override public boolean onTrackballEvent(MotionEvent ev) { if (isInPlaybackState() && mMediaController != null) { toggleMediaControlsVisiblity(); } return false; } @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 (int) mMediaPlayer.getDuration(); } return -1; } @Override public int getCurrentPosition() { if (isInPlaybackState()) { return (int) mMediaPlayer.getCurrentPosition(); } return 0; } @Override public void seekTo(int msec) { if (isInPlaybackState()) { mSeekStartTime = System.currentTimeMillis(); 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() { return 0; } // REMOVED: getAudioSessionId(); // REMOVED: onAttachedToWindow(); // REMOVED: onDetachedFromWindow(); // REMOVED: onLayout(); // REMOVED: draw(); // REMOVED: measureAndLayoutSubtitleWidget(); // REMOVED: setSubtitleWidget(); // REMOVED: getSubtitleLooper(); //------------------------- // Extend: Aspect Ratio //------------------------- private static final int[] s_allAspectRatio = { IRenderView.AR_ASPECT_FIT_PARENT, IRenderView.AR_ASPECT_FILL_PARENT, IRenderView.AR_ASPECT_WRAP_CONTENT, // IRenderView.AR_MATCH_PARENT, IRenderView.AR_16_9_FIT_PARENT, IRenderView.AR_4_3_FIT_PARENT}; private int mCurrentAspectRatioIndex = 0; private int mCurrentAspectRatio = s_allAspectRatio[0]; public int toggleAspectRatio() { mCurrentAspectRatioIndex++; mCurrentAspectRatioIndex %= s_allAspectRatio.length; mCurrentAspectRatio = s_allAspectRatio[mCurrentAspectRatioIndex]; if (mRenderView != null) mRenderView.setAspectRatio(mCurrentAspectRatio); return mCurrentAspectRatio; } //------------------------- // Extend: Render //------------------------- public static final int RENDER_NONE = 0; public static final int RENDER_SURFACE_VIEW = 1; public static final int RENDER_TEXTURE_VIEW = 2; private List<Integer> mAllRenders = new ArrayList<Integer>(); private int mCurrentRenderIndex = 0; private int mCurrentRender = RENDER_NONE; private void initRenders() { mAllRenders.clear(); if (mSettings.getEnableSurfaceView()) mAllRenders.add(RENDER_SURFACE_VIEW); if (mSettings.getEnableTextureView() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) mAllRenders.add(RENDER_TEXTURE_VIEW); if (mSettings.getEnableNoView()) mAllRenders.add(RENDER_NONE); if (mAllRenders.isEmpty()) mAllRenders.add(RENDER_SURFACE_VIEW); mCurrentRender = mAllRenders.get(mCurrentRenderIndex); setRender(mCurrentRender); } public int toggleRender() { mCurrentRenderIndex++; mCurrentRenderIndex %= mAllRenders.size(); mCurrentRender = mAllRenders.get(mCurrentRenderIndex); setRender(mCurrentRender); return mCurrentRender; } @NonNull public static String getRenderText(Context context, int render) { String text; switch (render) { case RENDER_NONE: text = context.getString(R.string.VideoView_render_none); break; case RENDER_SURFACE_VIEW: text = context.getString(R.string.VideoView_render_surface_view); break; case RENDER_TEXTURE_VIEW: text = context.getString(R.string.VideoView_render_texture_view); break; default: text = context.getString(R.string.N_A); break; } return text; } //------------------------- // Extend: Player //------------------------- public int togglePlayer() { if (mMediaPlayer != null) mMediaPlayer.release(); if (mRenderView != null) mRenderView.getView().invalidate(); openVideo(); return mSettings.getPlayer(); } @NonNull public static String getPlayerText(Context context, int player) { String text; switch (player) { case Settings.PV_PLAYER__AndroidMediaPlayer: text = context.getString(R.string.VideoView_player_AndroidMediaPlayer); break; case Settings.PV_PLAYER__IjkMediaPlayer: text = context.getString(R.string.VideoView_player_IjkMediaPlayer); break; case Settings.PV_PLAYER__IjkExoMediaPlayer: text = context.getString(R.string.VideoView_player_IjkExoMediaPlayer); break; default: text = context.getString(R.string.N_A); break; } return text; } public IMediaPlayer createPlayer(int playerType) { IMediaPlayer mediaPlayer = null; switch (playerType) { case Settings.PV_PLAYER__IjkExoMediaPlayer: { IjkExoMediaPlayer IjkExoMediaPlayer = new IjkExoMediaPlayer(mAppContext); mediaPlayer = IjkExoMediaPlayer; } break; case Settings.PV_PLAYER__AndroidMediaPlayer: { AndroidMediaPlayer androidMediaPlayer = new AndroidMediaPlayer(); mediaPlayer = androidMediaPlayer; } break; case Settings.PV_PLAYER__IjkMediaPlayer: default: { IjkMediaPlayer ijkMediaPlayer = null; if (mUri != null) { ijkMediaPlayer = new IjkMediaPlayer(); ijkMediaPlayer.native_setLogLevel(IjkMediaPlayer.IJK_LOG_DEBUG); if (mSettings.getUsingMediaCodec()) { ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1); if (mSettings.getUsingMediaCodecAutoRotate()) { ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 1); } else { ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 0); } if (mSettings.getMediaCodecHandleResolutionChange()) { ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-handle-resolution-change", 1); } else { ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-handle-resolution-change", 0); } } else { ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 0); } if (mSettings.getUsingOpenSLES()) { ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "opensles", 1); } else { ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "opensles", 0); } String pixelFormat = mSettings.getPixelFormat(); if (TextUtils.isEmpty(pixelFormat)) { ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", IjkMediaPlayer.SDL_FCC_RV32); } else { ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", pixelFormat); } ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 1); ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 0); ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "http-detect-range-support", 0); ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48); } mediaPlayer = ijkMediaPlayer; } break; } if (mSettings.getEnableDetachedSurfaceTextureView()) { mediaPlayer = new TextureMediaPlayer(mediaPlayer); } return mediaPlayer; } //------------------------- // Extend: Background //------------------------- private boolean mEnableBackgroundPlay = false; private void initBackground() { mEnableBackgroundPlay = mSettings.getEnableBackgroundPlay(); if (mEnableBackgroundPlay) { MediaPlayerService.intentToStart(getContext()); mMediaPlayer = MediaPlayerService.getMediaPlayer(); if (mHudViewHolder != null) mHudViewHolder.setMediaPlayer(mMediaPlayer); } } public boolean isBackgroundPlayEnabled() { return mEnableBackgroundPlay; } public void enterBackground() { MediaPlayerService.setMediaPlayer(mMediaPlayer); } public void stopBackgroundPlay() { MediaPlayerService.setMediaPlayer(null); } //------------------------- // Extend: Background //------------------------- public void showMediaInfo() { if (mMediaPlayer == null) return; int selectedVideoTrack = MediaPlayerCompat.getSelectedTrack(mMediaPlayer, ITrackInfo.MEDIA_TRACK_TYPE_VIDEO); int selectedAudioTrack = MediaPlayerCompat.getSelectedTrack(mMediaPlayer, ITrackInfo.MEDIA_TRACK_TYPE_AUDIO); int selectedSubtitleTrack = MediaPlayerCompat.getSelectedTrack(mMediaPlayer, ITrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT); TableLayoutBinder builder = new TableLayoutBinder(getContext()); builder.appendSection(R.string.mi_player); builder.appendRow2(R.string.mi_player, MediaPlayerCompat.getName(mMediaPlayer)); builder.appendSection(R.string.mi_media); builder.appendRow2(R.string.mi_resolution, buildResolution(mVideoWidth, mVideoHeight, mVideoSarNum, mVideoSarDen)); builder.appendRow2(R.string.mi_length, buildTimeMilli(mMediaPlayer.getDuration())); ITrackInfo trackInfos[] = mMediaPlayer.getTrackInfo(); if (trackInfos != null) { int index = -1; for (ITrackInfo trackInfo : trackInfos) { index++; int trackType = trackInfo.getTrackType(); if (index == selectedVideoTrack) { builder.appendSection(getContext().getString(R.string.mi_stream_fmt1, index) + " " + getContext().getString(R.string.mi__selected_video_track)); } else if (index == selectedAudioTrack) { builder.appendSection(getContext().getString(R.string.mi_stream_fmt1, index) + " " + getContext().getString(R.string.mi__selected_audio_track)); } else if (index == selectedSubtitleTrack) { builder.appendSection(getContext().getString(R.string.mi_stream_fmt1, index) + " " + getContext().getString(R.string.mi__selected_subtitle_track)); } else { builder.appendSection(getContext().getString(R.string.mi_stream_fmt1, index)); } builder.appendRow2(R.string.mi_type, buildTrackType(trackType)); builder.appendRow2(R.string.mi_language, buildLanguage(trackInfo.getLanguage())); IMediaFormat mediaFormat = trackInfo.getFormat(); if (mediaFormat == null) { } else if (mediaFormat instanceof IjkMediaFormat) { switch (trackType) { case ITrackInfo.MEDIA_TRACK_TYPE_VIDEO: builder.appendRow2(R.string.mi_codec, mediaFormat.getString(IjkMediaFormat.KEY_IJK_CODEC_LONG_NAME_UI)); builder.appendRow2(R.string.mi_profile_level, mediaFormat.getString(IjkMediaFormat.KEY_IJK_CODEC_PROFILE_LEVEL_UI)); builder.appendRow2(R.string.mi_pixel_format, mediaFormat.getString(IjkMediaFormat.KEY_IJK_CODEC_PIXEL_FORMAT_UI)); builder.appendRow2(R.string.mi_resolution, mediaFormat.getString(IjkMediaFormat.KEY_IJK_RESOLUTION_UI)); builder.appendRow2(R.string.mi_frame_rate, mediaFormat.getString(IjkMediaFormat.KEY_IJK_FRAME_RATE_UI)); builder.appendRow2(R.string.mi_bit_rate, mediaFormat.getString(IjkMediaFormat.KEY_IJK_BIT_RATE_UI)); break; case ITrackInfo.MEDIA_TRACK_TYPE_AUDIO: builder.appendRow2(R.string.mi_codec, mediaFormat.getString(IjkMediaFormat.KEY_IJK_CODEC_LONG_NAME_UI)); builder.appendRow2(R.string.mi_profile_level, mediaFormat.getString(IjkMediaFormat.KEY_IJK_CODEC_PROFILE_LEVEL_UI)); builder.appendRow2(R.string.mi_sample_rate, mediaFormat.getString(IjkMediaFormat.KEY_IJK_SAMPLE_RATE_UI)); builder.appendRow2(R.string.mi_channels, mediaFormat.getString(IjkMediaFormat.KEY_IJK_CHANNEL_UI)); builder.appendRow2(R.string.mi_bit_rate, mediaFormat.getString(IjkMediaFormat.KEY_IJK_BIT_RATE_UI)); break; default: break; } } } } AlertDialog.Builder adBuilder = builder.buildAlertDialogBuilder(); adBuilder.setTitle(R.string.media_information); adBuilder.setNegativeButton(R.string.close, null); adBuilder.show(); } private String buildResolution(int width, int height, int sarNum, int sarDen) { StringBuilder sb = new StringBuilder(); sb.append(width); sb.append(" x "); sb.append(height); if (sarNum > 1 || sarDen > 1) { sb.append("["); sb.append(sarNum); sb.append(":"); sb.append(sarDen); sb.append("]"); } return sb.toString(); } private String buildTimeMilli(long duration) { long total_seconds = duration / 1000; long hours = total_seconds / 3600; long minutes = (total_seconds % 3600) / 60; long seconds = total_seconds % 60; if (duration <= 0) { return "--:--"; } if (hours >= 100) { return String.format(Locale.US, "%d:%02d:%02d", hours, minutes, seconds); } else if (hours > 0) { return String.format(Locale.US, "%02d:%02d:%02d", hours, minutes, seconds); } else { return String.format(Locale.US, "%02d:%02d", minutes, seconds); } } private String buildTrackType(int type) { Context context = getContext(); switch (type) { case ITrackInfo.MEDIA_TRACK_TYPE_VIDEO: return context.getString(R.string.TrackType_video); case ITrackInfo.MEDIA_TRACK_TYPE_AUDIO: return context.getString(R.string.TrackType_audio); case ITrackInfo.MEDIA_TRACK_TYPE_SUBTITLE: return context.getString(R.string.TrackType_subtitle); case ITrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT: return context.getString(R.string.TrackType_timedtext); case ITrackInfo.MEDIA_TRACK_TYPE_METADATA: return context.getString(R.string.TrackType_metadata); case ITrackInfo.MEDIA_TRACK_TYPE_UNKNOWN: default: return context.getString(R.string.TrackType_unknown); } } private String buildLanguage(String language) { if (TextUtils.isEmpty(language)) return "und"; return language; } public ITrackInfo[] getTrackInfo() { if (mMediaPlayer == null) return null; return mMediaPlayer.getTrackInfo(); } public void selectTrack(int stream) { MediaPlayerCompat.selectTrack(mMediaPlayer, stream); } public void deselectTrack(int stream) { MediaPlayerCompat.deselectTrack(mMediaPlayer, stream); } public int getSelectedTrack(int trackType) { return MediaPlayerCompat.getSelectedTrack(mMediaPlayer, trackType); } }
/* * Copyright (C) 2015 Bilibili * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com> * * 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 com.example.k.bilibiliapi.video; import android.view.View; import android.widget.MediaController; public interface IMediaController { void hide(); boolean isShowing(); void setAnchorView(View view); void setEnabled(boolean enabled); void setMediaPlayer(MediaController.MediaPlayerControl player); void show(int timeout); void show(); //---------- // Extends //---------- void showOnce(View view); }
package com.example.k.bilibiliapi.video; import android.content.Context; import android.os.Handler; import android.os.Message; import android.util.SparseArray; import android.view.View; import android.widget.TableLayout; import com.example.k.bilibiliapi.R; import java.util.Locale; import tv.danmaku.ijk.media.player.IMediaPlayer; import tv.danmaku.ijk.media.player.IjkMediaPlayer; import tv.danmaku.ijk.media.player.MediaPlayerProxy; public class InfoHudViewHolder { private TableLayoutBinder mTableLayoutBinder; private SparseArray<View> mRowMap = new SparseArray<View>(); private IMediaPlayer mMediaPlayer; private long mLoadCost = 0; private long mSeekCost = 0; public InfoHudViewHolder(Context context, TableLayout tableLayout) { mTableLayoutBinder = new TableLayoutBinder(context, tableLayout); } private void appendSection(int nameId) { mTableLayoutBinder.appendSection(nameId); } private void appendRow(int nameId) { View rowView = mTableLayoutBinder.appendRow2(nameId, null); mRowMap.put(nameId, rowView); } private void setRowValue(int id, String value) { View rowView = mRowMap.get(id); if (rowView == null) { rowView = mTableLayoutBinder.appendRow2(id, value); mRowMap.put(id, rowView); } else { mTableLayoutBinder.setValueText(rowView, value); } } public void setMediaPlayer(IMediaPlayer mp) { mMediaPlayer = mp; if (mMediaPlayer != null) { mHandler.sendEmptyMessageDelayed(MSG_UPDATE_HUD, 500); } else { mHandler.removeMessages(MSG_UPDATE_HUD); } } private static String formatedDurationMilli(long duration) { if (duration >= 1000) { return String.format(Locale.US, "%.2f sec", ((float)duration) / 1000); } else { return String.format(Locale.US, "%d msec", duration); } } private static String formatedSpeed(long bytes,long elapsed_milli) { if (elapsed_milli <= 0) { return "0 B/s"; } if (bytes <= 0) { return "0 B/s"; } float bytes_per_sec = ((float)bytes) * 1000.f / elapsed_milli; if (bytes_per_sec >= 1000 * 1000) { return String.format(Locale.US, "%.2f MB/s", ((float)bytes_per_sec) / 1000 / 1000); } else if (bytes_per_sec >= 1000) { return String.format(Locale.US, "%.1f KB/s", ((float)bytes_per_sec) / 1000); } else { return String.format(Locale.US, "%d B/s", (long)bytes_per_sec); } } public void updateLoadCost(long time) { mLoadCost = time; } public void updateSeekCost(long time) { mSeekCost = time; } private static String formatedSize(long bytes) { if (bytes >= 100 * 1000) { return String.format(Locale.US, "%.2f MB", ((float)bytes) / 1000 / 1000); } else if (bytes >= 100) { return String.format(Locale.US, "%.1f KB", ((float)bytes) / 1000); } else { return String.format(Locale.US, "%d B", bytes); } } private static final int MSG_UPDATE_HUD = 1; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_UPDATE_HUD: { InfoHudViewHolder holder = InfoHudViewHolder.this; IjkMediaPlayer mp = null; if (mMediaPlayer == null) break; if (mMediaPlayer instanceof IjkMediaPlayer) { mp = (IjkMediaPlayer) mMediaPlayer; } else if (mMediaPlayer instanceof MediaPlayerProxy) { MediaPlayerProxy proxy = (MediaPlayerProxy) mMediaPlayer; IMediaPlayer internal = proxy.getInternalMediaPlayer(); if (internal != null && internal instanceof IjkMediaPlayer) mp = (IjkMediaPlayer) internal; } if (mp == null) break; int vdec = mp.getVideoDecoder(); switch (vdec) { case IjkMediaPlayer.FFP_PROPV_DECODER_AVCODEC: setRowValue(R.string.vdec, "avcodec"); break; case IjkMediaPlayer.FFP_PROPV_DECODER_MEDIACODEC: setRowValue(R.string.vdec, "MediaCodec"); break; default: setRowValue(R.string.vdec, ""); break; } float fpsOutput = mp.getVideoOutputFramesPerSecond(); float fpsDecode = mp.getVideoDecodeFramesPerSecond(); setRowValue(R.string.fps, String.format(Locale.US, "%.2f / %.2f", fpsDecode, fpsOutput)); long videoCachedDuration = mp.getVideoCachedDuration(); long audioCachedDuration = mp.getAudioCachedDuration(); long videoCachedBytes = mp.getVideoCachedBytes(); long audioCachedBytes = mp.getAudioCachedBytes(); long tcpSpeed = mp.getTcpSpeed(); long bitRate = mp.getBitRate(); long seekLoadDuration = mp.getSeekLoadDuration(); setRowValue(R.string.v_cache, String.format(Locale.US, "%s, %s", formatedDurationMilli(videoCachedDuration), formatedSize(videoCachedBytes))); setRowValue(R.string.a_cache, String.format(Locale.US, "%s, %s", formatedDurationMilli(audioCachedDuration), formatedSize(audioCachedBytes))); setRowValue(R.string.load_cost, String.format(Locale.US, "%d ms", mLoadCost)); setRowValue(R.string.seek_cost, String.format(Locale.US, "%d ms", mSeekCost)); setRowValue(R.string.seek_load_cost, String.format(Locale.US, "%d ms", seekLoadDuration)); setRowValue(R.string.tcp_speed, String.format(Locale.US, "%s", formatedSpeed(tcpSpeed, 1000))); setRowValue(R.string.bit_rate, String.format(Locale.US, "%.2f kbs", bitRate/1000f)); mHandler.removeMessages(MSG_UPDATE_HUD); mHandler.sendEmptyMessageDelayed(MSG_UPDATE_HUD, 500); } } } }; }
/* * Copyright (C) 2015 Bilibili * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com> * * 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 com.example.k.bilibiliapi.video; import android.graphics.SurfaceTexture; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.view.Surface; import android.view.SurfaceHolder; import android.view.View; import tv.danmaku.ijk.media.player.IMediaPlayer; public interface IRenderView { int AR_ASPECT_FIT_PARENT = 0; // without clip int AR_ASPECT_FILL_PARENT = 1; // may clip int AR_ASPECT_WRAP_CONTENT = 2; int AR_MATCH_PARENT = 3; int AR_16_9_FIT_PARENT = 4; int AR_4_3_FIT_PARENT = 5; View getView(); boolean shouldWaitForResize(); void setVideoSize(int videoWidth, int videoHeight); void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen); void setVideoRotation(int degree); void setAspectRatio(int aspectRatio); void addRenderCallback(@NonNull IRenderCallback callback); void removeRenderCallback(@NonNull IRenderCallback callback); interface ISurfaceHolder { void bindToMediaPlayer(IMediaPlayer mp); @NonNull IRenderView getRenderView(); @Nullable SurfaceHolder getSurfaceHolder(); @Nullable Surface openSurface(); @Nullable SurfaceTexture getSurfaceTexture(); } interface IRenderCallback { /** * @param holder * @param width could be 0 * @param height could be 0 */ void onSurfaceCreated(@NonNull ISurfaceHolder holder, int width, int height); /** * @param holder * @param format could be 0 * @param width * @param height */ void onSurfaceChanged(@NonNull ISurfaceHolder holder, int format, int width, int height); void onSurfaceDestroyed(@NonNull ISurfaceHolder holder); } }
/* * Copyright (C) 2015 Bilibili * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com> * * 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 com.example.k.bilibiliapi.video; import android.content.Context; import android.support.annotation.NonNull; import android.view.View; import com.example.k.bilibiliapi.R; import java.lang.ref.WeakReference; public final class MeasureHelper { private WeakReference<View> mWeakView; private int mVideoWidth; private int mVideoHeight; private int mVideoSarNum; private int mVideoSarDen; private int mVideoRotationDegree; private int mMeasuredWidth; private int mMeasuredHeight; private int mCurrentAspectRatio = IRenderView.AR_ASPECT_FIT_PARENT; public MeasureHelper(View view) { mWeakView = new WeakReference<View>(view); } public View getView() { if (mWeakView == null) return null; return mWeakView.get(); } public void setVideoSize(int videoWidth, int videoHeight) { mVideoWidth = videoWidth; mVideoHeight = videoHeight; } public void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen) { mVideoSarNum = videoSarNum; mVideoSarDen = videoSarDen; } public void setVideoRotation(int videoRotationDegree) { mVideoRotationDegree = videoRotationDegree; } /** * Must be called by View.onMeasure(int, int) * * @param widthMeasureSpec * @param heightMeasureSpec */ public void doMeasure(int widthMeasureSpec, int heightMeasureSpec) { //Log.i("@@@@", "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", " // + MeasureSpec.toString(heightMeasureSpec) + ")"); if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270) { int tempSpec = widthMeasureSpec; widthMeasureSpec = heightMeasureSpec; heightMeasureSpec = tempSpec; } int width = View.getDefaultSize(mVideoWidth, widthMeasureSpec); int height = View.getDefaultSize(mVideoHeight, heightMeasureSpec); if (mCurrentAspectRatio == IRenderView.AR_MATCH_PARENT) { width = widthMeasureSpec; height = heightMeasureSpec; } else if (mVideoWidth > 0 && mVideoHeight > 0) { int widthSpecMode = View.MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = View.MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = View.MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = View.MeasureSpec.getSize(heightMeasureSpec); if (widthSpecMode == View.MeasureSpec.AT_MOST && heightSpecMode == View.MeasureSpec.AT_MOST) { float specAspectRatio = (float) widthSpecSize / (float) heightSpecSize; float displayAspectRatio; switch (mCurrentAspectRatio) { case IRenderView.AR_16_9_FIT_PARENT: displayAspectRatio = 16.0f / 9.0f; if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270) displayAspectRatio = 1.0f / displayAspectRatio; break; case IRenderView.AR_4_3_FIT_PARENT: displayAspectRatio = 4.0f / 3.0f; if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270) displayAspectRatio = 1.0f / displayAspectRatio; break; case IRenderView.AR_ASPECT_FIT_PARENT: case IRenderView.AR_ASPECT_FILL_PARENT: case IRenderView.AR_ASPECT_WRAP_CONTENT: default: displayAspectRatio = (float) mVideoWidth / (float) mVideoHeight; if (mVideoSarNum > 0 && mVideoSarDen > 0) displayAspectRatio = displayAspectRatio * mVideoSarNum / mVideoSarDen; break; } boolean shouldBeWider = displayAspectRatio > specAspectRatio; switch (mCurrentAspectRatio) { case IRenderView.AR_ASPECT_FIT_PARENT: case IRenderView.AR_16_9_FIT_PARENT: case IRenderView.AR_4_3_FIT_PARENT: if (shouldBeWider) { // too wide, fix width width = widthSpecSize; height = (int) (width / displayAspectRatio); } else { // too high, fix height height = heightSpecSize; width = (int) (height * displayAspectRatio); } break; case IRenderView.AR_ASPECT_FILL_PARENT: if (shouldBeWider) { // not high enough, fix height height = heightSpecSize; width = (int) (height * displayAspectRatio); } else { // not wide enough, fix width width = widthSpecSize; height = (int) (width / displayAspectRatio); } break; case IRenderView.AR_ASPECT_WRAP_CONTENT: default: if (shouldBeWider) { // too wide, fix width width = Math.min(mVideoWidth, widthSpecSize); height = (int) (width / displayAspectRatio); } else { // too high, fix height height = Math.min(mVideoHeight, heightSpecSize); width = (int) (height * displayAspectRatio); } break; } } else if (widthSpecMode == View.MeasureSpec.EXACTLY && heightSpecMode == View.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 == View.MeasureSpec.EXACTLY) { // only the width is fixed, adjust the height to match aspect ratio if possible width = widthSpecSize; height = width * mVideoHeight / mVideoWidth; if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) { // couldn't match aspect ratio within the constraints height = heightSpecSize; } } else if (heightSpecMode == View.MeasureSpec.EXACTLY) { // only the height is fixed, adjust the width to match aspect ratio if possible height = heightSpecSize; width = height * mVideoWidth / mVideoHeight; if (widthSpecMode == View.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 == View.MeasureSpec.AT_MOST && height > heightSpecSize) { // too tall, decrease both width and height height = heightSpecSize; width = height * mVideoWidth / mVideoHeight; } if (widthSpecMode == View.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 } mMeasuredWidth = width; mMeasuredHeight = height; } public int getMeasuredWidth() { return mMeasuredWidth; } public int getMeasuredHeight() { return mMeasuredHeight; } public void setAspectRatio(int aspectRatio) { mCurrentAspectRatio = aspectRatio; } @NonNull public static String getAspectRatioText(Context context, int aspectRatio) { String text; switch (aspectRatio) { case IRenderView.AR_ASPECT_FIT_PARENT: text = context.getString(R.string.VideoView_ar_aspect_fit_parent); break; case IRenderView.AR_ASPECT_FILL_PARENT: text = context.getString(R.string.VideoView_ar_aspect_fill_parent); break; case IRenderView.AR_ASPECT_WRAP_CONTENT: text = context.getString(R.string.VideoView_ar_aspect_wrap_content); break; case IRenderView.AR_MATCH_PARENT: text = context.getString(R.string.VideoView_ar_match_parent); break; case IRenderView.AR_16_9_FIT_PARENT: text = context.getString(R.string.VideoView_ar_16_9_fit_parent); break; case IRenderView.AR_4_3_FIT_PARENT: text = context.getString(R.string.VideoView_ar_4_3_fit_parent); break; default: text = context.getString(R.string.N_A); break; } return text; } }
/* * Copyright (C) 2015 Bilibili * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com> * * 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 com.example.k.bilibiliapi.video; import tv.danmaku.ijk.media.player.IMediaPlayer; import tv.danmaku.ijk.media.player.IjkMediaPlayer; import tv.danmaku.ijk.media.player.MediaPlayerProxy; import tv.danmaku.ijk.media.player.TextureMediaPlayer; public class MediaPlayerCompat { public static String getName(IMediaPlayer mp) { if (mp == null) { return "null"; } else if (mp instanceof TextureMediaPlayer) { StringBuilder sb = new StringBuilder("TextureMediaPlayer <"); IMediaPlayer internalMediaPlayer = ((TextureMediaPlayer) mp).getInternalMediaPlayer(); if (internalMediaPlayer == null) { sb.append("null>"); } else { sb.append(internalMediaPlayer.getClass().getSimpleName()); sb.append(">"); } return sb.toString(); } else { return mp.getClass().getSimpleName(); } } public static IjkMediaPlayer getIjkMediaPlayer(IMediaPlayer mp) { IjkMediaPlayer ijkMediaPlayer = null; if (mp == null) { return null; } if (mp instanceof IjkMediaPlayer) { ijkMediaPlayer = (IjkMediaPlayer) mp; } else if (mp instanceof MediaPlayerProxy && ((MediaPlayerProxy) mp).getInternalMediaPlayer() instanceof IjkMediaPlayer) { ijkMediaPlayer = (IjkMediaPlayer) ((MediaPlayerProxy) mp).getInternalMediaPlayer(); } return ijkMediaPlayer; } public static void selectTrack(IMediaPlayer mp, int stream) { IjkMediaPlayer ijkMediaPlayer = getIjkMediaPlayer(mp); if (ijkMediaPlayer == null) return; ijkMediaPlayer.selectTrack(stream); } public static void deselectTrack(IMediaPlayer mp, int stream) { IjkMediaPlayer ijkMediaPlayer = getIjkMediaPlayer(mp); if (ijkMediaPlayer == null) return; ijkMediaPlayer.deselectTrack(stream); } public static int getSelectedTrack(IMediaPlayer mp, int trackType) { IjkMediaPlayer ijkMediaPlayer = getIjkMediaPlayer(mp); if (ijkMediaPlayer == null) return -1; return ijkMediaPlayer.getSelectedTrack(trackType); } }
/* * Copyright (C) 2015 Bilibili * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com> * * 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 com.example.k.bilibiliapi.video; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.IBinder; import android.support.annotation.Nullable; import tv.danmaku.ijk.media.player.IMediaPlayer; public class MediaPlayerService extends Service { private static IMediaPlayer sMediaPlayer; public static Intent newIntent(Context context) { Intent intent = new Intent(context, MediaPlayerService.class); return intent; } public static void intentToStart(Context context) { context.startService(newIntent(context)); } public static void intentToStop(Context context) { context.stopService(newIntent(context)); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } public static void setMediaPlayer(IMediaPlayer mp) { if (sMediaPlayer != null && sMediaPlayer != mp) { if (sMediaPlayer.isPlaying()) sMediaPlayer.stop(); sMediaPlayer.release(); sMediaPlayer = null; } sMediaPlayer = mp; } public static IMediaPlayer getMediaPlayer() { return sMediaPlayer; } }
package com.example.k.bilibiliapi.video; import android.app.Activity; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Resources; import android.media.AudioManager; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.OrientationEventListener; import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.ImageView; import android.widget.TextView; import com.example.k.bilibiliapi.R; import tv.danmaku.ijk.media.player.IMediaPlayer; import tv.danmaku.ijk.media.player.IjkMediaPlayer; import tv.danmaku.ijk.media.player.pragma.DebugLog; /** * Created by yilv on 2018/3/29. */ public class PlayerManager { /** * 可能会剪裁,保持原视频的大小,显示在中心,当原视频的大小超过view的大小超过部分裁剪处理 */ public static final String SCALETYPE_FITPARENT="fitParent"; /** * 可能会剪裁,等比例放大视频,直到填满View为止,超过View的部分作裁剪处理 */ public static final String SCALETYPE_FILLPARENT="fillParent"; /** * 将视频的内容完整居中显示,如果视频大于view,则按比例缩视频直到完全显示在view中 */ public static final String SCALETYPE_WRAPCONTENT="wrapContent"; /** * 不剪裁,非等比例拉伸画面填满整个View */ public static final String SCALETYPE_FITXY="fitXY"; /** * 不剪裁,非等比例拉伸画面到16:9,并完全显示在View中 */ public static final String SCALETYPE_16_9="16:9"; /** * 不剪裁,非等比例拉伸画面到4:3,并完全显示在View中 */ public static final String SCALETYPE_4_3="4:3"; /** * 状态常量 */ private final int STATUS_ERROR=-1; private final int STATUS_IDLE=0; private final int STATUS_LOADING=1; private final int STATUS_PLAYING=2; private final int STATUS_PAUSE=3; private final int STATUS_COMPLETED=4; private final Activity activity; private final IjkVideoView videoView; private final AudioManager audioManager; public GestureDetector gestureDetector; private boolean playerSupport; private boolean isLive = false;//是否为直播 private boolean fullScreenOnly; private boolean portrait; private final int mMaxVolume; private int screenWidthPixels; private int currentPosition; private int status=STATUS_IDLE; private long pauseTime; private String url; private float brightness=-1; private int volume=-1; private long newPosition = -1; private long defaultRetryTime=5000; private OrientationEventListener orientationEventListener; private PlayerStateListener playerStateListener; public void setPlayerStateListener(PlayerStateListener playerStateListener) { this.playerStateListener = playerStateListener; } private OnErrorListener onErrorListener=new OnErrorListener() { @Override public void onError(int what, int extra) { } }; private OnCompleteListener onCompleteListener=new OnCompleteListener() { @Override public void onComplete() { } }; private OnInfoListener onInfoListener=new OnInfoListener(){ @Override public void onInfo(int what, int extra) { } }; private OnControlPanelVisibilityChangeListener onControlPanelVisibilityChangeListener=new OnControlPanelVisibilityChangeListener() { @Override public void change(boolean isShowing) { } }; /** * try to play when error(only for live video) * @param defaultRetryTime millisecond,0 will stop retry,default is 5000 millisecond */ public void setDefaultRetryTime(long defaultRetryTime) { this.defaultRetryTime = defaultRetryTime; } public PlayerManager(final Activity activity) { try { IjkMediaPlayer.loadLibrariesOnce(null); IjkMediaPlayer.native_profileBegin("libijkplayer.so"); playerSupport=true; } catch (Throwable e) { Log.e("GiraffePlayer", "loadLibraries error", e); } this.activity=activity; screenWidthPixels = activity.getResources().getDisplayMetrics().widthPixels; videoView = (IjkVideoView) activity.findViewById(R.id.video_view); videoView.setOnCompletionListener(new IMediaPlayer.OnCompletionListener() { @Override public void onCompletion(IMediaPlayer mp) { statusChange(STATUS_COMPLETED); onCompleteListener.onComplete(); } }); videoView.setOnErrorListener(new IMediaPlayer.OnErrorListener() { @Override public boolean onError(IMediaPlayer mp, int what, int extra) { statusChange(STATUS_ERROR); onErrorListener.onError(what,extra); return true; } }); videoView.setOnInfoListener(new IMediaPlayer.OnInfoListener() { @Override public boolean onInfo(IMediaPlayer mp, int what, int extra) { switch (what) { case IMediaPlayer.MEDIA_INFO_BUFFERING_START: statusChange(STATUS_LOADING); break; case IMediaPlayer.MEDIA_INFO_BUFFERING_END: statusChange(STATUS_PLAYING); break; case IMediaPlayer.MEDIA_INFO_NETWORK_BANDWIDTH: //显示下载速度 // Toast.show("download rate:" + extra); break; case IMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START: statusChange(STATUS_PLAYING); break; } onInfoListener.onInfo(what,extra); return false; } }); audioManager = (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE); mMaxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); gestureDetector = new GestureDetector(activity, new PlayerGestureListener()); if (fullScreenOnly) { activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } portrait=getScreenOrientation()== ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; if (!playerSupport) { DebugLog.e("","播放器不支持此设备"); } } private void statusChange(int newStatus) { status = newStatus; if (!isLive && newStatus==STATUS_COMPLETED) { DebugLog.d("","statusChange STATUS_COMPLETED..."); if (playerStateListener != null){ playerStateListener.onComplete(); } }else if (newStatus == STATUS_ERROR) { DebugLog.d("","statusChange STATUS_ERROR..."); if (playerStateListener != null){ playerStateListener.onError(); } } else if(newStatus==STATUS_LOADING){ // $.id(R.id.app_video_loading).visible(); if (playerStateListener != null){ playerStateListener.onLoading(); } DebugLog.d("","statusChange STATUS_LOADING..."); } else if (newStatus == STATUS_PLAYING) { DebugLog.d("","statusChange STATUS_PLAYING..."); if (playerStateListener != null){ playerStateListener.onPlay(); } } } public void onPause() { pauseTime= System.currentTimeMillis(); if (status==STATUS_PLAYING) { videoView.pause(); if (!isLive) { currentPosition = videoView.getCurrentPosition(); } } } public void onResume() { pauseTime=0; if (status==STATUS_PLAYING) { if (isLive) { videoView.seekTo(0); } else { if (currentPosition>0) { videoView.seekTo(currentPosition); } } videoView.start(); } } public void onDestroy() { orientationEventListener.disable(); videoView.stopPlayback(); } public void play(String url) { this.url = url; if (playerSupport) { videoView.setVideoPath(url); videoView.start(); } } private String generateTime(long time) { int totalSeconds = (int) (time / 1000); int seconds = totalSeconds % 60; int minutes = (totalSeconds / 60) % 60; int hours = totalSeconds / 3600; return hours > 0 ? String.format("%02d:%02d:%02d", hours, minutes, seconds) : String.format("%02d:%02d", minutes, seconds); } private int getScreenOrientation() { int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); DisplayMetrics dm = new DisplayMetrics(); activity.getWindowManager().getDefaultDisplay().getMetrics(dm); int width = dm.widthPixels; int height = dm.heightPixels; int orientation; // if the device's natural orientation is portrait: if ((rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) && height > width || (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) && width > height) { switch (rotation) { case Surface.ROTATION_0: orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; break; case Surface.ROTATION_90: orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; break; case Surface.ROTATION_180: orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; break; case Surface.ROTATION_270: orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; break; default: orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; break; } } // if the device's natural orientation is landscape or if the device // is square: else { switch (rotation) { case Surface.ROTATION_0: orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; break; case Surface.ROTATION_90: orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; break; case Surface.ROTATION_180: orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; break; case Surface.ROTATION_270: orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; break; default: orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; break; } } return orientation; } /** * 滑动改变声音大小 * * @param percent */ private void onVolumeSlide(float percent) { if (volume == -1) { volume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC); if (volume < 0) volume = 0; } int index = (int) (percent * mMaxVolume) + volume; if (index > mMaxVolume) { index = mMaxVolume; } else if (index < 0){ index = 0; } // 变更声音 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, index, 0); // 变更进度条 int i = (int) (index * 1.0 / mMaxVolume * 100); String s = i + "%"; if (i == 0) { s = "off"; } DebugLog.d("","onVolumeSlide:"+s); } private void onProgressSlide(float percent) { long position = videoView.getCurrentPosition(); long duration = videoView.getDuration(); long deltaMax = Math.min(100 * 1000, duration - position); long delta = (long) (deltaMax * percent); newPosition = delta + position; if (newPosition > duration) { newPosition = duration; } else if (newPosition <= 0) { newPosition=0; delta=-position; } int showDelta = (int) delta / 1000; if (showDelta != 0) { String text = showDelta > 0 ? ("+" + showDelta) : "" + showDelta; DebugLog.d("","onProgressSlide:" + text); } } /** * 滑动改变亮度 * * @param percent */ private void onBrightnessSlide(float percent) { if (brightness < 0) { brightness = activity.getWindow().getAttributes().screenBrightness; if (brightness <= 0.00f){ brightness = 0.50f; }else if (brightness < 0.01f){ brightness = 0.01f; } } DebugLog.d("","brightness:"+brightness+",percent:"+ percent); WindowManager.LayoutParams lpa = activity.getWindow().getAttributes(); lpa.screenBrightness = brightness + percent; if (lpa.screenBrightness > 1.0f){ lpa.screenBrightness = 1.0f; }else if (lpa.screenBrightness < 0.01f){ lpa.screenBrightness = 0.01f; } activity.getWindow().setAttributes(lpa); } public void setFullScreenOnly(boolean fullScreenOnly) { this.fullScreenOnly = fullScreenOnly; tryFullScreen(fullScreenOnly); if (fullScreenOnly) { activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } else { activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); } } private void tryFullScreen(boolean fullScreen) { if (activity instanceof AppCompatActivity) { ActionBar supportActionBar = ((AppCompatActivity) activity).getSupportActionBar(); if (supportActionBar != null) { if (fullScreen) { supportActionBar.hide(); } else { supportActionBar.show(); } } } setFullScreen(fullScreen); } private void setFullScreen(boolean fullScreen) { if (activity != null) { WindowManager.LayoutParams attrs = activity.getWindow().getAttributes(); if (fullScreen) { attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; activity.getWindow().setAttributes(attrs); activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); } else { attrs.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN); activity.getWindow().setAttributes(attrs); activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); } } } /** * <pre> * fitParent:可能会剪裁,保持原视频的大小,显示在中心,当原视频的大小超过view的大小超过部分裁剪处理 * fillParent:可能会剪裁,等比例放大视频,直到填满View为止,超过View的部分作裁剪处理 * wrapContent:将视频的内容完整居中显示,如果视频大于view,则按比例缩视频直到完全显示在view中 * fitXY:不剪裁,非等比例拉伸画面填满整个View * 16:9:不剪裁,非等比例拉伸画面到16:9,并完全显示在View中 * 4:3:不剪裁,非等比例拉伸画面到4:3,并完全显示在View中 * </pre> * @param scaleType */ public void setScaleType(String scaleType) { if (SCALETYPE_FITPARENT.equals(scaleType)) { videoView.setAspectRatio(IRenderView.AR_ASPECT_FIT_PARENT); }else if (SCALETYPE_FILLPARENT.equals(scaleType)) { videoView.setAspectRatio(IRenderView.AR_ASPECT_FILL_PARENT); }else if (SCALETYPE_WRAPCONTENT.equals(scaleType)) { videoView.setAspectRatio(IRenderView.AR_ASPECT_WRAP_CONTENT); }else if (SCALETYPE_FITXY.equals(scaleType)) { videoView.setAspectRatio(IRenderView.AR_MATCH_PARENT); }else if (SCALETYPE_16_9.equals(scaleType)) { videoView.setAspectRatio(IRenderView.AR_16_9_FIT_PARENT); }else if (SCALETYPE_4_3.equals(scaleType)) { videoView.setAspectRatio(IRenderView.AR_4_3_FIT_PARENT); } } public void start() { videoView.start(); } public void pause() { videoView.pause(); } public boolean onBackPressed() { if (!fullScreenOnly && getScreenOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); return true; } return false; } class Query { private final Activity activity; private View view; public Query(Activity activity) { this.activity=activity; } public Query id(int id) { view = activity.findViewById(id); return this; } public Query image(int resId) { if (view instanceof ImageView) { ((ImageView) view).setImageResource(resId); } return this; } public Query visible() { if (view != null) { view.setVisibility(View.VISIBLE); } return this; } public Query gone() { if (view != null) { view.setVisibility(View.GONE); } return this; } public Query invisible() { if (view != null) { view.setVisibility(View.INVISIBLE); } return this; } public Query clicked(View.OnClickListener handler) { if (view != null) { view.setOnClickListener(handler); } return this; } public Query text(CharSequence text) { if (view!=null && view instanceof TextView) { ((TextView) view).setText(text); } return this; } public Query visibility(int visible) { if (view != null) { view.setVisibility(visible); } return this; } private void size(boolean width, int n, boolean dip){ if(view != null){ ViewGroup.LayoutParams lp = view.getLayoutParams(); if(n > 0 && dip){ n = dip2pixel(activity, n); } if(width){ lp.width = n; }else{ lp.height = n; } view.setLayoutParams(lp); } } public void height(int height, boolean dip) { size(false,height,dip); } public int dip2pixel(Context context, float n){ int value = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, n, context.getResources().getDisplayMetrics()); return value; } public float pixel2dip(Context context, float n){ Resources resources = context.getResources(); DisplayMetrics metrics = resources.getDisplayMetrics(); float dp = n / (metrics.densityDpi / 160f); return dp; } } public class PlayerGestureListener extends GestureDetector.SimpleOnGestureListener { private boolean firstTouch; private boolean volumeControl; private boolean toSeek; /** * 双击 */ @Override public boolean onDoubleTap(MotionEvent e) { videoView.toggleAspectRatio(); return true; } @Override public boolean onDown(MotionEvent e) { firstTouch = true; return super.onDown(e); } /** * 滑动 */ @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { float mOldX = e1.getX(), mOldY = e1.getY(); float deltaY = mOldY - e2.getY(); float deltaX = mOldX - e2.getX(); if (firstTouch) { toSeek = Math.abs(distanceX) >= Math.abs(distanceY); volumeControl=mOldX > screenWidthPixels * 0.5f; firstTouch = false; } if (toSeek) { if (!isLive) { onProgressSlide(-deltaX / videoView.getWidth()); } } else { float percent = deltaY / videoView.getHeight(); if (volumeControl) { onVolumeSlide(percent); } else { onBrightnessSlide(percent); } } return super.onScroll(e1, e2, distanceX, distanceY); } @Override public boolean onSingleTapUp(MotionEvent e) { return true; } } /** * is player support this device * @return */ public boolean isPlayerSupport() { return playerSupport; } /** * 是否正在播放 * @return */ public boolean isPlaying() { return videoView!=null?videoView.isPlaying():false; } public void stop(){ videoView.stopPlayback(); } public int getCurrentPosition(){ return videoView.getCurrentPosition(); } public IjkVideoView getVideoView(){ if (videoView != null){ return videoView; } return null; } /** * get video duration * @return */ public int getDuration(){ return videoView.getDuration(); } public PlayerManager playInFullScreen(boolean fullScreen){ if (fullScreen) { activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } return this; } public PlayerManager onError(OnErrorListener onErrorListener) { this.onErrorListener = onErrorListener; return this; } public PlayerManager onComplete(OnCompleteListener onCompleteListener) { this.onCompleteListener = onCompleteListener; return this; } public PlayerManager onInfo(OnInfoListener onInfoListener) { this.onInfoListener = onInfoListener; return this; } public PlayerManager onControlPanelVisibilityChange(OnControlPanelVisibilityChangeListener listener){ this.onControlPanelVisibilityChangeListener = listener; return this; } /** * set is live (can't seek forward) * @param isLive * @return */ public PlayerManager live(boolean isLive) { this.isLive = isLive; return this; } public PlayerManager toggleAspectRatio(){ if (videoView != null) { videoView.toggleAspectRatio(); } return this; } public interface PlayerStateListener{ void onComplete(); void onError(); void onLoading(); void onPlay(); } public interface OnErrorListener{ void onError(int what, int extra); } public interface OnCompleteListener{ void onComplete(); } public interface OnControlPanelVisibilityChangeListener{ void change(boolean isShowing); } public interface OnInfoListener{ void onInfo(int what, int extra); } }
/* * Copyright (C) 2015 Bilibili * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com> * * 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 com.example.k.bilibiliapi.video; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; import com.example.k.bilibiliapi.R; public class Settings { private Context mAppContext; private SharedPreferences mSharedPreferences; public static final int PV_PLAYER__Auto = 0; public static final int PV_PLAYER__AndroidMediaPlayer = 1; public static final int PV_PLAYER__IjkMediaPlayer = 2; public static final int PV_PLAYER__IjkExoMediaPlayer = 3; public Settings(Context context) { mAppContext = context.getApplicationContext(); mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mAppContext); } public boolean getEnableBackgroundPlay() { String key = mAppContext.getString(R.string.pref_key_enable_background_play); return mSharedPreferences.getBoolean(key, false); } public int getPlayer() { String key = mAppContext.getString(R.string.pref_key_player); String value = mSharedPreferences.getString(key, ""); try { return Integer.valueOf(value).intValue(); } catch (NumberFormatException e) { return 0; } } public boolean getUsingMediaCodec() { String key = mAppContext.getString(R.string.pref_key_using_media_codec); return mSharedPreferences.getBoolean(key, false); } public boolean getUsingMediaCodecAutoRotate() { String key = mAppContext.getString(R.string.pref_key_using_media_codec_auto_rotate); return mSharedPreferences.getBoolean(key, false); } public boolean getMediaCodecHandleResolutionChange() { String key = mAppContext.getString(R.string.pref_key_media_codec_handle_resolution_change); return mSharedPreferences.getBoolean(key, false); } public boolean getUsingOpenSLES() { String key = mAppContext.getString(R.string.pref_key_using_opensl_es); return mSharedPreferences.getBoolean(key, false); } public String getPixelFormat() { String key = mAppContext.getString(R.string.pref_key_pixel_format); return mSharedPreferences.getString(key, ""); } public boolean getEnableNoView() { String key = mAppContext.getString(R.string.pref_key_enable_no_view); return mSharedPreferences.getBoolean(key, false); } public boolean getEnableSurfaceView() { String key = mAppContext.getString(R.string.pref_key_enable_surface_view); return mSharedPreferences.getBoolean(key, false); } public boolean getEnableTextureView() { String key = mAppContext.getString(R.string.pref_key_enable_texture_view); return mSharedPreferences.getBoolean(key, false); } public boolean getEnableDetachedSurfaceTextureView() { String key = mAppContext.getString(R.string.pref_key_enable_detached_surface_texture); return mSharedPreferences.getBoolean(key, false); } public boolean getUsingMediaDataSource() { String key = mAppContext.getString(R.string.pref_key_using_mediadatasource); return mSharedPreferences.getBoolean(key, false); } public String getLastDirectory() { String key = mAppContext.getString(R.string.pref_key_last_directory); return mSharedPreferences.getString(key, "/"); } public void setLastDirectory(String path) { String key = mAppContext.getString(R.string.pref_key_last_directory); mSharedPreferences.edit().putString(key, path).apply(); } }
/* * Copyright (C) 2015 Bilibili * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com> * * 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 com.example.k.bilibiliapi.video; import android.annotation.TargetApi; import android.content.Context; import android.graphics.SurfaceTexture; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.util.Log; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import java.lang.ref.WeakReference; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import tv.danmaku.ijk.media.player.IMediaPlayer; import tv.danmaku.ijk.media.player.ISurfaceTextureHolder; public class SurfaceRenderView extends SurfaceView implements IRenderView { private MeasureHelper mMeasureHelper; public SurfaceRenderView(Context context) { super(context); initView(context); } public SurfaceRenderView(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public SurfaceRenderView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public SurfaceRenderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initView(context); } private void initView(Context context) { mMeasureHelper = new MeasureHelper(this); mSurfaceCallback = new SurfaceCallback(this); getHolder().addCallback(mSurfaceCallback); //noinspection deprecation getHolder().setType(SurfaceHolder.SURFACE_TYPE_NORMAL); } @Override public View getView() { return this; } @Override public boolean shouldWaitForResize() { return true; } //-------------------- // Layout & Measure //-------------------- @Override public void setVideoSize(int videoWidth, int videoHeight) { if (videoWidth > 0 && videoHeight > 0) { mMeasureHelper.setVideoSize(videoWidth, videoHeight); getHolder().setFixedSize(videoWidth, videoHeight); requestLayout(); } } @Override public void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen) { if (videoSarNum > 0 && videoSarDen > 0) { mMeasureHelper.setVideoSampleAspectRatio(videoSarNum, videoSarDen); requestLayout(); } } @Override public void setVideoRotation(int degree) { Log.e("", "SurfaceView doesn't support rotation (" + degree + ")!\n"); } @Override public void setAspectRatio(int aspectRatio) { mMeasureHelper.setAspectRatio(aspectRatio); requestLayout(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (this.getWidth() != 0) { setMeasuredDimension(this.getWidth(), this.getHeight()); return; } int width = getDefaultSize(0, widthMeasureSpec); int height = getDefaultSize(0, heightMeasureSpec); setMeasuredDimension(width, height); } //-------------------- // SurfaceViewHolder //-------------------- private static final class InternalSurfaceHolder implements IRenderView.ISurfaceHolder { private SurfaceRenderView mSurfaceView; private SurfaceHolder mSurfaceHolder; public InternalSurfaceHolder(@NonNull SurfaceRenderView surfaceView, @Nullable SurfaceHolder surfaceHolder) { mSurfaceView = surfaceView; mSurfaceHolder = surfaceHolder; } public void bindToMediaPlayer(IMediaPlayer mp) { if (mp != null) { if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) && (mp instanceof ISurfaceTextureHolder)) { ISurfaceTextureHolder textureHolder = (ISurfaceTextureHolder) mp; textureHolder.setSurfaceTexture(null); } mp.setDisplay(mSurfaceHolder); } } @NonNull @Override public IRenderView getRenderView() { return mSurfaceView; } @Nullable @Override public SurfaceHolder getSurfaceHolder() { return mSurfaceHolder; } @Nullable @Override public SurfaceTexture getSurfaceTexture() { return null; } @Nullable @Override public Surface openSurface() { if (mSurfaceHolder == null) return null; return mSurfaceHolder.getSurface(); } } //------------------------- // SurfaceHolder.Callback //------------------------- @Override public void addRenderCallback(IRenderCallback callback) { mSurfaceCallback.addRenderCallback(callback); } @Override public void removeRenderCallback(IRenderCallback callback) { mSurfaceCallback.removeRenderCallback(callback); } private SurfaceCallback mSurfaceCallback; private static final class SurfaceCallback implements SurfaceHolder.Callback { private SurfaceHolder mSurfaceHolder; private boolean mIsFormatChanged; private int mFormat; private int mWidth; private int mHeight; private WeakReference<SurfaceRenderView> mWeakSurfaceView; private Map<IRenderCallback, Object> mRenderCallbackMap = new ConcurrentHashMap<IRenderCallback, Object>(); public SurfaceCallback(@NonNull SurfaceRenderView surfaceView) { mWeakSurfaceView = new WeakReference<SurfaceRenderView>(surfaceView); } public void addRenderCallback(@NonNull IRenderCallback callback) { mRenderCallbackMap.put(callback, callback); ISurfaceHolder surfaceHolder = null; if (mSurfaceHolder != null) { if (surfaceHolder == null) surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder); callback.onSurfaceCreated(surfaceHolder, mWidth, mHeight); } if (mIsFormatChanged) { if (surfaceHolder == null) surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder); callback.onSurfaceChanged(surfaceHolder, mFormat, mWidth, mHeight); } } public void removeRenderCallback(@NonNull IRenderCallback callback) { mRenderCallbackMap.remove(callback); } @Override public void surfaceCreated(SurfaceHolder holder) { mSurfaceHolder = holder; mIsFormatChanged = false; mFormat = 0; mWidth = 0; mHeight = 0; ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder); for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) { renderCallback.onSurfaceCreated(surfaceHolder, 0, 0); } } @Override public void surfaceDestroyed(SurfaceHolder holder) { mSurfaceHolder = null; mIsFormatChanged = false; mFormat = 0; mWidth = 0; mHeight = 0; ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder); for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) { renderCallback.onSurfaceDestroyed(surfaceHolder); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { mSurfaceHolder = holder; mIsFormatChanged = true; mFormat = format; mWidth = width; mHeight = height; // mMeasureHelper.setVideoSize(width, height); ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder); for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) { renderCallback.onSurfaceChanged(surfaceHolder, format, width, height); } } } //-------------------- // Accessibility //-------------------- @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); event.setClassName(SurfaceRenderView.class.getName()); } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { info.setClassName(SurfaceRenderView.class.getName()); } } }
/* * Copyright (C) 2015 Bilibili * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com> * * 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 com.example.k.bilibiliapi.video; import android.content.Context; import android.support.v7.app.AlertDialog; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TableLayout; import android.widget.TextView; import com.example.k.bilibiliapi.R; public class TableLayoutBinder { private Context mContext; public ViewGroup mTableView; public TableLayout mTableLayout; public TableLayoutBinder(Context context) { this(context, R.layout.table_media_info); } public TableLayoutBinder(Context context, int layoutResourceId) { mContext = context; mTableView = (ViewGroup) LayoutInflater.from(mContext).inflate(layoutResourceId, null); mTableLayout = (TableLayout) mTableView.findViewById(R.id.table); } public TableLayoutBinder(Context context, TableLayout tableLayout) { mContext = context; mTableView = tableLayout; mTableLayout = tableLayout; } public View appendRow1(String name, String value) { return appendRow(R.layout.table_media_info_row1, name, value); } public View appendRow1(int nameId, String value) { return appendRow1(mContext.getString(nameId), value); } public View appendRow2(String name, String value) { return appendRow(R.layout.table_media_info_row2, name, value); } public View appendRow2(int nameId, String value) { return appendRow2(mContext.getString(nameId), value); } public View appendSection(String name) { return appendRow(R.layout.table_media_info_section, name, null); } public View appendSection(int nameId) { return appendSection(mContext.getString(nameId)); } public View appendRow(int layoutId, String name, String value) { ViewGroup rowView = (ViewGroup) LayoutInflater.from(mContext).inflate(layoutId, mTableLayout, false); setNameValueText(rowView, name, value); mTableLayout.addView(rowView); return rowView; } public ViewHolder obtainViewHolder(View rowView) { ViewHolder viewHolder = (ViewHolder) rowView.getTag(); if (viewHolder == null) { viewHolder = new ViewHolder(); viewHolder.mNameTextView = (TextView) rowView.findViewById(R.id.name); viewHolder.mValueTextView = (TextView) rowView.findViewById(R.id.value); rowView.setTag(viewHolder); } return viewHolder; } public void setNameValueText(View rowView, String name, String value) { ViewHolder viewHolder = obtainViewHolder(rowView); viewHolder.setName(name); viewHolder.setValue(value); } public void setValueText(View rowView, String value) { ViewHolder viewHolder = obtainViewHolder(rowView); viewHolder.setValue(value); } public ViewGroup buildLayout() { return mTableView; } public AlertDialog.Builder buildAlertDialogBuilder() { AlertDialog.Builder dlgBuilder = new AlertDialog.Builder(mContext); dlgBuilder.setView(buildLayout()); return dlgBuilder; } private static class ViewHolder { public TextView mNameTextView; public TextView mValueTextView; public void setName(String name) { if (mNameTextView != null) { mNameTextView.setText(name); } } public void setValue(String value) { if (mValueTextView != null) { mValueTextView.setText(value); } } } }
/* * Copyright (C) 2015 Bilibili * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com> * * 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 com.example.k.bilibiliapi.video; import android.annotation.TargetApi; import android.content.Context; import android.graphics.SurfaceTexture; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.util.Log; import android.view.Surface; import android.view.SurfaceHolder; import android.view.TextureView; import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import java.lang.ref.WeakReference; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import tv.danmaku.ijk.media.player.IMediaPlayer; import tv.danmaku.ijk.media.player.ISurfaceTextureHolder; import tv.danmaku.ijk.media.player.ISurfaceTextureHost; @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public class TextureRenderView extends TextureView implements IRenderView { private static final String TAG = "TextureRenderView"; private MeasureHelper mMeasureHelper; public TextureRenderView(Context context) { super(context); initView(context); } public TextureRenderView(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public TextureRenderView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public TextureRenderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initView(context); } private void initView(Context context) { mMeasureHelper = new MeasureHelper(this); mSurfaceCallback = new SurfaceCallback(this); setSurfaceTextureListener(mSurfaceCallback); } @Override public View getView() { return this; } @Override public boolean shouldWaitForResize() { return false; } @Override protected void onDetachedFromWindow() { mSurfaceCallback.willDetachFromWindow(); super.onDetachedFromWindow(); mSurfaceCallback.didDetachFromWindow(); } //-------------------- // Layout & Measure //-------------------- @Override public void setVideoSize(int videoWidth, int videoHeight) { if (videoWidth > 0 && videoHeight > 0) { mMeasureHelper.setVideoSize(videoWidth, videoHeight); requestLayout(); } } @Override public void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen) { if (videoSarNum > 0 && videoSarDen > 0) { mMeasureHelper.setVideoSampleAspectRatio(videoSarNum, videoSarDen); requestLayout(); } } @Override public void setVideoRotation(int degree) { mMeasureHelper.setVideoRotation(degree); setRotation(degree); } @Override public void setAspectRatio(int aspectRatio) { mMeasureHelper.setAspectRatio(aspectRatio); requestLayout(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mMeasureHelper.doMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(mMeasureHelper.getMeasuredWidth(), mMeasureHelper.getMeasuredHeight()); } //-------------------- // TextureViewHolder //-------------------- public IRenderView.ISurfaceHolder getSurfaceHolder() { return new InternalSurfaceHolder(this, mSurfaceCallback.mSurfaceTexture, mSurfaceCallback); } private static final class InternalSurfaceHolder implements IRenderView.ISurfaceHolder { private TextureRenderView mTextureView; private SurfaceTexture mSurfaceTexture; private ISurfaceTextureHost mSurfaceTextureHost; public InternalSurfaceHolder(@NonNull TextureRenderView textureView, @Nullable SurfaceTexture surfaceTexture, @NonNull ISurfaceTextureHost surfaceTextureHost) { mTextureView = textureView; mSurfaceTexture = surfaceTexture; mSurfaceTextureHost = surfaceTextureHost; } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public void bindToMediaPlayer(IMediaPlayer mp) { if (mp == null) return; if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) && (mp instanceof ISurfaceTextureHolder)) { ISurfaceTextureHolder textureHolder = (ISurfaceTextureHolder) mp; mTextureView.mSurfaceCallback.setOwnSurfaceTexture(false); SurfaceTexture surfaceTexture = textureHolder.getSurfaceTexture(); if (surfaceTexture != null) { mTextureView.setSurfaceTexture(surfaceTexture); } else { textureHolder.setSurfaceTexture(mSurfaceTexture); textureHolder.setSurfaceTextureHost(mTextureView.mSurfaceCallback); } } else { mp.setSurface(openSurface()); } } @NonNull @Override public IRenderView getRenderView() { return mTextureView; } @Nullable @Override public SurfaceHolder getSurfaceHolder() { return null; } @Nullable @Override public SurfaceTexture getSurfaceTexture() { return mSurfaceTexture; } @Nullable @Override public Surface openSurface() { if (mSurfaceTexture == null) return null; return new Surface(mSurfaceTexture); } } //------------------------- // SurfaceHolder.Callback //------------------------- @Override public void addRenderCallback(IRenderCallback callback) { mSurfaceCallback.addRenderCallback(callback); } @Override public void removeRenderCallback(IRenderCallback callback) { mSurfaceCallback.removeRenderCallback(callback); } private SurfaceCallback mSurfaceCallback; private static final class SurfaceCallback implements SurfaceTextureListener, ISurfaceTextureHost { private SurfaceTexture mSurfaceTexture; private boolean mIsFormatChanged; private int mWidth; private int mHeight; private boolean mOwnSurfaceTexture = true; private boolean mWillDetachFromWindow = false; private boolean mDidDetachFromWindow = false; private WeakReference<TextureRenderView> mWeakRenderView; private Map<IRenderCallback, Object> mRenderCallbackMap = new ConcurrentHashMap<IRenderCallback, Object>(); public SurfaceCallback(@NonNull TextureRenderView renderView) { mWeakRenderView = new WeakReference<TextureRenderView>(renderView); } public void setOwnSurfaceTexture(boolean ownSurfaceTexture) { mOwnSurfaceTexture = ownSurfaceTexture; } public void addRenderCallback(@NonNull IRenderCallback callback) { mRenderCallbackMap.put(callback, callback); ISurfaceHolder surfaceHolder = null; if (mSurfaceTexture != null) { if (surfaceHolder == null) surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), mSurfaceTexture, this); callback.onSurfaceCreated(surfaceHolder, mWidth, mHeight); } if (mIsFormatChanged) { if (surfaceHolder == null) surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), mSurfaceTexture, this); callback.onSurfaceChanged(surfaceHolder, 0, mWidth, mHeight); } } public void removeRenderCallback(@NonNull IRenderCallback callback) { mRenderCallbackMap.remove(callback); } @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { mSurfaceTexture = surface; mIsFormatChanged = false; mWidth = 0; mHeight = 0; ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), surface, this); for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) { renderCallback.onSurfaceCreated(surfaceHolder, 0, 0); } } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { mSurfaceTexture = surface; mIsFormatChanged = true; mWidth = width; mHeight = height; ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), surface, this); for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) { renderCallback.onSurfaceChanged(surfaceHolder, 0, width, height); } } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { mSurfaceTexture = surface; mIsFormatChanged = false; mWidth = 0; mHeight = 0; ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), surface, this); for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) { renderCallback.onSurfaceDestroyed(surfaceHolder); } Log.d(TAG, "onSurfaceTextureDestroyed: destroy: " + mOwnSurfaceTexture); return mOwnSurfaceTexture; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { } //------------------------- // ISurfaceTextureHost //------------------------- @Override public void releaseSurfaceTexture(SurfaceTexture surfaceTexture) { if (surfaceTexture == null) { Log.d(TAG, "releaseSurfaceTexture: null"); } else if (mDidDetachFromWindow) { if (surfaceTexture != mSurfaceTexture) { Log.d(TAG, "releaseSurfaceTexture: didDetachFromWindow(): release different SurfaceTexture"); surfaceTexture.release(); } else if (!mOwnSurfaceTexture) { Log.d(TAG, "releaseSurfaceTexture: didDetachFromWindow(): release detached SurfaceTexture"); surfaceTexture.release(); } else { Log.d(TAG, "releaseSurfaceTexture: didDetachFromWindow(): already released by TextureView"); } } else if (mWillDetachFromWindow) { if (surfaceTexture != mSurfaceTexture) { Log.d(TAG, "releaseSurfaceTexture: willDetachFromWindow(): release different SurfaceTexture"); surfaceTexture.release(); } else if (!mOwnSurfaceTexture) { Log.d(TAG, "releaseSurfaceTexture: willDetachFromWindow(): re-attach SurfaceTexture to TextureView"); setOwnSurfaceTexture(true); } else { Log.d(TAG, "releaseSurfaceTexture: willDetachFromWindow(): will released by TextureView"); } } else { if (surfaceTexture != mSurfaceTexture) { Log.d(TAG, "releaseSurfaceTexture: alive: release different SurfaceTexture"); surfaceTexture.release(); } else if (!mOwnSurfaceTexture) { Log.d(TAG, "releaseSurfaceTexture: alive: re-attach SurfaceTexture to TextureView"); setOwnSurfaceTexture(true); } else { Log.d(TAG, "releaseSurfaceTexture: alive: will released by TextureView"); } } } public void willDetachFromWindow() { Log.d(TAG, "willDetachFromWindow()"); mWillDetachFromWindow = true; } public void didDetachFromWindow() { Log.d(TAG, "didDetachFromWindow()"); mDidDetachFromWindow = true; } } //-------------------- // Accessibility //-------------------- @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); event.setClassName(TextureRenderView.class.getName()); } @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(TextureRenderView.class.getName()); } }
这就是所有需要的文件了。
点击此处下载编译好的so库。
在ijkplayerso
文件夹中将libs文件夹中的几个文件夹复制到自己项目的app的libs文件夹中。
布局文件
<FrameLayout
android:layout_width="400dp"
android:layout_height="300dp">
<com.example.k.bilibiliapi.video.IjkVideoView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.example.k.bilibiliapi.video.IjkVideoView>
java文件中
IjkVideoView VideoPlayer=findViewById(R.id.video_view);
VideoPlayer.setAspectRatio(IRenderView.AR_ASPECT_FIT_PARENT);
VideoPlayer.setVideoURI(Uri.parse(url));
VideoPlayer.start();
只有基本的播放,没有设置缓冲图,没有控制按钮,返回的时候没有释放资源,会一直播放停不下来。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。