Android SurfaceView+MediaPlayer實現幾個不同的視訊輪流播放
MediaPlayer
1)如何獲得MediaPlayer例項:
可以使用直接new的方式:
MediaPlayer mp = new MediaPlayer();
也可以使用create的方式,如:
MediaPlayer mp = MediaPlayer.create(this, R.raw.test);
網路或是內部儲存直接setDataSource一個路徑
2) 如何設定要播放的檔案:
MediaPlayer要播放的檔案主要包括3個來源:
a. 使用者在應用中事先自帶的resource資源
例如:MediaPlayer.create(this, R.raw.test);
b. 儲存在SD卡或其他檔案路徑下的媒體檔案
例如:mp.setDataSource(“/sdcard/test.mp3”);
c. 網路上的媒體檔案
例如:mp.setDataSource(“http://www.citynorth.cn/music/confucius.mp3“);
MediaPlayer的setDataSource一共四個方法:
setDataSource (String path)
setDataSource (FileDescriptor fd)
setDataSource (Context context, Uri uri)
setDataSource (FileDescriptor fd, long offset, long length)
其中使用FileDescriptor時,需要將檔案放到與res資料夾平級的assets資料夾裡,然後使用:
AssetFileDescriptor fileDescriptor = getAssets().openFd(“rain.mp3”);
m_mediaPlayer.setDataSource(fileDescriptor.getFileDescriptor(),fileDescriptor.getStartOffset(), fileDescriptor.getLength());
來設定datasource
3)對播放器的主要控制方法:
Android通過控制播放器的狀態的方式來控制媒體檔案的播放,其中:
prepare()和prepareAsync() 提供了同步和非同步兩種方式設定播放器進入prepare狀態,需要注意的是,如果MediaPlayer例項是由create方法建立的,那麼第一次啟動播放前不需要再呼叫prepare()了,因為create方法裡已經呼叫過了。
start()是真正啟動檔案播放的方法,
pause()和stop()比較簡單,起到暫停和停止播放的作用,
seekTo()是定位方法,可以讓播放器從指定的位置開始播放,需要注意的是該方法是個非同步方法,也就是說該方法返回時並不意味著定位完成,尤其是播放的網路檔案,真正定位完成時會觸發OnSeekComplete.onSeekComplete(),如果需要是可以呼叫setOnSeekCompleteListener(OnSeekCompleteListener)設定監聽器來處理的。
release()可以釋放播放器佔用的資源,一旦確定不再使用播放器時應當儘早呼叫它釋放資源。
reset()可以使播放器從Error狀態中恢復過來,重新會到Idle狀態。
4)設定播放器的監聽器:
MediaPlayer提供了一些設定不同監聽器的方法來更好地對播放器的工作狀態進行監聽,以期及時處理各種情況,
如: setOnCompletionListener(MediaPlayer.OnCompletionListener listener)、
setOnErrorListener(MediaPlayer.OnErrorListener listener)等,設定播放器時需要考慮到播放器可能出現的情況設定好監聽和處理邏輯,以保持播放器的健壯性。
SurfaceView
SurfaceView是檢視(View)的繼承類,這個視圖裡內嵌了一個專門用於繪製的Surface。你可以控制這個Surface的格式和尺寸。Surfaceview控制這個Surface的繪製位置。(Surface是原始影象緩衝區(raw buffer)的一個控制代碼,而原始影象緩衝區是由螢幕影象合成器(screen compositor)管理的)
你可以通過SurfaceHolder介面訪問這個surface,getHolder()方法可以得到這個介面。
surfaceview的核心在於提供了兩個執行緒:UI執行緒和渲染執行緒。這裡應注意:
1> 所有SurfaceView和SurfaceHolder.Callback的方法都應該在UI執行緒裡呼叫,一般來說就是應用程式主執行緒。渲染執行緒所要訪問的各種變數應該作同步處理。
2> 由於surface可能被銷燬,它只在SurfaceHolder.Callback.surfaceCreated()和 SurfaceHolder.Callback.surfaceDestroyed()之間有效,所以要確保渲染執行緒訪問的是合法有效的surface。
關於SurfaceView我們要知道和注意以下幾點:
(1)SurfaceView 允許我們在非UI主執行緒中改變SurfaceView的內容,一般View是不允許線上程中訪問的。由於這個特點遊戲開發在介面處理上大多會選擇SurfaceView。
(2)在使用SurfaceView的時候一定要等建立成功以後在使用,也就是在SurfaceView的getHolder中添加回調addCallback中的surfaceCreated。
(3)SurfaceView可能被銷燬,而且有可能在Activity的OnDestroy()之前銷燬,所以在開發中一定要做處理。
(4)SurfaceView位於UI的最底層的檢視層次中,允許在其上面新增一下圖層,但不能是透明的。
(5)SurfaceView的構成比其它的View複雜,佔用資源也多,所以除了使用view不能實現的,再建議使用。
下面是一個例子:
public class TextActivity extends AppCompatActivity {
private SurfaceView sfv;//能夠播放影象的控制元件
private SeekBar sb;//進度條
private String path;//本地檔案路徑
private SurfaceHolder holder;
private MediaPlayer player;//媒體播放器
private Button Play;//播放按鈕
private Timer timer;//定時器
private TimerTask task;//定時器任務
private int position = 0;
private EditText et;
int ipx = 0;
//視訊檔案需要放在res下新建的raw檔案下
int[] resId = {R.raw.video_1, R.raw.video_2, R.raw.video_3, R.raw.video_4};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_text);
initView();
}
//初始化控制元件,並且為進度條和影象控制元件新增監聽
private void initView() {
sfv = (SurfaceView) findViewById(R.id.sfv);
sb = (SeekBar) findViewById(R.id.sb);
Play = (Button) findViewById(R.id.play);
et = (EditText) findViewById(R.id.et);
Play.setEnabled(false);
holder = sfv.getHolder();
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
//當進度條停止拖動的時候,把媒體播放器的進度跳轉到進度條對應的進度
if (player != null) {
player.seekTo(seekBar.getProgress());
}
}
});
holder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
//為了避免影象控制元件還沒有建立成功,使用者就開始播放視訊,造成程式異常,所以在建立成功後才使播放按鈕可點選
Log.d("TAG", "surfaceCreated");
Play.setEnabled(true);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.d("TAG", "surfaceChanged");
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
//當程式沒有退出,但不在前臺執行時,因為surfaceview很耗費空間,所以會自動銷燬,
// 這樣就會出現當你再次點選程序序的時候點選播放按鈕,聲音繼續播放,卻沒有影象
//為了避免這種不友好的問題,簡單的解決方式就是隻要surfaceview銷燬,我就把媒體播放器等
//都銷燬掉,這樣每次進來都會重新播放,當然更好的做法是在這裡再記錄一下當前的播放位置,
//每次點選進來的時候把位置賦給媒體播放器,很簡單加個全域性變數就行了。
Log.d("TAG", "surfaceDestroyed");
if (player != null) {
position = player.getCurrentPosition();
stop();
}
}
});
}
private void play() {
Play.setEnabled(false);//在播放時不允許再點選播放按鈕
if (isPause) {//如果是暫停狀態下播放,直接start
isPause = false;
player.start();
return;
}
try {
player = MediaPlayer.create(this, resId[ipx % 4]);
player.setDisplay(holder);//將影像播放控制元件與媒體播放控制元件關聯起來
// player.setLooping(true); //設定單個迴圈播放
player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {//視訊播放完成後,釋放資源
Play.setEnabled(true);
ipx++;
mp.reset();
play();
//迴圈播放下一個
mp = MediaPlayer.create(getBaseContext(), resId[ipx % 4]);
mp.setDisplay(holder);
mp.start();
}
});
player.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
//媒體播放器就緒後,設定進度條總長度,開啟計時器不斷更新進度條,播放視訊
Log.d("TAG", "onPrepared");
sb.setMax(player.getDuration());
timer = new Timer();
task = new TimerTask() {
@Override
public void run() {
if (player != null) {
int time = player.getCurrentPosition();
sb.setProgress(time);
}
}
};
timer.schedule(task, 0, 500);
sb.setProgress(position);
mp.seekTo(position);
mp.start();
}
});
player.prepareAsync();
} catch (Exception e) {
e.printStackTrace();
}
}
public void play(View v) {
play();
Log.d("TAG", path);
}
private boolean isPause;
private void pause() {
if (player != null && player.isPlaying()) {
player.pause();
isPause = true;
Play.setEnabled(true);
}
}
public void pause(View v) {
pause();
}
private void replay() {
isPause = false;
if (player != null) {
stop();
play();
}
}
public void replay(View v) {
replay();
}
public void back(View v) {
finish();
onDestroy();
}
private void stop() {
isPause = false;
if (player != null) {
sb.setProgress(0);
player.stop();
player.release();
player = null;
if (timer != null) {
timer.cancel();
}
Play.setEnabled(true);
}
}
public void stop(View v) {
stop();
}
@Override
protected void onDestroy() {
super.onDestroy();
stop();
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/et"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="請輸入檔名稱例如:aa.mp4,務必確保檔案放在sdcard目錄下"
android:visibility="gone" />
<SurfaceView
android:id="@+id/sfv"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<SeekBar
android:id="@+id/sb"
android:layout_width="match_parent"
android:visibility="gone"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="45dp"
android:background="@android:color/darker_gray"
android:orientation="horizontal">
<Button
android:id="@+id/play"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:onClick="play"
android:background="@android:color/holo_blue_light"
android:textColor="@android:color/white"
android:text="播放" />
<Button
android:id="@+id/pause"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@android:color/holo_blue_light"
android:textColor="@android:color/white"
android:onClick="pause"
android:text="暫停" />
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:onClick="stop"
android:background="@android:color/holo_blue_light"
android:textColor="@android:color/white"
android:text="停止" />
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@android:color/holo_blue_light"
android:textColor="@android:color/white"
android:onClick="replay"
android:text="重播" />
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@android:color/holo_blue_light"
android:textColor="@android:color/white"
android:onClick="back"
android:text="退出" />
</LinearLayout>
</LinearLayout>