一、SurfaceView和VIew的區別
1、VIew主要適用於主動更新情況,並且只能在主線程繪製和更新畫面,以及在繪圖時沒有使用雙緩衝機制
2、surfaceView主要適用於被動更新,如頻繁的刷新,因爲它可以通過子線程來進行頁面的刷新,而且在底層已經實現雙緩衝機制,繪圖時不會出現閃爍問題
總而言之,SurfaceView繼承自View,主要用於視頻、音頻或耗時的繪圖和經常更新視圖(地圖等等)顯示,如果自定義的view需要頻繁刷新,或者刷新時處理的數據量大,則可以考慮surfaceview取代view,因爲View在主線程中會阻塞主線程,若view的刷新操作超過16ms則用戶視覺會感到卡頓,由於surfaceView是子線程處理就不會
以下一段結論是抄自老羅之旅的 :http://blog.csdn.net/luoshengyang/article/details/8661317/
SurfaceView類的成員變量mRequestedType描述的是SurfaceView的繪圖表面的類型,一般來說,它的值可能等於SURFACE_TYPE_NORMAL,也可能等於SURFACE_TYPE_PUSH_BUFFERS。
當一個SurfaceView的繪圖表面的類型等於SURFACE_TYPE_NORMAL的時候,就表示該SurfaceView的繪圖表面所使用的內存是一塊普通的內存。一般來說,這塊內存是由SurfaceFlinger服務來分配的,我們可以在應用程序內部自由地訪問它,即可以在它上面填充任意的UI數據,然後交給SurfaceFlinger服務來合成,並且顯示在屏幕上。在這種情況下,SurfaceFlinger服務使用一個Layer對象來描述該SurfaceView的繪圖表面。
當一個SurfaceView的繪圖表面的類型等於SURFACE_TYPE_PUSH_BUFFERS的時候,就表示該SurfaceView的繪圖表面所使用的內存不是由SurfaceFlinger服務分配的,因而我們不能夠在應用程序內部對它進行操作。例如,當一個SurfaceView是用來顯示攝像頭預覽或者視頻播放的時候,我們就會將它的繪圖表面的類型設置爲SURFACE_TYPE_PUSH_BUFFERS,這樣攝像頭服務或者視頻播放服務就會爲該SurfaceView繪圖表面創建一塊內存,並且將採集的預覽圖像數據或者視頻幀數據源源不斷地填充到該內存中去。注意,這塊內存有可能是來自專用的硬件的,例如,它可能是來自視頻卡的。在這種情況下,SurfaceFlinger服務使用一個LayerBuffer對象來描述該SurfaceView的繪圖表面。
從上面的描述就得到一個重要的結論:繪圖表面類型爲SURFACE_TYPE_PUSH_BUFFERS的SurfaceView的UI是不能由應用程序來控制的,而是由專門的服務來控制的,例如,攝像頭服務或者視頻播放服務,同時,SurfaceFlinger服務會使用一種特殊的LayerBuffer來描述這種繪圖表面。使用LayerBuffer來描述的繪圖表面在進行渲染的時候,可以使用硬件加速,例如,使用copybit或者overlay來加快渲染速度,從而可以獲得更流暢的攝像頭預覽或者視頻播放。
注意,我們在創建了一個SurfaceView之後,可以調用它的成員函數getHolder獲得一個SurfaceHolder對象,然後再調用該SurfaceHolder對象的成員函數setType來修改該SurfaceView的繪圖表面的類型,即修改該SurfaceView的成員變量mRequestedType的值
這段結論就說明決定surfaceView的內存是普通內存(由開發者自己決定用來繪製什麼)還是專用的內存(開發者無法使用這塊內存)由mRequestType決定,而我們可以通過holde.setType()設置,用於決定是顯示視頻還是自定義的surfaceView。
以下直接用demo講解自定義的view和專用於視頻的surfaceView
1、自定義surfaceView,用於繪製sin曲線
package com.example.administrator.surfaceviewdemo;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
/**
* author : zhongwr on 2016/7/12
* SurfaceView類的成員函數draw和dispatchDraw的參數canvas所描述的都是建立在宿主窗口的繪圖表面上的畫布,
* 因此,在這塊畫布上繪製的任何UI都是出現在宿主窗口的繪圖表面上的。
* <p/>
* 本來SurfaceView類的成員函數draw是用來將自己的UI繪製在宿主窗口的繪圖表面上的,
* 但是這裏我們可以看到,如果當前正在處理的SurfaceView不是用作宿主窗口面板的時候,
* 即其成員變量mWindowType的值不等於WindowManager.LayoutParams.TYPE_APPLICATION_PANEL的時候,
* SurfaceView類的成員函數draw只是簡單地將它所佔據的區域繪製爲黑色。
*/
public class SinSurfaceView extends SurfaceView implements Runnable {
private SurfaceHolder mSurfaceHolder;
/**
* 用於保存正弦路徑座標
*/
private Path mPath;
private Paint mPaint;
public SinSurfaceView(Context context) {
super(context);
init();
}
public SinSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public SinSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5);
mPaint.setColor(Color.GREEN);
//連接處更加平滑
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPath = new Path();
/**通過holder去申請繪圖表面的畫布,surfaceview其實draw()或dispathDraw()都只是一塊默認的黑色區域,並不是用作宿主
* 真正要做的事情由開發者自行繪製,繪製之前就是通過holder獲取一塊內存區域的畫布,
* 然後可在UI線程或工作線程在這個畫布上進行繪製所需要的視圖,最後還是通過holder提交這個畫布就可以顯示
* */
mSurfaceHolder = getHolder();
//回調
mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
/***
* surfaceview的繪圖表面(就是activity宿主創建一個透明的表面用於surfaceView繪製)被創建時執行
* 在updateWindow()創建宿主(activity的窗口)的繪圖表面時會回調,雖然surfaceView是獨立於一個線程但還是離不開宿主窗口,
* 最後還是要粘貼到window中
*
* surfaceCreated方法,是當SurfaceView被顯示時會調用的方法,所以你需要再這邊開啓繪製的線 程
*
* @param holder
*/
@Override
public void surfaceCreated(SurfaceHolder holder) {
new Thread(SinSurfaceView.this).start();
}
/**
* 創建、更新會認爲發生變化也會回調這個方法
* @param holder
* @param format
* @param width
* @param height
*/
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
/***
*surfaceDestroyed方法是當SurfaceView被隱藏會銷燬時調用的方法,在這裏你可以關閉繪製的線程
* @param holder
*/
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
isDrawing = false;
}
});
}
/***
* 是否在繪製:用於關閉子線程:true則表示一直循環
*/
private boolean isDrawing = true;
private int drawX;
private int drawY;
/***
* 注意這個是在子線程中繪製的
*/
@Override
public void run() {
while (isDrawing) {
drawX++;
drawY = (int) (100 * Math.sin(drawX * 2 * Math.PI / 180) + 400);
mPath.lineTo(drawX, drawY);
draw(mPath);
}
}
/***
* 注意這個是在子線程中繪製的,surface支持子線程更新ui,所以
*/
private void draw(Path path) {
Canvas canvas = null;
//給畫布加鎖,防止線程安全,防止該內存區域被其他線程公用
canvas = mSurfaceHolder.lockCanvas();
if (null != canvas) {
//清屏操作或者設置背景
canvas.drawColor(Color.WHITE);
canvas.drawPath(mPath, mPaint);
//提交顯示視圖並解鎖,防止長期佔用此內存
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
2、surfaceView用於顯視頻的demo
一、注意兩點:
(1)mediaPlayer = new MediaPlayer()必須只初始化一次,若是放在play()方法中初始化,由於可能會多次執行直接或間接play()這個方法,導致多次初始化,以至於在onPrepared(MediaPlayer mp)的回調中(假設監聽器用的是同一個,看demo),mediaPlayer和mp不是同一個,因爲多次初始化原因,導致不是同一個對象,保證是一個對象就行
(2)播放本地視頻音頻時,明明就有文件,但是通過/sdcard/xxxx.mp4或者storage/sdcard0/xxx.mp4都說找不到這個路徑,然後查看異常日誌會發現如下日誌
<1>、 QCMediaPlayer mediaplayer NOT present
發現這個不要驚慌,雖然我也搞了好長時間。最後才發現,其實就是沒加sd卡的權限,只要在manifest中加入權限 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />基本就好了,跟網上說的
使用高通的手機平臺都有這個問題,MTK 平臺的就沒有。主要是高通平臺不支持直接 new Mediaplayer(); 必須用 MediaPlayer.create(xxx)方法 好像並沒有什麼關係
<2>、Should
have subtitle controller already set
發現這個異常也不要驚慌,畢竟這個好像是sdk4.4之後源碼才加入的,可以不用管,不會影響視頻的播放
二、談談mediaPlayer的創建
要是仔細的話你會發現,系統有好幾個MediaPlayer.create(xxx),但是我們幾乎都是自己new MediaPlayer()創建,這兩個有什麼區別呢,接下來說說
要是看過 MediaPlayer.create(xxx)這個方法的源碼,你會發現,最後它也是通過new MediaPlayer(),只不過MediaPlayer.create(xxx)是已經初始化了的,然後值得注意的是mediaPlayer.prepare()和mediaPlayer.setDatesource(context,Uri);
mediaPlayer.prepare()主要是同步預加載,若是預加載資源則mediaPlayer.seekTo()是不會生效的,同樣mediaPlayer.getCurrentPosition()也不會生效,因爲這都是mediaPlayer.prepareSync()這個異步加載纔會生效的,即他們都是異步加載的方法 。
mediaPlayer.setDatesource(context,Uri); 這個方法加載資源時不需要權限的,直接可以播放,所以網絡上出現問題(2)的<1>時,網上都說用create()方法創建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("mp3或者mp4的地址");
3.MediaPlayer常用API
MediaPlayer的setDataSource一共四個方法:
setDataSource (String path)
setDataSource (FileDescriptor fd)
setDataSource (Context context, Uri uri)
setDataSource (FileDescriptor fd, long offset, long length)
對播放器的主要控制方法:
Android通過控制播放器的狀態的方式來控制媒體文件的播放,其中:
1.prepare()和prepareAsync() 提供了同步和異步兩種方式設置播放器進入prepare狀態,需要注意的是,如果MediaPlayer實例是由create方法創建的,那麼第一次啓動播放前不需要再調用prepare()了,因爲create方法裏已經調用過了。
2. start()是真正啓動文件播放的方法
3.pause()和stop()比較簡單,起到暫停和停止播放的作用
4.seekTo()是定位方法,可以讓播放器從指定的位置開始播放,需要注意的是該方法是個異步方法,也就是說該方法返回時並不意味着定位完成,尤其是播放的網絡文件,真正定位完成時會觸發OnSeekComplete.onSeekComplete(),如果需要是可以調用setOnSeekCompleteListener(OnSeekCompleteListener)設置監聽器來處理的。
5.release()可以釋放播放器佔用的資源,一旦確定不再使用播放器時應當儘早調用它釋放資源。
6.reset()可以使播放器從Error狀態中恢復過來,重新會到Idle狀態。
廢話不說了:直接上代碼:其它代碼註釋很明瞭,直接看應該就能看懂,功能自己擴展
package com.example.administrator.surfaceviewdemo;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.TimedText;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.lang.ref.WeakReference;
/**
* 視頻播放類
* @author zhognwr
*/
public class PlayVedioActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "PlayVedioActivity";
private SeekBar videoSeekBar;
private TextView tvPlayingTime;
private TextView tvVideoTotalTime;
private ImageView ivPlay;
private ImageView ivPause;
private MediaPlayer mediaPlayer;
private SurfaceView surfaceView;
private EditText etPath;
/**
* 當前播放視頻位置
*/
private int currentPosition;
public static void startInstance(Context context) {
context.startActivity(new Intent(context, PlayVedioActivity.class));
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.video_player_layout);
initView();
initListener();
initData();
}
@Override
protected void onPause() {
super.onPause();
updateSeekBarHandler.removeMessages(0);
mediaPlayer.reset();
mediaPlayer.release();
}
private void initData() {
mediaPlayer= new MediaPlayer();
// 把輸送給surfaceView的視頻畫面,直接顯示到屏幕上,不要維持它自身的緩衝區
surfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
// surfaceView.getHolder().setFixedSize(176, 144);
surfaceView.getHolder().setKeepScreenOn(true);
surfaceView.getHolder().addCallback(new SurfaceCallback());
}
private void initListener() {
ivPause.setOnClickListener(this);
ivPlay.setOnClickListener(this);
videoSeekBar.setOnSeekBarChangeListener(new SeekBarListener());
surfaceView.getHolder().addCallback(new SurfaceCallback());
}
private void initView() {
surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
videoSeekBar = (SeekBar) findViewById(R.id.videoProgress);
tvPlayingTime = (TextView) findViewById(R.id.textHasPalyTime);
tvVideoTotalTime = (TextView) findViewById(R.id.video_total_time);
ivPlay = (ImageView) findViewById(R.id.imagePlay);
ivPause = (ImageView) findViewById(R.id.iv_pause);
etPath = (EditText) findViewById(R.id.et_path);
}
@Override
public void onClick(View v) {
if (v == ivPlay) {//播放
if (!mediaPlayer.isPlaying()) {//播放、繼續播放
// pause = false;
mediaPlayer.start();
}
} else if (v == ivPause) {//暫停
// pause = true;
mediaPlayer.pause();
}
}
/**
* 重新開始播放
*/
protected void replay() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.seekTo(0);
Toast.makeText(this, "重新播放", Toast.LENGTH_SHORT).show();
return;
}
}
/**
* 暫停或繼續
*/
protected void pause() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
Toast.makeText(this, "暫停播放", Toast.LENGTH_LONG).show();
}
}
/**
* SeekBar監聽類,監聽用戶對seekbar滑動的位置,已達到控制視頻播放進度
*/
private class SeekBarListener implements SeekBar.OnSeekBarChangeListener {
/***
* 一直滑動不斷變化的seekBar時,觸發
*
* @param seekBar
* @param progress
* @param fromUser
*/
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
Log.d(TAG, "Changed progress " + progress);
Log.d(TAG, "Changed getProgress() " + seekBar.getProgress());
tvPlayingTime.setText(getTime(seekBar.getProgress() / 1000));
}
/**
* 用戶開始滑動seekBar時觸發
*
* @param seekBar
*/
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
Log.d(TAG, "start getProgress() " + seekBar.getProgress());
}
/**
* 當用戶結束對滑塊的滑動時,將mediaPlayer播放位置設爲滑塊結束對應位置
*
* @param seekBar
*/
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
Log.d(TAG, "Stop getProgress() " + seekBar.getProgress());
currentPosition = seekBar.getProgress();
mediaPlayer.seekTo(currentPosition);
tvPlayingTime.setText(getTime(seekBar.getProgress() / 1000));
}
}
/*
* 當SurfaceView所在的Activity離開了前臺,SurfaceView會被destroy,
* 當Activity又回到了前臺時,SurfaceView會被重新創建,並且是在OnResume()方法之後被創建
*/
private class SurfaceCallback implements SurfaceHolder.Callback {
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
/**
* 創建SurfaceView時開始從上次位置播放或重新播放
*
* @param holder
*/
@SuppressLint("NewApi")
public void surfaceCreated(SurfaceHolder holder) {
// audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
// maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);// 取得最大音量
// curVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);// 獲取當前音量
// 創建SurfaceHolder的時候,如果存在上次播放的位置,則按照上次播放位置進行播放
play("/sdcard/basketball.mp4");
}
/**
* 離開SurfaceView時停止播放,保存播放位置
*/
public void surfaceDestroyed(SurfaceHolder holder) {
// 隱藏view的時候銷燬SurfaceHolder的時候記錄當前的播放位置並停止播放
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
currentPosition = mediaPlayer.getCurrentPosition();
mediaPlayer.stop();
updateSeekBarHandler.removeMessages(0);
}
}
}
/**
* 開始播放
*
* @param playUrl 播放視頻的地址
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
protected void play(String playUrl) {
// 獲取視頻文件地址:放在sd卡根目錄下的視頻地址,這個地址也支持網絡地址:比如http://...
// String path = "storage/sdcard0/test.mp4";
// 獲取視頻文件地址
String path = etPath.getText().toString().trim();
// String path = "/sdcard/basketball.mp4";
File file = new File(path);
if (!file.exists()) {
Toast.makeText(this, "視頻文件路徑錯誤或忘了加sd卡權限了", Toast.LENGTH_LONG).show();
return;
}
path = file.getAbsolutePath();
Log.d(TAG, "path = " + path);
try {
//mediaPlayer= new MediaPlayer(); 這個初始化不能放在這裏,因爲surfaceView可能會多次執行,導致mediaPlayer重新被初始化,由於監聽器用的是同一個導致回調的時候會出現不適同一個mediaplayer的情況
//此方法創建同步加載,直接start
// mediaPlayer = MediaPlayer.create(this, Uri.parse(path), null);
mediaPlayer.reset();
// 設置播放的視頻源
mediaPlayer.setDataSource(path);
// 設置顯示視頻的SurfaceHolder
mediaPlayer.setDisplay(surfaceView.getHolder());
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setOnPreparedListener(mediaPlayerListener);
mediaPlayer.setOnCompletionListener(mediaPlayerListener);
mediaPlayer.setOnInfoListener(mediaPlayerListener);
mediaPlayer.setOnErrorListener(mediaPlayerListener);
mediaPlayer.setOnBufferingUpdateListener(mediaPlayerListener);
mediaPlayer.setOnSeekCompleteListener(mediaPlayerListener);
mediaPlayer.prepareAsync();// 緩衝,裝載
// mediaPlayer.start();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 內部消息隊列類,主要獲取updateThread發來的CurrentPosition和MaxPosition設置給SeekBar
*/
private Handler updateSeekBarHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "handleMessage curPosition = " + mediaPlayer.getCurrentPosition());
videoSeekBar.setProgress(mediaPlayer.getCurrentPosition());
tvPlayingTime.setText(getTime(mediaPlayer.getCurrentPosition() / 1000));
sendEmptyMessageDelayed(0, 1000);
}
};
private MediaPlayerListener mediaPlayerListener = new MediaPlayerListener();
private class MediaPlayerListener implements MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnInfoListener
, MediaPlayer.OnErrorListener, MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnSeekCompleteListener {
/**
* 視頻裝載完成,可以播放
*
* @param mp
*/
public void onPrepared(MediaPlayer mp) {
Log.d(TAG, "裝載完成");
Log.d(TAG, "裝載完成 media same ? "+(mp==mediaPlayer));
mediaPlayer.seekTo(currentPosition);
mp.start();
// 按照初始位置播放
// 設置進度條的最大進度爲視頻流的最大播放時長
videoSeekBar.setMax(mediaPlayer.getDuration());
tvVideoTotalTime.setText(getTime(mediaPlayer.getDuration() / 1000));
//更新seekBar進度
updateSeekBarHandler.sendEmptyMessageDelayed(0, 1000);
// 開始線程,更新進度條的刻度
int current = mediaPlayer.getCurrentPosition();
videoSeekBar.setProgress(current);
}
/**
* 在播放結束被回調
*
* @param mp
*/
@Override
public void onCompletion(MediaPlayer mp) {
Log.d(TAG, "onCompletion " + mp.getCurrentPosition());
mediaPlayer.seekTo(currentPosition);
}
/**
* 播放視頻,處理視頻過程的視頻信息
*
* @param mp
* @param what
* @param extra
* @return
*/
@Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
Log.d(TAG, "---------onInfo------what---" + what + "--extra--" + extra);
if (MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START == what) {
//緩衝視頻開始
} else if (MediaPlayer.MEDIA_INFO_BUFFERING_START == what) {
//網絡連接異常
} else if (MediaPlayer.MEDIA_INFO_BUFFERING_END == what) {
//緩衝完成
} else if (MediaPlayer.MEDIA_INFO_METADATA_UPDATE == what) {
Log.d(TAG, "---------MEDIA_INFO_METADATA_UPDATE---------");
}
return false;
}
/***
* 播放出錯
*
* @param mp
* @param what
* @param extra
* @return
*/
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
// 發生錯誤重新播放
// play("");
Log.d(TAG, "onError");
if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED ||
what == MediaPlayer.MEDIA_ERROR_TIMED_OUT) {// 網絡連接異常
return true;
}
return false;
}
@Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
Log.d(TAG, "percent = " + percent);
}
/***
*
* seekTo()是定位方法,可以讓播放器從指定的位置開始播放,需要注意的是該方法是個異步方法,
* 也就是說該方法返回時並不意味着定位完成,尤其是播放的網絡文件,
* 真正定位完成時會觸發OnSeekComplete.onSeekComplete(),
* 如果需要是可以調用setOnSeekCompleteListener(OnSeekCompleteListener)設置監聽器來處理的。
* @param mp
*/
@Override
public void onSeekComplete(MediaPlayer mp) {
Log.d(TAG, "---------onSeekComplete---------");
mp.start();
}
}
/**
* 秒轉換成00:00
*
* @param ms
* @return
*/
private String getTime(int ms) {
try {
return appendStrs(ms / 60 + "") + ":" + appendStrs(ms % 60 + "");
} catch (Exception e) {
}
return "00:00";
}
/**
* 字符串前面補加0
*
* @param str
* @return
*/
private String appendStrs(String str) {
String appendStr = "00";
if (null == str || str.trim().isEmpty()) {
return appendStr;
}
if (str.length() == appendStr.length()) {
return str;
}
return appendStr.substring(0, appendStr.length() - str.length()) + str;
}
}
視頻Demo:http://download.csdn.net/detail/zhongwn/9576021