Android本地視訊播放器mediaplay版
本文為自定義的視訊播放器,可進行螢幕切換(由於換屏時大小變化,電腦截圖就分開截圖了),效果如下圖:
--------------播放視訊概括:
SurfaceView+MediaPlayer以及 VideoView 2種方式
SurfaceVIew中有個SurfaceHolder,通過surfaceView.getHolder( )方法獲取,如果需要相容2.3系統,還要再加上setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);否則只有聲音沒有影象。有3個生命週期 surfaceCreated、surfaceChanged、surfaceDestroyed。
讀取raw目錄下的2個方法
Uri.parse("android.resource://"+ getPackageName()+ "/" + R.raw.video));
AssetFileDescriptorafd = getResources().openRawResourceFd(
R.raw.por);
mp_test.setDataSource(afd.getFileDescriptor(),
afd.getStartOffset(), afd.getLength());
afd.close();
設定app主題無title
@android:style/Theme.Light.NoTitleBar
遮蔽變化引起的activity重啟
android:configChanges="keyboard|orientation|screenSize"
設定螢幕常亮
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
設定activity方向為縱向
android:screenOrientation="portrait"
獲得當前螢幕是橫屏還是豎屏
getResources().getConfiguration().orientation
手動切換橫豎屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
監聽橫豎屏切換的動作
onConfigurationChanged
程式碼隱藏title
requestWindowFeature(Window.FEATURE_NO_TITLE);(必須在setcontent之前)
程式碼請求全屏模式
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
程式碼清除全屏模式
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
適配不同的video尺寸達到不拉伸視訊的效果
setVideoParms 方法,根據視訊的比例和顯示區域的比例計算得來
在一套佈局裡寫2套控制元件,適配橫豎屏
適當利用LinearLayout的weight屬性做適配
使用audioManager獲取,調節系統系統音量
seekBar的初始化,改變時的監聽
系統音量改變時會發出broadcast
android.media.VOLUME_CHANGED_ACTION監聽系統音量變化
seekBar監聽時注意是否fromUser,mediaPlayer注意try
當前activity add的flag,當activity離開時就會clear掉
-------------專案程式碼VideoActivity類:
/**
* 播放視訊頁面
*
* @author hasee
*
*/
public class VideoActivity extends Activity implements OnPreparedListener,
OnSeekBarChangeListener {
private MediaPlayer mediaPlayer;
private SurfaceView sv_video;
private RelativeLayout rl_top;
private Button bt_start_or_pause;// 播放或暫停按鈕
private SeekBar sb_progress;// 視訊進度條
ImageView mChangeSceen;
ImageView mCenterPause;
LinearLayout mllmenu;
LinearLayout mlltitle;
private SeekBar sb_vol;// 音量進度條
private AudioManager am;
private int currentPosition;// 記錄當前進度值
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);// 雙緩衝
setContentView(R.layout.activity_video);
initView();
initData();
}
private void initView() {
sv_video = (SurfaceView) findViewById(R.id.sv_video);
bt_start_or_pause = (Button) findViewById(R.id.bt_start_or_pause);
rl_top = (RelativeLayout) findViewById(R.id.rl_top);
sb_progress = (SeekBar) findViewById(R.id.sb_progress);
sb_vol = (SeekBar) findViewById(R.id.sb_vol);
mChangeSceen= (ImageView) findViewById(R.id.bt_change);
mCenterPause= (ImageView) findViewById(R.id.video_center_pause);
mllmenu= (LinearLayout) findViewById(R.id.video_menu);
mlltitle= (LinearLayout) findViewById(R.id.video_menu_title);
// sv_video.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);//
// 相容2.3及以下版本,否則只有聲音沒有畫面
sv_video.getHolder().addCallback(new Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
// 當頁面可見時候
playVideo();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// 當頁面不可見時候
stopVideo();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
}
});
}
private void initData() {
registerReceiver(volReceiver, new IntentFilter(
"android.media.VOLUME_CHANGED_ACTION"));
am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
// 保持螢幕常亮
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
sb_vol.setMax(am.getStreamMaxVolume(AudioManager.STREAM_MUSIC));
sb_vol.setProgress(am.getStreamVolume(AudioManager.STREAM_MUSIC));
sb_progress.setOnSeekBarChangeListener(this);
sb_vol.setOnSeekBarChangeListener(this);
}
private void playVideo() {
mediaPlayer = new MediaPlayer();
try {
// 獲取RAW目錄下的檔案
// AssetFileDescriptor afd = getResources().openRawResourceFd(
// R.raw.por);
// mediaPlayer.setDataSource(afd.getFileDescriptor(), 0,
// afd.getLength());
// 獲取RAW目錄下的檔案
mediaPlayer.setDataSource(
this, Uri.parse("android.resource://" + getPackageName() + "/"+ R.raw.test));
mediaPlayer.setLooping(true);
mediaPlayer.setOnPreparedListener(this);
mediaPlayer.setDisplay(sv_video.getHolder());
mediaPlayer.prepareAsync();
} catch (Exception e) {
e.printStackTrace();
}
}
private void stopVideo() {
canProgress = false;
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
try {
currentPosition = mediaPlayer.getCurrentPosition();
mediaPlayer.pause();
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
} catch (Exception e) {
}
}
}
@Override
public void onPrepared(MediaPlayer mp) {
// 說明mediaPlayer準備好了
try {
sb_progress.setMax(mp.getDuration());
setParam(mp, isLand());
mp.start();
if (currentPosition > 0) {
mp.seekTo(currentPosition);
currentPosition = 0;
}
startProgress();
} catch (Exception e) {
}
}
/**
* 判斷當前是否橫屏
*
* @return
*/
private boolean isLand() {
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
return false;
} else {
return true;
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
Log.e("pid", "豎屏了");
setParam(mediaPlayer, false);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
} else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
Log.e("pid", "橫屏了");
// 去除狀態列
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
setParam(mediaPlayer, true);
}
}
/**
* 設定視訊尺寸,達到不被拉伸的效果
*
* @param mp
* @param isLand
*/
private void setParam(MediaPlayer mp, boolean isLand) {
float screenWidth = getWindowManager().getDefaultDisplay().getWidth();
float screenHeight = screenWidth / 16f * 9f;
if (isLand) {
screenHeight = getWindowManager().getDefaultDisplay().getHeight();
}
float videoWdith = mp.getVideoWidth();
float videoHeight = mp.getVideoHeight();
float screenPor = screenWidth / screenHeight;// 16:9
float videoPor = videoWdith / videoHeight;// 9:16
ViewGroup.LayoutParams pa = sv_video.getLayoutParams();
if (videoPor <= screenPor) {
pa.height = (int) screenHeight;
pa.width = (int) (screenHeight * videoPor);
} else {
pa.width = (int) screenWidth;
pa.height = (int) (screenWidth / videoPor);
}
ViewGroup.LayoutParams rl_pa = rl_top.getLayoutParams();
rl_pa.width = pa.width;
rl_pa.height = pa.height;
rl_top.setLayoutParams(rl_pa);
sv_video.setLayoutParams(pa);
}
private boolean canProgress = true;
private void startProgress() {
canProgress = true;
new Thread() {
public void run() {
while (canProgress) {
try {
sleep(500);
sb_progress.setProgress(mediaPlayer
.getCurrentPosition());
} catch (Exception e) {
}
}
};
}.start();
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(volReceiver);
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
if (!fromUser)
return;
switch (seekBar.getId()) {
case R.id.sb_progress:
try {
if (mediaPlayer != null) {
mediaPlayer.seekTo(progress);
}
} catch (Exception e) {
}
break;
case R.id.sb_vol:
am.setStreamVolume(AudioManager.STREAM_MUSIC, progress,
AudioManager.FLAG_SHOW_UI);
break;
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
/**
* 系統音量改變時的廣播接收者
*/
private BroadcastReceiver volReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
sb_vol.setProgress(am.getStreamVolume(AudioManager.STREAM_MUSIC));
}
};
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt_start_or_pause:
try {
if (bt_start_or_pause.getText().equals("播放")) {
// 當前是播放,那麼暫停
mediaPlayer.pause();
// canProgress = false;
bt_start_or_pause.setText("暫停");
mCenterPause.setVisibility(View.VISIBLE);
} else if (bt_start_or_pause.getText().equals("暫停")) {
// 當前是暫停,那麼播放
mediaPlayer.start();
// canProgress = true;
bt_start_or_pause.setText("播放");
mCenterPause.setVisibility(View.GONE);
}
} catch (Exception e) {
}
break;
case R.id.bt_change:
// 點選了橫豎屏切換,拿到當前螢幕方向
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
// 當前是豎屏,切換成橫屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} else {
// 當前是橫屏,切換成豎屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
break;
case R.id.video_center_pause:
try {
if (bt_start_or_pause.getText().equals("播放")) {
// 當前是播放,那麼暫停
mediaPlayer.pause();
// canProgress = false;
bt_start_or_pause.setText("暫停");
} else if (bt_start_or_pause.getText().equals("暫停")) {
// 當前是暫停,那麼播放
mediaPlayer.start();
// canProgress = true;
bt_start_or_pause.setText("播放");
}
} catch (Exception e) {
}
mCenterPause.setVisibility(View.GONE);
break;
}
}
/** 定時隱藏 */
private Handler mDismissHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
mllmenu.setVisibility(View.GONE);
mlltitle.setVisibility(View.GONE); //標題欄的自動隱藏
}
};
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction()==MotionEvent.ACTION_DOWN){
mllmenu.setVisibility(View.VISIBLE);
mlltitle.setVisibility(View.VISIBLE); //標題欄的顯示
}
mDismissHandler.sendEmptyMessageDelayed(0,3000);
return super.onTouchEvent(event);
}
}
-----------------------activity_video.xml佈局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fff"
>
<RelativeLayout
android:id="@+id/rl_top"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000" >
<LinearLayout
android:id="@+id/video_menu_title"
android:layout_width="match_parent"
android:layout_height="50dp"
android:visibility="gone"
android:background="#000">
<TextView
android:textColor="#fff"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="視訊標題"/>
</LinearLayout>
<SurfaceView
android:id="@+id/sv_video"
android:layout_below="@+id/video_menu_title"
android:layout_width="match_parent"
android:layout_height="300dip"
android:layout_centerInParent="true" />
<ImageView
android:id="@+id/video_center_pause"
android:onClick="onClick"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerInParent="true"
android:visibility="gone"
android:src="@drawable/video_pause"/>
<LinearLayout
android:id="@+id/video_menu"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#000"
android:visibility="gone"
android:orientation="horizontal"
android:layout_alignParentBottom="true">
<Button
android:id="@+id/bt_start_or_pause"
android:layout_width="60dp"
android:layout_height="50dp"
android:text="播放"
android:onClick="onClick"
/>
<SeekBar
android:id="@+id/sb_progress"
android:layout_width="0dip"
android:layout_height="50dip"
android:layout_weight="3"
android:indeterminate="false"/>
<SeekBar
android:id="@+id/sb_vol"
android:layout_width="0dip"
android:layout_height="50dip"
android:layout_weight="1" />
<ImageView
android:id="@+id/bt_change"
android:layout_width="40dp"
android:layout_height="40dp"
android:onClick="onClick"
android:src="@drawable/changescreen"/>
</LinearLayout>
</RelativeLayout>
</RelativeLayout>
注意在androidmanifest清單檔案對activity中新增:
<span style="font-size:18px;"> android:configChanges="keyboard|orientation|screenSize"</span>
在res檔案中建立raw資料夾,然後貼上進去test.MP4檔案播放.
由於谷歌已經停止對eclipse的版本更新,我使用Android studio軟體開發,專案注意點圖片:
Mainactivity類並沒有什麼程式碼,主要提供開發者一個自己專案頁面,然後在裡面用Intent跳轉到VideoActivity.class就可以了,想要專案給我留言.
總結:
- 視訊播放的注意點:
- 子執行緒裡去更改視訊進度條
- 注意try Catch對MediaPlayer進行操作的地方(包括自定義相機中的Camera也是)
- 記得SurFaceView的三個生命週期方法,每次最小化以後,回來再播放,都已經不是同一個Holder了
- 獲取視訊寬高比例和螢幕寬高比例進行比較,隨後就可以根據螢幕的寬或者高來確定怎麼拉伸視訊不會導致變形
- MediaPlayer的釋放:
- 先暫停
- 再停止
- 再釋放
- 再置空
- 這樣最安全
- 涉及到螢幕翻轉的生命週期問題
- 要在AndroidManifest.xml中對視訊的Activity做相應配置
- 使用廣播來監聽對應的音量變化,因為有時候我們是用音量鍵或者在設定裡更改音量
- AssetFileDescriptor可以用來讀取raw下檔案的類
2,mediaPlayer的native機制:
a. 因為mediaPlayer中的很多方法如isPlaying()方法都是JNI機制寫的,因此它的GC回收機制和平常的方法不一樣。因此,很容易會出現非法堆疊異常等情況,因此最好加上一個tryCatch進行處理。
b. 內部都是native方法,這種native方法,在不一定的情況下,GC會進行回收,因此不知道什麼時候呼叫就會跳出來一個IllegalStateException異常