Android播放器开发基础(一)
- MediaPlayer
- Android原生VideoView
- SurfaceView基础
- 播发器示例及列表播放器
Android中播放器的开发主要是在原生的MediaPayer组件或者第三方SDK的基础上进行功能和界面的开发。而第三方的SDK的方法基本和原生MediaPlayer一致,因为会考虑的和原生的使用一致。MediaPlayer组件上对解码、状态、响应做了处理,内部也基本是使用了主流的播放器的内核,如FFmpeg解码器,通过内核解码不同的视音频格式的媒体转换为图像、音频流,图像再通过如SurfaceView这样的控件显示绘制出来,用户通过在例如VideoView这样的控件上操作控制播放过程。
1.MediaPlayer的状态
1.1 MediaPlayer中使用了状态机模式,包括10种状态,以及一系列操作,每个操作都可能影响到当前的状态。
实际开发过程中,只需要知道使用MediaPlayer播放器就可以了,如果还要开发视频则还需要了解VideoView甚至SurfaceView。MediaPlayer使用了状态机设计模式,操作以后MediaPlayer或到达一个状态,这个状态也限制了下一步的操作方式,所以要使用MediaPlayer首先需了解其10个基本状态,以及对应的操作方法,如果在不同的状态使用了一些不支持的操作则会造成MediaPlayer异常。
状态 | 含义 |
Idle | 空闲状态,刚被new出来或者reset()操作过。 |
Initialized | 初始化状态,已经被设置过,使用了setDataResource()会到这个状态。 |
Prepared | 准备完成状态,调用prepare()或prepareAsync()方法后,表示准备好了,等待播放。 |
Preparing | prepare()在异步操作prepareAsync()时的过度状态,正在准备中状态,如果准备完成后会回调OnPrepareCompleted()。 |
Started | 播放状态,调用start()方法以后开始播放。 |
Paused | 暂停中状态,调用pause()方法后暂停播放。 |
Stoped | 停止状态,调用stop()方法后停止播放,如果需要重新播放则需要使用prepare()方法重新准备再start()。 |
PlayBackCompleted | 播放完成状态,如果没有设置循环播放则在播放完成以后会触发onCmpletion()同时进入到这个状态,如果需要再次进入播放状态,直接start()就可以了 |
End | 结束状态,在使用完成MediaPlayer以后,主动调用release()释放资源以后就会处于End状态,通过reset()重新进入Idle状态。 |
Error | 错误状态,当播放过程中发生错误或者由于在不同的状态中做了一些非法的操作都会触发onError()同时进入到Error状态,如果需要再次播放,则需要reset()操作。 |
视频获取第一帧预览图
private Bitmap createVideoThumbnail2(String url, int width, int height) {
Bitmap bitmap = null;
// MediaMetadataRetriever获取帧的管理类
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
int kind = MediaStore.Video.Thumbnails.MINI_KIND;
try {
if (Build.VERSION.SDK_INT >= 14) {
// 设置数据源
retriever.setDataSource(url, new HashMap<String, String>());
} else {
retriever.setDataSource(url);
}
// 获取帧
bitmap = retriever.getFrameAtTime();
} catch (IllegalArgumentException ex) {
// Assume this is a corrupt video file
} catch (RuntimeException ex) {
// Assume this is a corrupt video file.
} finally {
try {
retriever.release();
} catch (RuntimeException ex) {
// Ignore failures while cleaning up.
}
}
if (kind == Images.Thumbnails.MICRO_KIND && bitmap != null) {
bitmap = ThumbnailUtils.extractThumbnail(bitmap, width, height, ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
}
return bitmap;
}
2. Android原生VideoView
播放器开发分为视频播放器和音频播放器,前者是需要同时绘制画面到屏幕上,后者则只是进行音频播放。视频播放中,Android通过SurfaceView来绘制画面。SurfaceView作为一个特殊的View,其内部内嵌了使用异步刷新的Surface来绘制画面,所以SurefaceView 是不会阻塞UI线程的,有时在绘制复杂的界面时也会通过SurfaceView绘制,以防止出现ANR,提高用户体验。在Android中提供了一个通用的控件VideoView,其继承了SurfaceView并封装了对MediaPlayer的操作,简单播放器开发就可以直接使用VideoView。
VideoView仅支持Android原生的MediaPlayer,而在很多情况下MediaPlayer的支持的媒体类型或媒体格式版本不能满足实际开发需求,如不支持Rtmp实时流,这样一来需要使用第三方解码器,而且还需要自定义一个VideoView来兼容当前解码器
2.1 VideoView的结构
VideoView继承了SurfaceView,所以它是一个可以进行图像绘制的控件,实现了MediaPlayerControl接口——一个MediaContoller的内部接口,而VideoView中包含MediaController,这样一来用户在操作MediaController的时候可以传递到VideoView中,再进一步通过MediaPlayer处理。
实际上VideoView既是一个显示控件,也是一个处理交互的控制器,同时为了进一步视图和控制器分离,将进度条、控制按钮都放到MedaiContoller中,通过MediaPlayerController接口来回调SurfaceView,形成了一个MVC的模式
(引用其他的博客)
3. SurfaceView基础
SurfaceView 是继承View的,但是其内部封装了一个Surface异步显示控件,Surface可以绘制图像,绘制的同时也不占用UI线程。
主要类:
- SurfaceView
内部包含了SurfaceHolder以及Surface,SurfaceView控制这个Surface在屏幕中绘制的位置
- SurfaceHolder
一个用于控制Surface的接口,用于控制Surface的尺寸位置,监听Surface变化以及改变Surface的像素
- Surface
Surface是原始图像缓冲区的一个句柄,而原始图像缓冲区是屏幕图像合成器管理的,S>urface内部封装了Canvas画布,可以通过这个画布得到当前绘制的图案
- Canvas
// 获取surfaceHolder
SurfaceHolder surfaceHolder = SurfaceView.getHolder();
// surface 获取到SurfaceView中的Surface
Surface surface = surfaceHolder.getSurface();
// Canvas
Rect rect = new Rect(100,50,400,350);
Canvas canvas = surface.lockCanvas(rect);
surfaceHolder.unlockCanvasAndPost(canvas);
SurfaceView是用来显示和控制Surface的,而SurfaceHolder是暴露给用户操作Surface的,在视频开发中使用MediaPlayer解码通过Surface显示视频内容,MediaPlayer绑定Surface可以通MediaPlayer.setDisplay(SurfaceHolder或者MediaPlayer.setSurface(Surface),前者是SurfaceView中获取SurfaceHolder间接设置Surface的方式,后者是直接设置,所以视频播放中更换Surface就可以切换视频播放的位置而不用重新实例化一个MediaPlayer
mediaPlayer.setDisplay(big_surface_view.getHolder());
代码示例:(借用别人实现的代码)
下面的代码实现了一个自定义的SurfaceView,当SurfaceView创建时,通过设置监听SurfaceHolder.Callback,监听到内部Surface创建后开启一个线程获取到Surface上的Canvas,改变Canvas上的绘制信息,然后让surface重新绘制,得到最新的显示
/*
* author: conowen
* e-mail: conowen@hotmail.com
* date : 2012.8.4
*/
package com.conowen.SurfaceViewDemo;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MySurfaceView extends SurfaceView implements
SurfaceHolder.Callback {
private String TAG = "conowen";
private SurfaceHolder sfh;
private boolean ThreadFlag;
private int counter;
private Canvas canvas;
private Thread mThread = new Thread(new Runnable() {
@Override
public void run() {
while (ThreadFlag) {
// 锁定画布,得到Canvas对象
canvas = sfh.lockCanvas();
// 设定Canvas对象的背景颜色
canvas.drawColor(Color.GREEN);
// 创建画笔
Paint p = new Paint();
// 设置画笔颜色
p.setColor(Color.RED);
// 设置文字大小
p.setTextSize(40);
// 创建一个Rect对象rect
// public Rect (int left, int top, int right, int bottom)
Rect rect = new Rect(100, 50, 400, 350);
// 在canvas上绘制rect
canvas.drawRect(rect, p);
// 在canvas上显示时间
// public void drawText (String text, float x, float y, Paint
// paint)
canvas.drawText("时间 = " + (counter++) + " 秒", 500, 200, p);
if (canvas != null) {
// 解除锁定,并提交修改内容,更新屏幕
sfh.unlockCanvasAndPost(canvas);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
public MySurfaceView(Context context) {
super(context);
// 通过SurfaceView获得SurfaceHolder对象
sfh = this.getHolder();
// 为SurfaceHolder添加回调结构SurfaceHolder.Callback
sfh.addCallback(this);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
Log.i(TAG, "surfaceChanged");
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.i(TAG, "surfaceCreated");
counter = 0;
ThreadFlag = true;
mThread.start();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.i(TAG, "surfaceDestroyed");
ThreadFlag = false;
}
}
下节内容:
4. 播发器示例及列表播放器
5. 播放器开发小技巧