surfaceview
既然surfaceview是配合mediaplayer使用的,mediaplayer也提供了相应的方法设置surfaceview显示图片,只需要为mediaplayer指定surfaceview显示图像即可。它的完整签名如下:
void setdisplay(surfaceholder sh)
它需要传递一个surfaceholder对象,surfaceholder可以理解为surfaceview装载需要显示的一帧帧图像的容器,它可以通过surfaceholder.getholder()方法获得。
使用mediaplayer配合surfaceview播放视频的步骤与播放使用mediaplayer播放mp3大体一致,只需要额外设置显示的surfaceview即可。
surfaceview双缓冲
上面有提到,surfaceview和大部分视频应用一样,把视频流解析成一帧帧的图像进行 显示,但是如果把这个解析的过程放到一个线程中完成,可能在上一帧图像已经显示过后,下一帧图像还没有来得及解析,这样会导致画面的不流畅或者声音和视频 不同步的问题。所以surfaceview和大部分视频应用一样,通过双缓冲的机制来显示帧图像。那么什么是双缓冲呢?双缓冲可以理解为有两个线程轮番去 解析视频流的帧图像,当一个线程解析完帧图像后,把图像渲染到界面中,同时另一线程开始解析下一帧图像,使得两个线程轮番配合去解析视频流,以达到流畅播 放的效果。
surfaceholder
surfaceview内部实现了双缓冲的机制,但是实现这个功能是非常消耗系统内存的。因为移动设备的局限性,android在设计的时候规 定,surfaceview如果为用户可见的时候,创建surfaceview的surfaceholder用于显示视频流解析的帧图片,如果发现 surfaceview变为用户不可见的时候,则立即销毁surfaceview的surfaceholder,以达到节约系统资源的目的。
如果开发人员不对surfaceholder进行维护,会出现最小化程序后,再打开应用的时候,视频的声音在继续播放,但是不显示画面了的情况,这 就是因为当surfaceview不被用户可见的时候,之前的surfaceholder已经被销毁了,再次进入的时候,界面上的 surfaceholder已经是新的surfaceholder了。所以surfaceholder需要我们开发人员去编码维护,维护 surfaceholder需要用到它的一个回调,surfaceholder.callback(),它需要实现三个如下三个方法:
void surfacedestroyed(surfaceholder holder):当surfaceholder被销毁的时候回调。
void surfacecreated(surfaceholder holder):当surfaceholder被创建的时候回调。
void surfacechange(surfaceholder holder):当surfaceholder的尺寸发生变化的时候被回调。
以下是这三个方法的调用的过程,在应用中分别为surfaceholder实现了这三个方法,先进入应用,surfaceholder被创建,创建 好之后会改变surfaceholder的大小,然后按home键回退到桌面销毁surfaceholder,最后再进入应用,重新 surfaceholder并改变其大小。
surfaceview的demo示例
上面讲了那么多关于surfaceview的内容,下面通过一个demo简单演示一下 surfaceview如何播放视频,加了一个滚动条,用于显示进度,还可以拖动滚动条选择播放位置,demo的注释比较完整.
import java.io.file;
import android.media.audiomanager;
import android.media.mediaplayer;
import android.media.mediaplayer.oncompletionlistener;
import android.media.mediaplayer.onerrorlistener;
import android.media.mediaplayer.onpreparedlistener;
import android.os.bundle;
import android.app.activity;
import android.util.log;
import android.view.surfaceholder;
import android.view.surfaceholder.callback;
import android.view.surfaceview;
import android.view.view;
import android.widget.button;
import android.widget.edittext;
import android.widget.seekbar;
import android.widget.seekbar.onseekbarchangelistener;
import android.widget.toast;
public class mainactivity extends activity {
private final string tag = "main";
private edittext et_path;
private surfaceview sv;
private button btn_play, btn_pause, btn_replay, btn_stop;
private mediaplayer mediaplayer;
private seekbar seekbar;
private int currentposition = 0;
private boolean isplaying;
@override
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.activity_main);
seekbar = (seekbar) findviewbyid(r.id.seekbar);
sv = (surfaceview) findviewbyid(r.id.sv);
et_path = (edittext) findviewbyid(r.id.et_path);
btn_play = (button) findviewbyid(r.id.btn_play);
btn_pause = (button) findviewbyid(r.id.btn_pause);
btn_replay = (button) findviewbyid(r.id.btn_replay);
btn_stop = (button) findviewbyid(r.id.btn_stop);
btn_play.setonclicklistener(click);
btn_pause.setonclicklistener(click);
btn_replay.setonclicklistener(click);
btn_stop.setonclicklistener(click);
// 为surfaceholder添加回调
sv.getholder().addcallback(callback);
// 4.0版本之下需要设置的属性
// 设置surface不维护自己的缓冲区,而是等待屏幕的渲染引擎将内容推送到界面
// sv.getholder().settype(surfaceholder.surface_type_push_buffers);
// 为进度条添加进度更改事件
seekbar.setonseekbarchangelistener(change);
}
private callback callback = new callback() {
// surfaceholder被修改的时候回调
public void surfacedestroyed(surfaceholder holder) {
log.i(tag, "surfaceholder 被销毁");
// 销毁surfaceholder的时候记录当前的播放位置并停止播放
if (mediaplayer != null && mediaplayer.isplaying()) {
currentposition = mediaplayer.getcurrentposition();
mediaplayer.stop();
public void surfacecreated(surfaceholder holder) {
log.i(tag, "surfaceholder 被创建");
if (currentposition > 0) {
// 创建surfaceholder的时候,如果存在上次播放的位置,则按照上次播放位置进行播放
play(currentposition);
currentposition = 0;
public void surfacechanged(surfaceholder holder, int format, int width,
int height) {
log.i(tag, "surfaceholder 大小被改变");
};
private onseekbarchangelistener change = new onseekbarchangelistener() {
public void onstoptrackingtouch(seekbar seekbar) {
// 当进度条停止修改的时候触发
// 取得当前进度条的刻度
int progress = seekbar.getprogress();
// 设置当前播放的位置
mediaplayer.seekto(progress);
public void onstarttrackingtouch(seekbar seekbar) {
public void onprogresschanged(seekbar seekbar, int progress,
boolean fromuser) {
private view.onclicklistener click = new view.onclicklistener() {
public void onclick(view v) {
switch (v.getid()) {
case r.id.btn_play:
play(0);
break;
case r.id.btn_pause:
pause();
case r.id.btn_replay:
replay();
case r.id.btn_stop:
stop();
default:
/*
* 停止播放
*/
protected void stop() {
mediaplayer.release();
mediaplayer = null;
btn_play.setenabled(true);
isplaying = false;
/**
* 开始播放
*
* @param msec 播放初始位置
protected void play(final int msec) {
// 获取视频文件地址
string path = et_path.gettext().tostring().trim();
file file = new file(path);
if (!file.exists()) {
toast.maketext(this, "视频文件路径错误", 0).show();
return;
try {
mediaplayer = new mediaplayer();
mediaplayer.setaudiostreamtype(audiomanager.stream_music);
// 设置播放的视频源
mediaplayer.setdatasource(file.getabsolutepath());
// 设置显示视频的surfaceholder
mediaplayer.setdisplay(sv.getholder());
log.i(tag, "开始装载");
mediaplayer.prepareasync();
mediaplayer.setonpreparedlistener(new onpreparedlistener() {
public void onprepared(mediaplayer mp) {
log.i(tag, "装载完成");
mediaplayer.start();
// 按照初始位置播放
mediaplayer.seekto(msec);
// 设置进度条的最大进度为视频流的最大播放时长
seekbar.setmax(mediaplayer.getduration());
// 开始线程,更新进度条的刻度
new thread() {
public void run() {
isplaying = true;
while (isplaying) {
int current = mediaplayer
.getcurrentposition();
seekbar.setprogress(current);
sleep(500);
} catch (exception e) {
e.printstacktrace();
}.start();
btn_play.setenabled(false);
});
mediaplayer.setoncompletionlistener(new oncompletionlistener() {
public void oncompletion(mediaplayer mp) {
// 在播放完毕被回调
mediaplayer.setonerrorlistener(new onerrorlistener() {
public boolean onerror(mediaplayer mp, int what, int extra) {
// 发生错误重新播放
return false;
* 重新开始播放
protected void replay() {
mediaplayer.seekto(0);
toast.maketext(this, "重新播放", 0).show();
btn_pause.settext("暂停");
* 暂停或继续
protected void pause() {
if (btn_pause.gettext().tostring().trim().equals("继续")) {
toast.maketext(this, "继续播放", 0).show();
mediaplayer.pause();
btn_pause.settext("继续");
toast.maketext(this, "暂停播放", 0).show();
}
转载:http://blog.csdn.net/chaoyu168/article/details/51179636