Android-视频播放器开发

android中在实际的视频开发中多用即成的框架,或者自己封装相关的Manager来实现;下面介绍一个简单的android视频开发的过程:

  1. 实现网络和本地视频播放
  2. 点击控制播放,暂停
  3. 支持进度条的拖动,实时跟新进度,时间
  4. 支持横竖屏切换
  5. 在横屏状态下,音量键显示并且可调节
  6. 支持手势触摸,即:上下滑动左半屏,弹出dialog,控制屏幕亮度,并且显示进度条;上下滑动右半屏,弹出dialog,控制声音大小,并且显示进度条;

First

首先demo是基于videoView为基础,当然简单的开发,没有过多要求可以用原生的控制器可以可以解决,在这里控制都是自定义;首先为保证横竖屏幕的宽高不适配,需要自定义vieoView重写测量方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class AlpshVideo extends VideoView {
public AlpshVideo(Context context) {
super(context);
}
public AlpshVideo(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AlpshVideo(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//我们重新计算高度
int width = getDefaultSize(0, widthMeasureSpec);
int height = getDefaultSize(0, heightMeasureSpec);
setMeasuredDimension(width, height);
}
}

接着xml中嵌入自定义的vieoView:

1
2
3
4
5
<com.example.video.customView.AlpshVideo
android:id="@+id/videoView"
android:layout_width="match_parent"
android:layout_height="240dp"
/>

在MainActivity中加载本地或者网络视频:

1
2
3
4
5
6
//加载本地
mVideoView.setVideoPath("android.resource://"+getPackageName()+"/"+R.raw.welcome);
mVideoView.start();
//加载网络url
//mVideoView.setVideoURI(Uri.parse("xxxx"));

我们这里以本地为例;ok这样videoView就可以开始比方视频;

自定义控制器—->布局

接下来看代码,xml比较简单,仅展示代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
<?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/activity_main"
android:layout_width="match_parent" android:layout_height="match_parent">
<FrameLayout
android:id="@+id/fr_video"
android:layout_width="match_parent"
android:layout_height="240dp">
<com.example.video.customView.AlpshVideo
android:id="@+id/videoView"
android:layout_width="match_parent"
android:layout_height="240dp"
/>
<LinearLayout
android:orientation="vertical"
android:layout_gravity="bottom"
android:layout_width="match_parent"
android:layout_height="50dp">
<SeekBar
android:id="@+id/media_progress"
android:thumb="@null"
android:progressDrawable="@drawable/seekbar_style2"
android:max="100"
android:progress="20"
android:indeterminate="false"
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_marginLeft="-20dp"
android:layout_marginRight="-20dp"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#101010"
android:gravity="center_vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical">
<ImageView
android:id="@+id/media_actions"
android:layout_marginLeft="15dp"
android:layout_width="20dp"
android:layout_height="match_parent"
android:src="@drawable/stop_btn"/>
<TextView
android:id="@+id/media_time"
android:layout_marginLeft="30dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00:00:00"
android:textColor="#fff"
android:textSize="12sp"/>
<TextView
android:layout_marginLeft="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="/"
android:textColor="#fff"
android:textSize="12sp"/>
<TextView
android:id="@+id/media_total_time"
android:layout_marginLeft="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00:00:00"
android:textColor="#fff"
android:textSize="12sp"/>
<ImageView
android:id="@+id/media_sounds"
android:visibility="gone"
android:layout_marginLeft="100dp"
android:layout_width="20dp"
android:layout_height="match_parent"
android:src="@drawable/icon_sounds"/>
<SeekBar
android:visibility="gone"
android:id="@+id/media_sounds_progress"
android:thumb="@null"
android:progressDrawable="@drawable/seekbar_style"
android:max="100"
android:progress="20"
android:indeterminate="false"
android:layout_width="100dp"
android:layout_height="5dp"
android:layout_marginLeft="-10dp"
/>
</LinearLayout>
<ImageView
android:id="@+id/media_full_screen"
android:layout_marginRight="15dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@drawable/ic_full_screen"/>
</RelativeLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<include layout="@layout/dialog"
android:visibility="gone"/>
</LinearLayout>
</FrameLayout>
</RelativeLayout>

控制器中播放暂停按钮的实现

初始化音量进度条:

1
2
3
4
5
6
7
//获取音频管理器
mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
int streamMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
int streamVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
mMediaSoundsProgress.setMax(streamMaxVolume);
mMediaSoundsProgress.setProgress(streamVolume);

点击事件的普片切换比较简单,通过按钮的图形化切换来控制videoView的开始和暂停:在播放过程中要实时更新播放时间和进度,这里用handler来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void setPlayer() {
mMediaActions.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mVideoView.isPlaying()){
mMediaActions.setImageResource(R.drawable.play_btn);
mVideoView.pause();
mHandler.removeMessages(UpDate);
}else {
mMediaActions.setImageResource(R.drawable.stop_btn);
mVideoView.start();
mHandler.sendEmptyMessage(UpDate);
}
}
});
}

在上面代码中看到,由播放状态改变为暂停状态,实时跟新的hanler停止,切换为播放再次开启;下面看下线程中实时更新的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == UpDate){
//当前时间
int currentPosition = mVideoView.getCurrentPosition();
//总时间
int duration = mVideoView.getDuration();
//设置时间
updateTime(mMediaTime,currentPosition);
updateTime(mMediaTotalTime,duration);
//设置进度
mMediaProgress.setMax(duration);
mMediaProgress.setProgress(currentPosition);
mHandler.sendEmptyMessageDelayed(UpDate,500);
}
}
};

到这一步,视频可以播放,播放进度条实时跟新,播放时间可以实时更新;

当然在一个视频播放器中,必须实现进度条的拖动来实现进度的控制(就是快进和快退);那么需要监听播放进度条的拖动事件:

private void setScrollSeek() {
//播放器的进度条监听
mMediaProgress.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean b) {
updateTime(mMediaTime,progress);
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {
    mHandler.removeMessages(UpDate);
}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {
    int progress = seekBar.getProgress();
    mVideoView.seekTo(progress);
    mHandler.sendEmptyMessage(UpDate);
}

});

横竖屏切换;

VideoView重新测量

手机翻转然后横竖屏自由切换,当然在第一步中有提到自定义videoView重新测量,也是为了横竖屏切换时候做准备,防止半屏显示不全的出现:首先在清单文件中配置:(而且切换横屏之后音量键开始显示)

1
2
3
4
5
6
7
8
9
<activity
android:configChanges="orientation|screenSize|keyboard|keyboardHidden"
android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

这样设置之后可以全屏,但是videoView并不可以适配,所以需要MainActivity中重写onConfigurationChanged:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
//当为横屏时候
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE){
setConfigWh(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);
isFull = true;
//音量键的显示
mMediaSounds.setVisibility(View.VISIBLE);
mMediaSoundsProgress.setVisibility(View.VISIBLE);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
}else{
//当为竖屏时候
setConfigWh(ViewGroup.LayoutParams.MATCH_PARENT, DensityUtil.dp2px(this,240));
isFull = false;
mMediaSounds.setVisibility(View.GONE);
mMediaSoundsProgress.setVisibility(View.GONE);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
}
}

全屏

在竖屏状态下,点击右下角的全屏按钮 也会显示横屏,所以需要设置其点击事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void setFullScreen() {
//设置全屏播放
mMediaFullScreen.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (isFull){
//此时是全屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}else {
//此时是半屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
}
});
}

当这里横竖屏切换和适配就可以正常显示了

音量显示

切换到横屏之后音量键显示,则需要显示器控制音量的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//声音调节进度条
mMediaSoundsProgress.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean b) {
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,progress,0);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});

当这里一个简单的播放器基本完备

手势

接下来来添加手势

大概策略是这样:横屏状态下(需要添加boolean判断来限定横屏之下触发):当上下滑动屏幕左半边,亮度改变,弹出一个类似dialog显示连读进度条的改变,结束触摸,则弹窗消失;当上下滑动屏幕右半边,声音改变,弹出一个类似dialog显示连读进度条的改变,结束触摸,则弹窗消失,在这个过程中控制台区域的声音进度条也会实时改变;

手势添加

先来看手势的添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
private void setScrollScreen() {
mVideoView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
//获取屏幕宽和高
widthPixels = getResources().getDisplayMetrics().widthPixels;
heightPixels = getResources().getDisplayMetrics().heightPixels;
//偏移量的记录
float dx = x - lastX;
float dy = y - lastY;
float absX = Math.abs(dx);
float absY = Math.abs(dy);
if (absX > trueshold && absY > trueshold){
if (absX >= absY){
isadjust = false;
}else if (absY > absX){
isadjust = true;
}
}else if (absX >= trueshold && absY < trueshold){
isadjust = false;
}else if(absX < trueshold && absY >= trueshold){
isadjust = true;
}
//开始判断 音量调节 和 亮度调节
if (isadjust){
//Log.i("==widthPixels",x + "");
if (x > widthPixels/2){
//声音
if (dy > 0){
//降低声音
}else {
//增大声音
}
//改变声音
changeVoice(-dy);
}else if(x < widthPixels/2){
//亮度
if (dy > 0){
//降低亮度
//Log.i("==降低亮度",dy + "");
}else {
//增大亮度
//Log.i("==增大亮度",dy + "");
}
//改变亮度
changeBrightness(-dy);
}
}
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_UP:
mDialogLl.setVisibility(View.GONE);
break;
}
return true;
}
});
}

就是一个onTouch的触摸事件,首先判断手势是否合法,规定一个偏移量,当,当dx dy全部超过偏移量,dy>dx 手势触发 isadjust = true;
当dx<偏移量,dy大于偏移量手势触发 isadjust = true;
触发之后判断 当x > widthPixels/2,在屏幕右半边,触发音量控制
当当x < widthPixels/2,在屏幕左半边,触发屏幕亮度控制

ok~接下来看屏幕亮度变化的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//控制亮度
public void changeBrightness(float absY){
mDialogLl.setVisibility(View.VISIBLE);
WindowManager.LayoutParams attributes = getWindow().getAttributes();
mScreenBrightness = attributes.screenBrightness;
float index = absY / heightPixels;
mScreenBrightness += index;
//进行判断
if (mScreenBrightness > 1.0f){
mScreenBrightness = 1.0f;
}else if (mScreenBrightness < 0.01f){
mScreenBrightness = 0.01f;
}
attributes.screenBrightness = mScreenBrightness;
getWindow().setAttributes(attributes);
//设置弹窗
mDialogPic.setImageResource(R.drawable.icon_media_screen);
mDialogProgress.setMax(10);
mDialogProgress.setProgress((int)(mScreenBrightness * 10));
Log.i("==mScreenBrightness",mScreenBrightness * 100 + "");
}

声音变化的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//控制声音
public void changeVoice(float absY){
mDialogLl.setVisibility(View.VISIBLE);
int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
int volume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
int dVoice = (int) (absY / heightPixels * maxVolume * 3);
int max = Math.max(dVoice + volume, 0);
//设置声音
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,max,0);
mMediaSoundsProgress.setProgress(max);
//设置弹窗
mDialogPic.setImageResource(R.drawable.icon_media_sounds);
mDialogProgress.setMax(maxVolume);
mDialogProgress.setProgress(max);
}

这样就实现声音,屏幕亮度和手势的交互;当然不要忘了结束触摸要隐藏dialog窗口:

1
2
3
case MotionEvent.ACTION_UP:
mDialogLl.setVisibility(View.GONE);
break;

到此为止,一个横竖屏切换自如,快进快退可实现,手势和声音,屏幕亮度可以实时交互,播放信息实时更新的android播放器就完成