1 介绍
MediaPlayer+SurfaceView具有更好的拓展性
MediaPlayer除了可以播放音乐外,还可以播放视频,但是使用MediaPlayer播放音乐时,没有提供图像输出界面,可以使用SurfaceView组件来显示视频画面,首先,必须在布局文件activity_main.xml文件中定义SurfaceView组件,第二步就是创建MediaPlayer对象,加载要播放的视频,第三步就是将所要播放的视频画面输出到SurfaceView,使用MediaPlayer对象的setDisplay()方法可以输出视频画面到SurfaceView,setDisplay()方法的格式为:mediaplayer.setDisplay(surfaceview.getHolder());里面带的参数是SurfaceView对象的getHolder()方法。第四步,调用MediaPlayer对象的play(),pause(),stop()分别播放,暂停,停止视频的播放。
2 MediaPlayer+SurfaceView基本实现
2.1 Main2Activity.java
(仅播放没有暂停停止)
public class Main2Activity extends Activity implements
SurfaceHolder.Callback{
MediaPlayer player;
SurfaceView surface;
SurfaceHolder surfaceHolder;
String url1 = "http://192.168.1.250:443/live/1";
String url2 = "http://192.168.1.250:443/live/2.mpeg";
String path =Environment.getExternalStorageDirectory().getPath()+"2.mpeg";
@Override
public void onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
initView();
}
private void initView(){
surface =(SurfaceView) findViewById(R.id.surface);
surfaceHolder = surface.getHolder();//SurfaceHolder是SurfaceView的控制接口
surfaceHolder.addCallback(this); // 因为这个类实现了SurfaceHolder.Callback接口,所以回调参数直接this
}
@Override
public void surfaceChanged(SurfaceHolderarg0, int arg1, int arg2, int arg3) {
}
@Override
public void surfaceCreated(SurfaceHolderarg0) {
// 必须在surface创建后才能初始化MediaPlayer,否则不会显示图像
player = new MediaPlayer();
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
player.setDisplay(surfaceHolder);
// 设置显示视频显示在SurfaceView上
try {
player.setDataSource(url2);
player.prepare();
player.start();
} catch (Exceptione) {
e.printStackTrace();
}
}
@Override
public void surfaceDestroyed(SurfaceHolderarg0) {
// TODOAuto-generated method stub
}
@Override
protected void onDestroy(){
// TODOAuto-generated method stub
super.onDestroy();
if (player.isPlaying()){
player.stop();
}
player.release();
// Activity销毁时停止播放,释放资源。不做这个操作,即使退出还是能听到视频播放的声音
}
}
3 使用Service服务
3.1 MainActivity.java
public class MainActivity extends Activity { public static SurfaceView sv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); sv = (SurfaceView) findViewById(R.id.surfaceView1);// 获得SurfaceView控件 } /* *点击那4个按钮后触发的事件 */ public void click(View v) { Intent intent = new Intent(MainActivity.this, ServiceTest.class); int op = -1;// 定义一个中间变量 switch (v.getId()) { case R.id.play: op = 1; Toast.makeText(MainActivity.this, "视频正在播放", Toast.LENGTH_SHORT) .show(); break; case R.id.pause: op = 2; Toast.makeText(MainActivity.this, "视频暂停播放", Toast.LENGTH_SHORT) .show(); break; case R.id.stop: op = 3; Toast.makeText(MainActivity.this, "视频停止播放", Toast.LENGTH_SHORT) .show(); break; case R.id.finish: if (intent != null) { stopService(intent); } finish(); break; default: break; } Bundle bundle = new Bundle();// 声明一个Bundle对象并实例化 bundle.putInt("middle", op);// 把中间变量op放置到middle这个键 intent.putExtras(bundle);// 用intent把bundle放入进去 startService(intent);// 开始服务 } }
3.1.1Bundle介绍
Bundle主要用于传递数据;它保存的数据,是以key-value(键值对)的形式存在的。
我们经常使用Bundle在Activity之间传递数据,传递的数据可以是boolean、byte、int、long、float、double、string等基本类型或它们对应的数组,也可以是对象或对象数组。当Bundle传递的是对象或对象数组时,必须实现Serializable 或Parcelable接口。下面分别介绍Activity之间如何传递基本类型、传递对象。
写数据的方法如下:
1. // "com.test" is the package name of the destination class
2. // "com.test.Activity02" is the full class path of the destination class
3. Intent intent = new Intent().setClassName("com.bundletest", "com.bundletest.Bundle02");
4.
5. Bundle bundle = new Bundle();
6. bundle.putString("name", "skywang");
7. bundle.putInt("height", 175);
8. intent.putExtras(bundle);
9.
10.startActivity(intent);
11.
12.// end current class
13.finish();
对应的读数据的方法如下:
1. Bundle bundle = this.getIntent().getExtras();
2.
3. String name = bundle.getString("name");
4. int height = bundle.getInt("height");
3.2 ServiceTest.java
package com.example.lcn_louis.videoplayactivity; /** * Created by LCN_Louis on 2017/7/3. */ import android.app.Service; import android.content.Intent; import android.media.MediaPlayer; import android.os.Bundle; import android.os.Environment; import android.os.IBinder; public class ServiceTest extends Service { public static MediaPlayer player;// 声明MediaPlayer对象 private String url = "http://192.168.1.250:443/live/2"; @Override public IBinder onBind(Intent arg0) { // TODO Auto-generated method stub return null; } // 创建服务 @Override public void onCreate() { // TODO Auto-generated method stub if (player == null) { try { player = new MediaPlayer();//实例化MediaPlayer对象 //player.setDataSource(Environment.getExternalStorageDirectory().getPath()+"2");// 设置要播放的视频 player.setDataSource(url); player.setLooping(false);// 设置视频不循环播放 super.onCreate(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } // 开始服务 @Override public int onStartCommand(Intent intent, int flags, int startId) { // TODO Auto-generated method stub Bundle bundle = intent.getExtras();// 获得从MainActivity传来的bundle对象 int op = bundle.getInt("middle");// 获得从MainActivity.java里传递过来的op switch (op) { case 1:// 当op为1,即点击了播放按钮 play();// 调用play()方法 break; case 2:// 当op为2,即点击了暂停按钮 pause();// 调用pause()方法 break; case 3:// 当op为3,即点击了停止按钮 stop();// 调用stop()方法 break; default: break; } return super.onStartCommand(intent, flags, startId); } // 播放视频play()方法 private void play() { // TODO Auto-generated method stub if (player != null && !player.isPlaying()) { player.setDisplay(MainActivity.sv.getHolder());//把视频画面显示出来 try { player.prepare();//预加载视频 } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } player.start();// 开始播放音乐 } } private void pause() { // TODO Auto-generated method stub if (player != null && player.isPlaying()) { player.pause();// 暂停视频的播放 } } private void stop() { // TODO Auto-generated method stub if (player != null) { player.seekTo(0);//如果点击播放按钮的话会从头播放 player.stop();// 停止音乐的播放 } } @Override public void onDestroy() { // TODO Auto-generated method stub if (player != null) { player.stop();//停止播放视频 player.release();// 释放资源 player = null; } super.onDestroy(); } }
3.3 AndroidManifest.xml
3.3.1在AndroidMainfest.xml中添加网络权限(很重要)
在AndroidManifest.xml 文件中manifest标签之后以内,application以外添加如下权限
<uses-permission android:name="android.permission.INTERNET"/>
3.3.2在AndroidManifest.xml中添加Service服务
<!-- 添加service的权限-->
<service android:name=".ServiceTest"></service>
</application>
<!-- 添加读写sd卡的权限-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
4 分段式无缝播放
4.1 主要方法
当我们进入视频播放界面,第一段视频准备完毕,开始播放后,
便开始着手初始化另一个新的MediaPlayer,这个新的MediaPlayer的数据源当然是接下来要播放的下一段视频的url!
当这个MediaPlayer对象的准备工作都搞定后,剩下的工作就是:
我们需要“一颗钉子”,来将两个分段的视频段连接起来。
而这个钉子就是Android r16后添加的一个方法:setNextMediaPlayer()方法。
在第一个MediaPlayer类执行结束前的任何时间调用setNextMediaPlayer(MediaPlayernext)这个方法,
该方法的参数是第二个文件创建的MediaPlayer实例。然后Android系统将会在您第一个停止的时候紧接着播放第二个文件。
但我认为,在这个说明里,你应该注意到的关键点是:第一个MediaPlayer类执行结束前的任何时间调用这个方法。
也就是说,你必须在前一个MediaPlayer对象播放完毕之前使用该方法。
例如我后来发现,如果理想的在我们前面提到的OnCompletionListener监听中使用该方法,是无效的。
并且,似乎并不如该说明而言的“Android系统将会在您第一个停止的时候紧接着播放第二个文件”。
也就是说,这个切换播放的动作不是自动的,还需要我们手动的做一个小的控制,马上接下来就会说到。
到了这里,我们要实现的思路已经很明确了:在一段视频播放的同时,做下一段视频的player的初始化准备工作。
而此时另一个格外需要记住的就是:不要再在UI线程去开启新的MediaPlayer的赋值工作.
原理很简单,其实也是Android开发所必须记住的,即是永远不要在UI线程里去做耗时的操作。
这样做的后果基本有几种,一种是报告“在主线程做了太多操作”的异常,而另外也可能出现,屏幕响应迟缓,
也就是说,例如你的视频播放界面可能还存在一些按钮和响应事件之类,这个响应会出现延迟。最后,当然也很可能出现ANR。
所以,我们还需要做的工作就是,将其它负责后续播放的MediaPlayer对象的初始化与赋值工作放在新的线程里去执行。
而最后我们需要做的,则是在OnCompletionListener里进行监听,当一段视频播放完毕后,
马上执行mp.setDisplay(null),然后调用负责下一个视频分段播放的MediaPlayer执行setDisplay(surfaceHolder)。
4.2 Main2Activity
public class Main2Activity extends Activityimplements
SurfaceHolder.Callback {
MediaPlayer player;
private MediaPlayerfirstPlayer, //负责播放进入视频播放界面后的第一段视频
nextMediaPlayer, //负责一段视频播放结束后,播放下一段视频
cachePlayer, //负责setNextMediaPlayer的player缓存对象
currentPlayer; //负责当前播放视频段落的player对象
SurfaceView surface;
SurfaceHolder surfaceHolder;
String url1 = "http://192.168.1.250:443/live/1";
String url2 = "http://192.168.1.250:443/live/2.mpeg";
String path =Environment.getExternalStorageDirectory().getPath()+"2.mpeg";
//存放所有视频端的url
private ArrayList<String> VideoListQueue = new ArrayList<String>();
//所有player对象的缓存
private HashMap<String, MediaPlayer> playersCache = new HashMap<String, MediaPlayer>();
//当前播放到的视频段落数
private int currentVideoIndex;
@Override
public void onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);//横屏
initView();
}
private void initView() {
surface =(SurfaceView) findViewById(R.id.surface);
surfaceHolder = surface.getHolder(); // SurfaceHolder是SurfaceView的控制接口
surfaceHolder.addCallback(this); // 因为这个类实现了SurfaceHolder.Callback接口,所以回调参数直接this
}
@Override
public void surfaceChanged(SurfaceHolderarg0, int arg1, int arg2, int arg3) {
}
@Override
public void surfaceCreated(SurfaceHolderarg0) {
//surfaceView创建完毕后,首先获取该直播间所有视频分段的url
getVideoUrls();
//然后初始化播放手段视频的player对象
initFirstPlayer();
}
private void initFirstPlayer() {
firstPlayer =new MediaPlayer();
firstPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
firstPlayer.setDisplay(surfaceHolder);
firstPlayer
.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
onVideoPlayCompleted(mp);
}
});
//设置cachePlayer为该player对象
cachePlayer = firstPlayer;
initNexttPlayer();
//player对象初始化完成后,开启播放
startPlayFirstVideo();
}
private void startPlayFirstVideo() {
try {
firstPlayer.setDataSource(VideoListQueue.get(currentVideoIndex));
firstPlayer.prepare();
firstPlayer.start();
} catch (IOExceptione) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
private void initNexttPlayer() {
new Thread(new Runnable() {
@RequiresApi(api= Build.VERSION_CODES.JELLY_BEAN)
@Override
public void run(){
for (int i = 1; i < VideoListQueue.size(); i++) {
nextMediaPlayer = new MediaPlayer();
nextMediaPlayer
.setAudioStreamType(AudioManager.STREAM_MUSIC);
nextMediaPlayer
.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
onVideoPlayCompleted(mp);
}
});
try {
nextMediaPlayer.setDataSource(VideoListQueue.get(i));
nextMediaPlayer.prepare();
} catch (IOExceptione) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
//set next mediaplayer
cachePlayer.setNextMediaPlayer(nextMediaPlayer);
//set new cachePlayer
cachePlayer = nextMediaPlayer;
playersCache.put(String.valueOf(i), nextMediaPlayer);
}
}
}).start();
}
private void onVideoPlayCompleted(MediaPlayer mp) {
mp.setDisplay(null);
//get next player
currentPlayer = playersCache.get(String.valueOf(++currentVideoIndex));
if (currentPlayer != null) {
currentPlayer.setDisplay(surfaceHolder);
} else {
Toast.makeText(Main2Activity.this, "视频播放完毕..", Toast.LENGTH_SHORT)
.show();
}
}
private void getVideoUrls() {
for (int i = 0; i < 2; i++){
String url = getURI(i);
VideoListQueue.add(url);
}
}
private StringgetURI(int index) {
String url = null;
if(index==0)
{
url="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4";
}
else if(index==1)
{
url="http://www.zcycjy.com/coursePath/%E7%BB%A7%E7%BB%AD%E6%95%99%E8%82%B2/%E6%96%B0%E8%AF%BE%E7%A8%8B%E8%A7%86%E9%A2%91/%E2%80%9C%E8%90%A5%E6%94%B9%E5%A2%9E%E2%80%9D%E6%94%BF%E7%AD%96%E8%A7%A3%E8%AF%BB%E5%8F%8A%E4%BC%81%E4%B8%9A%E7%A8%8E%E5%8A%A1%E9%A3%8E%E9%99%A9%E4%B8%8E%E7%AD%B9%E5%88%92%E6%8E%A2%E8%AE%A81.mp4";
}
return url;
}
@Override
public void surfaceDestroyed(SurfaceHolderarg0) {
// TODO Auto-generated method stub
}
@Override
protected void onDestroy() {
super.onDestroy();
if (firstPlayer != null) {
if (firstPlayer.isPlaying()) {
firstPlayer.stop();
}
firstPlayer.release();
}
if (nextMediaPlayer != null) {
if (nextMediaPlayer.isPlaying()) {
nextMediaPlayer.stop();
}
nextMediaPlayer.release();
}
if (currentPlayer != null) {
if (currentPlayer.isPlaying()) {
currentPlayer.stop();
}
currentPlayer.release();
}
currentPlayer = null;
}// Activity销毁时停止播放,释放资源。不做这个操作,即使退出还是能听到视频播放的声音
}
5 参考文献:
【1】http://blog.csdn.net/ghost_programmer/article/details/44980989
【2】http://www.voidcn.com/blog/xingpidong/article/p-6251882.html
【3】http://www.voidcn.com/blog/u012561176/article/p-3109884.html
【4】http://www.voidcn.com/blog/menglele1314/article/p-4946676.html
【5】http://blog.csdn.net/u013555975/article/details/50537895