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

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

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

一、屏幕旋转处理

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

android:screenOrientation="userLandscape"android:configChanges="keyboardHidden|orientation|screenSize"

对上述参数介绍:

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播放网络视频




更多相关文章

  1. Android 性能优化系列视频(五)
  2. Android Studio App设置TextView文字内容大小颜色
  3. 短视频直播源码Android关于分屏的知识总结
  4. [Android Training视频系列]1.1 Creating an Android Project
  5. Android IOS WebRTC 音视频开发总结(十五)-- courseIndex
  6. 无废话Android之android下junit测试框架配置、保存文件到手机内
  7. Android视频采集
  8. Android实现网络视频播放
  9. Android 视频列表(RecyclerView)实现自动播放

随机推荐

  1. Android(安卓)自定义 Adapter
  2. 高通android10.0默认赋予第三方apk权限
  3. Android多媒体扫描过程(Android(安卓)Medi
  4. Android(安卓)SDK的docs访问速度很慢
  5. Android使用ViewPager实现左右滑动效果
  6. i-jetty环境搭配与编译
  7. Android(安卓)Logcat 直接输出JSON格式的
  8. 再看Android(安卓)屏幕切换引起的生命周
  9. pull解析
  10. Android(安卓)/ iOS 静态代码扫描工具调