9.1.5 使用MediaPlayer播放
第6章和第7章(其中处理音频和网络音频)介绍了MediaPlayer类。同样,MediaPlayer类也可以通过大致的方式用于播放视频。
与使用VideoView或通过意图播放视频相比,将MediaPlayer对象用于视频播放能够为控制播放本身提供最大的灵活性。事实上,在VideoView和通过意图的触发的活动中,用于处理播放的具体机制也是MediaPlayer。
1.MediaPlayer的状态
MediaPlayer对象以状态机的方式工作,即它需要以特定的顺序执行操作,而且只有当该对象处于正确的状态时才可以调用各种方法来处理这些操作。
MediaPlayer类定义了几个监听,从而使用它的应用程序会获得其各种状态的通知,并据此采取相应的操作。
2.MediaPlayer示例
下面是使用MediaPlayer创建一个自定义视频播放应用程序的完整示例。
1 package com.nthm.androidtestActivity; 2 3 import java.io.IOException; 4 import com.nthm.androidtest.R; 5 import android.app.Activity; 6 import android.media.MediaPlayer; 7 import android.media.MediaPlayer.OnCompletionListener; 8 import android.media.MediaPlayer.OnErrorListener; 9 import android.media.MediaPlayer.OnInfoListener; 10 import android.media.MediaPlayer.OnPreparedListener; 11 import android.media.MediaPlayer.OnSeekCompleteListener; 12 import android.media.MediaPlayer.OnVideoSizeChangedListener; 13 import android.os.Bundle; 14 import android.os.Environment; 15 import android.view.Display; 16 import android.view.SurfaceHolder; 17 import android.view.SurfaceHolder.Callback; 18 import android.view.SurfaceView; 19 import android.widget.LinearLayout;
活动将实现MediaPlayer状态变化的所有监听器以及SurfaceHolder.Callback接口,从而我们能够获得SurfaceView变化的通知。
1 public class CustomVideoPlayer extends Activity implements 2 OnCompletionListener, OnErrorListener, OnInfoListener, 3 OnPreparedListener, OnSeekCompleteListener, OnVideoSizeChangedListener, 4 Callback { 5 private Display currentDisplay; 6 private SurfaceView surfaceView; 7 private SurfaceHolder surfaceHolder;
应用程序的关键部件将是MediaPlayer对象。
1 private MediaPlayer mediaPlayer; 2 private int videoWidth=0; 3 private int videoHeight=0; 4 private boolean readyToPlay=false; 5 public final static String LOGTAG="CUSTOM_VIDEO_PLAYER"; 6 @Override 7 protected void onCreate(Bundle savedInstanceState) { 8 super.onCreate(savedInstanceState); 9 setContentView(R.layout.customvideoplayer);
在设置内容视图之后,可以获得在布局XML中定义的SurfaceView的引用以及SurfaceHolder的引用,从而能够监控在底层表面上发生的事情。
1 surfaceView=(SurfaceView) findViewById(R.id.SurfaceView); 2 surfaceHolder=surfaceView.getHolder();
由于活动实现了SurfaceHolder.Callback,因此将把它指定为回调监听器。
1 surfaceHolder.addCallback(this);
需要确保底层表面是一个推送缓冲区表面,目前需要将它用于视频播放和摄像头预览。
1 surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
现在开始构造实际的MediaPlayer对象。无需传入任何参数,以下代码将返回一个处于“空闲”状态的通用MediaPlayer对象。
1 mediaPlayer=new MediaPlayer();
还将指定活动应该是各种事件的监听器。
1 mediaPlayer.setOnCompletionListener(this); 2 mediaPlayer.setOnErrorListener(this); 3 mediaPlayer.setOnInfoListener(this); 4 mediaPlayer.setOnPreparedListener(this); 5 mediaPlayer.setOnSeekCompleteListener(this); 6 mediaPlayer.setOnVideoSizeChangedListener(this);
在完成onCreate方法之前,将通知MediaPlayer对象所需要播放的内容,在此示例中,我们选择一个视频文件
1 String filePath=Environment.getExternalStorageDirectory().getPath()+"/test.mp4";
MediaPlayer对象上的setDataSource方法可能会抛出多个异常,因此需呀对他们进行合适的处理。在本例中仅仅是退出;但是在你的应用程序中,你可能希望向用户提供一个机会来选择不同的文件或解释发生了什么错误。
1 try { 2 mediaPlayer.setDataSource(filePath); 3 } catch (IllegalArgumentException e) { 4 e.printStackTrace(); 5 finish(); 6 } catch (SecurityException e) { 7 e.printStackTrace(); 8 finish(); 9 } catch (IllegalStateException e) { 10 e.printStackTrace(); 11 finish(); 12 } catch (IOException e) { 13 e.printStackTrace(); 14 finish(); 15 } 16 currentDisplay=getWindowManager().getDefaultDisplay(); 17 }
因为活动实现了SurfaceHolder.Callback,并将其指定为回调监听器,所以将触发以下3个方法。
当创建SurfaceView中的底层表面时,将会调用surfaceCreated方法。
1 @Override 2 public void surfaceCreated(SurfaceHolder holder) {
当创建表面时,通过调用MediaPlayer的setDisplay方法并传入到SurfaceHolder对象,可以指定MediaPlayer将该表面用于播放。
1 mediaPlayer.setDisplay(holder);
最后,在指定表面完成之后可以调用prepare方法。prepare方法会阻塞而不是在后台工作。因此,为了在后台完成它所做的工作,从而不至于绑定应用程序,可以使用prepareAsync方法。不管采用哪种方法,由于实现了OnPreparedListener,并把活动设置为监听器,因此当完成操作时将调用onPrepared方法。
prepare方法可能会抛出几个需要处理的异常。为简洁起见,本示例仅仅是记录错误日志并退出来。在你的应用程序中,你可能需要适当的处理这些异常。
1 try { 2 mediaPlayer.prepare(); 3 } catch (IllegalStateException e) { 4 e.printStackTrace(); 5 finish(); 6 } catch (IOException e) { 7 e.printStackTrace(); 8 finish(); 9 } 10 }
当SurfaceView的底层的宽度、高度或其他的参数发生变化时,将调用surfaceChanged方法。在本示例中,不需要在这种情况下做任何事情。
1 @Override 2 public void surfaceChanged(SurfaceHolder holder, int format, int width, 3 int height) { 4 5 }
当销毁SurfaceView的底层表面时,将调用surfaceDestroyed方法。在本示例中,当发生这种情况时不做任何事情。
1 @Override 2 public void surfaceDestroyed(SurfaceHolder holder) { 3 4 }
由于实现了MediaPlayer.OnCompletionListener,并把活动自身注册为监听器,因此当MediaPlayer完成播放文件时,将调用onCompletion方法。可以使用它来加载另一个视频,或者指定其他一些诸如加载另外一个屏幕的动作。在本示例中仅仅是退出。
1 @Override 2 public void onCompletion(MediaPlayer mp) { 3 finish(); 4 }
由于活动实现了MediaPlayer.OnErrorListene,而且把它注册为MediaPlayer对象的错误监听器,因此当发生一个错误时将调用下面的onError方法,但是,可用的错误信息不多,只有两个常量。
从该方法返回false表明错误没有被处理。如果注册了而一个OnCompletionListener,那么将调用它的onCompletion方法,同时把MediaPlayer对象置于“错误”状态。通过调用reset方法,可以将它重置为“空闲”状态。
1 @Override 2 public boolean onError(MediaPlayer mp, int what, int extra) { 3 return false; 4 }
当出现关于播放媒体的特定信息或者需要发出警告时,将调用在OnInfoListener中指定的onInfo方法。
1 @Override 2 public boolean onInfo(MediaPlayer mp, int what, int extra) { 3 return false; 4 }
在MediaPlayer成功的准备开始播放后,将调用onPrepared方法。可以将该方法指定为正在实现的OnPreparedListener接口的一部分,一旦调用该方法,MediaPlayer就进入“准备就绪”状态,准备开始播放。
1 @Override 2 public void onPrepared(MediaPlayer mp) {
在播放视频之前,应该设置表面的大小以匹配视频或显示器的大小,这取决于哪个大小较小。
首先使用MediaPlayer对象上的getVideoWidth和getVideoHeight方法获得视频的尺寸。
1 videoHeight=mp.getVideoHeight(); 2 videoWidth=mp.getVideoWidth();
如果视频的宽度或高度大于显示器大小,就需要找出用该使用的比率。
1 if(videoWidth>currentDisplay.getWidth()||videoHeight>currentDisplay.getHeight()){ 2 float heightRatio=(float)videoHeight/(float)currentDisplay.getHeight(); 3 float widthRatio=(float)videoWidth/(float)currentDisplay.getWidth(); 4 if(heightRatio>1||widthRatio>1){
我们将使用较大额比率,同时通过将视频大小除以较大的比率来设置videoHeight和videoWidth。
1 if(heightRatio>widthRatio){ 2 videoHeight=(int)Math.ceil((float)videoHeight/(float)heightRatio); 3 videoWidth=(int)Math.ceil((float)videoWidth/(float)heightRatio); 4 }else{ 5 videoHeight=(int)Math.ceil((float)videoHeight/(float)widthRatio); 6 videoWidth=(int)Math.ceil((float)videoWidth/(float)widthRatio); 7 } 8 } 9 }
现在可以设置用来显示视频的SurfaceView大小,它可以是视频的实际尺寸,或者如果视频大于显示器,那么应该是调整后的尺寸。
1 surfaceView.setLayoutParams(new LinearLayout.LayoutParams(videoWidth, videoHeight));
最后,可以通过调用MediaPlayer对象上的start方法来开始播放视频。
1 mp.start(); 2 }
将onSeekComplete方法指定为正在实现的OnSeekCompleteListener的一部分,同时把活动注册为MediaPlayer对象的监听。当完成seek命令时调用该方法。
1 @Override 2 public void onSeekComplete(MediaPlayer mp) { 3 4 }
将onVideoSizeChanged方法指定为正在实现的OnVideoSizeChangedListener的一部分,同时把活动注册为MediaPlayer对象的监听器。当大小发生变化调用该方法;同时,当指定数据源和读取视频的元数据后将至少调用它一次。
1 @Override 2 public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { 3 4 } 5 }
下面是用于上述活动的布局XML文件customvideoplayer.xml。
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 android:layout_width="match_parent" 3 android:layout_height="match_parent" 4 android:orientation="vertical" 5 android:id="@+id/MainView" 6 > 7 <SurfaceView 8 android:id="@+id/SurfaceView" 9 android:layout_width="wrap_content" 10 android:layout_height="wrap_content" 11 /> 12 </LinearLayout>
3.使用MediaController的MediaPlayer
在VideoView示例中使用的MediaController视图也可与MediaPlayer一起使用,但是,为了使它能够正确的工作,需要花费大量的工作。
首先,除了已经实现的其他类之外,我们的类还将实现MediaController.MediaPlayerControl。
1 import android.widget.MediaController.MediaPlayerControl; 2 3 public class CustomVideoPlayer extends Activity implements 4 OnCompletionListener, OnErrorListener, OnInfoListener, 5 OnPreparedListener, OnSeekCompleteListener, OnVideoSizeChangedListener, 6 Callback,MediaPlayerControl{
此接口定义了MediaController中用来控制播放的一系列函数,需要在活动中实现他们。下面是这些函数及其在CustomVideoPlayer示例中的实现。其中的几个函数只返回true,仅仅指明其拥有此功能。其余的函数将会调用MediaPlayer对象上对应的函数。
1 @Override 2 public void start() { 3 mediaPlayer.start(); 4 } 5 6 @Override 7 public void pause() { 8 if(mediaPlayer.isPlaying()){ 9 mediaPlayer.pause(); 10 } 11 } 12 13 @Override 14 public int getDuration() { 15 return mediaPlayer.getDuration(); 16 } 17 18 @Override 19 public int getCurrentPosition() { 20 return mediaPlayer.getCurrentPosition(); 21 } 22 23 @Override 24 public void seekTo(int pos) { 25 mediaPlayer.seekTo(pos); 26 } 27 28 @Override 29 public boolean isPlaying() { 30 return mediaPlayer.isPlaying(); 31 } 32 33 @Override 34 public int getBufferPercentage() { 35 return 0; 36 } 37 38 @Override 39 public boolean canPause() { 40 return false; 41 } 42 43 @Override 44 public boolean canSeekBackward() { 45 return false; 46 } 47 48 @Override 49 public boolean canSeekForward() { 50 return false; 51 } 52 53 @Override 54 public int getAudioSessionId() { 55 return 0; 56 }
现在可以自由的添加实际的MediaController对象。我们将连同其余的实例变量一起声明它。
1 private MediaController controller;
在onCreate方法中将其实例化。
1 controller=new MediaController(this);
只有在MediaPlayer准备好之后才会对他进行实际的设置和使用。在onPrepared方法的末尾可以添加以下内容。首先通过调用setMediaPlayer方法指定实现了MediaController.MediaPlayerControl的对象。在本示例中它是当前活动,因此传入this。
然后设置活动的根视图,从而MediaController可以确定如何显示自身。在上述布局XML中,给定LinearLayout根对象的ID为MainView,因此可以在这里引用它。
最后启用控制器并显示。
1 controller.setMediaPlayer(this); 2 controller.setAnchorView(this.findViewById(R.id.MainView)); 3 controller.setEnabled(true); 4 controller.show();
为了使控制器能够在消失之后重新显示(MediaController的默认行为是在超时之后自动隐藏),可以重写活动中的onTouchEvent方法,以显示或隐藏他。
1 @Override 2 public boolean onTouch(View v, MotionEvent event) { 3 if(controller.isShowing()){ 4 controller.hide(); 5 }else{ 6 controller.show(); 7 } 8 return false; 9 }
posted on 2014-09-03 15:50 宁静致远,一览众山小 阅读(509) 评论(0) 编辑 收藏 举报