视频播放器+控制器--封装
效果图:
1.切换横竖屏
2.重力感应切换横竖屏
3.判断网络状态
videoview布局:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/root_layout" android:layout_width="match_parent" android:layout_height="match_parent"> <com.easefun.polyvsdk.ijk.IjkVideoView android:id="@+id/videoView" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerInParent="true" /> <RelativeLayout android:id="@+id/controllerLayout" android:layout_width="match_parent" android:layout_height="40dp" android:layout_alignParentBottom="true" android:background="#aa000000"> <ImageView android:id="@+id/play" android:layout_width="wrap_content" android:layout_height="match_parent" android:paddingLeft="6dp" android:paddingRight="6dp" tools:src="@drawable/icon_play" /> <TextView android:id="@+id/currentTime" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_toRightOf="@id/play" android:gravity="center_vertical" android:paddingLeft="5dp" android:paddingRight="5dp" android:textColor="@color/white" android:textSize="12sp" tools:text="00:12" /> <SeekBar android:id="@+id/seekBar" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginRight="80dp" android:layout_toRightOf="@id/currentTime" android:maxHeight="40dp" android:minHeight="40dp" android:progressDrawable="@drawable/player_setting_bright_progressbar" android:thumb="@drawable/seekbar_button" android:thumbOffset="0dp" /> <ImageView android:id="@+id/changeScreen" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_alignParentRight="true" android:paddingLeft="6dp" android:paddingRight="6dp" android:src="@drawable/icon_fullscreen" /> <TextView android:id="@+id/totalTime" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_toLeftOf="@id/changeScreen" android:gravity="center_vertical" android:paddingLeft="5dp" android:paddingRight="5dp" android:textColor="@color/white" android:textSize="12sp" tools:text="05:17" /> </RelativeLayout> </RelativeLayout>
XueHuVideoView类
public class XueHuVideoView extends RelativeLayout { @BindView(R.id.videoView) IjkVideoView videoView; @BindView(R.id.play) ImageView play; @BindView(R.id.currentTime) TextView currentTime; @BindView(R.id.seekBar) SeekBar seekBar; @BindView(R.id.changeScreen) ImageView changeScreen; @BindView(R.id.totalTime) TextView totalTime; @BindView(R.id.controllerLayout) RelativeLayout controllerLayout; private ConnectStateReceiver receiver; private Timer timer; private String videoName; private Handler mHandler; private SimpleDateFormat dateFormat = new SimpleDateFormat("mm:ss"); private String videoPath; private final int FULL = 0; private final int MIN = 1; private int duration; public int webStatus = -1; public XueHuVideoView(Context context) { super(context); initView(context); } public XueHuVideoView(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public XueHuVideoView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } private void initView(Context context) { View view = LayoutInflater.from(context).inflate(R.layout.xuehu_videoview_layout, null); ButterKnife.bind(this, view); addView(view); } private void setVideoListener() { videoView.setOnPreparedListener(new OnPreparedListener() { @Override public void onPrepared(IMediaPlayer iMediaPlayer) { duration = videoView.getDuration(); seekBar.setMax(duration); currentTime.setText("00:00"); totalTime.setText(dateFormat.format(new Date(duration))); } }); seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser) { videoView.seekTo(progress); } } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }); videoView.setOnCompletionListener(new IMediaPlayer.OnCompletionListener() { @Override public void onCompletion(IMediaPlayer iMediaPlayer) { if (duration > 0) { EventBus.getDefault().post(new VideoCompleteEvent()); videoPause(); } } }); } public void videoPause() { if (null != timer) { timer.cancel(); } videoView.pause(); play.setImageResource(R.drawable.icon_play); LoginResponseEntity.Data user = UserData.getUser(); if (null != user && !TextUtils.isEmpty(videoName)) { int currentPosition = videoView.getCurrentPosition(); UserData.saveVideoPlayRecord(String.valueOf(user.getUserId()), videoName.trim(), currentPosition); } } public void videoStart() { if (!TextUtils.isEmpty(videoName)) { if (null != timer) { timer.cancel(); } timer = new Timer(); setVideoPlayRecord(); videoView.start(); play.setImageResource(R.drawable.icon_suspended); timer.schedule(new TimerTask() { @Override public void run() { mHandler.post(new Runnable() { @Override public void run() { seekBar.setProgress(videoView.getCurrentPosition()); currentTime.setText(dateFormat.format(new Date(videoView.getCurrentPosition()))); } }); } }, 0, 1000); } } public void setVideoPath(String url) { setVideoListener(); videoPath = url; videoView.setVideoPath(url); videoStart(); if (videoPath.startsWith("http")) { registerReceiver(); } videoName = url.substring(url.lastIndexOf(File.separator) + 1); } public void registerReceiver() { if (null == receiver) { receiver = new ConnectStateReceiver(this); IntentFilter filter = new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE"); getContext().registerReceiver(receiver, filter); } } public void unRegisterReceiver() { if (null != receiver) { getContext().unregisterReceiver(receiver); receiver = null; } } public void setVideoPlayRecord() { LoginResponseEntity.Data user = UserData.getUser(); if (null != user && !TextUtils.isEmpty(videoName)) { int record = UserData.getVideoPlayRecord(String.valueOf(user.getUserId()), videoName.trim()); if (record > 0) { videoView.seekTo(record); seekBar.setProgress(record); } } } @OnClick({R.id.root_layout, R.id.play, R.id.changeScreen}) public void onClick(View view) { switch (view.getId()) { case R.id.root_layout: controllerLayout.setVisibility(controllerLayout.getVisibility() == View.VISIBLE ? View.INVISIBLE : View.VISIBLE); break; case R.id.play: if (videoView.isPlaying()) { videoPause(); } else { videoStart(); } break; case R.id.changeScreen: if (!TextUtils.isEmpty(videoName)) { int tag = (int) (changeScreen.getTag()); switch (tag) { case FULL: VideoPlayFullActivity.start(getContext(), videoPath); break; case MIN: EventBus.getDefault().post(new FullVideoCloseEvent()); break; } } break; } } public void setHandler(Handler mHandler) { this.mHandler = mHandler; } public void setChangeScreenMin() { changeScreen.setImageResource(R.drawable.icon_switch_panel); changeScreen.setTag(MIN); } public void setChangeScreenFull() { changeScreen.setImageResource(R.drawable.icon_fullscreen); changeScreen.setTag(FULL); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); unRegisterReceiver(); } }
调用(拿全屏播放举例):
public class VideoPlayFullActivity extends Activity { @Autowired String videoPath; @BindView(R.id.videoView) XueHuVideoView videoView; private Handler mHandler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ARouter.getInstance().inject(this); setContentView(R.layout.activity_video_full); ButterKnife.bind(this); EventBusHelp.register(this); videoView.setHandler(mHandler); videoView.setVideoPath(videoPath); videoView.setChangeScreenMin(); ScreenRotateUtil.getInstance(this).start(this); videoView.videoStart(); } @Override protected void onStart() { super.onStart(); videoView.videoStart(); } public static void start(Context context, String videoPath) { Intent starter = new Intent(context, VideoPlayFullActivity.class); starter.putExtra(IntentKey.videoPath, videoPath); if (!(context instanceof Activity)) { starter.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } context.startActivity(starter); } @Override protected void onPause() { super.onPause(); videoView.videoPause(); } @Override protected void onDestroy() { super.onDestroy(); if (null != mHandler) { mHandler.removeCallbacksAndMessages(null); mHandler = null; } ScreenRotateUtil.getInstance(this).stop(); } @Subscribe(threadMode = ThreadMode.MAIN) public void onEvent(FullVideoCloseEvent event) { finish(); } @Subscribe(threadMode = ThreadMode.MAIN) public void onEvent(VideoCompleteEvent event) { finish(); } @Subscribe(threadMode = ThreadMode.MAIN) public void onEvent(VideoCloseEvent event) { finish(); } }
网络状态监听广播
public class ConnectStateReceiver extends BroadcastReceiver { private XueHuVideoView videoView; public ConnectStateReceiver() { super(); } public ConnectStateReceiver(XueHuVideoView videoView) { this.videoView = videoView; } @Override public void onReceive(Context context, Intent intent) { ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Service.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = cm.getActiveNetworkInfo(); if (null != networkInfo) { int type = networkInfo.getType(); if (type != videoView.webStatus) { videoView.webStatus = type; if (type == ConnectivityManager.TYPE_MOBILE) { NetConnectDialog.show(context, videoView); } } } } }
提示对话框
public class NetConnectDialog { public static void show(Context context, final XueHuVideoView videoView) { videoView.videoPause(); AlertDialog alertDialog = new AlertDialog(context).builder(); alertDialog.setCancelable(false); alertDialog.setTitle("流量使用提示"); alertDialog.setMsg("当前网络无Wi-Fi,继续使用可能会被运营商收取流量费用"); alertDialog.setPositiveButton("继续使用", new View.OnClickListener() { @Override public void onClick(View v) { videoView.videoStart(); } }); alertDialog.setNegativeButton("停止使用", new View.OnClickListener() { @Override public void onClick(View view) { EventBus.getDefault().post(new VideoCloseEvent()); } }); alertDialog.show(); } }
重力感应
public class ScreenRotateUtil { private static final String TAG = ScreenRotateUtil.class.getSimpleName(); private ScreenRotateUtil() { } private static ScreenRotateUtil mInstance; private Activity mActivity; private boolean isClickFullScreen; // 记录全屏按钮的状态,默认false private boolean isOpenSensor = true; // 是否打开传输,默认打开 private boolean isLandscape = true; // 默认是竖屏 private boolean isChangeOrientation = true; // 记录点击全屏后屏幕朝向是否改变,默认会自动切换 private boolean isEffetSysSetting = false; // 手机系统的重力感应设置是否生效,默认无效,想要生效改成true就好了 private SensorManager sm; private OrientationSensorListener listener; private Sensor sensor; /** * 接收重力感应监听的结果,来改变屏幕朝向 */ private Handler mHandler = new Handler(Looper.getMainLooper()) { public void handleMessage(Message msg) { if (msg.what == 888) { int orientation = msg.arg1; if (null == mActivity) { return; } /** * 根据手机屏幕的朝向角度,来设置内容的横竖屏,并且记录状态 */ if (orientation > 45 && orientation < 135) { mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE); isLandscape = true; } else if (orientation > 135 && orientation < 225) { mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT); isLandscape = false; } else if (orientation > 225 && orientation < 315) { mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); isLandscape = true; } else if ((orientation > 315 && orientation < 360) || (orientation > 0 && orientation < 45)) { mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); isLandscape = false; } } } }; /** * 初始化,获取实例 * * @param context * @return */ public static ScreenRotateUtil getInstance(Context context) { if (mInstance == null) { synchronized (ScreenRotateUtil.class) { if (mInstance == null) { mInstance = new ScreenRotateUtil(context); } } } return mInstance; } /** * 初始化重力感应传感器 * * @param context */ private ScreenRotateUtil(Context context) { // 初始化重力感应器 sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); sensor = sm.getDefaultSensor(Sensor.TYPE_GRAVITY); listener = new OrientationSensorListener(mHandler); } /** * 重力感应监听者 */ public class OrientationSensorListener implements SensorEventListener { private static final int _DATA_X = 0; private static final int _DATA_Y = 1; private static final int _DATA_Z = 2; public static final int ORIENTATION_UNKNOWN = -1; private Handler rotateHandler; public OrientationSensorListener(Handler handler) { rotateHandler = handler; } public void onAccuracyChanged(Sensor arg0, int arg1) { } public void onSensorChanged(SensorEvent event) { float[] values = event.values; int orientation = ORIENTATION_UNKNOWN; float X = -values[_DATA_X]; float Y = -values[_DATA_Y]; float Z = -values[_DATA_Z]; float magnitude = X * X + Y * Y; // Don't trust the angle if the magnitude is small compared to the y // value if (magnitude * 4 >= Z * Z) { // 屏幕旋转时 float OneEightyOverPi = 57.29577957855f; float angle = (float) Math.atan2(-Y, X) * OneEightyOverPi; orientation = 90 - Math.round(angle); // normalize to 0 - 359 range while (orientation >= 360) { orientation -= 360; } while (orientation < 0) { orientation += 360; } } /** * 获取手机系统的重力感应开关设置,这段代码看需求,不要就删除 * screenchange = 1 表示开启,screenchange = 0 表示禁用 * 要是禁用了就直接返回 */ if (isEffetSysSetting) { try { int isRotate = Settings.System.getInt(mActivity.getContentResolver(), Settings.System.ACCELEROMETER_ROTATION); // 如果用户禁用掉了重力感应就直接return if (isRotate == 0) return; } catch (Settings.SettingNotFoundException e) { e.printStackTrace(); } } // 只有点了按钮时才需要根据当前的状态来更新状态 if (isClickFullScreen) { if (isLandscape && screenIsPortrait(orientation)) { // 之前是横屏,并且当前是竖屏的状态 updateState(false, false, true, true); } else if (!isLandscape && screenIsLandscape(orientation)) { // 之前是竖屏,并且当前是横屏的状态 updateState(true, false, true, true); } else if (isLandscape && screenIsLandscape(orientation)) { // 之前是横屏,现在还是横屏的状态 isChangeOrientation = false; } else if (!isLandscape && screenIsPortrait(orientation)) { // 之前是竖屏,现在还是竖屏的状态 isChangeOrientation = false; } } // 判断是否要进行中断信息传递 if (!isOpenSensor) { return; } if (rotateHandler != null) { rotateHandler.obtainMessage(888, orientation, 0).sendToTarget(); } } } /** * 更新状态 * * @param isLandscape 横屏 * @param isClickFullScreen 全屏点击 * @param isOpenSensor 打开传输 * @param isChangeOrientation 朝向改变 */ private void updateState(boolean isLandscape, boolean isClickFullScreen, boolean isOpenSensor, boolean isChangeOrientation) { this.isLandscape = isLandscape; this.isClickFullScreen = isClickFullScreen; this.isOpenSensor = isOpenSensor; this.isChangeOrientation = isChangeOrientation; } /** * 当前屏幕朝向是否横屏 * * @param orientation * @return */ private boolean screenIsLandscape(int orientation) { return ((orientation > 45 && orientation <= 135) || (orientation > 225 && orientation <= 315)); } /** * 当前屏幕朝向是否竖屏 * * @param orientation * @return */ private boolean screenIsPortrait(int orientation) { return (((orientation > 315 && orientation <= 360) || (orientation >= 0 && orientation <= 45)) || (orientation > 135 && orientation <= 225)); } /** * 根据朝向来改变屏幕朝向 * * @param isLandscape * @param isNeedChangeOrientation 是否需要改变判断值 */ private void changeOrientation(boolean isLandscape, boolean isNeedChangeOrientation) { if (isLandscape) { // 切换成竖屏 mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); if (isNeedChangeOrientation) this.isLandscape = false; } else { // 切换成横屏 mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); if (isNeedChangeOrientation) this.isLandscape = true; } } /** * 开启监听 * 绑定切换横竖屏Activity的生命周期 * * @param activity */ public void start(Activity activity) { mActivity = activity; sm.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_UI); } /** * 注销监听 */ public void stop() { sm.unregisterListener(listener); mActivity = null; // 防止内存泄漏 } /** * 当前屏幕的朝向,是否是横屏 * * @return */ public boolean isLandscape() { return this.isLandscape; } /** * 设置系统横竖屏按钮是否生效,默认无效 * * @param isEffet */ public void setEffetSysSetting(boolean isEffet) { isEffetSysSetting = isEffet; } /** * 旋转的开关,全屏按钮点击时调用 */ public void toggleRotate() { /** * 先判断是否已经开启了重力感应,没开启就直接普通的切换横竖屏 */ if (isEffetSysSetting) { try { int isRotate = Settings.System.getInt(mActivity.getContentResolver(), Settings.System.ACCELEROMETER_ROTATION); // 如果用户禁用掉了重力感应就直接切换 if (isRotate == 0) { changeOrientation(isLandscape, true); return; } } catch (Settings.SettingNotFoundException e) { e.printStackTrace(); } } /** * 如果开启了重力i感应就需要修改状态 */ isOpenSensor = false; isClickFullScreen = true; if (isChangeOrientation) { changeOrientation(isLandscape, false); } else { isLandscape = !isLandscape; changeOrientation(isLandscape, false); } } }
有几个需要注意的坑
1.ijkVideoView的setVideoPath方法是直接播放的,但是setOnPreparedListener准备是需要时间的,如果4G进入视频页,弹出提示对话框时需要暂停播放,但此时还没有准备好的话,你是没办法暂停的,所以的我用的蠢办法是直接退出整个界面,看你还怎么播
2.全屏切换横竖屏时,清单文件需要配置android:configChanges="orientation|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
3.判断网络状态是,因为广播是一直在的,会一直弹出提示对话框,所以需要标识符来记录之前的网络状态,如果变了且是4G,才会弹框
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
2016-08-30 广播接受者判断网络连接状态
2016-08-30 Android手机系统设置页面跳转