package com.dl7.player.media; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.PointF; import android.media.AudioManager; import android.net.ConnectivityManager; import android.net.Uri; import android.os.BatteryManager; import android.os.Build; import android.os.Handler; import android.os.Message; import android.provider.Settings; import android.support.annotation.IntDef; import android.support.v4.content.ContextCompat; import android.support.v4.view.MotionEventCompat; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPropertyAnimatorListenerAdapter; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; import android.view.GestureDetector; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.OrientationEventListener; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.AdapterView; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.SeekBar; import android.widget.TextView; import android.widget.Toast; import com.dl7.player.R; import com.dl7.player.danmaku.BaseDanmakuConverter; import com.dl7.player.danmaku.BiliDanmukuParser; import com.dl7.player.danmaku.OnDanmakuListener; import com.dl7.player.utils.AnimHelper; import com.dl7.player.utils.MotionEventUtils; import com.dl7.player.utils.NavUtils; import com.dl7.player.utils.NetWorkUtils; import com.dl7.player.utils.SDCardUtils; import com.dl7.player.utils.SoftInputUtils; import com.dl7.player.utils.StringUtils; import com.dl7.player.utils.WindowUtils; import com.dl7.player.widgets.MarqueeTextView; import com.dl7.player.widgets.ShareDialog; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.List; import master.flame.danmaku.controller.DrawHandler; import master.flame.danmaku.controller.IDanmakuView; import master.flame.danmaku.danmaku.loader.ILoader; import master.flame.danmaku.danmaku.loader.IllegalDataException; import master.flame.danmaku.danmaku.loader.android.DanmakuLoaderFactory; import master.flame.danmaku.danmaku.model.BaseDanmaku; import master.flame.danmaku.danmaku.model.DanmakuTimer; import master.flame.danmaku.danmaku.model.android.DanmakuContext; import master.flame.danmaku.danmaku.model.android.Danmakus; import master.flame.danmaku.danmaku.parser.BaseDanmakuParser; import master.flame.danmaku.danmaku.parser.IDataSource; import tv.danmaku.ijk.media.player.IMediaPlayer; import tv.danmaku.ijk.media.player.IjkMediaPlayer; import static android.view.GestureDetector.OnGestureListener; import static android.view.GestureDetector.SimpleOnGestureListener; import static android.widget.SeekBar.OnSeekBarChangeListener; import static com.dl7.player.utils.StringUtils.generateTime; import static tv.danmaku.ijk.media.player.IMediaPlayer.OnInfoListener; /** * Created by long on 2016/10/24. */ public class IjkPlayerView extends FrameLayout implements View.OnClickListener { // 进度条最大值 private static final int MAX_VIDEO_SEEK = 1000; // 默认隐藏控制栏时间 private static final int DEFAULT_HIDE_TIMEOUT = 5000; // 更新进度消息 private static final int MSG_UPDATE_SEEK = 10086; // 使能翻转消息 private static final int MSG_ENABLE_ORIENTATION = 10087; // 尝试重连消息 private static final int MSG_TRY_RELOAD = 10088; // 无效变量 private static final int INVALID_VALUE = -1; // 原生的IjkPlayer private IjkVideoView mVideoView; // 视频开始前的缩略图,根据需要外部进行加载 public ImageView mPlayerThumb; // 加载 private ProgressBar mLoadingView; // 音量 private TextView mTvVolume; // 亮度 private TextView mTvBrightness; // 快进 private TextView mTvFastForward; // 触摸信息布局 private FrameLayout mFlTouchLayout; // 全屏下的后退键 private ImageView mIvBack; // 全屏下的标题 private MarqueeTextView mTvTitle; // 全屏下的TopBar private LinearLayout mFullscreenTopBar; // 窗口模式的后退键 private ImageView mIvBackWindow; // 窗口模式的TopBar private FrameLayout mWindowTopBar; // 播放键 private ImageView mIvPlay; private ImageView mIvPlayCircle; // 当前时间 private TextView mTvCurTime; // 进度条 private SeekBar mPlayerSeek; // 结束时间 private TextView mTvEndTime; // 全屏切换按钮 private ImageView mIvFullscreen; // BottomBar private LinearLayout mLlBottomBar; // 整个视频框架布局 private FrameLayout mFlVideoBox; // 锁屏键 private ImageView mIvPlayerLock; // 还原屏幕 private TextView mTvRecoverScreen; // 宽高比选项 private TextView mTvSettings; private RadioGroup mAspectRatioOptions; // 关联的Activity private AppCompatActivity mAttachActivity; // 重试 private TextView mTvReload; private View mFlReload; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == MSG_UPDATE_SEEK) { final int pos = _setProgress(); if (!mIsSeeking && mIsShowBar && mVideoView.isPlaying()) { // 这里会重复发送MSG,已达到实时更新 Seek 的效果 msg = obtainMessage(MSG_UPDATE_SEEK); sendMessageDelayed(msg, 1000 - (pos % 1000)); } } else if (msg.what == MSG_ENABLE_ORIENTATION) { if (mOrientationListener != null) { mOrientationListener.enable(); } } else if (msg.what == MSG_TRY_RELOAD) { if (mIsNetConnected) { reload(); } Log.i("TTAG", "sendMessageDelayed " + mVideoView.isPlaying()); msg = obtainMessage(MSG_TRY_RELOAD); sendMessageDelayed(msg, 3000); } } }; // 音量控制 private AudioManager mAudioManager; // 手势控制 private GestureDetector mGestureDetector; // 最大音量 private int mMaxVolume; // 锁屏 private boolean mIsForbidTouch = false; // 是否显示控制栏 private boolean mIsShowBar = true; // 是否全屏 private boolean mIsFullscreen; // 是否播放结束 private boolean mIsPlayComplete = false; // 是否正在拖拽进度条 private boolean mIsSeeking; // 目标进度 private long mTargetPosition = INVALID_VALUE; // 当前进度 private int mCurPosition = INVALID_VALUE; // 当前音量 private int mCurVolume = INVALID_VALUE; // 当前亮度 private float mCurBrightness = INVALID_VALUE; // 初始高度 private int mInitHeight; // 屏幕宽/高度 private int mWidthPixels; // 屏幕UI可见性 private int mScreenUiVisibility; // 屏幕旋转角度监听 private OrientationEventListener mOrientationListener; // 进来还未播放 private boolean mIsNeverPlay = true; // 外部监听器 private OnInfoListener mOutsideInfoListener; private IMediaPlayer.OnCompletionListener mCompletionListener; // 禁止翻转,默认为禁止 private boolean mIsForbidOrientation = true; // 是否固定全屏状态 private boolean mIsAlwaysFullScreen = false; // 记录按退出全屏时间 private long mExitTime = 0; // 视频Matrix private Matrix mVideoMatrix = new Matrix(); private Matrix mSaveMatrix = new Matrix(); // 是否需要显示恢复屏幕按钮 private boolean mIsNeedRecoverScreen = false; // 选项列表高度 private int mAspectOptionsHeight; // 异常中断时的播放进度 private int mInterruptPosition; private boolean mIsReady = false; public IjkPlayerView(Context context) { this(context, null); } public IjkPlayerView(Context context, AttributeSet attrs) { super(context, attrs); _initView(context); } private void _initView(Context context) { if (context instanceof AppCompatActivity) { mAttachActivity = (AppCompatActivity) context; } else { throw new IllegalArgumentException("Context must be AppCompatActivity"); } View.inflate(context, R.layout.layout_player_view, this); mVideoView = (IjkVideoView) findViewById(R.id.video_view); mPlayerThumb = (ImageView) findViewById(R.id.iv_thumb); mLoadingView = (ProgressBar) findViewById(R.id.pb_loading); mTvVolume = (TextView) findViewById(R.id.tv_volume); mTvBrightness = (TextView) findViewById(R.id.tv_brightness); mTvFastForward = (TextView) findViewById(R.id.tv_fast_forward); mFlTouchLayout = (FrameLayout) findViewById(R.id.fl_touch_layout); mIvBack = (ImageView) findViewById(R.id.iv_back); mTvTitle = (MarqueeTextView) findViewById(R.id.tv_title); mFullscreenTopBar = (LinearLayout) findViewById(R.id.fullscreen_top_bar); mIvBackWindow = (ImageView) findViewById(R.id.iv_back_window); mWindowTopBar = (FrameLayout) findViewById(R.id.window_top_bar); mIvPlay = (ImageView) findViewById(R.id.iv_play); mTvCurTime = (TextView) findViewById(R.id.tv_cur_time); mPlayerSeek = (SeekBar) findViewById(R.id.player_seek); mTvEndTime = (TextView) findViewById(R.id.tv_end_time); mIvFullscreen = (ImageView) findViewById(R.id.iv_fullscreen); mLlBottomBar = (LinearLayout) findViewById(R.id.ll_bottom_bar); mFlVideoBox = (FrameLayout) findViewById(R.id.fl_video_box); mIvPlayerLock = (ImageView) findViewById(R.id.iv_player_lock); mIvPlayCircle = (ImageView) findViewById(R.id.iv_play_circle); mTvRecoverScreen = (TextView) findViewById(R.id.tv_recover_screen); mTvReload = (TextView) findViewById(R.id.tv_reload); mFlReload = findViewById(R.id.fl_reload_layout); // 视频宽高比设置 mTvSettings = (TextView) findViewById(R.id.tv_settings); mAspectRatioOptions = (RadioGroup) findViewById(R.id.aspect_ratio_group); mAspectOptionsHeight = getResources().getDimensionPixelSize(R.dimen.aspect_btn_size) * 4; mAspectRatioOptions.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { if (checkedId == R.id.aspect_fit_parent) { mVideoView.setAspectRatio(IRenderView.AR_ASPECT_FIT_PARENT); } else if (checkedId == R.id.aspect_fit_screen) { mVideoView.setAspectRatio(IRenderView.AR_ASPECT_FILL_PARENT); } else if (checkedId == R.id.aspect_16_and_9) { mVideoView.setAspectRatio(IRenderView.AR_16_9_FIT_PARENT); } else if (checkedId == R.id.aspect_4_and_3) { mVideoView.setAspectRatio(IRenderView.AR_4_3_FIT_PARENT); } AnimHelper.doClipViewHeight(mAspectRatioOptions, mAspectOptionsHeight, 0, 150); } }); _initMediaQuality(); _initVideoSkip(); _initReceiver(); mIvPlay.setOnClickListener(this); mIvBack.setOnClickListener(this); mIvFullscreen.setOnClickListener(this); mIvBackWindow.setOnClickListener(this); mIvPlayerLock.setOnClickListener(this); mIvPlayCircle.setOnClickListener(this); mTvRecoverScreen.setOnClickListener(this); mTvSettings.setOnClickListener(this); mTvReload.setOnClickListener(this); } /** * 初始化 */ private void _initMediaPlayer() { // 加载 IjkMediaPlayer 库 IjkMediaPlayer.loadLibrariesOnce(null); IjkMediaPlayer.native_profileBegin("libijkplayer.so"); // 声音 mAudioManager = (AudioManager) mAttachActivity.getSystemService(Context.AUDIO_SERVICE); mMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); // 亮度 try { int e = Settings.System.getInt(mAttachActivity.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS); float progress = 1.0F * (float) e / 255.0F; WindowManager.LayoutParams layout = mAttachActivity.getWindow().getAttributes(); layout.screenBrightness = progress; mAttachActivity.getWindow().setAttributes(layout); } catch (Settings.SettingNotFoundException var7) { var7.printStackTrace(); } // 进度 mPlayerSeek.setMax(MAX_VIDEO_SEEK); mPlayerSeek.setOnSeekBarChangeListener(mSeekListener); // 视频监听 mVideoView.setOnInfoListener(mInfoListener); // 触摸控制 mGestureDetector = new GestureDetector(mAttachActivity, mPlayerGestureListener); mFlVideoBox.setClickable(true); mFlVideoBox.setOnTouchListener(mPlayerTouchListener); // 屏幕翻转控制 mOrientationListener = new OrientationEventListener(mAttachActivity) { @Override public void onOrientationChanged(int orientation) { _handleOrientation(orientation); } }; if (mIsForbidOrientation) { // 禁止翻转 mOrientationListener.disable(); } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (mInitHeight == 0) { mInitHeight = getHeight(); mWidthPixels = getResources().getDisplayMetrics().widthPixels; } } /**============================ 外部调用接口 ============================*/ /** * Activity.onResume() 里调用 */ public void onResume() { Log.i("TTAG", "onResume"); if (mIsScreenLocked) { // 如果出现锁屏则需要重新渲染器Render,不然会出现只有声音没有动画 // 目前只在锁屏时会出现图像不动的情况,如果有遇到类似情况可以尝试按这个方法解决 mVideoView.setRender(IjkVideoView.RENDER_TEXTURE_VIEW); mIsScreenLocked = false; } mVideoView.resume(); if (!mIsForbidTouch && !mIsForbidOrientation) { mOrientationListener.enable(); } if (mCurPosition != INVALID_VALUE) { // 重进后 seekTo 到指定位置播放时,通常会回退到前几秒,关键帧?? seekTo(mCurPosition); mCurPosition = INVALID_VALUE; } } /** * Activity.onPause() 里调用 */ public void onPause() { Log.i("TTAG", "onPause"); mCurPosition = mVideoView.getCurrentPosition(); mVideoView.pause(); mIvPlay.setSelected(false); mOrientationListener.disable(); _pauseDanmaku(); } /** * Activity.onDestroy() 里调用 * * @return 返回播放进度 */ public int onDestroy() { // 记录播放进度 int curPosition = mVideoView.getCurrentPosition(); mVideoView.destroy(); IjkMediaPlayer.native_profileEnd(); if (mDanmakuView != null) { // don't forget release! mDanmakuView.release(); mDanmakuView = null; } if (mShareDialog != null) { mShareDialog.dismiss(); mShareDialog = null; } mHandler.removeMessages(MSG_TRY_RELOAD); mHandler.removeMessages(MSG_UPDATE_SEEK); // 注销广播 mAttachActivity.unregisterReceiver(mBatteryReceiver); mAttachActivity.unregisterReceiver(mScreenReceiver); mAttachActivity.unregisterReceiver(mNetReceiver); // 关闭屏幕常亮 mAttachActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); return curPosition; } /** * 处理音量键,避免外部按音量键后导航栏和状态栏显示出来退不回去的状态 * * @param keyCode * @return */ public boolean handleVolumeKey(int keyCode) { if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { _setVolume(true); return true; } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { _setVolume(false); return true; } else { return false; } } /** * 回退,全屏时退回竖屏 * * @return */ public boolean onBackPressed() { if (recoverFromEditVideo()) { return true; } if (mIsAlwaysFullScreen) { _exit(); return true; } else if (mIsFullscreen) { mAttachActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); if (mIsForbidTouch) { // 锁住状态则解锁 mIsForbidTouch = false; mIvPlayerLock.setSelected(false); _setControlBarVisible(mIsShowBar); } return true; } return false; } /** * 初始化,必须要先调用 * * @return */ public IjkPlayerView init() { _initMediaPlayer(); return this; } /** * 切换视频 * * @param url * @return */ public IjkPlayerView switchVideoPath(String url) { return switchVideoPath(Uri.parse(url)); } /** * 切换视频 * * @param uri * @return */ public IjkPlayerView switchVideoPath(Uri uri) { if (mQualityData != null) { mQualityData.clear(); mQualityData = null; } reset(); return setVideoPath(uri); } /** * 设置播放资源 * * @param url * @return */ public IjkPlayerView setVideoPath(String url) { return setVideoPath(Uri.parse(url)); } /** * 设置播放资源 * * @param uri * @return */ public IjkPlayerView setVideoPath(Uri uri) { mVideoView.setVideoURI(uri); if (mCurPosition != INVALID_VALUE) { seekTo(mCurPosition); mCurPosition = INVALID_VALUE; } else { seekTo(0); } return this; } /** * 设置标题,全屏的时候可见 * * @param title */ public IjkPlayerView setTitle(String title) { mTvTitle.setText(title); return this; } /** * 设置只显示全屏状态 */ public IjkPlayerView alwaysFullScreen() { mIsAlwaysFullScreen = true; _setFullScreen(true); mIvFullscreen.setVisibility(GONE); mAttachActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); _setUiLayoutFullscreen(); return this; } /** * 开始播放 * * @return */ public void start() { if (mIsPlayComplete) { if (mDanmakuView != null && mDanmakuView.isPrepared()) { mDanmakuView.seekTo((long) 0); mDanmakuView.pause(); } mIsPlayComplete = false; } if (!mVideoView.isPlaying()) { mIvPlay.setSelected(true); // if (mInterruptPosition > 0) { // mLoadingView.setVisibility(VISIBLE); // mHandler.sendEmptyMessage(MSG_TRY_RELOAD); // } else { mVideoView.start(); // 更新进度 mHandler.sendEmptyMessage(MSG_UPDATE_SEEK); // } } if (mIsNeverPlay) { mIsNeverPlay = false; mIvPlayCircle.setVisibility(GONE); mLoadingView.setVisibility(VISIBLE); mIsShowBar = false; // 放这边装载弹幕,不然会莫名其妙出现多切几次到首页会弹幕自动播放问题,这里处理下 _loadDanmaku(); } // 视频播放时开启屏幕常亮 mAttachActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } /** * 重新开始 */ public void reload() { Log.w("TTAGT", "reload " + mInterruptPosition); mFlReload.setVisibility(GONE); mLoadingView.setVisibility(VISIBLE); if (mIsReady) { // 确保网络正常时 if (NetWorkUtils.isNetworkAvailable(mAttachActivity)) { mVideoView.reload(); mVideoView.start(); // start(); if (mInterruptPosition > 0) { seekTo(mInterruptPosition); mInterruptPosition = 0; } } } else { mVideoView.release(false); mVideoView.setRender(IjkVideoView.RENDER_TEXTURE_VIEW); start(); } // 更新进度 mHandler.removeMessages(MSG_UPDATE_SEEK); mHandler.sendEmptyMessage(MSG_UPDATE_SEEK); } /** * 是否正在播放 * * @return */ public boolean isPlaying() { return mVideoView.isPlaying(); } /** * 暂停 */ public void pause() { mIvPlay.setSelected(false); if (mVideoView.isPlaying()) { mVideoView.pause(); } _pauseDanmaku(); // 视频暂停时关闭屏幕常亮 mAttachActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } /** * 跳转 * * @param position 位置 */ public void seekTo(int position) { mVideoView.seekTo(position); mDanmakuTargetPosition = position; } /** * 停止 */ public void stop() { pause(); mVideoView.stopPlayback(); } /** * 重置状态 */ public void reset() { if (mIsEnableDanmaku && mDanmakuView != null) { _toggleDanmakuView(false); mDanmakuView.release(); mDanmakuView = null; mIsEnableDanmaku = false; } mIsNeverPlay = true; mCurPosition = 0; stop(); mVideoView.setRender(IjkVideoView.RENDER_TEXTURE_VIEW); } /**============================ 控制栏处理 ============================*/ /** * SeekBar监听 */ private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() { private long curPosition; @Override public void onStartTrackingTouch(SeekBar bar) { mIsSeeking = true; _showControlBar(3600000); mHandler.removeMessages(MSG_UPDATE_SEEK); curPosition = mVideoView.getCurrentPosition(); } @Override public void onProgressChanged(SeekBar bar, int progress, boolean fromUser) { if (!fromUser) { // We're not interested in programmatically generated changes to // the progress bar's position. return; } long duration = mVideoView.getDuration(); // 计算目标位置 mTargetPosition = (duration * progress) / MAX_VIDEO_SEEK; int deltaTime = (int) ((mTargetPosition - curPosition) / 1000); String desc; // 对比当前位置来显示快进或后退 if (mTargetPosition > curPosition) { desc = generateTime(mTargetPosition) + "/" + generateTime(duration) + "\n" + "+" + deltaTime + "秒"; } else { desc = generateTime(mTargetPosition) + "/" + generateTime(duration) + "\n" + deltaTime + "秒"; } _setFastForward(desc); } @Override public void onStopTrackingTouch(SeekBar bar) { _hideTouchView(); mIsSeeking = false; // 视频跳转 seekTo((int) mTargetPosition); mTargetPosition = INVALID_VALUE; _setProgress(); _showControlBar(DEFAULT_HIDE_TIMEOUT); } }; /** * 隐藏视图Runnable */ private Runnable mHideBarRunnable = new Runnable() { @Override public void run() { _hideAllView(false); } }; /** * 隐藏除视频外所有视图 */ private void _hideAllView(boolean isTouchLock) { // mPlayerThumb.setVisibility(View.GONE); mFlTouchLayout.setVisibility(View.GONE); mFullscreenTopBar.setVisibility(View.GONE); mWindowTopBar.setVisibility(View.GONE); mLlBottomBar.setVisibility(View.GONE); _showAspectRatioOptions(false); if (!isTouchLock) { mIvPlayerLock.setVisibility(View.GONE); mIsShowBar = false; } if (mIsEnableDanmaku) { mDanmakuPlayerSeek.setVisibility(GONE); } if (mIsNeedRecoverScreen) { mTvRecoverScreen.setVisibility(GONE); } } /** * 设置控制栏显示或隐藏 * * @param isShowBar */ private void _setControlBarVisible(boolean isShowBar) { if (mIsNeverPlay) { mIvPlayCircle.setVisibility(isShowBar ? View.VISIBLE : View.GONE); } else if (mIsForbidTouch) { mIvPlayerLock.setVisibility(isShowBar ? View.VISIBLE : View.GONE); } else { mLlBottomBar.setVisibility(isShowBar ? View.VISIBLE : View.GONE); if (!isShowBar) { _showAspectRatioOptions(false); } // 全屏切换显示的控制栏不一样 if (mIsFullscreen) { // 只在显示控制栏的时候才设置时间,因为控制栏通常不显示且单位为分钟,所以不做实时更新 mTvSystemTime.setText(StringUtils.getCurFormatTime()); mFullscreenTopBar.setVisibility(isShowBar ? View.VISIBLE : View.GONE); mWindowTopBar.setVisibility(View.GONE); mIvPlayerLock.setVisibility(isShowBar ? View.VISIBLE : View.GONE); if (mIsEnableDanmaku) { mDanmakuPlayerSeek.setVisibility(isShowBar ? View.VISIBLE : View.GONE); } if (mIsNeedRecoverScreen) { mTvRecoverScreen.setVisibility(isShowBar ? View.VISIBLE : View.GONE); } } else { mWindowTopBar.setVisibility(isShowBar ? View.VISIBLE : View.GONE); mFullscreenTopBar.setVisibility(View.GONE); mIvPlayerLock.setVisibility(View.GONE); if (mIsEnableDanmaku) { mDanmakuPlayerSeek.setVisibility(GONE); } if (mIsNeedRecoverScreen) { mTvRecoverScreen.setVisibility(View.GONE); } } } } /** * 开关控制栏,单击界面的时候 */ private void _toggleControlBar() { mIsShowBar = !mIsShowBar; _setControlBarVisible(mIsShowBar); if (mIsShowBar) { // 发送延迟隐藏控制栏的操作 mHandler.postDelayed(mHideBarRunnable, DEFAULT_HIDE_TIMEOUT); // 发送更新 Seek 消息 mHandler.sendEmptyMessage(MSG_UPDATE_SEEK); } } /** * 显示控制栏 * * @param timeout 延迟隐藏时间 */ private void _showControlBar(int timeout) { if (!mIsShowBar) { _setProgress(); mIsShowBar = true; } _setControlBarVisible(true); mHandler.sendEmptyMessage(MSG_UPDATE_SEEK); // 先移除隐藏控制栏 Runnable,如果 timeout=0 则不做延迟隐藏操作 mHandler.removeCallbacks(mHideBarRunnable); if (timeout != 0) { mHandler.postDelayed(mHideBarRunnable, timeout); } } /** * 切换播放状态,点击播放按钮时 */ private void _togglePlayStatus() { Log.d("TTAG", "_togglePlayStatus " + mIsReady + " - " + mVideoView.isPlaying()); // if (mIsReady) { if (mVideoView.isPlaying()) { pause(); } else { start(); } // } } /** * 刷新隐藏控制栏的操作 */ private void _refreshHideRunnable() { mHandler.removeCallbacks(mHideBarRunnable); mHandler.postDelayed(mHideBarRunnable, DEFAULT_HIDE_TIMEOUT); } /** * 切换控制锁 */ private void _togglePlayerLock() { mIsForbidTouch = !mIsForbidTouch; mIvPlayerLock.setSelected(mIsForbidTouch); if (mIsForbidTouch) { mOrientationListener.disable(); _hideAllView(true); } else { if (!mIsForbidOrientation) { mOrientationListener.enable(); } mFullscreenTopBar.setVisibility(View.VISIBLE); mLlBottomBar.setVisibility(View.VISIBLE); if (mIsEnableDanmaku) { mDanmakuPlayerSeek.setVisibility(VISIBLE); } if (mIsNeedRecoverScreen) { mTvRecoverScreen.setVisibility(VISIBLE); } } } /** * 切换视频分辨率控制 */ private void _toggleMediaQuality() { if (mFlMediaQuality.getVisibility() == GONE) { mFlMediaQuality.setVisibility(VISIBLE); } if (mIsShowQuality) { ViewCompat.animate(mFlMediaQuality).translationX(mFlMediaQuality.getWidth()).setDuration(DEFAULT_QUALITY_TIME); mIsShowQuality = false; } else { ViewCompat.animate(mFlMediaQuality).translationX(0).setDuration(DEFAULT_QUALITY_TIME); mIsShowQuality = true; } } /** * 显示宽高比设置 * * @param isShow */ private void _showAspectRatioOptions(boolean isShow) { if (isShow) { AnimHelper.doClipViewHeight(mAspectRatioOptions, 0, mAspectOptionsHeight, 150); } else { ViewGroup.LayoutParams layoutParams = mAspectRatioOptions.getLayoutParams(); layoutParams.height = 0; } } @Override public void onClick(View v) { Log.w("TTAG", "onClick " + v.getId()); _refreshHideRunnable(); int id = v.getId(); if (id == R.id.iv_back) { if (mIsAlwaysFullScreen) { _exit(); return; } mAttachActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } else if (id == R.id.iv_back_window) { mAttachActivity.finish(); } else if (id == R.id.iv_play || id == R.id.iv_play_circle) { _togglePlayStatus(); } else if (id == R.id.iv_fullscreen) { _toggleFullScreen(); } else if (id == R.id.iv_player_lock) { _togglePlayerLock(); } else if (id == R.id.iv_media_quality) { if (!mIsShowQuality) { _toggleMediaQuality(); } } else if (id == R.id.iv_cancel_skip) { mHandler.removeCallbacks(mHideSkipTipRunnable); _hideSkipTip(); } else if (id == R.id.tv_do_skip) { mLoadingView.setVisibility(VISIBLE); // 视频跳转 seekTo(mSkipPosition); mHandler.removeCallbacks(mHideSkipTipRunnable); _hideSkipTip(); _setProgress(); } else if (id == R.id.iv_danmaku_control) { _toggleDanmakuShow(); } else if (id == R.id.tv_open_edit_danmaku) { if (mDanmakuListener == null || mDanmakuListener.isValid()) { editVideo(); mEditDanmakuLayout.setVisibility(VISIBLE); SoftInputUtils.setEditFocusable(mAttachActivity, mEtDanmakuContent); } } else if (id == R.id.iv_cancel_send) { recoverFromEditVideo(); } else if (id == R.id.iv_do_send) { recoverFromEditVideo(); sendDanmaku(mEtDanmakuContent.getText().toString(), false); mEtDanmakuContent.setText(""); } else if (id == R.id.input_options_more) { _toggleMoreColorOptions(); } else if (id == R.id.iv_screenshot) { _doScreenshot(); } else if (id == R.id.tv_recover_screen) { mVideoView.resetVideoView(true); mIsNeedRecoverScreen = false; mTvRecoverScreen.setVisibility(GONE); } else if (id == R.id.tv_settings) { _showAspectRatioOptions(true); } else if (id == R.id.tv_reload) { reload(); } } /**==================== 屏幕翻转/切换处理 ====================*/ /** * 使能视频翻转 */ public IjkPlayerView enableOrientation() { mIsForbidOrientation = false; mOrientationListener.enable(); return this; } /** * 全屏切换,点击全屏按钮 */ private void _toggleFullScreen() { if (WindowUtils.getScreenOrientation(mAttachActivity) == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { mAttachActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } else { mAttachActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } } /** * 设置全屏或窗口模式 * * @param isFullscreen */ private void _setFullScreen(boolean isFullscreen) { mIsFullscreen = isFullscreen; // 处理弹幕相关视图 _toggleDanmakuView(isFullscreen); _handleActionBar(isFullscreen); _changeHeight(isFullscreen); mIvFullscreen.setSelected(isFullscreen); mHandler.post(mHideBarRunnable); mIvMediaQuality.setVisibility(isFullscreen ? VISIBLE : GONE); mLlBottomBar.setBackgroundResource(isFullscreen ? R.color.bg_video_view : android.R.color.transparent); if (mIsShowQuality && !isFullscreen) { _toggleMediaQuality(); } // 处理三指旋转缩放,如果之前进行了相关操作则全屏时还原之前旋转缩放的状态,窗口模式则将整个屏幕还原为未操作状态 if (mIsNeedRecoverScreen) { if (isFullscreen) { mVideoView.adjustVideoView(1.0f); mTvRecoverScreen.setVisibility(mIsShowBar ? View.VISIBLE : View.GONE); } else { mVideoView.resetVideoView(false); mTvRecoverScreen.setVisibility(GONE); } } // 非全屏隐藏宽高比设置 if (!isFullscreen) { _showAspectRatioOptions(false); } } /** * 处理屏幕翻转 * * @param orientation */ private void _handleOrientation(int orientation) { if (mIsNeverPlay) { return; } if (mIsFullscreen && !mIsAlwaysFullScreen) { // 根据角度进行竖屏切换,如果为固定全屏则只能横屏切换 if (orientation >= 0 && orientation <= 30 || orientation >= 330) { // 请求屏幕翻转 mAttachActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } } else { // 根据角度进行横屏切换 if (orientation >= 60 && orientation <= 120) { mAttachActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE); } else if (orientation >= 240 && orientation <= 300) { mAttachActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } } } /** * 当屏幕执行翻转操作后调用禁止翻转功能,延迟3000ms再使能翻转,避免不必要的翻转 */ private void _refreshOrientationEnable() { if (!mIsForbidOrientation) { mOrientationListener.disable(); mHandler.removeMessages(MSG_ENABLE_ORIENTATION); mHandler.sendEmptyMessageDelayed(MSG_ENABLE_ORIENTATION, 3000); } } /** * 隐藏/显示 ActionBar * * @param isFullscreen */ private void _handleActionBar(boolean isFullscreen) { ActionBar supportActionBar = mAttachActivity.getSupportActionBar(); if (supportActionBar != null) { if (isFullscreen) { supportActionBar.hide(); } else { supportActionBar.show(); } } } /** * 改变视频布局高度 * * @param isFullscreen */ private void _changeHeight(boolean isFullscreen) { if (mIsAlwaysFullScreen) { return; } ViewGroup.LayoutParams layoutParams = getLayoutParams(); if (isFullscreen) { // 高度扩展为横向全屏 layoutParams.height = mWidthPixels; } else { // 还原高度 layoutParams.height = mInitHeight; } setLayoutParams(layoutParams); } /** * 设置UI沉浸式显示 */ private void _setUiLayoutFullscreen() { if (Build.VERSION.SDK_INT >= 14) { // 获取关联 Activity 的 DecorView View decorView = mAttachActivity.getWindow().getDecorView(); // 沉浸式使用这些Flag decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY ); mAttachActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); } } /** * 屏幕翻转后的处理,在 Activity.configurationChanged() 调用 * SYSTEM_UI_FLAG_LAYOUT_STABLE:维持一个稳定的布局 * SYSTEM_UI_FLAG_FULLSCREEN:Activity全屏显示,且状态栏被隐藏覆盖掉 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN:Activity全屏显示,但状态栏不会被隐藏覆盖,状态栏依然可见,Activity顶端布局部分会被状态遮住 * SYSTEM_UI_FLAG_HIDE_NAVIGATION:隐藏虚拟按键(导航栏) * SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION:效果同View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN * SYSTEM_UI_FLAG_IMMERSIVE:沉浸式,从顶部下滑出现状态栏和导航栏会固定住 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY:黏性沉浸式,从顶部下滑出现状态栏和导航栏过几秒后会缩回去 * * @param newConfig */ public void configurationChanged(Configuration newConfig) { _refreshOrientationEnable(); // 沉浸式只能在SDK19以上实现 if (Build.VERSION.SDK_INT >= 14) { if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { // 获取关联 Activity 的 DecorView View decorView = mAttachActivity.getWindow().getDecorView(); // 保存旧的配置 mScreenUiVisibility = decorView.getSystemUiVisibility(); // 沉浸式使用这些Flag decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY ); _setFullScreen(true); mAttachActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { View decorView = mAttachActivity.getWindow().getDecorView(); // 还原 decorView.setSystemUiVisibility(mScreenUiVisibility); _setFullScreen(false); mAttachActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); } } } /** * 从总显示全屏状态退出处理{@link #alwaysFullScreen()} */ private void _exit() { if (System.currentTimeMillis() - mExitTime > 2000) { Toast.makeText(mAttachActivity, "再按一次退出", Toast.LENGTH_SHORT).show(); mExitTime = System.currentTimeMillis(); } else { mAttachActivity.finish(); } } /**============================ 触屏操作处理 ============================*/ /** * 手势监听 */ private OnGestureListener mPlayerGestureListener = new SimpleOnGestureListener() { // 是否是按下的标识,默认为其他动作,true为按下标识,false为其他动作 private boolean isDownTouch; // 是否声音控制,默认为亮度控制,true为声音控制,false为亮度控制 private boolean isVolume; // 是否横向滑动,默认为纵向滑动,true为横向滑动,false为纵向滑动 private boolean isLandscape; // 是否从弹幕编辑状态返回 private boolean isRecoverFromDanmaku; @Override public boolean onDown(MotionEvent e) { isDownTouch = true; isRecoverFromDanmaku = recoverFromEditVideo(); return super.onDown(e); } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (!mIsForbidTouch && !mIsNeverPlay) { float mOldX = e1.getX(), mOldY = e1.getY(); float deltaY = mOldY - e2.getY(); float deltaX = mOldX - e2.getX(); if (isDownTouch) { // 判断左右或上下滑动 isLandscape = Math.abs(distanceX) >= Math.abs(distanceY); // 判断是声音或亮度控制 isVolume = mOldX > getResources().getDisplayMetrics().widthPixels * 0.5f; isDownTouch = false; } if (isLandscape) { _onProgressSlide(-deltaX / mVideoView.getWidth()); } else { float percent = deltaY / mVideoView.getHeight(); if (isVolume) { _onVolumeSlide(percent); } else { _onBrightnessSlide(percent); } } } return super.onScroll(e1, e2, distanceX, distanceY); } @Override public boolean onSingleTapConfirmed(MotionEvent e) { // 弹幕编辑状态返回则不执行单击操作 if (isRecoverFromDanmaku) { return true; } if (mIsShowQuality) { _toggleMediaQuality(); } else { _toggleControlBar(); } return true; } @Override public boolean onDoubleTap(MotionEvent e) { // 如果未进行播放或从弹幕编辑状态返回则不执行双击操作 if (mIsNeverPlay || isRecoverFromDanmaku) { return true; } if (!mIsForbidTouch) { _refreshHideRunnable(); _togglePlayStatus(); } return true; } }; /** * 隐藏视图Runnable */ private Runnable mHideTouchViewRunnable = new Runnable() { @Override public void run() { _hideTouchView(); } }; /** * 触摸监听 */ private OnTouchListener mPlayerTouchListener = new OnTouchListener() { // 触摸模式:正常、无效、缩放旋转 private static final int NORMAL = 1; private static final int INVALID_POINTER = 2; private static final int ZOOM_AND_ROTATE = 3; // 触摸模式 private int mode = NORMAL; // 缩放的中点 private PointF midPoint = new PointF(0, 0); // 旋转角度 private float degree = 0; // 用来标识哪两个手指靠得最近,我的做法是取最近的两指中点和余下一指来控制旋转缩放 private int fingerFlag = INVALID_VALUE; // 初始间距 private float oldDist; // 缩放比例 private float scale; @Override public boolean onTouch(View v, MotionEvent event) { Log.d("TTAG", "onTouch"); switch (MotionEventCompat.getActionMasked(event)) { case MotionEvent.ACTION_DOWN: mode = NORMAL; mHandler.removeCallbacks(mHideBarRunnable); break; case MotionEvent.ACTION_POINTER_DOWN: if (event.getPointerCount() == 3 && mIsFullscreen) { _hideTouchView(); // 进入三指旋转缩放模式,进行相关初始化 mode = ZOOM_AND_ROTATE; MotionEventUtils.midPoint(midPoint, event); fingerFlag = MotionEventUtils.calcFingerFlag(event); degree = MotionEventUtils.rotation(event, fingerFlag); oldDist = MotionEventUtils.calcSpacing(event, fingerFlag); // 获取视频的 Matrix mSaveMatrix = mVideoView.getVideoTransform(); } else { mode = INVALID_POINTER; } break; case MotionEvent.ACTION_MOVE: if (mode == ZOOM_AND_ROTATE) { // 处理旋转 float newRotate = MotionEventUtils.rotation(event, fingerFlag); mVideoView.setVideoRotation((int) (newRotate - degree)); // 处理缩放 mVideoMatrix.set(mSaveMatrix); float newDist = MotionEventUtils.calcSpacing(event, fingerFlag); scale = newDist / oldDist; mVideoMatrix.postScale(scale, scale, midPoint.x, midPoint.y); mVideoView.setVideoTransform(mVideoMatrix); } break; case MotionEvent.ACTION_POINTER_UP: if (mode == ZOOM_AND_ROTATE) { // 调整视频界面,让界面居中显示在屏幕 mIsNeedRecoverScreen = mVideoView.adjustVideoView(scale); if (mIsNeedRecoverScreen && mIsShowBar) { mTvRecoverScreen.setVisibility(VISIBLE); } } mode = INVALID_POINTER; break; } // 触屏手势处理 if (mode == NORMAL) { if (mGestureDetector.onTouchEvent(event)) { Log.d("TTAG", "onTouchEvent"); return true; } if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_UP) { _endGesture(); } } return false; } }; /** * 更新进度条 * * @return */ private int _setProgress() { if (mVideoView == null || mIsSeeking) { return 0; } // 视频播放的当前进度 int position = Math.max(mVideoView.getCurrentPosition(), mInterruptPosition); // 视频总的时长 int duration = mVideoView.getDuration(); if (duration > 0) { // 转换为 Seek 显示的进度值 long pos = (long) MAX_VIDEO_SEEK * position / duration; mPlayerSeek.setProgress((int) pos); if (mIsEnableDanmaku) { mDanmakuPlayerSeek.setProgress((int) pos); } } // 获取缓冲的进度百分比,并显示在 Seek 的次进度 int percent = mVideoView.getBufferPercentage(); mPlayerSeek.setSecondaryProgress(percent * 10); if (mIsEnableDanmaku) { mDanmakuPlayerSeek.setSecondaryProgress(percent * 10); } // 更新播放时间 mTvCurTime.setText(generateTime(position)); mTvEndTime.setText(generateTime(duration)); // 返回当前播放进度 return position; } /** * 设置快进 * * @param time */ private void _setFastForward(String time) { if (mFlTouchLayout.getVisibility() == View.GONE) { mFlTouchLayout.setVisibility(View.VISIBLE); } if (mTvFastForward.getVisibility() == View.GONE) { mTvFastForward.setVisibility(View.VISIBLE); } mTvFastForward.setText(time); } /** * 隐藏触摸视图 */ private void _hideTouchView() { if (mFlTouchLayout.getVisibility() == View.VISIBLE) { mTvFastForward.setVisibility(View.GONE); mTvVolume.setVisibility(View.GONE); mTvBrightness.setVisibility(View.GONE); mFlTouchLayout.setVisibility(View.GONE); } } /** * 快进或者快退滑动改变进度,这里处理触摸滑动不是拉动 SeekBar * * @param percent 拖拽百分比 */ private void _onProgressSlide(float percent) { int position = mVideoView.getCurrentPosition(); long duration = mVideoView.getDuration(); // 单次拖拽最大时间差为100秒或播放时长的1/2 long deltaMax = Math.min(100 * 1000, duration / 2); // 计算滑动时间 long delta = (long) (deltaMax * percent); // 目标位置 mTargetPosition = delta + position; if (mTargetPosition > duration) { mTargetPosition = duration; } else if (mTargetPosition <= 0) { mTargetPosition = 0; } int deltaTime = (int) ((mTargetPosition - position) / 1000); String desc; // 对比当前位置来显示快进或后退 if (mTargetPosition > position) { desc = generateTime(mTargetPosition) + "/" + generateTime(duration) + "\n" + "+" + deltaTime + "秒"; } else { desc = generateTime(mTargetPosition) + "/" + generateTime(duration) + "\n" + deltaTime + "秒"; } _setFastForward(desc); } /** * 设置声音控制显示 * * @param volume */ private void _setVolumeInfo(int volume) { if (mFlTouchLayout.getVisibility() == View.GONE) { mFlTouchLayout.setVisibility(View.VISIBLE); } if (mTvVolume.getVisibility() == View.GONE) { mTvVolume.setVisibility(View.VISIBLE); } mTvVolume.setText((volume * 100 / mMaxVolume) + "%"); } /** * 滑动改变声音大小 * * @param percent */ private void _onVolumeSlide(float percent) { if (mCurVolume == INVALID_VALUE) { mCurVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); if (mCurVolume < 0) { mCurVolume = 0; } } int index = (int) (percent * mMaxVolume) + mCurVolume; if (index > mMaxVolume) { index = mMaxVolume; } else if (index < 0) { index = 0; } // 变更声音 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, index, 0); // 变更进度条 _setVolumeInfo(index); } /** * 递增或递减音量,量度按最大音量的 1/15 * * @param isIncrease 递增或递减 */ private void _setVolume(boolean isIncrease) { int curVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); if (isIncrease) { curVolume += mMaxVolume / 15; } else { curVolume -= mMaxVolume / 15; } if (curVolume > mMaxVolume) { curVolume = mMaxVolume; } else if (curVolume < 0) { curVolume = 0; } // 变更声音 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, curVolume, 0); // 变更进度条 _setVolumeInfo(curVolume); mHandler.removeCallbacks(mHideTouchViewRunnable); mHandler.postDelayed(mHideTouchViewRunnable, 1000); } /** * 设置亮度控制显示 * * @param brightness */ private void _setBrightnessInfo(float brightness) { if (mFlTouchLayout.getVisibility() == View.GONE) { mFlTouchLayout.setVisibility(View.VISIBLE); } if (mTvBrightness.getVisibility() == View.GONE) { mTvBrightness.setVisibility(View.VISIBLE); } mTvBrightness.setText(Math.ceil(brightness * 100) + "%"); } /** * 滑动改变亮度大小 * * @param percent */ private void _onBrightnessSlide(float percent) { if (mCurBrightness < 0) { mCurBrightness = mAttachActivity.getWindow().getAttributes().screenBrightness; if (mCurBrightness < 0.0f) { mCurBrightness = 0.5f; } else if (mCurBrightness < 0.01f) { mCurBrightness = 0.01f; } } WindowManager.LayoutParams attributes = mAttachActivity.getWindow().getAttributes(); attributes.screenBrightness = mCurBrightness + percent; if (attributes.screenBrightness > 1.0f) { attributes.screenBrightness = 1.0f; } else if (attributes.screenBrightness < 0.01f) { attributes.screenBrightness = 0.01f; } _setBrightnessInfo(attributes.screenBrightness); mAttachActivity.getWindow().setAttributes(attributes); } /** * 手势结束调用 */ private void _endGesture() { if (mTargetPosition >= 0 && mTargetPosition != mVideoView.getCurrentPosition()) { // 更新视频播放进度 seekTo((int) mTargetPosition); mPlayerSeek.setProgress((int) (mTargetPosition * MAX_VIDEO_SEEK / mVideoView.getDuration())); if (mIsEnableDanmaku) { mDanmakuPlayerSeek.setProgress((int) (mTargetPosition * MAX_VIDEO_SEEK / mVideoView.getDuration())); } mTargetPosition = INVALID_VALUE; } // 隐藏触摸操作显示图像 _hideTouchView(); _refreshHideRunnable(); mCurVolume = INVALID_VALUE; mCurBrightness = INVALID_VALUE; } /** * ============================ 播放状态控制 ============================ */ // 这个用来控制弹幕启动和视频同步 private boolean mIsRenderingStart = false; // 缓冲开始,这个用来控制弹幕启动和视频同步 private boolean mIsBufferingStart = false; // 视频播放状态监听 private OnInfoListener mInfoListener = new OnInfoListener() { @Override public boolean onInfo(IMediaPlayer iMediaPlayer, int status, int extra) { _switchStatus(status); if (mOutsideInfoListener != null) { mOutsideInfoListener.onInfo(iMediaPlayer, status, extra); } return true; } }; /** * 视频播放状态处理 * * @param status */ private void _switchStatus(int status) { Log.e("TTAG", "status " + status); switch (status) { case IMediaPlayer.MEDIA_INFO_BUFFERING_START: mIsBufferingStart = true; _pauseDanmaku(); if (!mIsNeverPlay) { mLoadingView.setVisibility(View.VISIBLE); } mHandler.removeMessages(MSG_TRY_RELOAD); case MediaPlayerParams.STATE_PREPARING: break; case MediaPlayerParams.STATE_PREPARED: mIsReady = true; break; case IMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START: mIsRenderingStart = true; case IMediaPlayer.MEDIA_INFO_BUFFERING_END: mIsBufferingStart = false; mLoadingView.setVisibility(View.GONE); mPlayerThumb.setVisibility(View.GONE); // 更新进度 mHandler.removeMessages(MSG_UPDATE_SEEK); mHandler.sendEmptyMessage(MSG_UPDATE_SEEK); if (mSkipPosition != INVALID_VALUE) { _showSkipTip(); // 显示跳转提示 } if (mVideoView.isPlaying() && mVideoView.getCurrentPosition() > 0) { mInterruptPosition = 0; _resumeDanmaku(); // 开启弹幕 if (!mIvPlay.isSelected()) { // 这里处理断网重连后不会播放情况 mVideoView.start(); mIvPlay.setSelected(true); } } break; case MediaPlayerParams.STATE_PLAYING: mHandler.removeMessages(MSG_TRY_RELOAD); Log.w("TTAG", "STATE_PLAYING " + mVideoView.getCurrentPosition()); if (mIsRenderingStart && !mIsBufferingStart && mVideoView.getCurrentPosition() > 0) { _resumeDanmaku(); // 开启弹幕 } break; case MediaPlayerParams.STATE_ERROR: mInterruptPosition = Math.max(mVideoView.getInterruptPosition(), mInterruptPosition); pause(); if (mVideoView.getDuration() == -1 && !mIsReady) { mLoadingView.setVisibility(View.GONE); mPlayerThumb.setVisibility(View.GONE); mIvPlayCircle.setVisibility(GONE); mFlReload.setVisibility(VISIBLE); } else { mLoadingView.setVisibility(VISIBLE); mHandler.sendEmptyMessage(MSG_TRY_RELOAD); } break; case MediaPlayerParams.STATE_COMPLETED: pause(); if (mVideoView.getDuration() == -1 || mVideoView.getInterruptPosition() < mVideoView.getDuration()) { mInterruptPosition = Math.max(mVideoView.getInterruptPosition(), mInterruptPosition); // if (mVideoView.getInterruptPosition() > 0) { // mInterruptPosition = mVideoView.getInterruptPosition(); // } else { Toast.makeText(mAttachActivity, "网络异常", Toast.LENGTH_SHORT).show(); // } } else { mIsPlayComplete = true; if (mCompletionListener != null) { mCompletionListener.onCompletion(mVideoView.getMediaPlayer()); } } break; } } /**============================ Listener ============================*/ /** * 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) { mVideoView.setOnPreparedListener(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) { mCompletionListener = l; // mVideoView.setOnCompletionListener(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) { mVideoView.setOnErrorListener(l); } /** * Register a callback to be invoked when an informational event * occurs during playback or setup. * * @param l The callback that will be run */ public void setOnInfoListener(OnInfoListener l) { mOutsideInfoListener = l; } /** * 设置弹幕监听器 * * @param danmakuListener */ public void setDanmakuListener(OnDanmakuListener danmakuListener) { mDanmakuListener = danmakuListener; } /** * ============================ 播放清晰度 ============================ */ // 默认显示/隐藏选择分辨率界面时间 private static final int DEFAULT_QUALITY_TIME = 300; /** * 依次分别为:流畅、清晰、高清、超清和1080P */ public static final int MEDIA_QUALITY_SMOOTH = 0; public static final int MEDIA_QUALITY_MEDIUM = 1; public static final int MEDIA_QUALITY_HIGH = 2; public static final int MEDIA_QUALITY_SUPER = 3; public static final int MEDIA_QUALITY_BD = 4; private static final int QUALITY_DRAWABLE_RES[] = new int[]{ R.drawable.ic_media_quality_smooth, R.drawable.ic_media_quality_medium, R.drawable.ic_media_quality_high, R.drawable.ic_media_quality_super, R.drawable.ic_media_quality_bd }; // 保存Video Url private SparseArray<String> mVideoSource = new SparseArray<>(); // 描述信息 private String[] mMediaQualityDesc; // 分辨率选择布局 private View mFlMediaQuality; // 清晰度 private TextView mIvMediaQuality; // 分辨率选择列表 private ListView mLvMediaQuality; // 分辨率选择列表适配器 private AdapterMediaQuality mQualityAdapter; // 列表数据 private List<MediaQualityInfo> mQualityData; // 是否显示分辨率选择列表 private boolean mIsShowQuality = false; // 当前选中的分辨率 private @MediaQuality int mCurSelectQuality = MEDIA_QUALITY_SMOOTH; @Retention(RetentionPolicy.SOURCE) @Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD}) @IntDef({MEDIA_QUALITY_SMOOTH, MEDIA_QUALITY_MEDIUM, MEDIA_QUALITY_HIGH, MEDIA_QUALITY_SUPER, MEDIA_QUALITY_BD}) public @interface MediaQuality { } /** * 初始化视频分辨率处理 */ private void _initMediaQuality() { mMediaQualityDesc = getResources().getStringArray(R.array.media_quality); mFlMediaQuality = findViewById(R.id.fl_media_quality); mIvMediaQuality = (TextView) findViewById(R.id.iv_media_quality); mIvMediaQuality.setOnClickListener(this); mLvMediaQuality = (ListView) findViewById(R.id.lv_media_quality); mQualityAdapter = new AdapterMediaQuality(mAttachActivity); mLvMediaQuality.setAdapter(mQualityAdapter); mLvMediaQuality.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (mCurSelectQuality != mQualityAdapter.getItem(position).getIndex()) { setMediaQuality(mQualityAdapter.getItem(position).getIndex()); mLoadingView.setVisibility(VISIBLE); start(); } _toggleMediaQuality(); } }); } /** * 切换视频 * * @param mediaSmooth 流畅 * @param mediaMedium 清晰 * @param mediaHigh 高清 * @param mediaSuper 超清 * @param mediaBd 1080P * @return */ public IjkPlayerView switchVideoSource(String mediaSmooth, String mediaMedium, String mediaHigh, String mediaSuper, String mediaBd) { reset(); return setVideoSource(mediaSmooth, mediaMedium, mediaHigh, mediaSuper, mediaBd); } /** * 设置视频源 * * @param mediaSmooth 流畅 * @param mediaMedium 清晰 * @param mediaHigh 高清 * @param mediaSuper 超清 * @param mediaBd 1080P */ public IjkPlayerView setVideoSource(String mediaSmooth, String mediaMedium, String mediaHigh, String mediaSuper, String mediaBd) { boolean isSelect = true; mQualityData = new ArrayList<>(); if (mediaSmooth != null) { mVideoSource.put(MEDIA_QUALITY_SMOOTH, mediaSmooth); mQualityData.add(new MediaQualityInfo(MEDIA_QUALITY_SMOOTH, mMediaQualityDesc[MEDIA_QUALITY_SMOOTH], isSelect)); mCurSelectQuality = MEDIA_QUALITY_SMOOTH; isSelect = false; } if (mediaMedium != null) { mVideoSource.put(MEDIA_QUALITY_MEDIUM, mediaMedium); mQualityData.add(new MediaQualityInfo(MEDIA_QUALITY_MEDIUM, mMediaQualityDesc[MEDIA_QUALITY_MEDIUM], isSelect)); if (isSelect) { mCurSelectQuality = MEDIA_QUALITY_MEDIUM; } isSelect = false; } if (mediaHigh != null) { mVideoSource.put(MEDIA_QUALITY_HIGH, mediaHigh); mQualityData.add(new MediaQualityInfo(MEDIA_QUALITY_HIGH, mMediaQualityDesc[MEDIA_QUALITY_HIGH], isSelect)); if (isSelect) { mCurSelectQuality = MEDIA_QUALITY_HIGH; } isSelect = false; } if (mediaSuper != null) { mVideoSource.put(MEDIA_QUALITY_SUPER, mediaSuper); mQualityData.add(new MediaQualityInfo(MEDIA_QUALITY_SUPER, mMediaQualityDesc[MEDIA_QUALITY_SUPER], isSelect)); if (isSelect) { mCurSelectQuality = MEDIA_QUALITY_SUPER; } isSelect = false; } if (mediaBd != null) { mVideoSource.put(MEDIA_QUALITY_BD, mediaBd); mQualityData.add(new MediaQualityInfo(MEDIA_QUALITY_BD, mMediaQualityDesc[MEDIA_QUALITY_BD], isSelect)); if (isSelect) { mCurSelectQuality = MEDIA_QUALITY_BD; } } mQualityAdapter.updateItems(mQualityData); mIvMediaQuality.setCompoundDrawablesWithIntrinsicBounds(null, ContextCompat.getDrawable(mAttachActivity, QUALITY_DRAWABLE_RES[mCurSelectQuality]), null, null); mIvMediaQuality.setText(mMediaQualityDesc[mCurSelectQuality]); setVideoPath(mVideoSource.get(mCurSelectQuality)); return this; } /** * 选择视频源 * * @param quality 分辨率 * {@link #MEDIA_QUALITY_SMOOTH,#MEDIA_QUALITY_MEDIUM,#MEDIA_QUALITY_HIGH,#MEDIA_QUALITY_SUPER,#MEDIA_QUALITY_BD} * @return */ public IjkPlayerView setMediaQuality(@MediaQuality int quality) { if (mCurSelectQuality == quality || mVideoSource.get(quality) == null) { return this; } mQualityAdapter.setMediaQuality(quality); mIvMediaQuality.setCompoundDrawablesWithIntrinsicBounds(null, ContextCompat.getDrawable(mAttachActivity, QUALITY_DRAWABLE_RES[quality]), null, null); mIvMediaQuality.setText(mMediaQualityDesc[quality]); mCurSelectQuality = quality; if (mVideoView.isPlaying()) { mCurPosition = mVideoView.getCurrentPosition(); mVideoView.release(false); } mVideoView.setRender(IjkVideoView.RENDER_TEXTURE_VIEW); setVideoPath(mVideoSource.get(quality)); return this; } /** * ============================ 跳转提示 ============================ */ // 取消跳转 private ImageView mIvCancelSkip; // 跳转时间 private TextView mTvSkipTime; // 执行跳转 private TextView mTvDoSkip; // 跳转布局 private View mLlSkipLayout; // 跳转目标时间 private int mSkipPosition = INVALID_VALUE; /** * 跳转提示初始化 */ private void _initVideoSkip() { mLlSkipLayout = findViewById(R.id.ll_skip_layout); mIvCancelSkip = (ImageView) findViewById(R.id.iv_cancel_skip); mTvSkipTime = (TextView) findViewById(R.id.tv_skip_time); mTvDoSkip = (TextView) findViewById(R.id.tv_do_skip); mIvCancelSkip.setOnClickListener(this); mTvDoSkip.setOnClickListener(this); } /** * 返回当前进度 * * @return */ public int getCurPosition() { return mVideoView.getCurrentPosition(); } /** * 设置跳转提示 * * @param targetPosition 目标进度,单位:ms */ public IjkPlayerView setSkipTip(int targetPosition) { mSkipPosition = targetPosition; return this; } /** * 显示跳转提示 */ private void _showSkipTip() { if (mSkipPosition != INVALID_VALUE && mLlSkipLayout.getVisibility() == GONE) { mLlSkipLayout.setVisibility(VISIBLE); mTvSkipTime.setText(generateTime(mSkipPosition)); AnimHelper.doSlideRightIn(mLlSkipLayout, mWidthPixels, 0, 800); mHandler.postDelayed(mHideSkipTipRunnable, DEFAULT_HIDE_TIMEOUT * 3); } } /** * 隐藏跳转提示 */ private void _hideSkipTip() { if (mLlSkipLayout.getVisibility() == GONE) { return; } ViewCompat.animate(mLlSkipLayout).translationX(-mLlSkipLayout.getWidth()).alpha(0).setDuration(500) .setListener(new ViewPropertyAnimatorListenerAdapter() { @Override public void onAnimationEnd(View view) { mLlSkipLayout.setVisibility(GONE); } }).start(); mSkipPosition = INVALID_VALUE; } /** * 隐藏跳转提示线程 */ private Runnable mHideSkipTipRunnable = new Runnable() { @Override public void run() { _hideSkipTip(); } }; /** * ============================ 弹幕 ============================ */ /** * 视频编辑状态:正常未编辑状态、在播放时编辑、暂停时编辑 */ private static final int NORMAL_STATUS = 501; private static final int INTERRUPT_WHEN_PLAY = 502; private static final int INTERRUPT_WHEN_PAUSE = 503; private int mVideoStatus = NORMAL_STATUS; // 弹幕格式:B站、A站和自定义 private static final int DANMAKU_TAG_BILI = 701; private static final int DANMAKU_TAG_ACFUN = 702; private static final int DANMAKU_TAG_CUSTOM = 703; @Retention(RetentionPolicy.SOURCE) @Target({ElementType.FIELD, ElementType.PARAMETER}) @IntDef({DANMAKU_TAG_BILI, DANMAKU_TAG_ACFUN, DANMAKU_TAG_CUSTOM}) public @interface DanmakuTag { } private @DanmakuTag int mDanmakuTag = DANMAKU_TAG_BILI; // 弹幕开源控件 private IDanmakuView mDanmakuView; // 弹幕显示/隐藏按钮 private ImageView mIvDanmakuControl; // 弹幕编辑布局打开按钮 private TextView mTvOpenEditDanmaku; // 使能弹幕才会显示的播放进度条 private SeekBar mDanmakuPlayerSeek; // 使能弹幕才会显示时间分割线 private TextView mTvTimeSeparator; // 弹幕编辑布局 private View mEditDanmakuLayout; // 弹幕内容编辑框 private EditText mEtDanmakuContent; // 取消弹幕发送 private ImageView mIvCancelSend; // 发送弹幕 private ImageView mIvDoSend; // 弹幕基础设置布局 private View mDanmakuOptionsBasic; // 弹幕字体大小选项卡 private RadioGroup mDanmakuTextSizeOptions; // 弹幕类型选项卡 private RadioGroup mDanmakuTypeOptions; // 弹幕当前颜色 private RadioButton mDanmakuCurColor; // 开关弹幕颜色选项卡 private ImageView mDanmakuMoreColorIcon; // 弹幕更多颜色设置布局 private View mDanmakuMoreOptions; // 弹幕颜色选项卡 private RadioGroup mDanmakuColorOptions; // 弹幕控制相关 private DanmakuContext mDanmakuContext; // 弹幕解析器 private BaseDanmakuParser mDanmakuParser; // 弹幕加载器 private ILoader mDanmakuLoader; // 弹幕数据转换器 private BaseDanmakuConverter mDanmakuConverter; // 弹幕监听器 private OnDanmakuListener mDanmakuListener; // 是否使能弹幕 private boolean mIsEnableDanmaku = false; // 弹幕颜色 private int mDanmakuTextColor = Color.WHITE; // 弹幕字体大小 private float mDanmakuTextSize = INVALID_VALUE; // 弹幕类型 private int mDanmakuType = BaseDanmaku.TYPE_SCROLL_RL; // 弹幕基础设置布局的宽度 private int mBasicOptionsWidth = INVALID_VALUE; // 弹幕更多颜色设置布局宽度 private int mMoreOptionsWidth = INVALID_VALUE; // 弹幕要跳转的目标位置,等视频播放再跳转,不然老出现只有弹幕在动的情况 private long mDanmakuTargetPosition = INVALID_VALUE; /** * 弹幕初始化 */ private void _initDanmaku() { // 弹幕控制 mDanmakuView = (IDanmakuView) findViewById(R.id.sv_danmaku); mIvDanmakuControl = (ImageView) findViewById(R.id.iv_danmaku_control); mTvOpenEditDanmaku = (TextView) findViewById(R.id.tv_open_edit_danmaku); mTvTimeSeparator = (TextView) findViewById(R.id.tv_separator); mEditDanmakuLayout = findViewById(R.id.ll_edit_danmaku); mEtDanmakuContent = (EditText) findViewById(R.id.et_danmaku_content); mIvCancelSend = (ImageView) findViewById(R.id.iv_cancel_send); mIvDoSend = (ImageView) findViewById(R.id.iv_do_send); mDanmakuPlayerSeek = (SeekBar) findViewById(R.id.danmaku_player_seek); mDanmakuPlayerSeek.setMax(MAX_VIDEO_SEEK); mDanmakuPlayerSeek.setOnSeekBarChangeListener(mSeekListener); int navigationBarHeight = NavUtils.getNavigationBarHeight(mAttachActivity); Log.i("TTAG", "navigationBarHeight -- " + navigationBarHeight); Log.i("TTAG", "navigationBarHeight -- " + NavUtils.checkDeviceHasNavigationBar(mAttachActivity)); if (navigationBarHeight > 0) { // 对于有虚拟键的设备需要将弹幕编辑布局右偏移防止被覆盖 mEditDanmakuLayout.setPadding(0, 0, navigationBarHeight, 0); } mIvDanmakuControl.setOnClickListener(this); mTvOpenEditDanmaku.setOnClickListener(this); mIvCancelSend.setOnClickListener(this); mIvDoSend.setOnClickListener(this); // 这些为弹幕配置处理 int oneBtnWidth = getResources().getDimensionPixelOffset(R.dimen.danmaku_input_options_color_radio_btn_size); // 布局宽度为每个选项卡宽度 * 12 个,有12种可选颜色 mMoreOptionsWidth = oneBtnWidth * 12; mDanmakuOptionsBasic = findViewById(R.id.input_options_basic); mDanmakuMoreOptions = findViewById(R.id.input_options_more); mDanmakuMoreOptions.setOnClickListener(this); mDanmakuCurColor = (RadioButton) findViewById(R.id.input_options_color_current); mDanmakuMoreColorIcon = (ImageView) findViewById(R.id.input_options_color_more_icon); mDanmakuTextSizeOptions = (RadioGroup) findViewById(R.id.input_options_group_textsize); mDanmakuTypeOptions = (RadioGroup) findViewById(R.id.input_options_group_type); mDanmakuColorOptions = (RadioGroup) findViewById(R.id.input_options_color_group); mDanmakuTextSizeOptions.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { if (checkedId == R.id.input_options_small_textsize) { mDanmakuTextSize = 25f * (mDanmakuParser.getDisplayer().getDensity() - 0.6f) * 0.7f; } else if (checkedId == R.id.input_options_medium_textsize) { mDanmakuTextSize = 25f * (mDanmakuParser.getDisplayer().getDensity() - 0.6f); } } }); mDanmakuTypeOptions.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { if (checkedId == R.id.input_options_rl_type) { mDanmakuType = BaseDanmaku.TYPE_SCROLL_RL; } else if (checkedId == R.id.input_options_top_type) { mDanmakuType = BaseDanmaku.TYPE_FIX_TOP; } else if (checkedId == R.id.input_options_bottom_type) { mDanmakuType = BaseDanmaku.TYPE_FIX_BOTTOM; } } }); mDanmakuColorOptions.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { // 取的是 tag 字符串值,需转换为颜色 String color = (String) findViewById(checkedId).getTag(); mDanmakuTextColor = Color.parseColor(color); mDanmakuCurColor.setBackgroundColor(mDanmakuTextColor); } }); } /** * 装载弹幕,在视频按了播放键才装载 */ private void _loadDanmaku() { if (mIsEnableDanmaku) { // 设置弹幕 mDanmakuContext = DanmakuContext.create(); //同步弹幕和video,貌似没法保持同步,可能我用的有问题,先注释掉- - // mDanmakuContext.setDanmakuSync(new VideoDanmakuSync(this)); if (mDanmakuParser == null) { mDanmakuParser = new BaseDanmakuParser() { @Override protected Danmakus parse() { return new Danmakus(); } }; } mDanmakuView.setCallback(new DrawHandler.Callback() { @Override public void prepared() { // 这里处理下有时调用 _resumeDanmaku() 时弹幕还没 prepared 的情况 if (mVideoView.isPlaying() && !mIsBufferingStart) { mDanmakuView.start(); } } @Override public void updateTimer(DanmakuTimer timer) { } @Override public void danmakuShown(BaseDanmaku danmaku) { } @Override public void drawingFinished() { } }); mDanmakuView.enableDanmakuDrawingCache(true); mDanmakuView.prepare(mDanmakuParser, mDanmakuContext); } } /** * 使能弹幕功能 * * @return */ public IjkPlayerView enableDanmaku() { mIsEnableDanmaku = true; _initDanmaku(); if (mIsAlwaysFullScreen) { _toggleDanmakuView(true); } return this; } /** * 使能弹幕功能 * * @param isEnable * @return */ public IjkPlayerView enableDanmaku(boolean isEnable) { mIsEnableDanmaku = isEnable; if (mIsEnableDanmaku) { _initDanmaku(); if (mIsAlwaysFullScreen) { _toggleDanmakuView(true); } } return this; } /** * 设置弹幕资源,默认资源格式需满足 bilibili 的弹幕文件格式, * 配合{@link #setDanmakuCustomParser}来进行自定义弹幕解析方式,{@link #setDanmakuCustomParser}必须先调用 * * @param stream 弹幕资源 * @return */ public IjkPlayerView setDanmakuSource(InputStream stream) { if (stream == null) { return this; } if (!mIsEnableDanmaku) { throw new RuntimeException("Danmaku is disable, use enableDanmaku() first"); } if (mDanmakuLoader == null) { mDanmakuLoader = DanmakuLoaderFactory.create(DanmakuLoaderFactory.TAG_BILI); } try { mDanmakuLoader.load(stream); } catch (IllegalDataException e) { e.printStackTrace(); } IDataSource<?> dataSource = mDanmakuLoader.getDataSource(); if (mDanmakuParser == null) { mDanmakuParser = new BiliDanmukuParser(); } mDanmakuParser.load(dataSource); return this; } /** * 设置弹幕资源,默认资源格式需满足 bilibili 的弹幕文件格式, * 配合{@link #setDanmakuCustomParser}来进行自定义弹幕解析方式,{@link #setDanmakuCustomParser}必须先调用 * * @param uri 弹幕资源 * @return */ public IjkPlayerView setDanmakuSource(String uri) { if (TextUtils.isEmpty(uri)) { return this; } if (!mIsEnableDanmaku) { throw new RuntimeException("Danmaku is disable, use enableDanmaku() first"); } if (mDanmakuLoader == null) { mDanmakuLoader = DanmakuLoaderFactory.create(DanmakuLoaderFactory.TAG_BILI); } try { mDanmakuLoader.load(uri); } catch (IllegalDataException e) { e.printStackTrace(); } IDataSource<?> dataSource = mDanmakuLoader.getDataSource(); if (mDanmakuParser == null) { mDanmakuParser = new BiliDanmukuParser(); } mDanmakuParser.load(dataSource); return this; } /** * 自定义弹幕解析器,配合{@link #setDanmakuSource}使用,先于{@link #setDanmakuSource}调用 * * @param parser 解析器 * @param loader 加载器 * @param converter 转换器 * @return */ public IjkPlayerView setDanmakuCustomParser(BaseDanmakuParser parser, ILoader loader, BaseDanmakuConverter converter) { mDanmakuParser = parser; mDanmakuLoader = loader; mDanmakuConverter = converter; return this; } /** * 显示/隐藏弹幕 * * @param isShow 是否显示 * @return */ public IjkPlayerView showOrHideDanmaku(boolean isShow) { if (isShow) { mIvDanmakuControl.setSelected(false); mDanmakuView.show(); } else { mIvDanmakuControl.setSelected(true); mDanmakuView.hide(); } return this; } /** * 发射弹幕 * * @param text 内容 * @param isLive 是否直播 * @return 弹幕数据 */ public void sendDanmaku(String text, boolean isLive) { if (!mIsEnableDanmaku) { throw new RuntimeException("Danmaku is disable, use enableDanmaku() first"); } if (TextUtils.isEmpty(text)) { Toast.makeText(mAttachActivity, "内容为空", Toast.LENGTH_SHORT).show(); return; } if (!mDanmakuView.isPrepared()) { Toast.makeText(mAttachActivity, "弹幕还没准备好", Toast.LENGTH_SHORT).show(); return; } BaseDanmaku danmaku = mDanmakuContext.mDanmakuFactory.createDanmaku(mDanmakuType); if (danmaku == null || mDanmakuView == null) { return; } if (mDanmakuTextSize == INVALID_VALUE) { mDanmakuTextSize = 25f * (mDanmakuParser.getDisplayer().getDensity() - 0.6f); } danmaku.text = text; danmaku.padding = 5; danmaku.isLive = isLive; danmaku.priority = 0; // 可能会被各种过滤器过滤并隐藏显示 danmaku.textSize = mDanmakuTextSize; danmaku.textColor = mDanmakuTextColor; danmaku.underlineColor = Color.GREEN; danmaku.setTime(mDanmakuView.getCurrentTime() + 500); mDanmakuView.addDanmaku(danmaku); if (mDanmakuListener != null) { if (mDanmakuConverter != null) { mDanmakuListener.onDataObtain(mDanmakuConverter.convertDanmaku(danmaku)); } else { mDanmakuListener.onDataObtain(danmaku); } } } /** * 编辑操作前调用,会控制视频的播放状态,如在编辑弹幕前调用,配合{@link #recoverFromEditVideo()}使用 */ public void editVideo() { if (mVideoView.isPlaying()) { pause(); mVideoStatus = INTERRUPT_WHEN_PLAY; } else { mVideoStatus = INTERRUPT_WHEN_PAUSE; } _hideAllView(false); } /** * 从编辑状态返回,如取消编辑或发射弹幕后配合{@link #editVideo()}调用 * * @return 是否从编辑状态回退 */ public boolean recoverFromEditVideo() { if (mVideoStatus == NORMAL_STATUS) { return false; } if (mIsFullscreen) { _recoverScreen(); } if (mVideoStatus == INTERRUPT_WHEN_PLAY) { start(); } mVideoStatus = NORMAL_STATUS; return true; } /** * 激活弹幕 */ private void _resumeDanmaku() { if (mDanmakuView != null && mDanmakuView.isPrepared() && mDanmakuView.isPaused()) { if (mDanmakuTargetPosition != INVALID_VALUE) { mDanmakuView.seekTo(mDanmakuTargetPosition); mDanmakuTargetPosition = INVALID_VALUE; } else { mDanmakuView.resume(); } } } /** * 暂停弹幕 */ private void _pauseDanmaku() { if (mDanmakuView != null && mDanmakuView.isPrepared()) { mDanmakuView.pause(); } } /** * 切换弹幕的显示/隐藏 */ private void _toggleDanmakuShow() { if (mIvDanmakuControl.isSelected()) { showOrHideDanmaku(true); } else { showOrHideDanmaku(false); } } /** * 切换弹幕相关控件View的显示/隐藏 * * @param isShow 是否显示 */ private void _toggleDanmakuView(boolean isShow) { if (mIsEnableDanmaku) { if (isShow) { mIvDanmakuControl.setVisibility(VISIBLE); mTvOpenEditDanmaku.setVisibility(VISIBLE); mTvTimeSeparator.setVisibility(VISIBLE); mDanmakuPlayerSeek.setVisibility(VISIBLE); mPlayerSeek.setVisibility(GONE); } else { mIvDanmakuControl.setVisibility(GONE); mTvOpenEditDanmaku.setVisibility(GONE); mTvTimeSeparator.setVisibility(GONE); mDanmakuPlayerSeek.setVisibility(GONE); mPlayerSeek.setVisibility(VISIBLE); } } } /** * 从弹幕编辑状态复原界面 */ private void _recoverScreen() { // 清除焦点 mEditDanmakuLayout.clearFocus(); mEditDanmakuLayout.setVisibility(GONE); // 关闭软键盘 SoftInputUtils.closeSoftInput(mAttachActivity); // 重新设置全屏界面UI标志位 _setUiLayoutFullscreen(); if (mDanmakuColorOptions.getWidth() != 0) { _toggleMoreColorOptions(); } } /** * 动画切换弹幕颜色选项卡显示 */ private void _toggleMoreColorOptions() { if (mBasicOptionsWidth == INVALID_VALUE) { mBasicOptionsWidth = mDanmakuOptionsBasic.getWidth(); } if (mDanmakuColorOptions.getWidth() == 0) { AnimHelper.doClipViewWidth(mDanmakuOptionsBasic, mBasicOptionsWidth, 0, 300); AnimHelper.doClipViewWidth(mDanmakuColorOptions, 0, mMoreOptionsWidth, 300); ViewCompat.animate(mDanmakuMoreColorIcon).rotation(180).setDuration(150).setStartDelay(250).start(); } else { AnimHelper.doClipViewWidth(mDanmakuOptionsBasic, 0, mBasicOptionsWidth, 300); AnimHelper.doClipViewWidth(mDanmakuColorOptions, mMoreOptionsWidth, 0, 300); ViewCompat.animate(mDanmakuMoreColorIcon).rotation(0).setDuration(150).setStartDelay(250).start(); } } /** * ============================ 电量、时间、锁屏、截屏 ============================ */ // 电量显示 private ProgressBar mPbBatteryLevel; // 系统时间显示 private TextView mTvSystemTime; // 截图按钮 private ImageView mIvScreenshot; // 电量变化广播接收器 private BatteryBroadcastReceiver mBatteryReceiver; // 锁屏状态广播接收器 private ScreenBroadcastReceiver mScreenReceiver; // 网络变化广播 private NetBroadcastReceiver mNetReceiver; // 判断是否出现锁屏,有则需要重新设置渲染器,不然视频会没有动画只有声音 private boolean mIsScreenLocked = false; // 截图分享弹框 private ShareDialog mShareDialog; // 对话框点击监听,内部和外部 private ShareDialog.OnDialogClickListener mDialogClickListener; private ShareDialog.OnDialogClickListener mInsideDialogClickListener = new ShareDialog.OnDialogClickListener() { @Override public void onShare(Bitmap bitmap, Uri uri) { if (mDialogClickListener != null) { mDialogClickListener.onShare(bitmap, mVideoView.getUri()); } File file = new File(mSaveDir, System.currentTimeMillis() + ".jpg"); try { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file)); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos); bos.flush(); bos.close(); Toast.makeText(mAttachActivity, "保存成功,路径为:" + file.getAbsolutePath(), Toast.LENGTH_SHORT).show(); } catch (IOException e) { Toast.makeText(mAttachActivity, "保存本地失败", Toast.LENGTH_SHORT).show(); } } }; private ShareDialog.OnDialogDismissListener mDialogDismissListener = new ShareDialog.OnDialogDismissListener() { @Override public void onDismiss() { recoverFromEditVideo(); } }; // 截图保存路径 private File mSaveDir; /** * 初始化电量、锁屏、时间处理 */ private void _initReceiver() { mPbBatteryLevel = (ProgressBar) findViewById(R.id.pb_battery); mTvSystemTime = (TextView) findViewById(R.id.tv_system_time); mTvSystemTime.setText(StringUtils.getCurFormatTime()); mBatteryReceiver = new BatteryBroadcastReceiver(); mScreenReceiver = new ScreenBroadcastReceiver(); mNetReceiver = new NetBroadcastReceiver(); //注册接受广播 mAttachActivity.registerReceiver(mBatteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); mAttachActivity.registerReceiver(mScreenReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF)); mAttachActivity.registerReceiver(mNetReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); mIvScreenshot = (ImageView) findViewById(R.id.iv_screenshot); mIvScreenshot.setOnClickListener(this); if (SDCardUtils.isAvailable()) { _createSaveDir(SDCardUtils.getRootPath() + File.separator + "IjkPlayView"); } } /** * 截图 */ private void _doScreenshot() { editVideo(); _showShareDialog(mVideoView.getScreenshot()); } /** * 显示对话框 * * @param bitmap */ private void _showShareDialog(Bitmap bitmap) { if (mShareDialog == null) { mShareDialog = new ShareDialog(); mShareDialog.setClickListener(mInsideDialogClickListener); mShareDialog.setDismissListener(mDialogDismissListener); if (mDialogClickListener != null) { mShareDialog.setShareMode(true); } } mShareDialog.setScreenshotPhoto(bitmap); mShareDialog.show(mAttachActivity.getSupportFragmentManager(), "share"); } /** * 设置截图分享监听 * * @param dialogClickListener * @return */ public IjkPlayerView setDialogClickListener(ShareDialog.OnDialogClickListener dialogClickListener) { mDialogClickListener = dialogClickListener; if (mShareDialog != null) { mShareDialog.setShareMode(true); } return this; } /** * 创建目录 * * @param path */ private void _createSaveDir(String path) { mSaveDir = new File(path); if (!mSaveDir.exists()) { mSaveDir.mkdirs(); } else if (!mSaveDir.isDirectory()) { mSaveDir.delete(); mSaveDir.mkdirs(); } } /** * 设置截图保存路径 * * @param path */ public IjkPlayerView setSaveDir(String path) { _createSaveDir(path); return this; } /** * 接受电量改变广播 */ class BatteryBroadcastReceiver extends BroadcastReceiver { // 低电量临界值 private static final int BATTERY_LOW_LEVEL = 15; @Override public void onReceive(Context context, Intent intent) { // 接收电量变化信息 if (intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) { int level = intent.getIntExtra("level", 0); int scale = intent.getIntExtra("scale", 100); // 电量百分比 int curPower = level * 100 / scale; int status = intent.getIntExtra("status", BatteryManager.BATTERY_HEALTH_UNKNOWN); // SecondaryProgress 用来展示低电量,Progress 用来展示正常电量 if (status == BatteryManager.BATTERY_STATUS_CHARGING) { mPbBatteryLevel.setSecondaryProgress(0); mPbBatteryLevel.setProgress(curPower); mPbBatteryLevel.setBackgroundResource(R.drawable.ic_battery_charging); } else if (curPower < BATTERY_LOW_LEVEL) { mPbBatteryLevel.setProgress(0); mPbBatteryLevel.setSecondaryProgress(curPower); mPbBatteryLevel.setBackgroundResource(R.drawable.ic_battery_red); } else { mPbBatteryLevel.setSecondaryProgress(0); mPbBatteryLevel.setProgress(curPower); mPbBatteryLevel.setBackgroundResource(R.drawable.ic_battery); } } } } /** * 锁屏状态广播接收者 */ private class ScreenBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { mIsScreenLocked = true; } } } private boolean mIsNetConnected; public class NetBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // 如果相等的话就说明网络状态发生了变化 if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { mIsNetConnected = NetWorkUtils.isNetworkAvailable(mAttachActivity); Log.e("TTAGT", "" + mIsNetConnected); // Log.w("NetBroadcastReceiver", ""+NetWorkUtils.isMobileConnected(mAttachActivity)); // Log.d("NetBroadcastReceiver", ""+NetWorkUtils.isWifiConnected(mAttachActivity)); // Log.i("NetBroadcastReceiver", ""+NetWorkUtils.getNetworkTypeName(mAttachActivity)); } } } }