1. TextureView
TextureView是在4.0(API level 14)引入的,用于承载显示‘数据流’的View, 如本地Camera采集的预览数据流和视频通话模块从网络包里解出实时视频‘数据流’。
和TextureView相关的一些View有SurfceView、GLSurfaceView、SurfaceTexture,详细说明可参考
1.1. TextureView和SurfaceView
应用程序的视频或者opengl内容往往是显示在一个特别的UI控件中:SurfaceView。
SurfaceView的工作方式是创建一个置于应用窗口之后的新窗口。这种 方式的效率非常高,因为SurfaceView窗口刷新的时候不需要重绘应用程序的窗口(android普通窗口的视图绘制机制是一层一层的,任何一个子元素或者 是局部的刷新都会导致整个视图结构全部重绘一次,因此效率非常低下,不过满足普通应用界面的需求还是绰绰有余),但是SurfaceView也有一些非常 不便的限制。
因为SurfaceView的内容不在应用窗口上,所以不能使用变换(平移、缩放、旋转等)。也难以放在ListView或者ScrollView中,不能使用UI控件的一些特性比如View.setAlpha()。
为了解决这个问上面那个我们刚说到的问题Android 4.0中引入了TextureView;
TextureView与SurfaceView相比,TextureView并没有创建一个单独的Surface用来绘制,这使得它可以像一般的View一样执行一些变换操作,设置透明度等。
1.2. textureView使用
TextureView的使用非常简单,你唯一要做的就是获取用于渲染内容的SurfaceTexture。具体做法是先创建TextureView对象,然后实现SurfaceTextureListener接口
TextureView textureView=findViewById(R.id.textureview);
textureView.setSurfaceTextureListener(surfaceTextureListener);
private TextureView.SurfaceTextureListener surfaceTextureListener=new TextureView.SurfaceTextureListener(){
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
}
};
1.3. TextureView.SurfaceTextureListener
- onSurfaceTextureAvailable
在调用TextureView的draw方法时,如果还没有初始化SurfaceTexture。那么就会初始化它。初始化好时,就会回调这个接口。
SurfaceTexture初始化好时,就表示可以接收外界的绘制指令了(可以异步接收)。接受的方式通常有两种,也就是图中的Surface进行接收。Surface提供dequeueBuffer/queueBuffer等硬件渲染接口,和lockCanvas/unlockCanvasAndPost等软件渲染接口,使内容流的源可以往BufferQueue中填graphic buffer。
然后SurfaceTexture会以GL纹理信息更新到TextureView对应的HardwareLayer中。然后就会在HardwareLayer中显示。 - onSurfaceTextureSizeChanged
视频尺寸改变后调用 - onSurfaceTextureDestroyed
SurfaceTexture即将被销毁时调用
returns true:调用此方法后,在SurfaceTexture内不进行渲染
returns false:需要调用SurfaceTexture.release()
一般都是returns true - onSurfaceTextureUpdated
通过SurfaceTexture.updateteximage()更新指定的SurfaceTexture时调用
1.3. Surface接收外界的视频等数据流
android.view.Surface是一个由屏幕合成程序管理的原始缓冲区句柄
我们如果想播放一个视频,那么player肯定需要一个surface来接收视频数据。这个surface可以是SurfaceView,也可以是textureView对应surface。
mMediaPlayer.setSurface(surface);
TextureView中真正用来接收处理视频流的是SurfaceTexture。new Surface(SurfaceTexture)就得到了对应的surface。
流程
创建textureView,设置和实现SurfaceTextureListener接口。
等待SurfaceTextureListener的回调,回调时,意味着SurfaceTexture准备好了,可以接收player的数据了。那么把对应的surface给到player。启动player的prepare(mediaSource)就可以了。
/**
* 定义TextureView监听类SurfaceTextureListener
* 重写4个方法
*/
private TextureView.SurfaceTextureListener surfaceTextureListener=new TextureView.SurfaceTextureListener(){
/**
* 初始化好SurfaceTexture后回调这个接口
*/
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
surface=new Surface(surfaceTexture);
//开启一个线程去播放视频
new PlayerVideoThread().start();
}
};
2. MediaPlayer
Android的多媒体框架支持各种常见的多媒体类型,这样在程序中可以很容易地集成音频、视频或者图片。Android下对于音频或者视频的支持均需要使用到MediaPlayer类
2.1. 状态图
椭圆表示MediaPlayer对象可能驻留的状态。弧线表示驱动对象状态转换的操作。有两种类型的弧。单箭头的弧表示同步方法调用,而双箭头的弧表示异步方法调用。
2.2. MediaPlayer方法
- void setDataSource(String path) 通过一个具体的路径来设置MediaPlayer的数据源,path可以是本地的一个路径,也可以是一个网络路径
- void setDataSource(Context context, Uri uri) 通过给定的Uri来设置MediaPlayer的数据源,这里的Uri可以是网络路径或是一个ContentProvider的Uri。
- void setDataSource(MediaDataSource dataSource) 通过提供的MediaDataSource来设置数据源
- void setDataSource(FileDescriptor fd) 通过文件描述符FileDescriptor来设置数据源
- int getCurrentPosition() 获取当前播放的位置
- int getAudioSessionId() 返回音频的session ID
- int getDuration() 得到文件的时间
- TrackInfo[] getTrackInfo() 返回一个track信息的数组
- getVideoHeight 视频的高度
- getVideoWidth 视频的宽度
- boolean isLooping () 是否循环播放
- boolean isPlaying() 是否正在播放
- void pause () 暂停
- void start () 开始
- void stop () 停止
- void prepare() 同步的方式装载流媒体文件。
- void prepareAsync() 异步的方式装载流媒体文件。
- void reset() 重置MediaPlayer至未初始化状态。
- void release () 回收流媒体资源。
- void seekTo(int msec) 指定播放的位置(以毫秒为单位的时间)
- void setAudioStreamType(int streamtype) 指定流媒体类型
- void setLooping(boolean looping) 设置是否单曲循环
- void setNextMediaPlayer(MediaPlayer next) 当当前这个MediaPlayer播放完毕后,MediaPlayer next开始播放
- void setWakeMode(Context context, int mode):设置CPU唤醒的状态。
- setScreenOnWhilePlaying 设置是否保持屏幕常亮
- setVolume 设置音量
- setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener) 网络流媒体的缓冲变化时回调
- setOnCompletionListener(MediaPlayer.OnCompletionListener listener) 流媒体播放结束时回调
- setOnErrorListener(MediaPlayer.OnErrorListener listener) 发生错误时回调
- setOnPreparedListener(MediaPlayer.OnPreparedListener listener):当装载流媒体完毕的时候回调。
- setOnVideoSizeChangedListener 视频尺寸监听
3. SeekBar
SeekBar是ProgressBar的扩展,它添加了一个可拖动的拇指。用户可以触摸拇指并向左或向右拖动以设置当前进度级别或使用箭头键。
3.1. SeekBar.OnSeekBarChangeListener
SeekBar可以绑定一个SeekBar.OnSeekBarChangeListener用于监听用户操作。
- onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
进度级别已经更改的通知 - onStartTrackingTouch(SeekBar seekBar)
用户已经开始了一个触摸手势的通知 - onStopTrackingTouch(SeekBar seekBar)
用户已经结束了一个触摸手势的通知
DEMO
activity_main.xml
MainActivity
package com.github.davidji80.videoplayer;
import android.graphics.SurfaceTexture;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.SystemClock;
import android.support.annotation.RequiresApi;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.widget.ImageView;
import android.widget.SeekBar;
public class MainActivity extends AppCompatActivity {
private final String Tag = MainActivity.class.getSimpleName();
//定义一个媒体播发对象
private MediaPlayer mMediaPlayer;
//定义一个缓冲区句柄(由屏幕合成程序管理)
private Surface surface;
//封面
private ImageView videoImage;
//进度条
private SeekBar seekBar;
//为多线程定义Handler
private Handler handler=new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextureView textureView=findViewById(R.id.textureview);
//为textureView设置监听
textureView.setSurfaceTextureListener(surfaceTextureListener);
videoImage=findViewById(R.id.video_image);
seekBar= findViewById(R.id.seekbar);
//为seekbar设置监听
seekBar.setOnSeekBarChangeListener(onSeekBarChangeListener);
}
/**
* 定义一个Runnable对象
* 用于更新播发进度
*/
private final Runnable mTicker = new Runnable(){
@Override
public void run() {
//延迟200ms再次执行runnable,就跟计时器一样效果
handler.postDelayed(mTicker,200);
if(mMediaPlayer!=null&&mMediaPlayer.isPlaying()){
//更新播放进度
seekBar.setProgress(mMediaPlayer.getCurrentPosition());
}
}
};
/**
* 定义一个线程,用于播发视频
*/
private class PlayerVideoThread extends Thread{
@Override
public void run(){
try {
mMediaPlayer= new MediaPlayer();
//把res/raw的资源转化为Uri形式访问(android.resource://)
Uri uri = Uri.parse("android.resource://com.github.davidji80.videoplayer/"+R.raw.ansen);
//设置播放资源(可以是应用的资源文件/url/sdcard路径)
mMediaPlayer.setDataSource(MainActivity.this,uri);
//设置渲染画板
mMediaPlayer.setSurface(surface);
//设置播放类型
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
//播放完成监听
mMediaPlayer.setOnCompletionListener(onCompletionListener);
//预加载监听
mMediaPlayer.setOnPreparedListener(onPreparedListener);
//设置是否保持屏幕常亮
mMediaPlayer.setScreenOnWhilePlaying(true);
//同步的方式装载流媒体文件
mMediaPlayer.prepare();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 当装载流媒体完毕的时候回调
*/
private MediaPlayer.OnPreparedListener onPreparedListener=new MediaPlayer.OnPreparedListener(){
@Override
public void onPrepared(MediaPlayer mp){
//隐藏图片
videoImage.setVisibility(View.GONE);
//开始播放
mMediaPlayer.start();
//设置总进度
seekBar.setMax(mMediaPlayer.getDuration());
Log.e(Tag+"Duration",Integer.toString(mMediaPlayer.getDuration()));
//用线程更新进度
handler.post(mTicker);
}
};
/**
* 流媒体播放结束时回调类
*/
private MediaPlayer.OnCompletionListener onCompletionListener=new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
videoImage.setVisibility(View.VISIBLE);
seekBar.setProgress(0);
//删除执行的Runnable 终止计时器
handler.removeCallbacks(mTicker);
}
};
/**
* 定义TextureView监听类SurfaceTextureListener
* 重写4个方法
*/
private TextureView.SurfaceTextureListener surfaceTextureListener=new TextureView.SurfaceTextureListener(){
/**
* 初始化好SurfaceTexture后调用
* @param surfaceTexture
* @param i
* @param i1
*/
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
surface=new Surface(surfaceTexture);
//开启一个线程去播放视频
new PlayerVideoThread().start();
}
/**
* 视频尺寸改变后调用
* @param surfaceTexture
* @param i
* @param i1
*/
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {
}
/**
* SurfaceTexture即将被销毁时调用
* @param surfaceTexture
* @return
*/
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
surface=null;
mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer=null;
return true;
}
/**
* 通过SurfaceTexture.updateteximage()更新指定的SurfaceTexture时调用
* @param surfaceTexture
*/
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
}
};
/**
* 定义SeekBar监听类OnSeekBarChangeListener
* 重写3个方法
*/
private SeekBar.OnSeekBarChangeListener onSeekBarChangeListener=new SeekBar.OnSeekBarChangeListener() {
/**
* 进度级别已经更改的通知
* @param seekBar
* @param i
* @param b
*/
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
}
/**
* 用户已经开始了一个触摸手势的通知
* @param seekBar
*/
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
//如果在播放中,指定视频播发位置
if(mMediaPlayer!=null&&mMediaPlayer.isPlaying()){
mMediaPlayer.pause();
}
}
/**
* 用户已经结束了一个触摸手势的通知
* @param seekBar
*/
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
Log.e(Tag+"Progress",Integer.toString(seekBar.getProgress()));
//如果在播放中,指定视频播发位置
if(mMediaPlayer!=null&&mMediaPlayer.isPlaying()){
mMediaPlayer.seekTo(seekBar.getProgress());
}else{
mMediaPlayer.seekTo(seekBar.getProgress());
mMediaPlayer.start();
}
}
};
}
代码
https://github.com/DavidJi80/Android
v0.3
参考
https://www.jb51.net/article/137687.htm
https://www.jianshu.com/p/a14955e52216
https://www.jianshu.com/p/4e2916889f27
https://www.jianshu.com/p/864ce5d5e2d8
https://www.jianshu.com/p/4bd3d91c7813
https://www.cnblogs.com/ldq2016/p/5365080.html
https://blog.csdn.net/muziby/article/details/79725326
https://jingyan.baidu.com/article/4853e1e54365561909f72690.html
https://blog.csdn.net/today_work/article/details/79300181
https://www.cnblogs.com/lee0oo0/articles/2557537.html
https://developer.android.google.cn/reference/android/widget/SeekBar.html
https://developer.android.google.cn/reference/android/view/TextureView.SurfaceTextureListener
https://developer.android.google.cn/reference/android/media/MediaPlayer