2012-03-05

Tags: android

在Android上使用MediaPlayer播放影片有些要注意的事項,不然在播放時會出現一些預想不到的狀況。在播放當時如果想動態調整影片畫面大小,其實還蠻簡單的;但是不同的實作會導致結果差異很大,錯誤的實作會讓執行時的效能很差。

以下是這部份實作的程式碼與心得

相關程式碼如下

ChgMediaPlayerLayoutActivity.java

public class ChgMediaPlayerLayoutActivity extends Activity {
    private static final String LOG_TAG = ChgMediaPlayerLayoutActivity.class.getSimpleName();
    private static final String VIDEO_FILE_PATH = "rtsp://....../video.3gp";
   
    private MediaPlayer mediaPlayer;
    private SurfaceView surfaceView;
    private SurfaceHolder surfaceHolder;       
    private Button playButton;
    private Button pauseButton;
    private Button stopButton;   
    private Button smallScreenButton;
    private Button fullScreenButton;
    private Button hiddenScreenButton;
   
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.chg_mediaplayer_layout);
        surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
        playButton = (Button)findViewById(R.id.playButton);
        pauseButton = (Button)findViewById(R.id.pauseButton);
        stopButton = (Button)findViewById(R.id.stopButton);
        smallScreenButton = (Button)findViewById(R.id.smallScreenButton);
        fullScreenButton = (Button)findViewById(R.id.fullScreenButton);
        hiddenScreenButton = (Button)findViewById(R.id.hiddenScreenButton);   
       
        mediaPlayer = new MediaPlayer();
        mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {           
            @Override
            public boolean onError(MediaPlayer mp, int what, int extra) {
                Log.e(LOG_TAG, "ErrorCode : " + what);
                return false;
            }
        });
        mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {           
            @Override
            public void onCompletion(MediaPlayer mp) {
                try {
                    Log.d(LOG_TAG, "onCompletion");                       
                    mediaPlayer.seekTo(0);
                }
                catch (Exception e) {
                    Log.e(LOG_TAG, e.getMessage(),e);
                }
            }
        });           
       
        surfaceHolder = surfaceView.getHolder();
        surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        surfaceHolder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                try {
                    Log.d(LOG_TAG, "surfaceCreated");
                    mediaPlayer.reset();
                   
                    //mediaPlayer.setDisplay(...)必需在callback的surfaceCreated(...) method裡叫用,否則會有聲音無影像
                    //也就是說mediaPlayer.setDisplay(...)必需在整個畫面render完成後才能叫用,否則會出現異常
                    mediaPlayer.setDisplay(surfaceHolder);                       
                   
                    mediaPlayer.setDataSource(VIDEO_FILE_PATH);
                    mediaPlayer.prepare();               
                }
                catch (Exception e) {
                    Log.e(LOG_TAG, e.getMessage(),e);
                }                   
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format,int width, int height) {
                Log.d(LOG_TAG, "surfaceChanged");
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                Log.d(LOG_TAG, "surfaceDestroyed");
            }
        });           

        playButton.setOnClickListener(new View.OnClickListener() {           
            @Override
            public void onClick(View v) {
                if(!mediaPlayer.isPlaying()){
                    mediaPlayer.start();                   
                }                               
            }
        });

        pauseButton.setOnClickListener(new View.OnClickListener() {           
            @Override
            public void onClick(View v) {
                if(mediaPlayer.isPlaying()){
                    mediaPlayer.pause();                   
                }                               
            }
        });       
       
        stopButton.setOnClickListener(new View.OnClickListener() {           
            @Override
            public void onClick(View v) {
                try {                   
                    if(mediaPlayer.isPlaying()){
                        mediaPlayer.pause();
                        mediaPlayer.seekTo(0);                       
                    }
                }
                catch (Exception e) {
                    Log.e(LOG_TAG, e.getMessage(),e);
                }
            }
        });       
       
        smallScreenButton.setOnClickListener(new View.OnClickListener() {           
            @Override
            public void onClick(View v) {
                LayoutParams layoutParams = surfaceView.getLayoutParams();
                layoutParams.width = 300;
                layoutParams.height = 200;
                surfaceView.setLayoutParams(layoutParams);
            }
        });       
       
        fullScreenButton.setOnClickListener(new View.OnClickListener() {           
            @Override
            public void onClick(View v) {
                LayoutParams layoutParams = surfaceView.getLayoutParams();
                layoutParams.width = LayoutParams.FILL_PARENT;
                layoutParams.height = LayoutParams.FILL_PARENT;
                surfaceView.setLayoutParams(layoutParams);
            }
        });       
       
        hiddenScreenButton.setOnClickListener(new View.OnClickListener() {           
            @Override
            public void onClick(View v) {
                LayoutParams layoutParams = surfaceView.getLayoutParams();
                layoutParams.width = 0;
                layoutParams.height = 0;
                surfaceView.setLayoutParams(layoutParams);
            }
        });           
    }   
   
    @Override
    protected void onStop() {
        super.onStop();
       
        if(mediaPlayer != null){
            mediaPlayer.release();
        }       
    }    
}
chg_mediaplayer_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" android:baselineAligned="false">

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:gravity="center_horizontal|center_vertical"
        android:orientation="vertical" >

        <SurfaceView
            android:id="@+id/surfaceView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >

        <Button
            android:id="@+id/playButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/play" />

        <Button
            android:id="@+id/pauseButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/pause" />

        <Button
            android:id="@+id/stopButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/stop" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >

        <Button
            android:id="@+id/smallScreenButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/smallScreen" />

        <Button
            android:id="@+id/fullScreenButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/fullScreen" />

        <Button
            android:id="@+id/hiddenScreenButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/hiddenScreen" />
    </LinearLayout>

</LinearLayout>

相關心得如下

MediaPlayer播放影片相關注意事項與心得
  1. MediaPlayer在叫用時,某些Method在叫用時有其先後順序,請參考官網明細說明
  2. 「new MediaPlayer()」與MediaPlayer.reset()的功能相似,如果想覆用已經生成MediaPlayer的instance,不想一直new出新的instance,可以呼叫MediaPlayer.reset(),將已生成的instance回到初始化的狀況,進而重覆使用
  3. 在Activity的不可視狀況時(onStop(...) method被叫用時),記的呼叫MediaPlayer.release()釋放系統的硬體資源
  4. MediaPlayer.setDisplay(...)必需在整個UI畫面render完成後才叫用,否則在播放時會有聲音無影像。如果是在SurfaceHolder的callback interface裡叫用,記的要放在surfaceCreated(...) method裡面
  5. 要快速的在影片中移動(seek)到特定時間點,可利用MediaPlayer.pause()配合MediaPlayer.seek(...)來達成。沒事別一直呼叫MediaPlayer.stop(),因為stop後要進行seek的動作,必需先呼叫MediaPlayer.prepare()後才可再呼叫MediaPlayer.seek(...),這個MediaPlayer.prepare()的叫用比較花時間
  6. 官網上的MediaPlayer State Diagram在不同手機上的狀況不見的會相同,有時會遇到State亂跳的狀況。如果想在異常時追查State的狀況,可以在MediaPlayer裡註冊OnErrorListener來追問題。不過...有的手機會吐奇怪的StateCode,不見的真的能查到問題所在...@@!!!
MediaPlayer動態調整影片畫面大小相關注意事項與心得
  1. SurfaceView的layout調整可利用SurfaceView.setLayoutParams(...)的方式進行調整。這種方式是執行速度與效能最好的,不會因為重設了Layout參數就必需讓整個SurfaceView重新產生,可以省下很多執行時間
  2. 如果想暫時隱藏SurfaceView,可以在呼叫SurfaceView.setLayoutParams(...)時,指定Layout的長寬為0,達到隱藏的目的。不要呼叫SurfaceView.setVisibility(View.INVISIBLE),這會導致SurfaceView從UI的ViewTree裡被移除;當你要再次顯示SurfaceView時,除了要重新產生新的SurfaceView之外,還要再跟MediaPlayer做一次完整的binding動作,執行的整度與效能都很糟。當Activity處於UI可見的狀態下,SurfaceView要想盡辦法讓它reuse,不要讓它進行重新生成instance的動作,這樣MediaPlayer的播放畫面在進行縮放大小時才會快速。