Android 簡單定製一個視訊播放器
阿新 • • 發佈:2019-02-07
安卓系統提供了VideoView用來播放一些特定格式的視訊,與MediaController結合使用可以對視訊播放進行簡單控制
例如:
在佈局檔案中先宣告個VideoView:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/content_main2"
android:layout_width="match_parent"
android:layout_height ="match_parent">
<VideoView
android:id="@+id/videoView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
然後,在儲存卡的根目錄下先放置一個命名為“00.MP4”的視訊檔案
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate (savedInstanceState);
setContentView(R.layout.activity_main2);
VideoView videoView = (VideoView) findViewById(R.id.videoView);
MediaController mediaController = new MediaController(this);
videoView.setMediaController(mediaController);
mediaController.setMediaPlayer (videoView);
//為videoView設定視訊路徑
String path = Environment.getExternalStorageDirectory().getAbsolutePath();
videoView.setVideoPath(path + "/00.mp4");
}
播放效果如下:
這裡再來自定義視訊播放控制介面與控制邏輯,增添音量調節,亮度調節,沉浸式狀態列等功能
豎屏狀態效果如下:
橫屏狀態下效果如下:
首先要先設計佈局樣式
<?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">
<RelativeLayout
android:id="@+id/rl_video"
android:layout_width="match_parent"
android:layout_height="240dp">
<VideoView
android:id="@+id/vv_player"
android:layout_width="match_parent"
android:layout_height="240dp" />
<LinearLayout
android:id="@+id/ll_control"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/vv_player"
android:background="#8768423e"
android:orientation="vertical">
<SeekBar
android:id="@+id/sb_play"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="false" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal">
<LinearLayout
android:id="@+id/ll_playControl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginLeft="5dp"
android:gravity="center"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_playControl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:src="@drawable/play_btn_style" />
<TextView
android:id="@+id/tv_currentTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="7dp"
android:text="00:00:00"
android:textColor="#ffffff"
android:textSize="15sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" / "
android:textColor="#ffffff"
android:textSize="15sp" />
<TextView
android:id="@+id/tv_totalTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00:00:00"
android:textColor="#ef6363"
android:textSize="15sp" />
</LinearLayout>
<ImageView
android:id="@+id/iv_screenSwitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginRight="5dp"
android:src="@drawable/full_screen" />
<LinearLayout
android:id="@+id/ll_volumeControl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:layout_toLeftOf="@id/iv_screenSwitch"
android:gravity="end"
android:orientation="horizontal"
android:visibility="gone">
<ImageView
android:id="@+id/iv_volume"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/volume" />
<SeekBar
android:id="@+id/sb_volume"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:indeterminate="false" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>
</RelativeLayout>
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
id為“ll_control”的LinearLayout包含了所有的控制View,將之置於VideoView上且設定半透明背景色,音量調節seekBar在豎屏狀態下不可見
主要程式碼如下:
初始化UI
private void initUI() {
videoView = (VideoView) findViewById(R.id.vv_player);
sb_play = (SeekBar) findViewById(R.id.sb_play);
sb_volume = (SeekBar) findViewById(R.id.sb_volume);
iv_playControl = (ImageView) findViewById(R.id.iv_playControl);
iv_screenSwitch = (ImageView) findViewById(R.id.iv_screenSwitch);
iv_volume = (ImageView) findViewById(R.id.iv_volume);
tv_currentTime = (TextView) findViewById(R.id.tv_currentTime);
tv_totalTime = (TextView) findViewById(R.id.tv_totalTime);
ll_volumeControl = (LinearLayout) findViewById(R.id.ll_volumeControl);
ll_control = (LinearLayout) findViewById(R.id.ll_control);
rl_video = (RelativeLayout) findViewById(R.id.rl_video);
sb_volume.setMax(audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC));
sb_volume.setProgress(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
}
初始化各種事件
private void initEvent() {
iv_playControl.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (videoView.isPlaying()) {
setPauseStatus();
videoView.pause();
uiHandler.removeMessages(UPDATE_TIME);
} else {
setPlayStatus();
videoView.start();
uiHandler.sendEmptyMessage(UPDATE_TIME);
}
}
});
sb_play.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
videoView.seekTo(progress);
Utils.updateTimeFormat(tv_currentTime, progress);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
uiHandler.removeMessages(UPDATE_TIME);
if (!videoView.isPlaying()) {
setPlayStatus();
videoView.start();
}
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
uiHandler.sendEmptyMessage(UPDATE_TIME);
}
});
sb_volume.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, progress, 0);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
iv_playControl.setImageResource(R.drawable.play_btn_style);
videoView.seekTo(0);
sb_play.setProgress(0);
Utils.updateTimeFormat(tv_currentTime, 0);
videoView.pause();
uiHandler.removeMessages(UPDATE_TIME);
}
});
iv_screenSwitch.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
iv_screenSwitch.setImageResource(R.drawable.exit_full_screen);
} else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
iv_screenSwitch.setImageResource(R.drawable.full_screen);
}
}
});
videoView.setOnTouchListener(this);
}
在橫屏和豎屏切換時,會回撥以下方法
public void onConfigurationChanged(Configuration newConfig)
在此要對View的大小進行調整以適應螢幕
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
screenWidth = getResources().getDisplayMetrics().widthPixels;
screenHeight = getResources().getDisplayMetrics().heightPixels;
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
setSystemUiHide();
setVideoViewScale(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
iv_screenSwitch.setImageResource(R.drawable.exit_full_screen);
ll_volumeControl.setVisibility(View.VISIBLE);
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
setVideoViewScale(ViewGroup.LayoutParams.MATCH_PARENT, Utils.dp2px(MainActivity.this, 240f));
iv_screenSwitch.setImageResource(R.drawable.full_screen);
ll_volumeControl.setVisibility(View.GONE);
setSystemUiVisible();
}
}
View大小調節
/**
* 設定佈局大小
*
* @param width 寬度
* @param height 高度
*/
private void setVideoViewScale(int width, int height) {
ViewGroup.LayoutParams params = rl_video.getLayoutParams();
params.width = width;
params.height = height;
rl_video.setLayoutParams(params);
ViewGroup.LayoutParams layoutParams = videoView.getLayoutParams();
layoutParams.width = width;
layoutParams.height = height;
videoView.setLayoutParams(layoutParams);
}
此外,為了使視訊在全屏播放時更加和諧,呼叫以下方法對系統狀態列和虛擬按鍵進行隱藏與顯示
private void setSystemUiHide() {
if (Build.VERSION.SDK_INT >= 19) {
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
}
private void setSystemUiVisible() {
if (Build.VERSION.SDK_INT >= 19) {
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
}
}
此外,通過手勢識別可以對亮度和音量進行調節
private void changeVolume(float offset) {
int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
int index = (int) (offset / screenHeight * maxVolume);
int volume = Math.max(currentVolume + index, 0);
volume = Math.min(volume, maxVolume);
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
sb_volume.setProgress(volume);
}
private void changeBrightness(float offset) {
WindowManager.LayoutParams attributes = getWindow().getAttributes();
float brightness = attributes.screenBrightness;
float index = offset / screenHeight / 2;
brightness = Math.max(brightness + index, WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF);
brightness = Math.min(WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL, brightness);
attributes.screenBrightness = brightness;
getWindow().setAttributes(attributes);
}
關於手勢識別 OnGestureListener的使用在我的上一篇部落格也介紹過了
此外,為了使seekBar的進度能夠在使用者通過音量鍵調節音量時也能自動變化,需要再加上一個廣播接收器
/**
* 音量變化廣播接收器
* Created by CZY on 2017/1/31.
*/
public class VolumeReceiver extends BroadcastReceiver {
private ImageView iv_volume;
private SeekBar seekBar_volume;
/**
* 音訊管理器
*/
private AudioManager audioManager;
public VolumeReceiver(Context context, ImageView iv_volume, SeekBar seekBar_volume) {
this.iv_volume = iv_volume;
this.seekBar_volume = seekBar_volume;
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("android.media.VOLUME_CHANGED_ACTION")) {
int volume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
if (volume == 0) {
iv_volume.setImageResource(R.drawable.mute);
} else {
iv_volume.setImageResource(R.drawable.volume);
}
seekBar_volume.setProgress(volume);
}
}
}
為了在螢幕切換時可以不重新建立Activity而只是回撥onConfigurationChanged
函式,可以為Activity增添以下屬性
android:configChanges="orientation|screenSize|keyboard|keyboardHidden"
且程式要讀取儲存卡檔案,需要申請許可權
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />