Android 视频播放器II

原生的视频播放器无法满足现阶段丰富的需求,所以这里简单介绍一下几个常用功能的自定义写法。


概括

--UI

--播放与暂停

--视频进度条

--音量进度条

--全屏

--手势操作



这里不po界面的代码了,结构很简单,一个视屏播放控件和他的进度条,下面有个控制器,左下角是播放控制按钮,依次向右是已播放时间和播放总时间,然后有一个全屏时才显示的音量图标以及音量进度条,最后是全屏切换按钮。


由于是第一次做进度条的界面,所以这里留下进度条的笔记(以视频进度条)。

            <!-- indeterminate 是否允许使用不确定模式,在不确定模式下,无法设置进度条的值-->
            <SeekBar
                android:id="@+id/play_seek"
                android:layout_width="match_parent"
                android:layout_height="5dp"
                android:layout_marginLeft="-20dp"
                android:layout_marginRight="-20dp"
                android:indeterminate="false"
                android:max="100"
                android:progress="20"
                android:progressDrawable="@drawable/seekbar_style2"
                android:thumb="@null" />

进度条的背景:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@android:id/background">
        <shape>
            <solid android:color="#707070" />
            <size android:height="5dp" />
        </shape>
    </item>

    <item android:id="@android:id/progress">
        <clip>
            <shape>
                <solid android:color="#994310" />
                <size android:height="5dp" />
            </shape>
        </clip>
    </item>
</layer-list>
这里使用了层叠的图形,背景是灰色,上面进度条的部分是橘色,这样设置进度时,就可以达到进度条滚动的效果。

播放与暂停

播放和暂停的响应比较简单,但是这里和下面会介绍的进度条的控制一样牵涉到播放时间的显示,所以首先我们需要为时间绑定一个每隔一段时间响应的handler,让他及时的 刷新时间显示。

    private Handler UIHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == UPDATE_UI) {
                // 获取视频当前播放时间
                int currentPosition = videoView.getCurrentPosition();
                // 获取视频播放总时间
                int totalduration = videoView.getDuration();

                // 格式化视频播放时间
                updateTextViewWithTimeFormat(time_current_tv, currentPosition);
                updateTextViewWithTimeFormat(time_total_tv, totalduration);

                play_seek.setMax(totalduration);
                play_seek.setProgress(currentPosition);
                UIHandler.sendEmptyMessageDelayed(UPDATE_UI, 500);
            }
        }
    };
当我们接受到更新时间的信息是,就主动更新时间,同时暂停和播放的时候要暂停和启动这个时间响应。设置了0.5秒的延迟,使得启动之后能不断的刷新时间。

下面是播放和暂停的事件:

        play_controller_img.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (videoView.isPlaying()) {
                    play_controller_img.setImageResource(R.drawable.play_btn_selector);
                    videoView.pause();
                    UIHandler.removeMessages(UPDATE_UI);
                } else {
                    play_controller_img.setImageResource(R.drawable.pause_btn_selector);
                    videoView.start();
                    UIHandler.sendEmptyMessage(UPDATE_UI);
                }
            }
        });

主要做了三件事,切换播放按钮的图片,控制视频播放,控制时间刷新。


视频进度条

视频进度条需要响应手指的拖动事件,控制视频播放到拖动的位置,也要修改时间显示。

        play_seek.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                updateTextViewWithTimeFormat(time_current_tv, progress);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                UIHandler.removeMessages(UPDATE_UI);
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                int progress = seekBar.getProgress();
                // 视频播放进度拖至滚动条位置
                videoView.seekTo(progress);
                UIHandler.sendEmptyMessage(UPDATE_UI);
            }
        });
这里progress传入的参数是播放的毫秒,可以根据他和总时间的百分比来更新时间。


音量进度条
想要控制音量,首先需要获取音量服务。

        // 获取音频服务
        mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
在初始化进度条的时候也要用到最大音量和当前音量。

        /**
         * 获取设备最大音量
         */
        int streamMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
        /**
         * 获取当前设备音量
         */
        int streamVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
        volume_seek.setMax(streamMaxVolume);
        volume_seek.setProgress(streamVolume);
最后绑定进度条的拖动事件:

        volume_seek.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                /**
                 * 设置当前音量
                 */
                mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, progress, 0);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });


全屏
全屏有两个模块,一个是全屏按钮的切换,一个是转动屏幕的切换,而且这两者同时设置的时候还会出现一些冲突。

首先,因为不希望屏幕方向等因素会影响视频的播放,也就是会重启activity导致视频重新播放的问题,需要在AndroidManifext.xml中做出如下设置:

        <activity
            android:name=".MainActivity"
            android:configChanges="orientation|screenSize|keyboard|keyboardHidden"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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


重点是configChanges的部分。

再来看全屏按钮的相应,如果横屏就转竖屏,如果竖屏就转横屏:

        screen_img.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                if (isFullScreen) {
                    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
                } else {
                    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
                }
            }
        });

然后是转动屏幕的切换:

这里有一个onConfigurationChanged函数可以用来监听当前屏幕的转动:

    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        /**
         * 屏幕为横屏
         */
        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
            setVideoViewScale(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
            volume_img.setVisibility(View.VISIBLE);
            volume_seek.setVisibility(View.VISIBLE);
            isFullScreen = true;
        }
        /**
         * 屏幕为竖屏
         */
        else {
            setVideoViewScale(ViewGroup.LayoutParams.MATCH_PARENT, DisplayUtil.dip2px(this, 240));
            volume_img.setVisibility(View.GONE);
            volume_seek.setVisibility(View.GONE);
            isFullScreen = false;
        }
    }

这里做了三件事,首先根据屏幕的方向来重新设置播放器的布局,然后控制音量图标和进度条的显示,最后重新设置isFullScreen变量。

手机竖屏的时候我们希望播放器高度只要240dp。

(这图片好大~~~~)

但是全屏的时候希望占满整个屏幕。

所以为了完成布局的重新布置,需要一个函数,更改视频播放器布局以及他外侧的布局:

    private void setVideoViewScale(int width, int height) {
        ViewGroup.LayoutParams layoutParams = videoView.getLayoutParams();
        layoutParams.width = width;
        layoutParams.height = height;
        videoView.setLayoutParams(layoutParams);

        layoutParams = videoLayout.getLayoutParams();
        layoutParams.width = width;
        layoutParams.height = height;
        videoLayout.setLayoutParams(layoutParams);
    }


接下来就是问题了,当这两者同时设置时,转动屏幕的响应是可行的,但是如果使用过全屏切换按钮之后,转动屏幕就不再相应了。

原因: setRequestedOrientation设置屏幕方向之后,比如说setRequestedOrientation(portrait)方法,就设定了屏幕方向是portrait,和在清单文件中配置Android:screenOrientation="portrait"是同等的效果;也即不再响应屏幕方向改变,只支持portrait方向;

解决方法:参考http://blog.csdn.net/u011449197/article/details/38843551

这里有一个改进,就是监听屏幕转动时角度的判断,解决方法中只监听了两种转向,如果多添加一些判断,就可以监听四种转向。

竖屏:

(((rotation >= 0) && (rotation <= 30)) || (rotation > 150 && rotation < 210) || (rotation >= 330))

横屏:

(((rotation >= 230) && (rotation <= 310)) || ((rotation >= 50) && (rotation <= 130)))

手势操作

在常用的视频软件中,可以通过手势操作,例如上滑和下滑来调节音量或者亮度,左右滑动控制播放进度,这里介绍上下滑动右侧界面调节音量、左侧界面调节亮度的功能实现。

        videoView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, 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:
                        float detlaX = x - lastX;
                        float detlaY = y - lastY;
                        float absdetlaX = Math.abs(detlaX);
                        float absdetlaY = Math.abs(detlaY);
                        if (absdetlaX > THRESHOLD && absdetlaY > THRESHOLD) {
                            if (absdetlaX < absdetlaY) {
                                isAdjust = true;
                            } else {
                                isAdjust = false;
                            }
                        } else if (absdetlaX < THRESHOLD && absdetlaY > THRESHOLD) {
                            isAdjust = true;
                        } else if (absdetlaX > THRESHOLD && absdetlaY < THRESHOLD) {
                            isAdjust = false;
                        }
                        if (isAdjust) {
                            /**
                             * 当前手势合法,区分是视频左部分(亮度)还是右部分(音量)
                             */
                            if (x < screen_width / 2) {
                                // 调节亮度
                                if (detlaY > 0) {
                                    // 降低亮度
                                } else {
                                    // 提高亮度                                    
                                }
                                changeBrightness(-detlaY);
                            } else {
                                //调节音量
                                if (detlaY > 0) {
                                    // 降低音量
                                } else {
                                    // 提高音量
                                }
                                changeVolume(-detlaY);
                            }
                        }
                        lastX = x;
                        lastY = y;
                        break;
                    case MotionEvent.ACTION_UP:
                        opetation_layout.setVisibility(View.GONE);
                        break;
                    default:
                        break;
                }
                return true;
            }
        });
为视频播放器添加触屏监听, 当触摸在左侧时,调节亮度,触摸在右侧时,调节音量。(常量THRESHOLD是20)
调节音量:

    private void changeVolume(float detlaY) {
        int max = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
        int current = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
        int index = (int) (detlaY / screen_height * max * 3);
        int volume = Math.max(index + current, 0);
        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
        if (opetation_layout.getVisibility() == View.GONE) {
            opetation_layout.setVisibility(View.VISIBLE);
        }
        ViewGroup.LayoutParams params = operation_percent.getLayoutParams();
        params.width = (int) (DisplayUtil.dip2px(this, 94) * (float) volume / max);
        operation_percent.setLayoutParams(params);
        volume_seek.setProgress(volume);
    }


operation_percent是一个调节音量时显示的背景。

亮度调节:

    private void changeBrightness(float detleY) {
        WindowManager.LayoutParams attributes = getWindow().getAttributes();
        mBrightness = attributes.screenBrightness;
        mBrightness += detleY / screen_height / 3;
        if (mBrightness > 1.0f) {
            mBrightness = 1.0f;
        }
        if (mBrightness < 0.01f) {
            mBrightness = 0.01f;
        }
        attributes.screenBrightness = mBrightness;
        getWindow().setAttributes(attributes);
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值