当前位置: 代码迷 >> Android >> Android 手机影音 学习历程记录(四)
  详细解决方案

Android 手机影音 学习历程记录(四)

热度:16   发布时间:2016-04-28 00:12:03.0
Android 手机影音 学习过程记录(四)

前一篇已经将视频播放页面的布局弄好了,这一篇主要来处理播放页面的各种逻辑(播放、暂停、上/下一个视频、音量、进度…),逻辑比较多,一点一点贴代码。

顶部布局的逻辑:

  1. 显示系统时间,时间是一秒一秒更新的,所以可以通过循环发消息的方法来更新系统时间。相关代码如下:

    private static final int UPDATE_SYSTEM_TIME = 0;//更新系统时间的消息//主线程收到消息后,继续发消息更新系统时间    switch (msg.what) {                case UPDATE_SYSTEM_TIME:                    updateSystemTime();                    break;    }    /**     * 更新系统的时间     */    private void updateSystemTime() {        LogUtils.i("updateSystemTime");        tvSystemTime.setText(StringUtil.formatSystemTime());        handler.sendEmptyMessageDelayed(UPDATE_SYSTEM_TIME, 1000);    }

    updateSystemTime();方法先在initData的时候调用一次。

  2. 显示电池电量,这里准备了7张图,用来表示手机电池电量的显示。在initData时,注册一个电池电量的广播接受者,当电量改变时,更新一下显示电量的图片。代码如下:

        /**     * 注册电量变化的广播接受者     */    private void registerBatteryChangeReceiver() {        IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);        batteryChangeReceiver = new BatteryChangeReceiver();        registerReceiver(batteryChangeReceiver, filter);    }
        private class BatteryChangeReceiver extends BroadcastReceiver {        @Override        public void onReceive(Context context, Intent intent) {            //level:表示系统电量等级,0-100            int level = intent.getIntExtra("level", 0);            updateBatteryBg(level);        }    }
    /** * 根据系统电量等级去设置对应的图片 * * @param level 电量等级 */private void updateBatteryBg(int level) {    if (level <= 0) {        ivBattery.setBackgroundResource(R.mipmap.ic_battery_0);    } else if (level > 0 && level <= 10) {        ivBattery.setBackgroundResource(R.mipmap.ic_battery_10);    } else if (level > 10 && level <= 20) {        ivBattery.setBackgroundResource(R.mipmap.ic_battery_20);    } else if (level > 20 && level <= 40) {        ivBattery.setBackgroundResource(R.mipmap.ic_battery_40);    } else if (level > 40 && level <= 60) {        ivBattery.setBackgroundResource(R.mipmap.ic_battery_60);    } else if (level > 60 && level <= 80) {        ivBattery.setBackgroundResource(R.mipmap.ic_battery_80);    } else {        ivBattery.setBackgroundResource(R.mipmap.ic_battery_100);    }}

    附一张图片,看前面的电量小图标,studio这一点很好
    这里写图片描述

    另外,提前说下,当退出该activity的时候,记得把所有的消息或者广播给移除掉。(养成良好的编程习惯。)

        @Override    protected void onDestroy() {        super.onDestroy();        handler.removeCallbacksAndMessages(null);//清除所有的回调和消息        unregisterReceiver(batteryChangeReceiver);    }
  3. 初始化音量,这里要将SeeKBar的长度设置成音量的长度,并将进度设置成当前的音量。

    /** * 初始化SeekBar的音量 */private void initVolume() {    audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);    int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);    currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);    LogUtils.i("maxVolume = " + maxVolume + "   currentVolume = " + currentVolume);    sbVolume.setMax(maxVolume);    sbVolume.setProgress(currentVolume);}

    initVolume();方法先在initData的时候调用一次

  4. 滑动SeekBar改变音量(注意:后面由于有这样一个功能:手指触摸屏幕显示上下控制面板,离开5秒后发一个广播隐藏控制面板,但如果手指已经在控制上下面板的话,需要将该广播取消,直到离开时,在重发广播。)

        sbVolume.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {        @Override        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {            if (fromUser) {                isMute = false;                currentVolume = progress;                updateVolume();            }        }        @Override        public void onStartTrackingTouch(SeekBar seekBar) {            handler.removeMessages(HIDE_CONTROL_LAYOUT);        }        @Override        public void onStopTrackingTouch(SeekBar seekBar) {            handler.sendEmptyMessageDelayed(HIDE_CONTROL_LAYOUT, 5000);        }    });
    /** * 更新音量 */private void updateVolume() {    if (isMute) {     //静音        audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0);        sbVolume.setProgress(0);    } else {        //第三个参数 flags 0 -> 表示隐藏系统的音量的浮动面板 1 -> 表示显示系统的音量的浮动面板        audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, currentVolume, 0);        sbVolume.setProgress(currentVolume);    }}
  5. 通过手指的上下滑动更新系统音量,该项目需要这样的功能。这里是重写了onTouchEvent方法,代码注释写得比较详细了。主要有这么一些处理:忽略用户的误滑,不断判断是上滑还是下滑,使音量改变明显。

        @Override    public boolean onTouchEvent(MotionEvent event) {        gestureDetector.onTouchEvent(event);        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                downY = event.getY();                break;            case MotionEvent.ACTION_MOVE:                //1.计算出手指滑动的距离                float deltaY = event.getY() - downY;                //mTouchSlop 滑动的界限值  --> 避免 用户误滑 造成音量改变                if (Math.abs(deltaY) < mTouchSlop) {                    break;                }                isMute = false;                //2.计算出滑动距离占总距离的百分比//                float percent = Math.abs(deltaY) / Math.min(screenWidth, screenHeight);                //3.根据百分比算出滑动的音量//                int moveVolume = (int) percent * maxVolume;                //4.由于moveVolume很小,经常为0,所以每次滑动我们就增减1个单位的音量                if (deltaY > 0) { //下滑                    currentVolume--;                } else if (deltaY < 0) {                    currentVolume++;                }                updateVolume();                downY = event.getY();                break;            case MotionEvent.ACTION_UP:                downY = 0;                break;        }        return super.onTouchEvent(event);    }

显示/隐藏上下控制面板

  1. 手指单击显示/隐藏上下控制面板,这里用android提供的GestureDetector类,重写里面的onSingleTapConfirmed()方法,因为项目中还有长按暂停/播放,双击全屏的功能,所以索性都覆盖了。记住要把这个手势事件要传递给onTouchEvent()中,才能生效。(上面的代码已经传递了)

        private class MyGestureListener extends GestureDetector.SimpleOnGestureListener {        @Override        public void onLongPress(MotionEvent e) {            super.onLongPress(e);//            LogUtils.i("onLongPress");            processClick(ivPlay);        }        @Override        public boolean onDoubleTap(MotionEvent e) {//            LogUtils.i("onDoubleTap");            processClick(ivScreen);            return super.onDoubleTap(e);        }        @Override        public boolean onSingleTapConfirmed(MotionEvent e) {//            LogUtils.i("onSingleTapConfirmed");            if (isShowControlLayout) {                //隐藏控制面板                hideControlLayout();            } else {                //显示控制面板                showControlLayout();            }            return super.onSingleTapConfirmed(e);        }    }
    private static final int HIDE_CONTROL_LAYOUT = 2;//隐藏控制面板的消息
        /**     * 隐藏控制面板     */    private void hideControlLayout() {        topView.animate().translationY(-topView.getHeight()).setDuration(200);        bottomView.animate().translationY(bottomView.getHeight()).setDuration(200);        isShowControlLayout = false;        handler.removeMessages(HIDE_CONTROL_LAYOUT);    }
        /**     * 显示控制面板     */    private void showControlLayout() {        topView.animate().translationY(0).setDuration(200);        bottomView.animate().translationY(0).setDuration(200);        isShowControlLayout = true;        handler.sendEmptyMessageDelayed(HIDE_CONTROL_LAYOUT, 5000);    }

底部布局的逻辑:

  1. 播放/暂停,这里要在视频完全准备好的时候,才能播放。上一篇的最后附的一张图已经显示了MediaPlayer的类图。

    /** * 播放currentPosition当前位置的视频 */private void playVideo() {    if (videoList == null || videoList.size() == 0) {        finish();        return;    }    ivPre.setEnabled(currentPosition != 0);    ivNext.setEnabled(currentPosition != (videoList.size() - 1));    VideoItem videoItem = videoList.get(currentPosition);    tvName.setText(videoItem.getTitle());    vv.setVideoURI(Uri.parse(videoItem.getPath()));//      vv.setMediaController(new MediaController(this));系统默认的控制器 --> 丑}
        vv.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {        @Override        public void onPrepared(MediaPlayer mp) {//                llLoading.setVisibility(View.GONE);            //给加载界面增加渐隐动画            llLoading.animate().alpha(0).setDuration(600).setListener(new Animator.AnimatorListener() {                @Override                public void onAnimationStart(Animator animation) {                }                @Override                public void onAnimationEnd(Animator animation) {                    llLoading.setVisibility(View.GONE);//隐藏加载页面                }                @Override                public void onAnimationCancel(Animator animation) {                }                @Override                public void onAnimationRepeat(Animator animation) {                }            });            vv.start();            ivPlay.setBackgroundResource(R.drawable.selector_btn_pause);            sbVideo.setMax((int) vv.getDuration());            tvTotalTime.setText(StringUtil.formatVideoDuration(vv.getDuration()));            updatePlayProgress();        }    });
    /** * 更新播放进度 */private void updatePlayProgress() {    LogUtils.i("updatePlayProgress");    tvCurrentTime.setText(StringUtil.formatVideoDuration(vv.getCurrentPosition()));    sbVideo.setProgress((int) vv.getCurrentPosition());    handler.sendEmptyMessageDelayed(UPDATE_PLAY_PROGRESS, 1000);}
  2. 上/下一个视频,要判断一下,是否有上一个或者下一个视频,避免出异常。

            case R.id.iv_play:            if (vv.isPlaying()) {                vv.pause();                handler.removeMessages(UPDATE_PLAY_PROGRESS);            } else {                vv.start();                handler.sendEmptyMessage(UPDATE_PLAY_PROGRESS);            }            updatePlayBtnBg();            break;        case R.id.iv_pre:            if (currentPosition > 0) {                currentPosition--;                playVideo();            }            break;
    /** * 更新播放按钮的背景图片 */private void updatePlayBtnBg() {    ivPlay.setBackgroundResource(vv.isPlaying() ?            R.drawable.selector_btn_pause            : R.drawable.selector_btn_play);}
  3. 退出、全屏
    注意:全屏的方法需要在vitamio的VideoView中写两个方法:1. 是否全屏,2. 切换到全屏

    /*** 切换全屏和默认屏幕*/public void switchScreen() {if(mVideoLayout == VIDEO_LAYOUT_SCALE){  //应该为全屏  setVideoLayout(VIDEO_LAYOUT_STRETCH, getVideoAspectRatio());}else{  //默认屏幕  setVideoLayout(VIDEO_LAYOUT_SCALE, getVideoAspectRatio());}}public boolean isFullScreen(){return mVideoLayout == VIDEO_LAYOUT_STRETCH;}
            case R.id.iv_exit:            finish();            break;        case R.id.iv_screen:            vv.switchScreen();            updateScreenBtnBg();            break;
    /** * 更新屏幕按钮的背景图片 */private void updateScreenBtnBg() {    ivScreen.setBackgroundResource(vv.isFullScreen() ?            R.drawable.selector_btn_defaultscreen : R.drawable.selector_btn_fullscreen);}
  4. 播放过程中的各种监听事件(顶部/底部的动画事件,视频进度条被拖动的事件,视频播放完成/缓冲/卡顿/错误的事件)

        topView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {        @Override        public void onGlobalLayout() {            topView.getViewTreeObserver().removeOnGlobalLayoutListener(this);            topView.animate().                    translationY(-topView.getHeight())                    .setDuration(0);        }    });    bottomView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {        @Override        public void onGlobalLayout() {            bottomView.getViewTreeObserver().removeOnGlobalLayoutListener(this);            bottomView.animate().                    translationY(bottomView.getHeight())                    .setDuration(0);        }    });    sbVideo.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {        @Override        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {            if (fromUser) {                vv.seekTo(progress);                tvCurrentTime.setText(StringUtil.formatVideoDuration(progress));            }        }        @Override        public void onStartTrackingTouch(SeekBar seekBar) {            handler.removeMessages(HIDE_CONTROL_LAYOUT);        }        @Override        public void onStopTrackingTouch(SeekBar seekBar) {            handler.sendEmptyMessageDelayed(HIDE_CONTROL_LAYOUT, 5000);        }    });    vv.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {        @Override        public void onCompletion(MediaPlayer mp) {            ivPlay.setBackgroundResource(R.drawable.selector_btn_play);        }    });    vv.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {        @Override        public void onBufferingUpdate(MediaPlayer mp, int percent) {//              LogUtil.e(this, "percent: "+percent);            //percent:0-100            int bufferedProgress = (int) ((percent / 100.0f) * vv.getDuration());            sbVideo.setSecondaryProgress(bufferedProgress);        }    });    vv.setOnInfoListener(new MediaPlayer.OnInfoListener() {        @Override        public boolean onInfo(MediaPlayer mp, int what, int extra) {            switch (what) {                case MediaPlayer.MEDIA_INFO_BUFFERING_START://当拖动卡顿开始时调用                    llBuffering.setVisibility(View.VISIBLE);                    break;                case MediaPlayer.MEDIA_INFO_BUFFERING_END://当拖动卡顿结束调用                    llBuffering.setVisibility(View.GONE);                    break;            }            return true;        }    });    vv.setOnErrorListener(new MediaPlayer.OnErrorListener() {        @Override        public boolean onError(MediaPlayer mp, int what, int extra) {            switch (what) {                case MediaPlayer.MEDIA_ERROR_UNKNOWN:                    Toast.makeText(VitamioVideoPlayerActivity.this, "不支持该格式", Toast.LENGTH_SHORT).show();                    break;            }            return false;        }    });

至此,视频播放的逻辑基本就处理好了,当然,如果该视频播放器要支持播放网络视频或者从第三方视频库选则用该播放器打开,则需要在开始获取传递过来的数据的时候做一个判断,如果 是从文件发起的播放请求则获取到Uri直接播放就可以,这里就不列出代码了。

版权声明:本文为博主原创文章,未经博主允许不得转载。

  相关解决方案