<b>Android Video</b><b>簡述</b>
Video的播放實作,至于錄制請參見《Android Camera簡述》。
<b>一、播放方式</b>
<b>1</b><b>)自帶播放器播放</b>
入口Action為ACTION_VIEW,Data為Uri,Type為MIME類型。
// 自帶播放器播放
private void sysPlayer(String filename) {
Uri uri = Uri.parse(filename);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri, "video/3gp");
startActivity(intent);
}
<b>2</b><b>)VideoView</b><b>播放</b>
VideoView元件進行播放。在《Android Camera簡述》的Camera攝像樣例中的預覽操作即是這種方式。(例子選擇VideoView播放,仍是用的同一個類==)
<b>3</b><b>)MediaPlayer</b><b>播放</b>
MediaPlayer進行播放控制。不再贅述了==,具體看下節例子。
<b>二、視訊播放器</b>
<b>1</b><b>)視訊播放活動</b>
檔案浏覽方式,找到3gp檔案進行播放。
1)直接點選,以MediaPlayer方式播放(簡單的自定義了下界面^^)
2)長按時,可選"自帶播放器播放"、"VideoView播放"、"MediaPlayer播放"三種方式
<b>2</b><b>)檔案浏覽器接口</b>
定義了檔案夾點選、檔案點選、檔案長按、錯誤四方法。
<b>3</b><b>)檔案清單擴充卡</b>
自定義檔案清單擴充卡。主要就是getView()内對一個Item布局的的指派,有圖檔、名稱、類型、大小四項。
<b>4</b><b>)檔案浏覽器</b>
自定義的檔案浏覽器。
1)屬性資源的使用。即在attrs.xml定義的FileBrowser的屬性。
不過,extName&fileImage以字尾數字不斷增加的好像不好定義,仍是自定義空間方式。
2)接口事件,參見OnFileBrowserListener.java。
<b></b>
public class FileBrowser extends ListView implements
AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener {
public static final int ERROR_ACCESS_DIR = 1; // 通路目錄錯誤
/*
* 這是其他屬性的命名空間。
*
* 而attrs.xml定義的FileBrowser的屬性,命名空間需如下定義,否則取不到屬性值。
* xmlns:空間名="http://schemas.android.com/apk/res/自定義元件所在包名"
*/
private final String namespace = "http://vaero.blog.51cto.com/";
/* 自定義屬性 */
private String baseDir; // 初始根目錄
private boolean backFinish; // 是否傳回退出
private int folderImageResId; // 檔案夾的圖檔資源id
private Map<String, Integer> fileImageResIdMap = new HashMap<String, Integer>(); // 各檔案類型的圖檔資源id
private int otherFileImageResId; // 其他檔案類型的圖檔資源id
private Stack<String> dirStack = new Stack<String>(); // 棧方式存放目前目錄
private List<File> fileList = new ArrayList<File>();; // 目前目錄内File
private FileListAdapter fileListAdapter; // 自定義檔案清單擴充卡
private OnFileBrowserListener listener; // 檔案浏覽器接口
public FileBrowser(Context context, AttributeSet attrs) {
super(context, attrs);
// 獲得TypedArray對象
TypedArray typedArray = context.obtainStyledAttributes(attrs,
R.styleable.FileBrowser);
// 擷取baseDir
baseDir = typedArray.getString(R.styleable.FileBrowser_baseDir);
if (null == baseDir) { // 未定義時為SDCard目錄
baseDir = Environment.getExternalStorageDirectory().toString();
}
setCurrentPath(baseDir); // 由dir設定目前棧
// 擷取backFinish,預設true
backFinish = typedArray.getBoolean(R.styleable.FileBrowser_backFinish,
false);
// 擷取folderImage,預設0
folderImageResId = typedArray.getResourceId(
R.styleable.FileBrowser_folderImage, 0);
// 擷取otherFileImage,預設0
otherFileImageResId = typedArray.getResourceId(
R.styleable.FileBrowser_otherFileImage, 0);
// 擷取extName&fileImage(以字尾數字不斷增加的方式動态擷取)
String extName;
int fileImageResId;
int index = 1;
while (true) {
extName = attrs.getAttributeValue(namespace, "extName" + index);
fileImageResId = attrs.getAttributeResourceValue(namespace,
"fileImage" + index, 0);
// 不存在extName&fileImage屬性時跳出循環
if ("".equals(extName) || extName == null || fileImageResId == 0) {
break;
}
fileImageResIdMap.put(extName, fileImageResId);
index++;
// 設定Item點選監聽接口
setOnItemClickListener(this);
setOnItemLongClickListener(this);
// 設定目前目錄的File
setCurrentDirFiles();
// 設定擴充卡
fileListAdapter = new FileListAdapter(context, fileList,
folderImageResId, fileImageResIdMap, otherFileImageResId);
setAdapter(fileListAdapter);
}
// 由dir設定目前棧
private void setCurrentPath(String dir) {
dirStack.clear();
String spDir = dir.charAt(0) == '/' ? dir.substring(1) : dir;
for (String s : spDir.split("/")) {
dirStack.push(s);
// 由棧擷取目前目錄
private String getCurrentDir() {
StringBuffer result = new StringBuffer();
result.append("/");
for (int i = 0; i < dirStack.size(); i++) {
result.append(dirStack.get(i));
result.append("/");
return result.toString();
// 設定目前目錄的File
private boolean setCurrentDirFiles() {
// 獲得目前目錄中所有的File對象
File[] files = new File(getCurrentDir()).listFiles();
if (null == files) { // 一些系統目錄可能獲不到,為null
dirStack.pop();
return false;
fileList.clear();
// 多于一級時,增加null以表示傳回上一級
if (!dirStack.isEmpty())
fileList.add(null);
for (File file : files) {
fileList.add(file);
// 類型順序:null、檔案夾、檔案;同類型順序:字典順序
Collections.sort(fileList, new Comparator<File>() {
@Override
public int compare(File f1, File f2) {
if (null == f1) {
return -1;
} else if (null == f2) {
return 1;
} else if (f1.isDirectory() && !f2.isDirectory()) {
} else if (!f1.isDirectory() && f2.isDirectory()) {
} else {
return f1.toString().compareTo(f2.toString()); // 字典順序比較
}
});
return true;
@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
File file = fileList.get(position);
if (file == null) { // null時,傳回上一級
dirStack.pop(); // 出棧
setCurrentDirFiles(); // 設定目前目錄的File
fileListAdapter.notifyDataSetChanged(); // 通知資料改變,重新整理清單
if (listener != null) {
listener.onDirItemClick(getCurrentDir());
} else if (file.isDirectory()) { // 目錄時
dirStack.push(file.getName()); // 壓棧
if (setCurrentDirFiles()) { // 設定目前目錄的File
fileListAdapter.notifyDataSetChanged(); // 通知資料改變,重新整理清單
if (listener != null) {
listener.onDirItemClick(getCurrentDir());
} else {
listener.onError(ERROR_ACCESS_DIR);
} else { // 檔案時
listener.onFileItemClick(file.getAbsolutePath());
public boolean onItemLongClick(AdapterView<?> parent, View view,
int position, long id) {
if (file.isFile()) { // 檔案時
listener.onFileItemLongClick(file.getAbsolutePath());
return true;
return false;
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && !backFinish
&& !dirStack.isEmpty()) {
return true;
return super.onKeyDown(keyCode, event);
// 設定檔案浏覽器接口
public void setOnFileBrowserListener(OnFileBrowserListener listener) {
this.listener = listener;
<b>5</b><b>)MediaPlayer</b><b>播放視訊</b>
MediaPlayer播放視訊。控制界面很簡單,描述如下:
1)暫停、播放、停止三按鈕
2)進度條實時進度顯示,及拖動跳轉
3)進度條左側目前時間、右側總時間顯示
ps:播放畫面自适應等比例滿屏
public class VideoPlayer extends Activity implements
MediaPlayer.OnCompletionListener, MediaPlayer.OnPreparedListener,
SurfaceHolder.Callback, SeekBar.OnSeekBarChangeListener {
public static final int STATUS_STOPPED = 1;
public static final int STATUS_PAUSING = 2;
public static final int STATUS_PLAYING = 3;
private MediaPlayer mPlayer; // MediaPlayer對象
private SurfaceHolder mSurfaceHolder; // SurfaceHolder對象
private SurfaceView surfaceView; // SurfaceView元件
private SeekBar seekBar; // SeekBar元件
private TextView nowTime, totleTime; // TextView元件
private LinearLayout linearLayout; // 播放控制布局
private String mTimerFormat = "%02d:%02d"; // 時間格式
/* 時間更新Handler */
private final Handler mHandler = new Handler();
private Runnable mUpdateTimer = new Runnable() {
public void run() {
updateTimerView();
};
private String filename; // 檔案名稱
private int mStatus = STATUS_STOPPED; // 目前狀态
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.video_player);
surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
seekBar = (SeekBar) findViewById(R.id.seekBar);
seekBar.setOnSeekBarChangeListener(this);
nowTime = (TextView) findViewById(R.id.nowTime);
totleTime = (TextView) findViewById(R.id.totleTime);
linearLayout = (LinearLayout) findViewById(R.id.linearLayout);
// 擷取檔案名稱
filename = getIntent().getStringExtra(VideoPlayerActivity.KEY_FILENAME);
/* 初始化mSurfaceHolder */
mSurfaceHolder = surfaceView.getHolder();
mSurfaceHolder.addCallback(this); // 設定回調接口
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); // 設定為Buffer類型(播放視訊&Camera預覽)
/* 初始化MediaPlayer */
mPlayer = new MediaPlayer();
mPlayer.setOnPreparedListener(this);
mPlayer.setOnCompletionListener(this);
try {
mPlayer.setDataSource(filename);
} catch (Exception e) {
e.printStackTrace();
// 暫停按鈕
public void pauseBtn(View v) {
if (mStatus == STATUS_PLAYING) {
mPlayer.pause();
mStatus = STATUS_PAUSING;
// 播放按鈕
public void playBtn(View v) {
if (mStatus == STATUS_PAUSING) {
mPlayer.start(); // 繼續播放
mStatus = STATUS_PLAYING;
updateTimerView(); // 更新時間
} else {
/* 重新開始播放 */
mPlayer.reset();
try {
mPlayer.setDataSource(filename);
} catch (Exception e) {
e.printStackTrace();
mPlayer.prepareAsync(); // 異步準備
// 停止按鈕
public void stopBtn(View v) {
if (mStatus != STATUS_STOPPED) {
mPlayer.stop(); // 停止播放
mPlayer.reset(); // 重置
mStatus = STATUS_STOPPED;
// 當Surface被建立的時被觸發
public void surfaceCreated(SurfaceHolder holder) {
mPlayer.setDisplay(holder); // 指定SurfaceHolder
mPlayer.prepareAsync(); // 異步準備(将回調OnPreparedListener接口)
// 當Surface尺寸等參數改變時觸發
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// 當surface銷毀時觸發
public void surfaceDestroyed(SurfaceHolder holder) {
public void onPrepared(MediaPlayer mp) {
/* 獲得視窗寬長 */
Display display = getWindowManager().getDefaultDisplay();
int wWidth = display.getWidth();
int wHeight = display.getHeight();
/* 獲得視訊寬長 */
int vWidth = mPlayer.getVideoWidth();
int vHeight = mPlayer.getVideoHeight();
/* 最适螢幕 */
float wRatio = (float) vWidth / (float) wWidth; // 寬度比
float hRatio = (float) vHeight / (float) wHeight; // 高度比
float ratio = Math.max(wRatio, hRatio); // 較大的比
vWidth = (int) Math.ceil((float) vWidth / ratio); // 新視訊寬度
vHeight = (int) Math.ceil((float) vHeight / ratio); // 新視訊高度
// 改變SurfaceHolder大小
mSurfaceHolder.setFixedSize(vWidth, vHeight);
// 設定新布局參數(這在samsung i9088上出現stretch的錯誤==)
// surfaceView.setLayoutParams(new LinearLayout.LayoutParams(vWidth, vHeight));
// 設定總時間顯示
setTimeView(totleTime, mPlayer.getDuration());
// 啟動播放
mPlayer.start();
// 設定狀态
mStatus = STATUS_PLAYING;
// 更新時間
updateTimerView();
public void onCompletion(MediaPlayer mp) {
mStatus = STATUS_STOPPED;
finish(); // 播放完成後退出
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// 播放控制布局的顯示&隐藏
linearLayout
.setVisibility(linearLayout.getVisibility() == View.VISIBLE ? View.GONE
: View.VISIBLE);
return super.onTouchEvent(event);
public void onBackPressed() {
super.onBackPressed();
stopBtn(null); // 停止
protected void onDestroy() {
super.onDestroy();
mPlayer.release(); // 釋放
// 更新時間
private void updateTimerView() {
int position = mPlayer.getCurrentPosition();
int duration = mPlayer.getDuration();
setTimeView(nowTime, position); // 設定時間顯示
long pos = seekBar.getMax() * position / duration;
seekBar.setProgress((int) pos); // 設定進度條
mHandler.postDelayed(mUpdateTimer, 1000);
} else if (mStatus == STATUS_STOPPED) {
nowTime.setText("00:00");
seekBar.setProgress(0);
// 設定時間顯示
private void setTimeView(TextView textView, int msec) {
if (msec >= 1000 * 60 * 60) { // >=1h
textView.setText(String.format(mTimerFormat, msec / 1000 / 60 / 60,
msec / 1000 / 60 % 60));
textView.setText(String.format(mTimerFormat, msec / 1000 / 60,
msec / 1000 % 60));
// SeekBar進度改變時
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
// SeekBar開始拖動時
public void onStartTrackingTouch(SeekBar seekBar) {
// SeekBar結束拖動時
public void onStopTrackingTouch(SeekBar seekBar) {
mPlayer.seekTo(seekBar.getProgress() * mPlayer.getDuration()
/ seekBar.getMax()); // 跳轉
<b>三、後記</b>
<b>1</b><b>)擴充内容</b>
1.1)應用資源系列之屬性[Attribute]資源
1.2)RMVB軟解,有興趣的可以找找(我稍搜了下,無發現==)
<b>2</b><b>)子產品概覽</b>
2.1)Video播放
<a target="_blank" href="http://blog.51cto.com/attachment/201204/090814440.png"></a>
<b>3</b><b>)運作效果</b>
3.1)Video播放
<a target="_blank" href="http://blog.51cto.com/attachment/201204/091013412.png"></a>
3.2)檔案浏覽器
<a target="_blank" href="http://blog.51cto.com/attachment/201204/090814882.png"></a>
3.3)長按選擇方式
<a target="_blank" href="http://blog.51cto.com/attachment/201204/090814263.png"></a>
3.4)自帶播放器播放
<a target="_blank" href="http://blog.51cto.com/attachment/201204/091013667.png"></a>
3.5)VideoView播放
<a target="_blank" href="http://blog.51cto.com/attachment/201204/091013734.png"></a>
3.6)MediaPlayer播放
本文轉自winorlose2000 51CTO部落格,原文連結:http://blog.51cto.com/vaero/834887,如需轉載請自行聯系原作者