天天看點

多媒體程式設計多媒體程式設計音頻播放器視訊播放器攝像頭

多媒體程式設計

  • 文字、圖檔、音頻、視訊(這裡僅僅對銀頻和視訊詳解)

音頻播放器

建立方法

方式一、
MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // no need to call prepare(); create() does that for you
方式二、
MediaPlayer player= new MediaPlayer();
           

播放視訊來源

1.播放本地檔案
player.setDataSource("sdcard/zxmzf.mp3");
2.播放内部URI檔案
Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();
3.播放網絡檔案
String url = "http://........"; // your URL here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // might take long! (for buffering, etc)
mediaPlayer.start();
           

狀态轉換圖

多媒體程式設計多媒體程式設計音頻播放器視訊播放器攝像頭

基本使用

MediaPlayer播放音頻的基本使用步驟:
①建立一個MediaPlayer執行個體
②設定播放的資料格式
③設定資料源
④準備播放
⑤開始播放
整體的示例代碼很簡單:

// 1. 建立一個音頻播放器執行個體
final MediaPlayer player = new MediaPlayer();
// 2. 設定資料格式
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
player.setDataSource("/mnt/sdcard/zxxpg.mp3");// 此處也可以是網絡路徑
// player.prepare(); // 此處會阻塞主線程,建議prepareAsync
player.prepareAsync();
// 4. 當準備成功後回調
player.setOnPreparedListener(new OnPreparedListener() {

    @Override
    public void onPrepared(MediaPlayer mp) {
        // 5. 開始播放
        player.start();
    }
});
           
音樂播放器的完善
該案例的實作分為以下幾步:
①點選播放、暫停、繼續按鈕達到播放、暫停、繼續的目的
②增加進度條,讓進度随着播放進度而調整
③手指拖動進度條時,可以修改播放的位置。
④使用文字顯示目前播放進度


由于這個小案例是在之前案例的基礎之上修改過來了,是以先把骨架代碼貼出來,然後再一步一步增加功能。
           
IService接口如下

public interface IService {
    public void callPlay();
    public void callPause();
    public void callReplay();
}
           
MainActivity類如下:

public class MainActivity extends Activity implements OnSeekBarChangeListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);

        // 啟動和綁定服務
        Intent service = new Intent(this, MusicService.class);
        startService(service);
        conn = new MyServiceConnection();
        bindService(service, conn, Context.BIND_AUTO_CREATE);
    }

    // 播放
    public void play(View v) {iService.callPlay();}
    // 暫停
    public void pause(View v) { iService.callPause();}
    // 重新播放
    public void replay(View v) {    iService.callReplay();}

    private class MyServiceConnection implements ServiceConnection {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {iService = (IService) service; }

        @Override
        public void onServiceDisconnected(ComponentName name) {}
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(conn);
    }
}
           
MusicService類檔案如下:

public class MusicService extends Service {
    @Override
    public void onCreate() {super.onCreate();}

    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }

    // 播放音樂
    public void play() {}
    // 暫停
    public void pause() {}
    // 繼續播放
    public void replay() {}
    // 中間人
    private class MyBinder extends Binder implements IService {
        @Override
        public void callPlay() {    play(); }
        @Override
        public void callPause() {pause();}
        @Override
        public void callReplay() {replay();}
    }
}
           
activity_main.xml檔案如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="play"
        android:text="播放音樂" />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="pause"
        android:text="暫停播放" />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="replay"
        android:text="繼續播放音樂" />
</LinearLayout>
           
整體來說骨架代碼還是很簡單的,當應用啟動時會啟動并綁定服務,這樣就可以通過中間人來調用服務中的方法了。

接下來就一步一步添加功能。

第一步,點選播放、暫停、繼續按鈕達到播放、暫停、繼續的目的

首先回想一下如何播放音樂檔案:初始化MediaPlay執行個體,配置資料源、類型,準備一下,然後就可以start播放了。

那麼在這一步中,可以在服務的onCreate()方法中,執行個體化MediaPlay對象;當調用播放時,初始化資源并播放;暫停和繼續播放就很簡單了,隻需要調用相應的方法就OK了。

增加的代碼如下:

// 播放器執行個體
private MediaPlayer mediaPlayer;
@Override
public void onCreate() {
    super.onCreate();
    // 初始化執行個體對象
    mediaPlayer = new MediaPlayer();
}

// 播放
public void play() {
try {
        // 重置資源
        mediaPlayer.reset();
        // 初始化配置
        mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        mediaPlayer.setDataSource("/mnt/sdcard/zuoqujia.mp3");
        // 準備
        mediaPlayer.prepareAsync();
        // 準備完成的回調
        mediaPlayer.setOnPreparedListener(new OnPreparedListener() {

            @Override
            public void onPrepared(MediaPlayer mp) {
                mediaPlayer.start();
            }
        });
    } catch (Exception e) {
        e.printStackTrace();
    }
}

// 暫停播放
public void pause() {mediaPlayer.pause();}

// 繼續播放
public void replay() {mediaPlayer.start();}
           
第二步,增加進度條,讓進度随着播放進度而調整

其實增加一個進度條是很簡單的,直接拖一個SeekBar就可以達到目的。但是怎麼樣在服務中達到修改UI的目的卻是這一步的難點,此外,SeekBar的進度條是要實時更新的,這些都是這一步的關鍵。

仔細回想一下之前的知識點,我們可以使用Handler在子線程中修改UI線程的資料,但是服務中怎麼擷取到Handler?可以這樣,把MainActivity中的Handler改變為靜态變量,這樣就可以在服務類中使用了。

音樂播放要求每秒更新一次進度條的位置,我們可以使用SE中的Timer和TimerTask達到此目的。

有了以上這些解決問題的技術,趕快來看看代碼是怎麼寫的。

首先在布局檔案中加入SeekBar控件,并在MainActivity的找到該控件。

<SeekBar
    android:id="@+id/sb_progress"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

private static SeekBar sbProgress; // 進度條

protected void onCreate(Bundle savedInstanceState) {
    ... ...
    sbProgress = (SeekBar) findViewById(R.id.sb_progress);
    ... ...
}

然後在MainActivity中增加靜态的Handler,在MusicService類增加發送消息的邏輯。

MainActivity類增加如下内容:

private static int duration; // 歌曲總時長,毫秒機關 

private static int currentPosition; // 歌曲目前進度,毫秒機關

public static Handler handler = new Handler() {

    public void handleMessage(android.os.Message msg) {
        switch (msg.what) {

        case MusicService.CURRENTPOSITION: // 設定進度
            // 擷取消息、更新進度條、更新目前播放位置
            currentPosition = msg.arg1;
            sbProgress.setProgress(currentPosition);
            break;

        case MusicService.DURATION: // 設定總時長
            duration = msg.arg1;
            sbProgress.setMax(duration);
            break;

        default:
            break;
        }
    };
};
           
MusicService增加如下代碼

public static final int DURATION = 0; // 音樂總時長
public static final int CURRENTPOSITION = 1;//音樂目前播放位置

public void play() {
    try {
        // 重置
        mediaPlayer.reset();
        // 初始化
        mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        mediaPlayer.setDataSource("/mnt/sdcard/zuoqujia.mp3");
        // 準備
        mediaPlayer.prepareAsync();
        // 準備完成的回調
        mediaPlayer.setOnPreparedListener(new OnPreparedListener() {

            @Override
            public void onPrepared(MediaPlayer mp) {
                // 開始播放
                mediaPlayer.start();

                // 擷取音樂總進度,并發送消息
                int duration = mediaPlayer.getDuration();
                Message msg = Message.obtain();
                msg.what = DURATION;
                msg.arg1 = duration;
                MainActivity.handler.sendMessage(msg);

                // 每隔一秒更新進度
                Timer timer = new Timer();
                TimerTask task = new TimerTask() {

                    @Override
                    public void run() {
                        //  擷取目前進度,并發送消息更新UI
                        int currentPosition = mediaPlayer.getCurrentPosition();
                        Message msg = Message.obtain();
                        msg.what = CURRENTPOSITION;
                        msg.arg1 = currentPosition;
                        MainActivity.handler.sendMessage(msg);
                    }
                };
                // 每隔一秒鐘調用一次
                timer.schedule(task, 0, 1000);

            }
        });
    } catch (Exception e) {
        e.printStackTrace();
    }
}

做完這些,音樂播放時,進度到就會“動”了。
           
第三步,手指拖動進度條時,可以修改播放的位置。

達到這個目的,可以利用MediaPlayer中的seekTo()方法,定位播放位置。

那麼我們需要做的很簡單了,在IService中增加一個callSeekToProgress()方法,并在MainActivity和MusicService調用和編寫。

IService增加如下内容:

public void callSeekToProgress(int currentPosition);

MainActivity增加如下内容:

// 把播放進度移動到指定位置
public void seekToProgress(int currentPosition) {
    iService.callSeekToProgress(currentPosition);
}

在MusicService增加如下内容:

// 中間人
private class MyBinder extends Binder implements IService {
    ... ...
    @Override
    public void callSeekToProgress(int currentPosition) {
        seekToProgress(currentPosition);
    }
}

// 修改播放位置
public void seekToProgress(int currentPosition) {
    mediaPlayer.seekTo(currentPosition);
}
           
第四步,使用文字顯示目前播放進度

寫完上面這些,增加這個功能就略顯容易了。在布局檔案中增加一個TextView,然後再Handler消息處理時,設定其内容就可以了。

tvCurrentProgress.setText("音樂共" + (int)(duration / 1000.0f) + "秒/目前" + (int)(currentPosition / 1000.0f) + "秒");
           

視訊播放器

SurfaceView

  • 對畫面的實時更新要求較高
  • 雙緩沖技術:記憶體中有兩個畫布,A畫布顯示至螢幕,B畫布在記憶體中繪制下一幀畫面,繪制完畢後B顯示至螢幕,A在記憶體中繼續繪制下一幀畫面
  • 播放視訊也是用MediaPlayer,不過跟音頻不同,要設定顯示在哪個SurfaceView,遺憾的是它隻支援3GP和MP4格式的檔案。
    SurfaceView sv = (SurfaceView) findViewById(R.id.sv);
    SurfaceHolder sh = sv.getHolder();
    
    MediaPlayer player = new MediaPlayer();
    player.reset();
    try {
        player.setDataSource("sdcard/2.3gp");
        player.setDisplay(sh);
        player.prepare();
    } catch (Exception e) {
        e.printStackTrace();
    }
    player.start();
               
  • SurfaceView是重量級元件,可見時才會建立
  • 給SurfaceHolder設定CallBack,類似于偵聽,可以知道SurfaceView的狀态
    sh.addCallback(new Callback() {
        //SurfaceView銷毀時調用
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            // TODO Auto-generated method stub
    
        }
        //SurfaceView建立時調用
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            // TODO Auto-generated method stub
    
        }
    
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                int height) {
            // TODO Auto-generated method stub
    
        }
    });
               
  • SurfaceView一旦不可見,就會被銷毀,一旦可見,就會被建立,銷毀時停止播放,再次建立時再開始播放

視訊播放(VideoView)

上面的SurfaceView略顯麻煩,Google為我們提供了另外一個封裝的控件VideoView,隻需要幾行代碼就可以輕松展示一個3GP或者MP4檔案。

VideoView vv = (VideoView) findViewById(R.id.vv);
vv.setVideoPath("/mnt/sdcard/proto.3gp");
vv.start();
           

vitamio架構

Android原生的SurfaceView和VideoView,在播放視訊格式上都有限制,隻能播放3GP和MP4。

Vitamio是一款開源架構,由一大堆C大神開發的,其中提供了Andoird版本的類庫支援。

其中有一個VideoView類,和Andoird中原生的VideoView方法差不多,但是完美支援市面上的各種視訊格式。

<io.vov.vitamio.widget.VideoView
      android:id="@+id/vv"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content" />
io.vov.vitamio.widget.VideoView vv = (VideoView) findViewById(R.id.vv);
    vv.setVideoPath("/mnt/sdcard/test.3gp");
           

攝像頭

  • 啟動系統提供的拍照程式
    //隐式啟動系統提供的拍照Activity
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    //設定照片的儲存路徑
    File file = new File(Environment.getExternalStorageDirectory(), "haha.jpg"); 
    intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); 
    startActivityForResult(intent, 0);
               
  • 啟動系統提供的攝像程式
    Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
    
    File file = new File(Environment.getExternalStorageDirectory(), "haha.3gp"); 
    intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); 
    //設定儲存視訊檔案的品質
    intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
    startActivityForResult(intent, 0);