一步步自定义视频播放器——TextureView使用

上一篇我们知道了SurfaceView的基本使用方法,SurfaceView由于使用的是独立的绘图层,并且使用独立的线程去进行绘制,不能进行Transition,Rotation,Scale等变换,这就导致一个问题SurfaceView在滑动的时候,SurfaceView的刷新由于不受主线程控制导致SurfaceView在滑动的时候会出现黑边的情况。

VideoView继承自SurfaceView,也不能像View一样在列表中滑动。
这篇文章介绍了TextureView,并给了效果图

什么是TextureView

TextureView是在4.0(API level 14)引入的,与SurfaceView相比,它不会创建新的窗口来显示内容。它是将内容流直接投放到View中,并且可以和其它普通View一样进行移动,旋转,缩放,动画等变化。TextureView必须在硬件加速的窗口中使用。现在的移动设备基本都有GPU进行硬件加速渲染。

SurfaceTexture

这篇文章中,我们已经知道了Surface是内存中一段绘图缓冲区
那么SurfaceTexture是什么呢?
SurfaceTexture用来捕获视频流中的图像帧,视频流可以是相机预览或者是视频解码数据。
TextureView可以通过getSurfaceTexture()方法来获取TextureView相应的SurfaceTexture。

因此我们使用TextureView时,首先要获取到用于渲染内容的SurfaceTexture。具体做法是先创建TextureView对象,然后实现SurfaceTextureListener接口

1
2
3
4
5
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.main_layout);
myTexture = new TextureView(this);
myTexture.setSurfaceTextureListener(this);
}

Activity implements了SurfaceTextureListener接口因此activity中需要重写如下方法:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public void onSurfaceTextureAvailable(SurfaceTexture arg0, int arg1, int arg2) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture arg0) {
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture arg0, int arg1,int arg2) {
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture arg0) {
}

这篇文章介绍了TextureView基本使用方法

TextureView与MediaPlayer结合

通过上面对SurfaceTexture的介绍,我们可以理解为:
SurfaceTexture是数据通道,把数据源MediaPlayer中获取到的图像帧数据转化为GL外部纹理。
TextureView是显示层,显示图像帧,从而实现视频播放功能。

由于SurfaceTexture的准备就绪、大小变化、销毁、更新等状态变化时都会回调相对应的方法。
所以我们要等到onSurfaceTextureAvailable被调用时,将SurfaceTexture关联MediaPlayer,作为播放视频的图像数据来源

注意
当遇见有声音无图像的情况时,可能是TextureView未创建,初始化,或者是SurfaceTexture未关联到MediaPlayer。有声音说明MediaPlayer数据源存在,无图像说明显示层TextureView未成功初始化或者未关联到MediaPlayer。

小例子

使用的时候注意要配置权限

1
<uses-permission android:name="android.permission.INTERNET" />

使用步骤

  1. 在布局中添加TextureView
  2. 创建MediaPlayer
  3. 初始化TextureView,并添加setSurfaceTextureListener() 监听
  4. 在onSurfaceTextureAvailable后,初始化Surface,初始化medipalyer
  5. 通过MediaPlayer.setSurface(surface),将MediaPlayer和TextureView关联
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
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_texture_view);
//初始化
mediaPlayer = new MediaPlayer();
textureView = findViewById(R.id.textureview);
textureView.setSurfaceTextureListener(this);
}

@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
mSurface = new Surface(surface);
play();
}

public void play() {
try {
mediaPlayer.reset();//重置
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(URL);

mediaPlayer.setSurface(mSurface);//添加渲染

mediaPlayer.prepareAsync();
mediaPlayer.setOnPreparedListener(this);
} catch (IOException e) {
e.printStackTrace();
}
}

完整代码

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
package com.example.com.myapplication;

import android.graphics.SurfaceTexture;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.Surface;
import android.view.TextureView;

import java.io.IOException;

public class TextureViewActivity extends AppCompatActivity implements TextureView.SurfaceTextureListener, MediaPlayer.OnPreparedListener {
private TextureView textureView;
private Surface mSurface;
private String URL = "http://fairee.vicp.net:83/2016rm/0116/baishi160116.mp4";
private MediaPlayer mediaPlayer;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_texture_view);
mediaPlayer = new MediaPlayer();
textureView = findViewById(R.id.textureview);
textureView.setSurfaceTextureListener(this);
}

public void play() {
try {
mediaPlayer.reset();//重置
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(URL);
mediaPlayer.setSurface(mSurface);
mediaPlayer.prepareAsync();
mediaPlayer.setOnPreparedListener(this);
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
mSurface = new Surface(surface);
play();
}

@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

}

@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
surface = null;
mSurface = null;
mediaPlayer.stop();
mSurface.release();
return true;
}

@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {

}

@Override
public void onPrepared(MediaPlayer mp) {
mp.start();
}

@Override
protected void onPause() {
super.onPause();
if (mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
}

@Override
protected void onDestroy() {
super.onDestroy();
//释放资源
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.stop();
mediaPlayer.release();
}
}
}

布局文件

1
2
3
4
5
6
7
8
9
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextureView
android:id="@+id/textureview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>

效果:

0%