android播放视频(三)利用原生的MediaPlayer+SurfaceView之提高

版权声明:本文为San石博主原创文章,转载请注明出处。 https://blog.csdn.net/shenxiaolei507/article/details/41678885

转载请注明地址: http://blog.csdn.net/shenxiaolei507

本文是在 android视频播放(二) 利用android原生的MediaPlayer+SurfaceView的基础上,进行的一些功能上再次提高和一些需求的实现,如果对android利用MediaPlayer+SurfaceView播放视频不熟悉,可以看下这篇文章。

众所周知,我们在开发视频播放的时候,往往不光是简单的对视频进行播放管理,还会有一些其它的需求和高级功能,比如切换到屏幕时我们要保持视频,屏幕旋转,视频大小模式,视频截图等等,好了,下面我们就开始讲解一部分功能的实现。

一、屏幕旋转处理

         在视频播放的时候,我们一般都会采用横屏的形式来播放视频,但是手机在用户没有关闭自动旋转的情况下,屏幕会在横竖屏之间切换,用户体验会非常不好,所以我们要对这种情况做处理。我们只需要在AndroidManifest的文件中,对播放视频的Activity加上这样的配置,我们就会一直处于横屏状态。

<span style="font-size:14px;">android:screenOrientation="userLandscape"
android:configChanges="keyboardHidden|orientation|screenSize"</span>

对上述参数介绍:

android:screenOrientation 为设置屏幕方向参数,userLandescape为横屏,其中参数有很多,这里不是咱们介绍重点,如果有兴趣大家可以自己摸索。这里推荐一篇文章 http://www.cnblogs.com/bluestorm/archive/2012/05/07/2486998.html

android:configChanges 为配置变化参数。其中keyboardHidden为键盘的可用性发生改变,orientation是屏幕的方向发生改变,screeSize为屏幕的大小发生改变,设置本参数的目的就是为了防止屏幕旋转时重新调用Activity的各个生命周期,关于android:configChanges我们要注意以下几点:

(1)不设置android:configChanges时屏幕旋转会重新调用activity的各个生命周期,切横屏时会调用一次,切竖屏时调用两次。

(2)只设置android:configChanges="orientation"时屏幕旋转会重新调用activity的各个生命周期,但是横竖屏时都只会调用一次。

(3)设置android:configChanges="orientation|keyboardHidden"时屏幕旋转不会调用activity各个生命周期,只会调用Activity的onConfigurationChanged方法,但在Android API13以后,因为屏幕的大小screeSize会随着屏幕的切换而改变,Android默认会再调用Activity的各个生命周期,所以我们也加上screeSize的属性,最后为了兼容API上下版本我们设置android:configChanges="keyboardHidden|orientation|screenSize"


二、视频界面大小模式处理

我们都知道视频播放的时候,有的用户习惯在全屏模式下观看,也有的用户习惯在窗口模式下观看,所以这样的需求在视频播放方面也比较多,下面我们就来看下具体的处理方法,

 /**
     * 改变视频的显示大小,全屏,窗口,内容
     */
    public void changeVideoSize() {
        // 改变视频大小
        String videoSizeString = videoSizeButton.getText().toString();
        // 获取视频的宽度和高度
        int width = mediaPlayer.getVideoWidth();
        int height = mediaPlayer.getVideoHeight();
        // 如果按钮文字为窗口则设置为窗口模式
        if ("窗口".equals(videoSizeString)) {
            /*
             * 如果为全屏模式则改为适应内容的,前提是视频宽高小于屏幕宽高,如果大于宽高 我们要做缩放
             * 如果视频的宽高度有一方不满足我们就要进行缩放. 如果视频的大小都满足就直接设置并居中显示。
             */
            if (width > screenWidth || height > screenHeight) {
                // 计算出宽高的倍数
                float vWidth = (float) width / (float) screenWidth;
                float vHeight = (float) height / (float) screenHeight;
                // 获取最大的倍数值,按大数值进行缩放
                float max = Math.max(vWidth, vHeight);
                // 计算出缩放大小,取接近的正值
                width = (int) Math.ceil((float) width / max);
                height = (int) Math.ceil((float) height / max);
            }
            // 设置SurfaceView的大小并居中显示
            RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(width,
                    height);
            layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
            surfaceView.setLayoutParams(layoutParams);
            videoSizeButton.setText("全屏");
        } else if ("全屏".equals(videoSizeString)) {
            // 设置全屏
            // 设置SurfaceView的大小并居中显示
            RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(screenWidth,
                    screenHeight);
            layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
            surfaceView.setLayoutParams(layoutParams);
            videoSizeButton.setText("窗口");
        }
    }

关于上述方法我主要讲解以下几点:

(1)通过MediaPlayer的getVideoWidth,getVideoHeight方法,可以获取到视频的原始大小。

(2)在视频的原始大小超出手机屏幕的范围的情况下,我们要对视频的大小进行等比例缩放,通过计算宽高的倍数,根据最大的倍数,算出缩放以后的宽高。

(3)设置视频的宽高大小是通过为SurfaceView设置LayoutParams来实现的,关于使用那个类下面的LayoutParams,主要参考SurfaceView的父布局。而surfaceHolder.setFixedSize(?, ?);是设置分辨率,视频窗口的大小是由surfaceView的大小决定的。


三、视频截图

关于视频截图的需求,在很多视频播放器上,都有涉及。而且很多开源的视频播放框架,也会提供这样的功能。关于网络视频截图的实现有一定的技术难度,本文暂时不介绍,现在来介绍一种本地视频截图的方法实现,采用的是android系统提供的API,下面来看代码

  /**
     * 保存视频截图.该方法只能支持本地视频文件
     * 
     * @param time视频当前位置
     */
    public void savaScreenShot(long time) {
        // 标记是否保存成功
        boolean isSave = false;
        // 获取文件路径
        String path = null;
        // 文件名称
        String fileName = null;
        if (time >= 0) {
            try {
                MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
                mediaMetadataRetriever.setDataSource(pathString);
                // 获取视频的播放总时长单位为毫秒
                String timeString = mediaMetadataRetriever
                        .extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
                // 转换格式为微秒
                long timelong = Long.parseLong(timeString) * 1000;
                // 计算当前视频截取的位置
                long index = (time * timelong) / mediaPlayer.getDuration();
                // 获取当前视频指定位置的截图,时间参数的单位是微秒,做了*1000处理
                // 第二个参数为指定位置,意思接近的位置截图
                Bitmap bitmap = mediaMetadataRetriever.getFrameAtTime(time * 1000,
                        MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
                // 释放资源
                mediaMetadataRetriever.release();
                // 判断外部设备SD卡是否存在
                if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                    // 存在获取外部文件路径
                    path = Environment.getExternalStorageDirectory().getPath();
                } else {
                    // 不存在获取内部存储
                    path = Environment.getDataDirectory().getPath();
                }
                // 设置文件名称 ,以事件毫秒为名称
                fileName = Calendar.getInstance().getTimeInMillis() + ".jpg";
                // 设置保存文件
                File file = new File(path + "/shen/" + fileName);

                if (!file.exists()) {
                    file.createNewFile();
                }
                FileOutputStream fileOutputStream = new FileOutputStream(file);
                bitmap.compress(CompressFormat.JPEG, 100, fileOutputStream);
                isSave = true;
            } catch (Exception e) {
                e.printStackTrace();
            }
            // 保存成功以后,展示图片
            if (isSave) {
                ImageView imageView = new ImageView(this);
                imageView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
                        LayoutParams.WRAP_CONTENT));
                imageView.setImageBitmap(BitmapFactory.decodeFile(path + "/shen/" + fileName));
                new AlertDialog.Builder(this).setView(imageView).show();
            }
        }

    }

关于视频的实现,主要通过以下代码实现的

MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
mediaMetadataRetriever.setDataSource(pathString);
// 获取当前视频指定位置的截图,时间参数的单位是微秒,做了*1000处理
// 第二个参数为指定位置,意思接近的位置截图
Bitmap bitmap = mediaMetadataRetriever.getFrameAtTime(time * 1000,
                       MediaMetadataRetriever.OPTION_CLOSEST_SYNC);

(1)通过setDataSource();设置播放视频的路径,该路径为本地路径。

(2)通过getFrameAtTime();方法获取本地截图返回Bitmap对象。该方法有两个参数第一个参数为指定的时间的位置,该时间位置的单位为微妙,比毫秒还小一级,1毫秒等于1000微秒。通过MediaPlayer.getCurrentPosition()方法获取的播放时间位置是毫秒,所以要乘以1000。第二个参数为与该位置的位置,该参数有很多方式,都是MediaMetadataRetriever的常量数值,其中有OPTION_PREVIOUS_SYNC表示返回指定位置稍早的截图,OPTION_NEXT_SYNC表示返回指定位置稍晚的截图,OPTION_CLOSEST_SYNC表示返回接近指定位置的截图,OPTION_CLOSEST表示返回比指定位置早更多的截图,OPTION_CLOSEST表示返回比指定位置晚更多的截图,而其中的OPTION_CLOSEST_SYNC为其中的默认参数设置。


四、关于一些MediaPlayer的一些接口的解释

    /**
     * 视频播放完成以后调用的回调函数
     */
    @Override
    public void onCompletion(MediaPlayer mp) {

    }
该方法为视频播放视频播放完毕监听。具体功能实现看具体需求。
    /**
     * 视频播放发生错误时调用的回调函数
     */
    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        switch (what){
            case MediaPlayer.MEDIA_ERROR_UNKNOWN:
                Log.e("text","发生未知错误");

                break;
            case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
                Log.e("text","媒体服务器死机");
                break;
            default:
                Log.e("text","onError+"+what);
                break;
        }
        switch (extra){
            case MediaPlayer.MEDIA_ERROR_IO:
                //io读写错误
                Log.e("text","文件或网络相关的IO操作错误");
                break;
            case MediaPlayer.MEDIA_ERROR_MALFORMED:
                //文件格式不支持
                Log.e("text","比特流编码标准或文件不符合相关规范");
                break;
            case MediaPlayer.MEDIA_ERROR_TIMED_OUT:
                //一些操作需要太长时间来完成,通常超过3 - 5秒。
                Log.e("text","操作超时");
                break;
            case MediaPlayer.MEDIA_ERROR_UNSUPPORTED:
                //比特流编码标准或文件符合相关规范,但媒体框架不支持该功能
                Log.e("text","比特流编码标准或文件符合相关规范,但媒体框架不支持该功能");
                break;
            default:
                Log.e("text","onError+"+extra);
                break;
        }
        //如果未指定回调函数, 或回调函数返回假,VideoView 会通知用户发生了错误。
        return false;
    }
该方法的为播放错误监听方法,具体的错误信息,在代码中已经很有详细的注释,这里不在进行解释。

    /**
     * 视频缓存大小监听,当视频播放以后 在started状态会调用
     */
    public void onBufferingUpdate(MediaPlayer mp, int percent) {
        // TODO Auto-generated method stub
        // percent 表示缓存加载进度,0为没开始,100表示加载完成,在加载完成以后也会一直调用该方法
        Log.e("text", "onBufferingUpdate-->" + percent);
    }
该方法为视频缓存大小监听大小方法,该方法是在视频处于Started(已经播放)状态后开始起作用。参数percent 表示缓存加载进度,0为没开始,100表示加载完成。在加载完成以后也会一直调用该方法,所以在处理的时候也要特别注意。


好了关于Android利用原生MediaPlayer+SurfaceView播放视频的提高和更多功能实现,就讲解到这里。欢迎大家多多指正!!!。


源码地址:Android利用MediaPlayer+SurfaceView播放网络视频




猜你喜欢

转载自blog.csdn.net/shenxiaolei507/article/details/41678885