Android-自定义播放器
来源:互联网 发布:深圳软件学校 编辑:程序博客网 时间:2024/06/08 17:37
说到视频播放,Android中提供了VideoView控件来实现,但是我们会发现我们市面上的所有的有关于视频播放的APP都不会单纯的使用VideoView控件来做视频播放,为什么呢?因为VideoView它实现了对于视频的播放,但是控制方式单一,并且样式普通,如果大家都使用的话,就不能吸引用户的注意力。所以我们就要使用自定义视频播放器。我们先说下Android中自带的VideoView是怎么使用的吧。
VideoView
使用VideoView传入视频的源路径有两种方法,分别是本地视频播放以及网络视频播放。
本地视频:
private void videolocal() { String path = "视频地址"; videoView.setVideoPath(path); videoplay();}
网络视频:
private void videoonline() {videoView.setVideoURI(Uri.parse("网络地址")); videoplay();}
然后我们对VideoView控件初始化完成以后,然后我们再对MediaController类(提供了一个悬浮的操作栏,包含了播放,暂停,快进,快退,上一个,下一个等功能键)的属性进行设置,然后把这个操作栏与VideoView互相绑定,然后调用VideoView的start方法就可以实现播放了。
private void videoplay() { MediaController controller = new MediaController(this); videoView.setMediaController(controller); controller.setMediaPlayer(videoView); videoView.start();//自动播放视频}
实现出来的效果我就不截图了,大家试下就知道了,接下来我就转到正题是怎么写出应该自己想要的视频播放器了。
自定义播放器
我们这里还是要借用Android中自带的VideoView来实现播放,因为这个控件已经封装好了所有的对于视频的解码以及播放等功能,那我们在哪里进行自定义呢?我们可以把控制视频播放以及控制一些参数的控件进行重新编写,我们接下来就介绍下,我们自己写我们想要的开始/暂停按钮以及视频播放的滚动条以及当前视频播放的时间的显示。
布局显示
我们先介绍下我们的布局吧。
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <RelativeLayout android:layout_width="match_parent" android:layout_height="240dp"> <VideoView android:id="@+id/id_VideoView" android:layout_width="match_parent" android:layout_height="240dp" /> <LinearLayout android:id="@+id/controllerbar_layout" android:layout_width="match_parent" android:layout_height="50dp" android:layout_alignParentBottom="true" android:orientation="vertical"> <SeekBar android:id="@+id/play_seek" android:layout_width="match_parent" android:layout_height="5dp" android:layout_marginLeft="-20dp" android:layout_marginRight="-20dp" android:indeterminate="false" android:max="100" android:progress="20" android:progressDrawable="@drawable/seekbar_style2" android:thumb="@null" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="#101010" android:gravity="center_vertical"> <LinearLayout android:id="@+id/left_layout" android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center_vertical" android:orientation="horizontal"> <ImageView android:id="@+id/pause_img" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="16dp" android:src="@drawable/pause_btn_style" /> <TextView android:id="@+id/time_current_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="32dp" android:text="00:00:00" android:textColor="#ffffff" android:textSize="14sp"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="5dp" android:text="/" android:textColor="#4c4c4c" android:textSize="14sp"/> <TextView android:id="@+id/time_total_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="5dp" android:text="00:00:00" android:textColor="#4c4c4c" android:textSize="14sp"/> </LinearLayout> <LinearLayout android:layout_width="10dp" android:layout_height="match_parent" android:layout_alignParentRight="true" android:layout_toRightOf="@+id/left_layout" android:gravity="center_vertical|right" android:orientation="horizontal"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/volume" android:visibility="gone"/> <SeekBar android:id="@+id/volume_seek" android:layout_width="100dp" android:layout_height="5dp" android:indeterminate="false" android:max="100" android:progress="20" android:progressDrawable="@drawable/seekbar_style" android:thumb="@null" android:visibility="gone"/> <View android:background="#1E1E1E" android:layout_marginLeft="32dp" android:layout_width="1dp" android:layout_height="match_parent" android:layout_marginBottom="5dp" android:layout_marginTop="5dp"/> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/screen_img" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" android:src="@drawable/full_screen"/> </LinearLayout> </RelativeLayout> </LinearLayout> </RelativeLayout></RelativeLayout>
我们通过SeekBar来实现对视频播放条的调整,但是我们不想用系统中自带的SeekBar样怎么办,那我们就要使用Android中对控件的美化了,对于这部分可以看我的另一篇博客:传送门。里面详细的介绍了shape的属性,我们这里就主要改变播放进度条的颜色和宽度所以我们就只要用solid以及size属性就好了。
<?xml version="1.0" encoding="utf-8"?><layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@android:id/background"> <shape> <solid android:color="#cfbfc0"/> <size android:height="5dp"/> </shape> </item> <item android:id="@android:id/progress"> <clip> <shape> <solid android:color="#ff6200"/> <size android:height="5dp"/> </shape> </clip> </item></layer-list>
然后通过SeekBar的progressDrawable的属性把这个xml文件传入就好了。
接下来我们想要按钮在按的时候显示出不用的颜色怎么办,那也是用控件美化就好了,但是因为是对不同状态的改变所以我们就要用到selector属性,然后指定什么状态显示什么图片就好了。
<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/play_pressed" android:state_pressed="true" /> <item android:drawable="@drawable/play" /></selector>
然后我们就是通过ImageView中src把该xml文件传入就好了。其他控件的处理也是差不多的,我就不累赘的讲述了,如果有不理解的可以给我留言。接下来我们就来说重点了,怎么实现我们自定义的控件对视频的操作。
播放/暂停 逻辑
我们就只要通过对VideoView的pause以及start方法的调用就可以实现了,但是因为我们相应的图片要发生相应的改变,我是这样处理的,我们监听该控件的OnClick方法,然后我们判断当前VideoView是处于什么状态来相应的改变控件的样式。
if(videoView.isPlaying()){ play_controller_img.setImageResource(R.drawable.play_btn_style); //暂停播放 videoView.pause();}else{ play_controller_img.setImageResource(R.drawable.pause_btn_style); //播放 videoView.start();}
当前视频播放时间以及进度
一般我们看视频都会看到视频当前已经播放到什么时间了,所以我们是怎么实现的呢,以为我们通过获得当前VideoView播放的时间是多少,然后我们再通过对时间进行格式化再把它显示到控件当中去。但是我们在这里要注意到我们播放的时间以及SeekBar的进度是时时刻刻都在播放的,所以我们要每隔一段时间就要重新绘制一边控件,所以这里我们就要用到Handler了,我们新建应该Handler,然后在Handler中每个500ms重新调用一遍自身就好了,这样的话就在不暂停或者退出的情况下就会每个500ms就重新调用格式化时间的代码了。
//实时刷新UIprivate Handler UIHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); if(msg.what==UPDATEUI){ int currentPosition = videoView.getCurrentPosition();//获取当前视频的播放时间 int totalduration = videoView.getDuration();//获取视频播放的总时间 updateTextViewWithTimeFormat(time_current_tv,currentPosition); updateTextViewWithTimeFormat(time_total_tv,totalduration); play_seek.setMax(totalduration); play_seek.setProgress(currentPosition); UIHandler.sendEmptyMessageDelayed(UPDATEUI,500);//自己给自己刷新 } }};//格式化时间private void updateTextViewWithTimeFormat(TextView textView,int millisecond){ int second = millisecond/1000;//传入是毫秒 int hh = second/3600; int mm=second%3600/60; int ss=second%60; String str = null; if(hh!=0){ str=String.format("%02d:%02d:%02d",hh,mm,ss); } else{ str=String.format("%02d:%02d",mm,ss); } textView.setText(str);}
这里有几个点一定要注意,我们获取视频的时间是返回的毫秒,并且我们在点击暂停/开始按钮以及关闭应用的时候我们就不能再刷新UI了,或者要重新开始刷新UI,所以我们要在onDestroy方法以及判断要暂停播放的时候加上这个代码来移除Handler。
//停止UI刷新UIHandler.removeMessages(UPDATEUI);
然后在开始的时候调用:
//恢复UI刷新UIHandler.sendEmptyMessage(UPDATEUI);
接下来我们就来说什么让拖动SeekBar来达到视频快进或者后退的方法吧,我们通过监听SeekBar的setOnSeekBarChangeListener,然后我们通过他给的三个方法分别是开始拖动,结束拖动,拖动过程,来改变视频的播放以及时间的显示。
play_seek.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {//拖动过程 updateTextViewWithTimeFormat(time_current_tv,progress); } @Override public void onStartTrackingTouch(SeekBar seekBar) {//开始拖动 UIHandler.removeMessages(UPDATEUI); } @Override public void onStopTrackingTouch(SeekBar seekBar) {//结束拖动 int progress = seekBar.getProgress(); videoView.seekTo(progress); UIHandler.sendEmptyMessage(UPDATEUI); }});
记住要取消和重启Handler就好了,然后过程中不要刷新视频就改变时间就好了。然后获取到了拖动结束的时间然后我们调用VideoView的seekTo方法,传入当前SeekBar的刻度就好了(因为我们的SeekBar的最大刻度就是视频的总时长所以我们直接使用这个值)。
然后现在我们就能达到下面这个效果了。
横竖屏切换
有时候我们当手机变为横屏的时候让视频全屏显示,这样看起来就比较好看了,使所以我们要实现这个功能的话我们就要先给活动设定一个manifests属性,要不然旋转屏幕以后就会重新刷新这个活动了,在activity下新增一个属性configChanges,接下来我们来解释下configChanges吧。
所以我们想要达到不刷新的效果就只要加上下面这些属性就好了。
android:configChanges="orientation|screenSize|keyboard|keyboardHidden"
然后我们会发现屏幕的显示在旋转以后不是特别好看,是因为我们固定了VideoView的宽高,所以我们要动态的改变VideoView的大小,所以我们就要利用LayoutParams,我们先获取到VideoView以及RelativeLayout的LayoutParams,然后改变LayoutParams中的宽高的值,在把LayoutParams设置回去就好了,反之也是这样的。
@Overridepublic void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if(getResources().getConfiguration().orientation==Configuration.ORIENTATION_LANDSCAPE){//当屏幕方向为横屏的时候 setVideoViewScale(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT); } else{ setVideoViewScale(ViewGroup.LayoutParams.MATCH_PARENT,DensityUtil.dip2px(this,240)); }}private void setVideoViewScale(int width,int height){ ViewGroup.LayoutParams layoutParams = videoView.getLayoutParams(); layoutParams.width=width; layoutParams.height=height; videoView.setLayoutParams(layoutParams); ViewGroup.LayoutParams ViewlayoutParams = videoLayout.getLayoutParams(); ViewlayoutParams.width=width; ViewlayoutParams.height=height; videoLayout.setLayoutParams(ViewlayoutParams);}
但是虽然只有实现了全屏的显示,但是不能做到视频居中,这里就要用到自定义VideoView了。
全屏按钮
有时候我们把自动旋转屏幕关掉的时候我们想看全屏的视频的时候就会用到全屏按钮了,这个就是我们先定义一个变量,表示现在是处于横屏还是竖屏状态,然后按情况手动旋转就好了,然后视频播放就是通过之前写的判断横竖屏相应的视频播放就好了,在自动的情况下记得修改好变量。
case R.id.screen_img: if(isFullScreen){ isFullScreen=false; setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);//切换竖屏 } else{ isFullScreen=true; setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);//切换横屏 } break;
音量控制
我们要先获得一个AudioManager实例,获取系统的声音控制,然后我们通过AudioManager中的方法获得系统最大音量和当前音量就好了,然后再监听volume_seek的SeekBar的监听器就好了,然后当滑块移动的时候调用AudioManager的setStreamVolume方法就好了。
先在initView中获取声音控制:
mAudioManager=(AudioManager)getSystemService(AUDIO_SERVICE);int streamMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);//最大音量int streamVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);volume_seek.setMax(streamMaxVolume);volume_seek.setProgress(streamVolume);
再新增SeekBar的监听事件:
volume_seek.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,progress,0);//设置当前音量 } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { }});
效果如下:
自定义VideoView
我们对于这个的全屏的效果还是不满意,因为视频分辨率的关系,视频的显示在全屏状态下还是有点怪的,因为有黑边。在上面我们已经说了想达到我们满意的效果就要用自定义VideoView,我们想要什么效果呢,肯定是全屏的时候要整个屏幕不要存在黑边,然后左半边屏幕可以通过滑动改变亮度,右半边可以滑动改变音量,就是这样的效果:
我们怎么实现呢?我们新建一个类,让这个类继承VideoView,然后我们项目中全部的VideoView全部换成该类,然后我们实现CustomVideoView的构造方法以及重写onMeasure方法,这个方法就是改变VideoView的大小,我们在在里面强行改变他的大小就好了。
package com.xjh.gin.myvideoplayers;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.widget.VideoView;/** * Created by Gin on 2017/12/24. */public class CustomVideoView extends VideoView { int defaultWidth = 1920; int defaultHeight = 1080; public CustomVideoView(Context context) { super(context); } public CustomVideoView(Context context, AttributeSet attrs) { super(context, attrs); } public CustomVideoView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//强行改变大小 super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width=getDefaultSize(defaultWidth,widthMeasureSpec); int height = getDefaultSize(defaultHeight,heightMeasureSpec); //Log.e("Main","width="+width+"height"+height); setMeasuredDimension(width,height); }}
然后我们在MainActivity中的旋转屏幕的方法中加上设置移除半屏或者全屏就好了。
public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if(getResources().getConfiguration().orientation==Configuration.ORIENTATION_LANDSCAPE){//当屏幕方向为横屏的时候 setVideoViewScale(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT); volume_img.setVisibility(View.VISIBLE); volume_seek.setVisibility(View.VISIBLE); isFullScreen=true; getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);//移除半屏 getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);//设置全屏 } else{ setVideoViewScale(ViewGroup.LayoutParams.MATCH_PARENT,DensityUtil.dip2px(this,240)); volume_img.setVisibility(View.GONE); volume_seek.setVisibility(View.GONE); isFullScreen=false; getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);//设置半屏 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);//移除全屏 }}
这样的话我们就实现了我们想要的全屏效果了,然后我们想要滑动控制屏幕亮度以及音量怎么办,我们先新建一个FrameLayout布局的XML文件,因为平常不要显示并且需要图像的覆盖,所以要用FrameLayout以及给FrameLayout设置visibility属性,这样就可以通过代码来控制显示的与否了。
<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/progress_layout" android:visibility="gone" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:layout_marginTop="-50dp" android:background="#00000000"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/operation_bg" android:layout_gravity="center" android:src="@drawable/video_voice_bg"/> <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|center" android:paddingBottom="25dp"> <ImageView android:src="@drawable/video_bg" android:layout_width="94dp" android:layout_height="wrap_content" android:layout_gravity="left" android:scaleType="fitXY"/> <ImageView android:id="@+id/operation_percent" android:src="@drawable/video_front" android:layout_width="94dp" android:layout_height="wrap_content" android:layout_gravity="left" android:scaleType="fitXY"/> </FrameLayout></FrameLayout>
我们来说下ImageView的scaleType属性吧。scaleType属性控制了图片在view中的显示方式,有下面这几种属性:
因为我们这个只是表示声音或者亮度的大小,就是一个白条,所以我们就拉伸图片充满整个ImageView就好了,所以用的是fitXY。
然后我们就是在音量或者亮度变化的时候把这个FrameLayout显示出来就好了,但是要改变FrameLayout中音量条或者亮度条的显示长度就是需要计算了,因为我们的进度条是94dp所以我们就用这个比例以及判断手指滑动的位置和长度来来判断是改变音量还是亮度或者是无效滑动并且按比例改变长度就好了。注意向下滑动的时候这个值是正数,所以我们在加减音量以及亮度的时候我们要取反就好了,在initEvent方法的代码中加入下面监听滑动的代码:
//控制VideoView的手势videoView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { float x = event.getX(); float y = event.getY(); switch(event.getAction()){ case MotionEvent.ACTION_DOWN://手指落下屏幕的那一刻(只会调用一次) StartX=x; StartY=y; break; case MotionEvent.ACTION_MOVE://手指在屏幕移动(调用多次) float detlaX=x-StartX; float detlaY=y-StartY;//手指滑动的偏移量 float absdetlaX=Math.abs(detlaX);//偏移量的绝对值 float absdetlaY=Math.abs(detlaY); if(absdetlaX>threshold&&absdetlaY>threshold){ if(absdetlaX<absdetlaY){ isAdjust=true; } else{ isAdjust=false; } } else if(absdetlaX<threshold&&absdetlaY>threshold){ isAdjust=true; } else if(absdetlaX>threshold&&absdetlaY<threshold){ isAdjust=true; } if(isAdjust){ if(x<screen_width/2){ changeBrightness(-detlaY); } else{ changeVolume(-detlaY); } } StartX=x; StartY=y; break; case MotionEvent.ACTION_UP://手指离开屏幕的那一刻(只会调用一次) progress_layout.setVisibility(View.GONE); break; } return true; }});
然后编写下面改变音量以及亮度的代码,我们都是通过AudioManager以及WindowManager来改变得。根据传来的偏移量来计算改变的值,这个值可以按照大家的习惯进行调节。然后我们在移动事件开始的时候让之前的FrameLayout显示出来,结束的时候关闭就好了。
//调节音量private void changeVolume(float detlaY){ int max=mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); int current=mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); int addvolume=(int)((detlaY/screen_height)*max*3); int volume=Math.max(0,current+addvolume); mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,volume,0); volume_seek.setProgress(volume); //图片 operation_bg.setImageResource(R.drawable.video_voice_bg); ViewGroup.LayoutParams layoutParams = operation_percent.getLayoutParams(); layoutParams.width= (int) (DensityUtil.dip2px(this,94)*(float)volume/max); operation_percent.setLayoutParams(layoutParams); if(progress_layout.getVisibility()==View.GONE){ progress_layout.setVisibility(View.VISIBLE); }}//调节亮度private void changeBrightness(float detlaY){ WindowManager.LayoutParams attributes = getWindow().getAttributes(); mBrightness=attributes.screenBrightness; float addBrightness=detlaY/screen_height/3; mBrightness+=addBrightness; if(mBrightness>1.0f){ mBrightness=1.0f; } if(mBrightness<0.01f){ mBrightness=0.01f; } attributes.screenBrightness=mBrightness; getWindow().setAttributes(attributes); //图片 operation_bg.setImageResource(R.drawable.video_brightness_bg); ViewGroup.LayoutParams layoutParams = operation_percent.getLayoutParams(); layoutParams.width= (int) (DensityUtil.dip2px(this,94)*mBrightness)*3; operation_percent.setLayoutParams(layoutParams); if(progress_layout.getVisibility()==View.GONE){ progress_layout.setVisibility(View.VISIBLE); }}
这个项目的GitHub地址:传送门
- android自定义播放器
- Android-自定义播放器
- android 自定义播放器播放视频
- android自定义视频播放器
- Android自定义音乐播放器
- Android自定义视频播放器
- android 自定义MP4播放器
- Android自定义一个播放器控件
- android surfaceView+mediaPlayer 自定义视频播放器
- Android自定义视频播放器(网络/本地)
- Android视频播放器Exoplayer自定义
- Android中自定义VideoView视频播放器
- android 自定义音效播放
- html5 自定义播放器
- 自定义视频播放器
- 自定义音频播放器
- 自定义播放器
- 自定义的播放器
- tensorflow gpu版本读取cifar10-binary出现类似卡死状态
- Java线程——(2)线程的管理(下)
- 斯坦福大学机器学习作业题Problem Set #1 Regression for denoising quasar spectra 上篇
- <在minecraft中创造一个寻宝游戏>-列表-频率
- 斯坦福大学机器学习作业题Problem Set #1 Regression for denoising quasar spectra 下篇
- Android-自定义播放器
- mongodb最详细的安装与配置
- Spring boot入门,整合mybatis开发案例
- Codeforces Testing Round #14 (Unrated) C. Minimum Sum
- JavaScript 继承---借用构造函数
- VS中如何添加自定义代码片段——偷懒小技巧
- USB协议介绍[6]-描述符和设备类
- C++经典习题
- USB协议介绍[7]-协议层(完)