package com.pukka.videoplayertv; import static android.view.KeyEvent.ACTION_DOWN; import android.annotation.TargetApi; import android.content.Context; import android.media.AudioManager; import android.media.MediaPlayer; import android.net.Uri; import android.os.Build; 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.view.ViewGroup; import android.widget.FrameLayout; import androidx.annotation.NonNull; import com.pukka.libcore.boxShared.BoxSharedParamsUtils; import com.pukka.libcore.utils.ContextUtils; import com.pukka.libcore.utils.LogUtils; import com.pukka.videoplayertv.exoplayer.PkExoMediaPlayer; import java.io.IOException; import java.lang.reflect.Method; import java.util.Map; /** * 视频播放控件 * 使用方法 * mMediaController = new TVmediaController(this, true); * mVideoView = (VideoViewTv) findViewById(R.id.video_view); * mVideoView.setVideoPath(mVideoPath); * mVideoView.setMediaController(mMediaController); * mVideoView.start(); */ public class VideoViewTv extends FrameLayout implements MediaPlayerControl { private static final String TAG = "VideoViewTv"; /** * 本次 APK 运行期间是否已经设置过默认音量 * 用途:控制只在首次进入视频播放时设置默认音量(30%),后续切换分集、切换影片不再重置。 * 进程重启(APK 重启)后静态变量自动重置为 false,从而恢复"首次进入详情设置默认音量"的流程。 * 不使用 SharedPreferences:因为需求要求 APK 重启后重新走首次设置流程,需内存级标志位。 */ private static boolean sDefaultVolumeApplied = false; // 播放视频路径 private Uri mUri; // 媒体视频headers private Map mHeaders; // 播放器可能的状态 // 播放器出错状态 public static final int STATE_ERROR = -1; // 闲置状态 public static final int STATE_IDLE = 0; // 准备状态 public static final int STATE_PREPARING = 1; // 准备结束状态 public static final int STATE_PREPARED = 2; // 播放状态 public static final int STATE_PLAYING = 3; // 暂停状态 public static final int STATE_PAUSED = 4; // 播放结束状态 public 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 // 播放的surfaceholder接口 private IRenderView.ISurfaceHolder mSurfaceHolder = null; // 播放器接口 private IMediaPlayer mMediaPlayer = null; // 视频宽度 private int mVideoWidth; // 视频高度 private int mVideoHeight; // surface宽度 private int mSurfaceWidth; // surface高度 private int mSurfaceHeight; // 视频控制器 private IMediaController mMediaController; // 视频播放完成的监听 private IMediaPlayer.OnCompletionListener mOnCompletionListener; // 视频准备结束的监听 private IMediaPlayer.OnPreparedListener mOnPreparedListener; private int mCurrentBufferPercentage; // 缓冲 private IMediaPlayer.OnBufferingUpdateListener mOnBufferingUpdateListener; // 视频播放过程中出错的监听 private IMediaPlayer.OnErrorListener mOnErrorListener; // 视频信息列表的监听 private IMediaPlayer.OnInfoListener mOnInfoListener; // recording the seek position while preparing private int mSeekWhenPrepared; // 暂停 private boolean mCanPause = true; // 后退 private boolean mCanSeekBack = true; // 前进 private boolean mCanSeekForward = true; // 视频呈现方式 private int mCurrentAspectRatio = IRenderView.AR_ASPECT_WRAP_CONTENT; // 播放视频VIEW private IRenderView mRenderView; private int mVideoSarNum; private int mVideoSarDen; // 直播[1]和点播[0]的区分 private int isLive = 0; // 判断是否是软终端,选择不同的播放器 0是常规 , 1是软终端 2是ExoMediaPlayer private int terminalType = 2; /** * 全屏模式 **/ public static final int MODE_FULL_SCREEN = 11; /** * 小窗口模式 **/ public static final int MODE_TINY_WINDOW = 12; /** * 普通模式 */ public static final int MODE_NORMAL = 13; private int mCurrentMode = MODE_NORMAL; public IOnPauseListener onPauseListener; public IOnResumeListener onResumeListener; /** * 是否是快进 */ private boolean isSeekForward; public boolean isSeekForward() { return isSeekForward; } /** * 首次播放的seek播放进度(比如说release 播放器以后要播放到指定position) */ private int firstPlayPosition; public void setFirstPlayPosition(int firstPlayPosition) { this.firstPlayPosition = firstPlayPosition; } public IMediaPlayer getMediaPlayer() { LogUtils.d("getMediaPlayer: "+mMediaPlayer.toString()); return mMediaPlayer; } /** * 设置成全屏播放模式 */ public void setModeFullScreen() { setmCurrentAspectRatio(IRenderView.AR_ASPECT_FIT_PARENT); this.mCurrentMode = MODE_FULL_SCREEN; // setRenderView(); FrameLayout.LayoutParams layoutParams_txt = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, Gravity.TOP); this.setLayoutParams(layoutParams_txt); } /** * 设置成全屏播放模式 * * @param width 全屏时的width * @param height 全屏时的height */ public void setModeFullScreen(int width, int height) { setmCurrentAspectRatio(IRenderView.AR_ASPECT_FIT_PARENT); this.mCurrentMode = MODE_FULL_SCREEN; mRenderView.setVideoSize(width > 0 ? width : mVideoWidth, height > 0 ? height : mVideoHeight); } /** * 设置成小屏播放模式 * * @param width 小屏时的width * @param height 小屏时的height */ public void setModeTinyScreen(int width, int height) { setmCurrentAspectRatio(IRenderView.AR_ASPECT_FIT_PARENT); this.mCurrentMode = MODE_TINY_WINDOW; LogUtils.d("smm -- ", "setModeTinyScreen width:"+width + " height:"+height); mRenderView.setVideoSize(width, height); setFocusable(false); // 隐藏播控 if (null != mMediaController) { mMediaController.setMediaPlayer(null); mMediaController.hide(); mMediaController = null; } } public void setmCurrentMode(int mCurrentMode) { this.mCurrentMode = mCurrentMode; } /** * 是否是全屏模式 * * @return */ public boolean isFullScreen() { return this.mCurrentMode == MODE_FULL_SCREEN; } /** * 设置成小屏播放模式,且隐藏播控 * * @param width 视频控件 宽 * @param height 视频控件 高 * @param left 左侧偏移量 * @param top 头部偏移量 */ public void setModeTinyWindow(int width, int height, int left, int top) { setmCurrentAspectRatio(IRenderView.AR_ASPECT_FIT_PARENT); this.mCurrentMode = MODE_TINY_WINDOW; FrameLayout.LayoutParams layoutParams_txt = new FrameLayout.LayoutParams(width, height, Gravity.TOP); layoutParams_txt.setMarginStart(left); layoutParams_txt.topMargin = top; setLayoutParams(layoutParams_txt); setFocusable(false); // 隐藏播控 if (null != mMediaController) { mMediaController.hide(); } } public void setTerminalType(int terminalType) { this.terminalType = terminalType; } public int getTerminalType() { return terminalType; } public void switchTerminal(int terminalType, String path) { this.terminalType = terminalType; mMediaPlayer.release(); mMediaPlayer = null; initVideoView(); setVideoPath(path); } /** * 获取播放器状态 */ public int getCurrentState() { return mCurrentState; } /** * 构造函数 */ public VideoViewTv(Context context) { super(context); initVideoView(); } /** * 构造函数 */ public VideoViewTv(Context context, AttributeSet attrs) { super(context, attrs); initVideoView(); } /** * 构造函数 */ public VideoViewTv(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initVideoView(); } private void initVideoView() { // Android 4.2 兼容:ExoPlayer 2.19 官方支持 API 19+,低版本强制切系统 MediaPlayer 内核, // 避免 PkExoMediaPlayer/ExoPlayer 类在 Dalvik 上触发 VerifyError if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { terminalType = 0; } if (!checkAudioCapability()){ terminalType = 0; } if (BoxSharedParamsUtils.getInstance().getModel().equals("NL_MTK9255_D1E8_RG_828_RX50")){ //这个型号设备Guava库版本过低,使用exo会崩溃 terminalType = 0; } setRenderView(); mVideoWidth = 0; mVideoHeight = 0; mCurrentState = STATE_IDLE; mTargetState = STATE_IDLE; } private boolean checkAudioCapability() { LogUtils.d("Audio", "getModel:" +BoxSharedParamsUtils.getInstance().getModel()); try { // 1. 先检查类是否存在 Class audioCapClass = Class.forName("com.google.android.exoplayer2.audio.AudioCapabilities"); // 2. 检查方法是否存在 Method getCapabilitiesMethod = audioCapClass.getDeclaredMethod("getCapabilities", Context.class); // 3. 调用方法 Object result = getCapabilitiesMethod.invoke(null, getContext()); return true; } catch (ClassNotFoundException e) { LogUtils.d("Audio", "AudioCapabilities类不存在"); return false; } catch (NoSuchMethodException e) { LogUtils.d("Audio", "getCapabilities方法不存在"); return false; } catch (Throwable t) { LogUtils.d("Audio", "其他错误:"+ t); return false; } } /** * 设置RenderView */ public void setRenderView() { LogUtils.d("smm -- ", "setRenderView"); SurfaceRenderView renderView = new SurfaceRenderView(getContext()); // 修改原视图 if (mRenderView != null) { if (mMediaPlayer != null) { mMediaPlayer.setDisplay(null); } LogUtils.d("smm --- mid -- ", "setRenderView"); View renderUIView = mRenderView.getView(); mRenderView.removeRenderCallback(mSHCallback); mRenderView = null; removeView(renderUIView); mSurfaceHolder = null; } if (renderView == null) { return; } mRenderView = renderView; // 设置视频显示方式 renderView.setAspectRatio(mCurrentAspectRatio); LogUtils.d("mCurrentAspectRatio=" + mCurrentAspectRatio); if (mVideoWidth > 0 && mVideoHeight > 0) { renderView.setVideoSize(mVideoWidth, mVideoHeight); } if (mVideoSarNum > 0 && mVideoSarDen > 0) { renderView.setVideoSampleAspectRatio(mVideoSarNum, mVideoSarDen); } LogUtils.d("mVideoHeight=" + mVideoHeight); View renderUIView = mRenderView.getView(); LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, Gravity.CENTER); LogUtils.d("LP -- width : " + lp.width + " height : " + lp.height); renderUIView.setLayoutParams(lp); // 将SurfaceRenderView添加到这个自定义控件中 addView(renderUIView); mRenderView.addRenderCallback(mSHCallback); LogUtils.d("smm --- end -- ", "setRenderView"); } public void setVolume(float leftVolume, float rightVolume) { LogUtils.d("setVolume() + leftVolume: " + leftVolume + " rightVolume: " + rightVolume + " mMediaPlayer= " + mMediaPlayer); if (mMediaPlayer != null) { mMediaPlayer.setVolume(leftVolume, rightVolume); } } /** * 打开视频 */ @TargetApi(Build.VERSION_CODES.M) private void openVideo() { LogUtils.d("openVideo() + uri : " + mUri); if (mMediaPlayer != null) { LogUtils.d("mMediaPlayer is null"); return; } if (mUri == null || mSurfaceHolder == null) { // not ready for playback just yet, will try again later LogUtils.d("mSurfaceHolder is null"); return; } // we shouldn't com.pukka.common_lib.clear the target state, because somebody might have // called start() previously // 重复的销毁播放器,把销毁播放放到epg来做 // release(false); // releaseError(true); // 音频管理器 AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); if (am != null) { // 仅在本次 APK 运行期间首次进入视频播放时设置默认音量(30%), // 后续切换分集、切换影片保持用户当前音量不变; // APK 重启后 sDefaultVolumeApplied 静态标志位自动重置,重新走首次设置流程 if (!sDefaultVolumeApplied) { int maxVolume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC); // 使用 Math.round 四舍五入,寻找最接近 10% 的真实硬件阶数(避免向下取整导致偏差过大) int targetVolume = Math.round(maxVolume * 0.1f); am.setStreamVolume(AudioManager.STREAM_MUSIC, targetVolume, 0); sDefaultVolumeApplied = true; LogUtils.d("openVideo() set STREAM_MUSIC volume to 30%: " + targetVolume + " / " + maxVolume); } else { LogUtils.d("openVideo() keep current STREAM_MUSIC volume, default already applied this session"); } } int audioStatus = am.requestAudioFocus(focusChange -> LogUtils.d("onAudioFocusChange --XX : " + focusChange), AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); LogUtils.d("requestAudioFocus -- : " + audioStatus); try { LogUtils.d("smm -- ", "openvideo -- create"); // 获得一个视频管理器 mMediaPlayer = createPlayer(); LogUtils.d("mMediaPlayer -- : " + mMediaPlayer); if (mMediaPlayer != null) { // a context for the subtitle renderers // REMOVED: SubtitleController // REMOVED: mAudioSession // 注册监听事件 mMediaPlayer.setOnPreparedListener(mPreparedListener); mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener); mMediaPlayer.setOnCompletionListener(mCompletionListener); mMediaPlayer.setOnErrorListener(mErrorListener); mMediaPlayer.setOnInfoListener(mInfoListener); mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener); mMediaPlayer.setOnSeekCompleteListener(seekCompleteListener); mCurrentBufferPercentage = 0; LogUtils.d("mMediaPlayer -- setOn over : "); mMediaPlayer.setDataSource(getContext(), mUri, mHeaders); LogUtils.d("smm -00- setDataSource url is -- :" + mUri); bindSurfaceHolder(mMediaPlayer, mSurfaceHolder); mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mMediaPlayer.setScreenOnWhilePlaying(true); mMediaPlayer.prepareAsync(); LogUtils.d("prepareAsync ---- start----"); /* * 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(); } else { LogUtils.d("mMediaPlayer ---- is null----"); } } 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); } catch (IllegalStateException ex) { ex.printStackTrace(); Log.w(TAG, "IllegalStateException: " + mUri, ex); } catch (NullPointerException ex) { ex.printStackTrace(); Log.w(TAG, "NullPointerException: " + ex); } finally { // REMOVED: mPendingSubtitleTracks.com.pukka.common_lib.clear(); } } /*** * 根据配置创建一个播放器 */ // @UnstableApi public IMediaPlayer createPlayer() { LogUtils.i("createPlayer -- start -- new "); IMediaPlayer mediaPlayer; // 1为软终端 采用杭研播放器 if (terminalType == 1) { LogUtils.d("SoftMediaPlayer() terminalType: "+terminalType); mediaPlayer = new SoftMediaPlayer(getContext()); }else if(terminalType == 2){ LogUtils.d("PkExoMediaPlayer() terminalType: "+terminalType); mediaPlayer = new PkExoMediaPlayer(ContextUtils.getContext()); } else { LogUtils.d("AndroidMediaPlayer() terminalType: "+terminalType); mediaPlayer = new AndroidMediaPlayer(); } LogUtils.i("createPlayer -- end"); return mediaPlayer; } /** * release the media player in any state * 复位播放器,释放播放器资源 * * @param clearTargetState clearTargetState **/ public void release(boolean clearTargetState) { LogUtils.i("release"); try { if (mMediaPlayer != null) { LogUtils.i("release---------start---------1" + clearTargetState); mCurrentState = STATE_IDLE; mMediaPlayer.setDisplay(null); // mMediaPlayer.setSurface(null); pause(); reset(); mMediaPlayer.release(); if(mMediaPlayer != null){ // terminalType 短路:非 Exo 内核时跳过 instanceof,避免 Dalvik 解析 // PkExoMediaPlayer 引发的 ExoPlayer 类加载 VerifyError(Android 4.2) if(terminalType == 2 && mMediaPlayer instanceof PkExoMediaPlayer){ PkExoMediaPlayer pkExoMediaPlayer = (PkExoMediaPlayer)mMediaPlayer; pkExoMediaPlayer.resetListeners(); } } mMediaPlayer = null; mUri = null; mVideoWidth = 0; mVideoHeight = 0; } } catch (Exception e) { releaseError(clearTargetState); e.printStackTrace(); } } public void releaseError(boolean clearTargetState) { try { if (null != mMediaPlayer) { mMediaPlayer = null; mCurrentState = STATE_IDLE; mUri = null; if (clearTargetState) { mTargetState = STATE_IDLE; } AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); am.abandonAudioFocus(focusChange -> LogUtils.d("onAudioFocusChange --YY : " + focusChange)); LogUtils.i("release---------end ----"); } } catch (Exception e) { e.printStackTrace(); } } /** * 复位播放器 */ public void reset() { try { if (mMediaPlayer != null) { mMediaPlayer.reset(); mCurrentState = STATE_IDLE; mTargetState = STATE_IDLE; } } catch (Exception e) { e.printStackTrace(); } } /** * 关联IjkVideoView与mediaController */ private void attachMediaController() { if (this.mCurrentMode == MODE_FULL_SCREEN || this.mCurrentMode == MODE_NORMAL) { if (mMediaPlayer != null && mMediaController != null) { // 将ijkvideoview对象传递给controller的成员变量 mMediaController.setMediaPlayer(this); // View anchorView = this.getParent() instanceof View ? // (View) this.getParent() : this; ViewGroup anchorView = this.getParent() instanceof ViewGroup ? (ViewGroup) this.getParent() : this; // ViewGroup anchorView = this; // 把控制条附加到播放视频的SurfaceView上 mMediaController.setAnchorView(anchorView); mMediaController.setEnabled(isInPlaybackState()); } } } /** * 为VideoView设置MediaController **/ public void setMediaController(IMediaController controller) { if (mMediaController != null) { mMediaController.hide(); } // 将AndroidMediaController对象赋值给IjkVideoView类的成员变量 mMediaController = controller; attachMediaController(); } public void setTouchModeEnabled(boolean isTouchMode) { if (mMediaController instanceof TvMediaController) { ((TvMediaController) mMediaController).setTouchModeEnabled(isTouchMode); } } /** * 判断是否可以进行播放 */ private boolean isInPlaybackState() { return (mMediaPlayer != null && mCurrentState != STATE_ERROR && mCurrentState != STATE_IDLE && mCurrentState != STATE_PREPARING); } /** * 判断是否可以获取总时长,当前时间等 */ private boolean isInOperation() { return (mMediaPlayer != null && mCurrentState != STATE_ERROR && mCurrentState != STATE_IDLE && mCurrentState != STATE_PREPARING && mCurrentState != STATE_PLAYBACK_COMPLETED); } /** * 判断是否可以快进 */ private boolean isInffwdState() { return (mMediaPlayer != null && mCurrentState != STATE_ERROR && mCurrentState != STATE_IDLE && mCurrentState != STATE_PREPARING && mCurrentState != STATE_PLAYBACK_COMPLETED); } /** * 设置视频路径 * * @param path the path of the video. */ public void setVideoPath(String path) { if(BoxSharedParamsUtils.getInstance().isNeed2http()) { String videoUrl = path.replace("https://", "http://"); setVideoURI(Uri.parse(videoUrl)); }else { setVideoURI(Uri.parse(path)); } } /** * 停止播放 释放播放资源 */ public void stopPlayback() { try { if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { mMediaPlayer.stop(); mMediaPlayer.release(); mMediaPlayer.reset(); mMediaPlayer = null; mCurrentState = STATE_IDLE; mTargetState = STATE_IDLE; AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); am.abandonAudioFocus(null); } } catch (Exception e) { e.printStackTrace(); } } /** * 设置视频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 headers) { mUri = uri; mHeaders = headers; mSeekWhenPrepared = 0; // 打开视频 openVideo(); // 重新绘制画面 requestLayout(); // 重新绘制画面 invalidate(); } public void setVideoURIFull(Uri uri, Map headers) { LogUtils.d("uri --YY : " + uri.toString()); mUri = uri; mHeaders = headers; mSeekWhenPrepared = 0; // 打开视频 // openVideo(); // 重新绘制画面 requestLayout(); // 重新绘制画面 invalidate(); } /** * 设置 */ // ***************************按键事件响应**********************************************// @Override public boolean dispatchKeyEvent(KeyEvent event) { return super.dispatchKeyEvent(event); } // ***************************按键事件响应**********************************************// // ************************* 画布Callback **********************************// /** * surface发生变化, 执行 */ IRenderView.IRenderCallback mSHCallback = new IRenderView.IRenderCallback() { @Override public void onSurfaceChanged(@NonNull IRenderView.ISurfaceHolder holder, int format, int w, int h) { LogUtils.d("onSurfaceChanged holder : " + holder); LogUtils.d("onSurfaceChanged : " + w + " height : " + h); if (holder.getRenderView() != mRenderView) { LogUtils.e("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); } LogUtils.d("onSurfaceChanged()"); // 添加一个判断,如果已经在播放了,不要重复起播 -- 21/10/12 // if(mCurrentState != STATE_PLAYING){ LogUtils.d("start -- 22 --"); start(); // } // if (mMediaController != null) { // mMediaController.show(); // LogUtils.d(" mMediaController.show()"); // } } } @Override public void onSurfaceCreated(@NonNull IRenderView.ISurfaceHolder holder, int width, int height) { LogUtils.d("onSurfaceCreated holder : " + holder); LogUtils.d("onSurfaceCreated : " + width + " height : " + height); if (holder.getRenderView() != mRenderView) { LogUtils.e("onSurfaceCreated: unmatched render callback\n"); return; } mSurfaceHolder = holder; if (mMediaPlayer != null) { LogUtils.d("bindSurfaceHolder : 00 "); bindSurfaceHolder(mMediaPlayer, holder); } else { LogUtils.d("openVideo : 00 "); openVideo(); } } @Override public void onSurfaceDestroyed(@NonNull IRenderView.ISurfaceHolder holder) { LogUtils.d("onSurfaceDestroyed : 00 " + holder); if (holder.getRenderView() != mRenderView) { LogUtils.e("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.stop(); mMediaPlayer.setDisplay(null); } } private void bindSurfaceHolder(IMediaPlayer mp, IRenderView.ISurfaceHolder holder) { if (mp == null) { LogUtils.d("bindSurfaceHolder -- mp -- " + mp); return; } if (holder == null) { LogUtils.d("bindSurfaceHolder -- holder -- " + holder); mp.setDisplay(null); return; } holder.bindToMediaPlayer(mp); } // ************************* 画布Callback **********************************// // ******************************播放器Callbak*****************************************// IMediaPlayer.OnVideoSizeChangedListener mSizeChangedListener = new IMediaPlayer.OnVideoSizeChangedListener() { public void onVideoSizeChanged(IMediaPlayer mp, int width, int height, int sarNum, int sarDen) { LogUtils.d("OnVideoSizeChangedListener --- IMediaPlayer = " + mp); try { if (mp != null) { mVideoWidth = mp.getVideoWidth(); mVideoHeight = mp.getVideoHeight(); mVideoSarNum = mp.getVideoSarNum(); mVideoSarDen = mp.getVideoSarDen(); LogUtils.d( "OnVideoSizeChangedListener --- mVideoWidth = " + mVideoWidth + "-- mVideoHeight -- : " + mVideoHeight + "-- mVideoSarNum -- : " + mVideoSarNum + "-- mVideoSarDen -- : " + mVideoSarDen); if (mVideoWidth != 0 && mVideoHeight != 0) { if (mRenderView != null) { mRenderView.setVideoSize(mVideoWidth, mVideoHeight); mRenderView.setVideoSampleAspectRatio(mVideoSarNum, mVideoSarDen); } // REMOVED: getHolder().setFixedSize(mVideoWidth, mVideoHeight); LogUtils.d("OnVideoSizeChangedListener()"); requestLayout(); } } } catch (Exception e) { e.printStackTrace(); LogUtils.e("OnVideoSizeChangedListener --- IMediaPlayer = " + mp); } } }; IMediaPlayer.OnPreparedListener mPreparedListener = new IMediaPlayer.OnPreparedListener() { public void onPrepared(IMediaPlayer mp) { try { LogUtils.d("准备好了,开始播放了。"); mCurrentState = STATE_PREPARED; mTargetState = STATE_PLAYING; // Get the capabilities of the player for this stream // REMOVED: Metadata if (mOnPreparedListener != null) { mOnPreparedListener.onPrepared(mMediaPlayer); } if (ConfigPlayer.getInstance().isNeedStartFirstFrame()) { if (mOnInfoListener != null) { mOnInfoListener.onInfo(mMediaPlayer, 3, 0); } } if (mMediaController != null) { mMediaController.setEnabled(true); } mVideoWidth = mp.getVideoWidth(); mVideoHeight = mp.getVideoHeight(); LogUtils.d( "onPrepared --- mVideoWidth -- :" + mVideoWidth + "-- mVideoHeight -- :" + mVideoHeight); int seekToPosition = 0; if (firstPlayPosition != 0) { seekTo(firstPlayPosition); setFirstPlayPosition(0); } else { // 定位到上次播放的位置 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); // mRenderView.setVideoRotation(90); // Log.i("wxf videowidth",mVideoWidth+" | "+mVideoHeight); // Log.i("wxf videosarnum","mVideoSarNum="+mVideoSarNum +" mVideoSarDen="+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) { LogUtils.d("start -- 11 --"); start(); // if (mMediaController != null) { // if (null != mMediaController && mCurrentMode != MODE_TINY_WINDOW) { // mMediaController.show(); // } // } } else if (!isPlaying() && (seekToPosition != 0 || getCurrentPosition() > 0)) { // if (mMediaController != null && mCurrentMode != MODE_TINY_WINDOW) { // // 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) { LogUtils.d("start -- 00 --"); start(); // if (mMediaController != null) { // mMediaController.show(); // LogUtils.d( "toggleMediaControlsVisiblity()"); // } } } } catch (Exception e) { e.printStackTrace(); LogUtils.e("OnVideoSizeChangedListener --- IMediaPlayer = " + mp); } } }; 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); } // LogUtils.d( "mediaInfo - arg1: " + arg1); // LogUtils.d( "mediaInfo - arg2: " + arg2); // switch (arg1) { // case IMediaPlayer.MEDIA_INFO_VIDEO_TRACK_LAGGING: // LogUtils.d( "MEDIA_INFO_VIDEO_TRACK_LAGGING:"); // break; // case IMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START: // LogUtils.d( "MEDIA_INFO_VIDEO_RENDERING_START:"); // break; // case IMediaPlayer.MEDIA_INFO_BUFFERING_START: // LogUtils.d( "MEDIA_INFO_BUFFERING_START:"); // break; // case IMediaPlayer.MEDIA_INFO_BUFFERING_END: // LogUtils.d( "MEDIA_INFO_BUFFERING_END:"); // break; // case IMediaPlayer.MEDIA_INFO_NETWORK_BANDWIDTH: // LogUtils.d( "MEDIA_INFO_NETWORK_BANDWIDTH: " + arg2); // break; // case IMediaPlayer.MEDIA_INFO_BAD_INTERLEAVING: // LogUtils.d( "MEDIA_INFO_BAD_INTERLEAVING:"); // break; // case IMediaPlayer.MEDIA_INFO_NOT_SEEKABLE: // LogUtils.d( "MEDIA_INFO_NOT_SEEKABLE:"); // break; // case IMediaPlayer.MEDIA_INFO_METADATA_UPDATE: // LogUtils.d( "MEDIA_INFO_METADATA_UPDATE:"); // break; // case IMediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE: // LogUtils.d( "MEDIA_INFO_UNSUPPORTED_SUBTITLE:"); // break; // case IMediaPlayer.MEDIA_INFO_SUBTITLE_TIMED_OUT: // LogUtils.d( "MEDIA_INFO_SUBTITLE_TIMED_OUT:"); // break; // case IMediaPlayer.MEDIA_INFO_VIDEO_ROTATION_CHANGED: // //mVideoRotationDegree = arg2; // LogUtils.d( "MEDIA_INFO_VIDEO_ROTATION_CHANGED: " + arg2); // if (mRenderView != null) // mRenderView.setVideoRotation(arg2); // break; // case IMediaPlayer.MEDIA_INFO_AUDIO_RENDERING_START: // LogUtils.d( "MEDIA_INFO_AUDIO_RENDERING_START:"); // break; // default: // // throw new IllegalStateException("Unexpected value: " + arg1); // } return true; } }; private IMediaPlayer.OnErrorListener mErrorListener = new IMediaPlayer.OnErrorListener() { public boolean onError(IMediaPlayer mp, int framework_err, int impl_err) { LogUtils.d("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 (impl_err == 3516 || impl_err == 3515) { // 组播切单播,组播播放报错,切单播 framework_err = impl_err; } 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 = getContext().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; LogUtils.d("onBufferingUpdate ---- : "+percent); // if (mOnBufferingUpdateListener != null) { // mOnBufferingUpdateListener.onBufferingUpdate(mp, percent); // } } }; private IMediaPlayer.OnSeekCompleteListener seekCompleteListener = new IMediaPlayer.OnSeekCompleteListener() { @Override public void onSeekComplete(IMediaPlayer mp) { } }; // 暂停 public interface IOnPauseListener { public void onPause(); } // 继续播放 public interface IOnResumeListener { public void onResume(); } // ************************************播放器Callbak***********************************// /// ** 媒体控制器 MediaPlayerControl **//// /** * MediaPlayerControl的start() 方法 * 播放 */ @Override public void start() { LogUtils.d( "start: " + " mMediaPlayer != null " + (mMediaPlayer != null) + ": mCurrentState: " + mCurrentState); if (isInPlaybackState()) { // LogUtils.d("点击了播放 : " + mCurrentState); mMediaPlayer.start(); mCurrentState = STATE_PLAYING; LogUtils.d("start()"); // if (null != mMediaController && mCurrentMode != MODE_TINY_WINDOW) { // mMediaController.show(); // } } mTargetState = STATE_PLAYING; } /** * MediaPlayerControl的pause() 方法 * 暂定 */ @Override public void pause() { try { if (isInPlaybackState()) { if (mMediaPlayer.isPlaying()) { LogUtils.d("pause : "); mMediaPlayer.pause(); mCurrentState = STATE_PAUSED; } } } catch (Exception e) { e.printStackTrace(); } mTargetState = STATE_PAUSED; // mMediaController.hide(); // if (null != mMediaController) { // mMediaController.show(0); // } } /** * MediaPlayerControl的pause() 方法 * 暂定 */ public void pause(boolean isShow) { if (isInPlaybackState()) { if (mMediaPlayer.isPlaying()) { mMediaPlayer.pause(); mCurrentState = STATE_PAUSED; } } mTargetState = STATE_PAUSED; // mMediaController.hide(); if (isShow) { mMediaController.show(0); } } /** * MediaPlayerControl的getDuration方法 * 时长 */ @Override public int getDuration() { /** * 会有出现一直error -38 报错的情况, * Attempt to call getDuration without a valid mediaplayer * 改 isInPlaybackState 为 isInOperation */ if (isInOperation()) { // if(isLive == 1){ // return 14400 * 1000; // } LogUtils.d("getDuration:" + mCurrentState); return (int) mMediaPlayer.getDuration(); } return -1; } /** * MediaPlayerControl的getCurrentPosition方法 * 播放位置 */ @Override public int getCurrentPosition() { if (isInOperation()) { // if(isLive == 1){ // return 14400 * 1000; // } LogUtils.d("getCurrentPosition:" + mCurrentState); return (int) mMediaPlayer.getCurrentPosition(); } return 0; } /** * 获取对应播放进度 * * @return 返回进度 * @throws Exception 获取异常 */ public long getMediaPlayerCurrentPosition() throws Exception { return mMediaPlayer.getCurrentPosition(); } /** * MediaPlayerControl的seekTo方法 * 播放位置 */ @Override public void seekTo(int msec) { try { if (isInPlaybackState()) { LogUtils.d("seekTo : " + mCurrentState); Log.i("wxf", "seekto====" + msec); mMediaPlayer.seekTo(msec); mSeekWhenPrepared = 0; } else { mSeekWhenPrepared = msec; } } catch (Exception e) { e.printStackTrace(); } } public void seekTo(int msec,boolean isPause) { try { if (isInPlaybackState()) { LogUtils.d("seekTo : " + mCurrentState); Log.i("wxf", "seekto====" + msec); mMediaPlayer.pause(); mMediaPlayer.seekTo(msec); mSeekWhenPrepared = 0; } else { mSeekWhenPrepared = msec; } } catch (Exception e) { e.printStackTrace(); } } public boolean isSwitchDirectionEnable() { if (mCurrentState == STATE_PLAYING || mCurrentState == STATE_PREPARING || mCurrentState == STATE_PREPARED) { return true; } return false; } /** * MediaPlayerControl的isPlaying()方法 * 判断是否正在播放 */ @Override public boolean isPlaying() { try { return isInPlaybackState() && mMediaPlayer.isPlaying(); } catch (Exception e) { e.printStackTrace(); } return false; } /** * MediaPlayerControl的getBufferPercentage方法 * 缓冲 */ @Override public int getBufferPercentage() { if (mMediaPlayer != null) { return mCurrentBufferPercentage; } return 0; } /*** * 不销毁播放器,切换到另一个频道 */ public void switchChannel(String channelUrl) { if (mMediaPlayer != null) { try { // mMediaPlayer.stop(); mMediaPlayer.reset(); mUri = Uri.parse(channelUrl); mMediaPlayer.setOnPreparedListener(mPreparedListener); mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener); mMediaPlayer.setOnCompletionListener(mCompletionListener); mMediaPlayer.setOnErrorListener(mErrorListener); mMediaPlayer.setOnInfoListener(mInfoListener); mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener); mMediaPlayer.setOnSeekCompleteListener(seekCompleteListener); mCurrentBufferPercentage = 0; mMediaPlayer.setDataSource(getContext(), mUri, mHeaders); bindSurfaceHolder(mMediaPlayer, mSurfaceHolder); mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mMediaPlayer.setScreenOnWhilePlaying(true); 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(); // if (null != mMediaController && mCurrentMode !=MODE_TINY_WINDOW) { // mMediaController.show(); // } } catch (IOException e) { e.printStackTrace(); mCurrentState = STATE_ERROR; mTargetState = STATE_ERROR; mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); } } } /** * 判断是否可以暂停 */ @Override public boolean canPause() { return mCanPause; } /** * 判断是否可以倒退 */ @Override public boolean canSeekBackward() { return mCanSeekBack; } /** * 判断是否可以前进 */ @Override public boolean canSeekForward() { return mCanSeekForward; } /** * 获取当前模式 */ @Override public int getCurrentMode() { return mCurrentMode; } public void setCurrentMode(int mCurrentMode) { this.mCurrentMode = mCurrentMode; } /** * 设置视频显示方式 **/ public void setmCurrentAspectRatio(int mCurrentAspectRatio) { this.mCurrentAspectRatio = mCurrentAspectRatio; if (mRenderView != null) { mRenderView.setAspectRatio(mCurrentAspectRatio); } } public void setmVideoWidth(int mVideoWidth) { this.mVideoWidth = mVideoWidth; } public void setmVideoHeight(int mVideoHeight) { Log.d("onSurfaceChanged -- ", "setmVideoHeight -- " + mVideoHeight); this.mVideoHeight = mVideoHeight; // setLayoutParams(new RelativeLayout.LayoutParams(mVideoWidth,this.mVideoWidth)); // if (mRenderView != null) { // mRenderView.setVideoSize(mVideoWidth, mVideoHeight); // } // requestLayout(); // //重新绘制画面 // invalidate(); } /** * 设置播放器的位置和宽高并刷新重新绘制画面 */ public void setVideoPositionSize() { // if (mRenderView != null) { // mRenderView.setVideoSize(mVideoWidth, mVideoHeight); // } requestLayout(); // 重新绘制画面 invalidate(); } /** * 区分是直播还是点播 * 直播 1 点播 0 */ public void setIsLive(int isLive) { this.isLive = isLive; } /// ** 媒体控制器 MediaPlayerControl **//// public void setmOnCompletionListener(IMediaPlayer.OnCompletionListener mOnCompletionListener) { this.mOnCompletionListener = mOnCompletionListener; } public void setmSizeChangedListener(IMediaPlayer.OnVideoSizeChangedListener mSizeChangedListener) { this.mSizeChangedListener = mSizeChangedListener; } public void setmPreparedListener(IMediaPlayer.OnPreparedListener mPreparedListener) { this.mOnPreparedListener = mPreparedListener; } public void setmCompletionListener(IMediaPlayer.OnCompletionListener mCompletionListener) { this.mCompletionListener = mCompletionListener; } public void setmInfoListener(IMediaPlayer.OnInfoListener mInfoListener) { this.mOnInfoListener = mInfoListener; } public void setmErrorListener(IMediaPlayer.OnErrorListener mErrorListener) { this.mErrorListener = mErrorListener; } public void setmBufferingUpdateListener(IMediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener) { this.mBufferingUpdateListener = mBufferingUpdateListener; } public void setmSeekListener(IMediaPlayer.OnSeekCompleteListener seekListener) { this.seekCompleteListener = seekListener; } public void setIOnPauseListener(IOnPauseListener onPauseListener) { this.onPauseListener = onPauseListener; } public void setIOnResumeListener(IOnResumeListener onResumeListener) { this.onResumeListener = onResumeListener; } public void removeListener(){ LogUtils.d("removeListener() ===="); mOnCompletionListener = null; mSizeChangedListener = null; mOnPreparedListener = null; mCompletionListener = null; mOnInfoListener = null; mErrorListener = null; mBufferingUpdateListener = null; seekCompleteListener = null; onPauseListener = null; onResumeListener = null; if(mMediaPlayer != null){ // terminalType 短路:同上,避免非 Exo 内核时 Dalvik 触发 PkExoMediaPlayer 类解析 if(terminalType == 2 && mMediaPlayer instanceof PkExoMediaPlayer){ PkExoMediaPlayer pkExoMediaPlayer = (PkExoMediaPlayer)mMediaPlayer; pkExoMediaPlayer.resetListeners(); } } } /** * 获取当前mediaplayer */ public IMediaPlayer getMediaPlayerName() { if (mMediaPlayer != null) { return mMediaPlayer; } return null; } /** * 返回正在播放的url * * @return */ public String getUrl() { if (mUri != null) { return String.valueOf(mUri); } return null; } /** * 设置是否循环播放 */ public void setLoopPlay() { if (mMediaPlayer != null) { mMediaPlayer.setLooping(true); } } public void setLoopPlay(boolean isLoop) { if (mMediaPlayer != null) { mMediaPlayer.setLooping(isLoop); } } /** * 清除掉surfaceView */ public void clearSurfaceView() { try { if (mRenderView != null) { LogUtils.d("clearSurfaceView -- : " + mRenderView); View renderUIView = mRenderView.getView(); mRenderView.removeRenderCallback(mSHCallback); LogUtils.d("clearSurfaceView --renderUIView-- : " + renderUIView); LogUtils.d("clearSurfaceView --this-- : " + this); if (renderUIView != null) { removeView(renderUIView); } mRenderView = null; mSurfaceHolder = null; } } catch (Exception e) { e.printStackTrace(); } } public void surfaceHolderIsNull(){ LogUtils.d("surfaceHolderIsNull path: " + mUri.getPath()); LogUtils.d("surfaceHolderIsNull mSurfaceHolder: " + mSurfaceHolder); LogUtils.d("surfaceHolderIsNull mRenderView: " + mRenderView); if (mSurfaceHolder==null){ setVideoPath(mUri.getPath()); } } public void setSpeed(float speed){ if(mMediaPlayer != null) { mMediaPlayer.setSpeed(speed); } } public float getSpeed(){ if(mMediaPlayer != null) { return mMediaPlayer.getSpeed(); }else{ return 0f; } } }