1. 程式人生 > >Android 簡單定製一個視訊播放器

Android 簡單定製一個視訊播放器

安卓系統提供了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" />