Android-视频播放器开发

2017/10/31 posted in  Android

android中在实际的视频开发中多用即成的框架,或者自己封装相关的Manager来实现;下面介绍一个简单的android视频开发的过程:

  1. 实现网络和本地视频播放
  2. 点击控制播放,暂停
  3. 支持进度条的拖动,实时跟新进度,时间
  4. 支持横竖屏切换
  5. 在横屏状态下,音量键显示并且可调节
  6. 支持手势触摸,即:上下滑动左半屏,弹出dialog,控制屏幕亮度,并且显示进度条;上下滑动右半屏,弹出dialog,控制声音大小,并且显示进度条;

First

首先demo是基于videoView为基础,当然简单的开发,没有过多要求可以用原生的控制器可以可以解决,在这里控制都是自定义;首先为保证横竖屏幕的宽高不适配,需要自定义vieoView重写测量方法:

public class AlpshVideo extends VideoView {
    public AlpshVideo(Context context) {
        super(context);
    }

    public AlpshVideo(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public AlpshVideo(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //我们重新计算高度
        int width = getDefaultSize(0, widthMeasureSpec);
        int height = getDefaultSize(0, heightMeasureSpec);
        setMeasuredDimension(width, height);
    }
}

接着xml中嵌入自定义的vieoView:

<com.example.video.customView.AlpshVideo
            android:id="@+id/videoView"
            android:layout_width="match_parent"
            android:layout_height="240dp"
            />

在MainActivity中加载本地或者网络视频:

 //加载本地
mVideoView.setVideoPath("android.resource://"+getPackageName()+"/"+R.raw.welcome);
mVideoView.start();

//加载网络url
//mVideoView.setVideoURI(Uri.parse("xxxx"));

我们这里以本地为例;ok这样videoView就可以开始比方视频;

自定义控制器—->布局

接下来看代码,xml比较简单,仅展示代码:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main"
    android:layout_width="match_parent" android:layout_height="match_parent">

    <FrameLayout
        android:id="@+id/fr_video"
        android:layout_width="match_parent"
        android:layout_height="240dp">

        <com.example.video.customView.AlpshVideo
            android:id="@+id/videoView"
            android:layout_width="match_parent"
            android:layout_height="240dp"
            />

        <LinearLayout
            android:orientation="vertical"
            android:layout_gravity="bottom"
            android:layout_width="match_parent"
            android:layout_height="50dp">

            <SeekBar
                android:id="@+id/media_progress"
                android:thumb="@null"
                android:progressDrawable="@drawable/seekbar_style2"
                android:max="100"
                android:progress="20"
                android:indeterminate="false"
                android:layout_width="match_parent"
                android:layout_height="5dp"
                android:layout_marginLeft="-20dp"
                android:layout_marginRight="-20dp"/>

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="#101010"
                android:gravity="center_vertical">

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:gravity="center_vertical">


                    <ImageView
                        android:id="@+id/media_actions"
                        android:layout_marginLeft="15dp"
                        android:layout_width="20dp"
                        android:layout_height="match_parent"
                        android:src="@drawable/stop_btn"/>

                    <TextView
                        android:id="@+id/media_time"
                        android:layout_marginLeft="30dp"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="00:00:00"
                        android:textColor="#fff"
                        android:textSize="12sp"/>

                    <TextView
                        android:layout_marginLeft="5dp"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="/"
                        android:textColor="#fff"
                        android:textSize="12sp"/>

                    <TextView
                        android:id="@+id/media_total_time"
                        android:layout_marginLeft="5dp"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="00:00:00"
                        android:textColor="#fff"
                        android:textSize="12sp"/>

                    <ImageView
                        android:id="@+id/media_sounds"
                        android:visibility="gone"
                        android:layout_marginLeft="100dp"
                        android:layout_width="20dp"
                        android:layout_height="match_parent"
                        android:src="@drawable/icon_sounds"/>


                    <SeekBar
                        android:visibility="gone"
                        android:id="@+id/media_sounds_progress"
                        android:thumb="@null"
                        android:progressDrawable="@drawable/seekbar_style"
                        android:max="100"
                        android:progress="20"
                        android:indeterminate="false"
                        android:layout_width="100dp"
                        android:layout_height="5dp"
                        android:layout_marginLeft="-10dp"
                        />
                </LinearLayout>

                <ImageView
                    android:id="@+id/media_full_screen"
                    android:layout_marginRight="15dp"
                    android:layout_alignParentRight="true"
                    android:layout_centerVertical="true"
                    android:layout_width="20dp"
                    android:layout_height="20dp"
                    android:src="@drawable/ic_full_screen"/>
            </RelativeLayout>
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center">
            <include layout="@layout/dialog"
                android:visibility="gone"/>
        </LinearLayout>

    </FrameLayout>
</RelativeLayout>

控制器中播放暂停按钮的实现

初始化音量进度条:

//获取音频管理器
mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
int streamMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
int streamVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);

mMediaSoundsProgress.setMax(streamMaxVolume);
mMediaSoundsProgress.setProgress(streamVolume);

点击事件的普片切换比较简单,通过按钮的图形化切换来控制videoView的开始和暂停:在播放过程中要实时更新播放时间和进度,这里用handler来实现:

private void setPlayer() {
    mMediaActions.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            if (mVideoView.isPlaying()){
                mMediaActions.setImageResource(R.drawable.play_btn);
                mVideoView.pause();
                mHandler.removeMessages(UpDate);
            }else {
                mMediaActions.setImageResource(R.drawable.stop_btn);
                mVideoView.start();
                mHandler.sendEmptyMessage(UpDate);
            }
        }
    });
}

在上面代码中看到,由播放状态改变为暂停状态,实时跟新的hanler停止,切换为播放再次开启;下面看下线程中实时更新的代码:

private Handler mHandler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        if (msg.what == UpDate){
            //当前时间
            int currentPosition = mVideoView.getCurrentPosition();

            //总时间
            int duration = mVideoView.getDuration();
            //设置时间
            updateTime(mMediaTime,currentPosition);
            updateTime(mMediaTotalTime,duration);
          //设置进度
          mMediaProgress.setMax(duration);
          mMediaProgress.setProgress(currentPosition);

          mHandler.sendEmptyMessageDelayed(UpDate,500);
        }
    }
};

到这一步,视频可以播放,播放进度条实时跟新,播放时间可以实时更新;

当然在一个视频播放器中,必须实现进度条的拖动来实现进度的控制(就是快进和快退);那么需要监听播放进度条的拖动事件:

private void setScrollSeek() {
//播放器的进度条监听
mMediaProgress.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean b) {
updateTime(mMediaTime,progress);
}

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
        mHandler.removeMessages(UpDate);
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
        int progress = seekBar.getProgress();
        mVideoView.seekTo(progress);
        mHandler.sendEmptyMessage(UpDate);
    }

});

横竖屏切换;

VideoView重新测量

手机翻转然后横竖屏自由切换,当然在第一步中有提到自定义videoView重新测量,也是为了横竖屏切换时候做准备,防止半屏显示不全的出现:首先在清单文件中配置:(而且切换横屏之后音量键开始显示)

<activity
    android:configChanges="orientation|screenSize|keyboard|keyboardHidden"
    android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

这样设置之后可以全屏,但是videoView并不可以适配,所以需要MainActivity中重写onConfigurationChanged:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    //当为横屏时候
    if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE){
        setConfigWh(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);
        isFull = true;

        //音量键的显示
        mMediaSounds.setVisibility(View.VISIBLE);
        mMediaSoundsProgress.setVisibility(View.VISIBLE);
        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

    }else{
        //当为竖屏时候
        setConfigWh(ViewGroup.LayoutParams.MATCH_PARENT, DensityUtil.dp2px(this,240));
        isFull = false;

        mMediaSounds.setVisibility(View.GONE);
        mMediaSoundsProgress.setVisibility(View.GONE);
        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
    }
}

全屏

在竖屏状态下,点击右下角的全屏按钮 也会显示横屏,所以需要设置其点击事件:

private void setFullScreen() {
    //设置全屏播放
    mMediaFullScreen.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            if (isFull){
                //此时是全屏
                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

            }else {
                //此时是半屏
                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

            }
        }
    });
}

当这里横竖屏切换和适配就可以正常显示了

音量显示

切换到横屏之后音量键显示,则需要显示器控制音量的功能:

//声音调节进度条
mMediaSoundsProgress.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean b) {
        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,progress,0);
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
    }
});

当这里一个简单的播放器基本完备

手势

接下来来添加手势

大概策略是这样:横屏状态下(需要添加boolean判断来限定横屏之下触发):当上下滑动屏幕左半边,亮度改变,弹出一个类似dialog显示连读进度条的改变,结束触摸,则弹窗消失;当上下滑动屏幕右半边,声音改变,弹出一个类似dialog显示连读进度条的改变,结束触摸,则弹窗消失,在这个过程中控制台区域的声音进度条也会实时改变;

手势添加

先来看手势的添加:

private void setScrollScreen() {
    mVideoView.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View view, MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                //获取屏幕宽和高
                widthPixels = getResources().getDisplayMetrics().widthPixels;
                heightPixels = getResources().getDisplayMetrics().heightPixels;
                //偏移量的记录
                float dx = x - lastX;
                float dy = y - lastY;
                float absX = Math.abs(dx);
                float absY = Math.abs(dy);

                if (absX > trueshold && absY > trueshold){
                    if (absX >= absY){
                        isadjust = false;
                    }else if (absY > absX){
                        isadjust = true;
                    }
                }else if (absX >= trueshold && absY < trueshold){
                    isadjust = false;
                }else if(absX < trueshold && absY >= trueshold){
                    isadjust = true;
                }

                //开始判断 音量调节 和 亮度调节
                if (isadjust){
                    //Log.i("==widthPixels",x + "");
                    if (x > widthPixels/2){
                        //声音
                        if (dy > 0){
                            //降低声音
                        }else {
                            //增大声音
                        }
                        //改变声音
                        changeVoice(-dy);

                    }else if(x < widthPixels/2){
                        //亮度
                        if (dy > 0){
                            //降低亮度
                            //Log.i("==降低亮度",dy + "");
                        }else {
                            //增大亮度
                            //Log.i("==增大亮度",dy + "");
                        }

                        //改变亮度
                        changeBrightness(-dy);
                    }
                }
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_UP:
                mDialogLl.setVisibility(View.GONE);
                break;
            }
        return true;
        }
    });
}

就是一个onTouch的触摸事件,首先判断手势是否合法,规定一个偏移量,当,当dx dy全部超过偏移量,dy>dx 手势触发 isadjust = true;

当dx<偏移量,dy大于偏移量手势触发 isadjust = true;

触发之后判断 当x > widthPixels/2,在屏幕右半边,触发音量控制

当当x < widthPixels/2,在屏幕左半边,触发屏幕亮度控制

ok~接下来看屏幕亮度变化的代码:

//控制亮度
public void changeBrightness(float absY){
    mDialogLl.setVisibility(View.VISIBLE);

    WindowManager.LayoutParams attributes = getWindow().getAttributes();
    mScreenBrightness = attributes.screenBrightness;
    float index = absY / heightPixels;
    mScreenBrightness += index;
    //进行判断
    if (mScreenBrightness > 1.0f){
        mScreenBrightness = 1.0f;
    }else if (mScreenBrightness < 0.01f){
        mScreenBrightness = 0.01f;
    }
    attributes.screenBrightness = mScreenBrightness;
    getWindow().setAttributes(attributes);


    //设置弹窗
    mDialogPic.setImageResource(R.drawable.icon_media_screen);
    mDialogProgress.setMax(10);
    mDialogProgress.setProgress((int)(mScreenBrightness * 10));
    Log.i("==mScreenBrightness",mScreenBrightness * 100 + "");
}

声音变化的代码:

//控制声音
public void changeVoice(float absY){

    mDialogLl.setVisibility(View.VISIBLE);

    int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
    int volume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);

    int dVoice = (int) (absY / heightPixels * maxVolume * 3);
    int max = Math.max(dVoice + volume, 0);
    //设置声音
    mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,max,0);
    mMediaSoundsProgress.setProgress(max);
    //设置弹窗
    mDialogPic.setImageResource(R.drawable.icon_media_sounds);
    mDialogProgress.setMax(maxVolume);
    mDialogProgress.setProgress(max);
}

这样就实现声音,屏幕亮度和手势的交互;当然不要忘了结束触摸要隐藏dialog窗口:

case MotionEvent.ACTION_UP:
    mDialogLl.setVisibility(View.GONE);
    break;

到此为止,一个横竖屏切换自如,快进快退可实现,手势和声音,屏幕亮度可以实时交互,播放信息实时更新的android播放器就完成