视频播放

视频播放的实现方式

  • 1、使用系统中已安装的播放器app
  • 2、使用VideoView配合MediaController实现 (系统的控制键)
  • 3、使用SurfaceView配合MediaP 实现(可自定义控制键,灵活度最高)

1、使用Intent播放视频

中添加 <provider> 标签的内容 android7.0新特性的FileProvider的需要

具体了解:Android 7.0 行为变更 通过FileProvider在应用间共享文件吧

<?  version=\"1.0\" encoding=\"utf-8\"?>
<manifest  ns:android=\"http://schemas.android.com/apk/res/android\"
    package=\"com.demo.videodemo\">

    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />
    <application
        android:allowBackup=\"true\"
        android:icon=\"@mipmap/ic_launcher\"
        android:label=\"@string/app_name\"
        android:roundIcon=\"@mipmap/ic_launcher_round\"
        android:supportsRtl=\"true\"
        android:theme=\"@style/AppTheme\">
        <activity android:name=\".MainActivity\">
            <intent-filter>
                <action android:name=\"android.intent.action.MAIN\" />

                <category android:name=\"android.intent.category.LAUNCHER\" />
            </intent-filter>
        </activity>
        <provider
            android:name=\"android.support.v4.content.FileProvider\"
            android:authorities=\"com.demo.videodemo.fileprovider\"
            android:exported=\"false\"
            android:grantUriPermissions=\"true\">
            < -data
                android:name=\"android.support.FILE_PROVIDER_PATHS\"
                android:resource=\"@ /file_paths\" />
        </provider>
    </application>

</manifest>

在res 新建 \'文件夹 新建 file_paths. 文件

<?  version=\"1.0\" encoding=\"utf-8\"?>
<paths  ns:android=\"http://schemas.android.com/apk/res/android\">
    <!--因为是根路径 不写path 即为根目录的文件  /storage/emulated/0/ 下的文件 -->
    <!--这里写video 根目录下的video目录下的文件-->
    <external-path name=\"external\" path=\"video\" />
</paths>

 Java代码: 下面是三种方式的模板 只实现了第一种 接下来的方法都是在这个代码上的了

public class MainActivity extends AppCompatActivity {
    private ListView mListView;
    //三种播放视频方式
    private List<String> mDatas = Arrays.asList(\"Use Intent\",
            \"Use VideoView\", \"Use MediaP  & SurfaceView\");

    private static final int REQ_CODE_STORAGE = 0X110;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mListView = findViewById(R.id.id_listview);
        //设置适配器
        mListView.setAdapter(new ArrayAdapter<String>(this, R.layout.item_main, mDatas));
        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                switch (position) {
                    case 0:
                        checkPermissionAndPlayVideo();
                        break;
                    case 1:
                        break;
                    case 2:
                        break;
                    default:
                        break;
                }
            }
        });
    }

    /**
     * 利用intent 播放 本地视频
     * 加内存 写 的权限
     * android 6.0 之后要动态申请权限
     */
    private void playVideoUseIntent() {
        //找到内存卡里面的视频 参数1:文件路径  参数2:文件名称
        File file = new File(Environment.getExternalStorageDirectory().getPath(), \"lalala.mp4\");
        Intent intent = new Intent(Intent.ACTION_VIEW);
        if (Build.VERSION.SDK_INT >= 24) {
            //android7.0之后的特性 fileprovider  使用content://Uri 爱替换 file://Uri 否则报错
            //完成 file 到 contentUri的转换
            Uri contentUri = FileProvider.getUriForFile(this, \"com.demo.videodemo.fileprovider\", file);
            intent.setDataAndType(contentUri, \"video/*\");
            //添加Uri权限
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        } else {//7.0之前的操作
            //设置数据源和类型 fileUri
            intent.setDataAndType(Uri.fromFile(file), \"video/*\");
        }
        startActivity(intent);
    }

    /**
     * 动态申请权限
     */
    private void checkPermissionAndPlayVideo() {
        //如果读内存卡的权限没被申请
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            //申请权限 onRequestPermissionsResult 拿到结果
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQ_CODE_STORAGE);
        } else {//如果已经有权限了
            playVideoUseIntent();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case REQ_CODE_STORAGE:
                //如果权限被授予
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    //执行播放视频的操作
                    playVideoUseIntent();
                } else {
                    Toast.makeText(this, \"该功能需要SDCard权限\", Toast.LENGTH_SHORT).show();
                }
                return;
        }
    }
}

效果:自带进度条 和横竖屏切换 

 \"\"

2、VideoView配合MediaController

  • 1、VideoView播放视频(VideoView 继承 SurfaceView)
  • 2、MediaController控制视频暂停、快进、快退
  • 3、考虑点击Home键视频的暂停与恢复

第一步:播放视频的实现

VideoViewActivity.java

public class VideoViewActivity extends AppCompatActivity {
    private VideoView mVideoView;
    private MediaController mMediaController;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video_view);
        mVideoView = findViewById(R.id.id_video_view);
        //找到内存卡里面的视频 参数1:文件路径  参数2:文件名称  /storage/emulated/0/video的.mp4文件
        File file = new File(Environment.getExternalStorageDirectory().getPath() + \"/video\", \"lala.mp4\");
        //1.完成视频的播放
        mVideoView.setVideoPath(file.getAbsolutePath());

        mMediaController = new MediaController(this);
        //2.MediaController 与VideoView结合
        mVideoView.setMediaController(mMediaController);
        mVideoView.start();
    }

    /**
     * MainActivity对启用的方法
     * @param context
     */
    public static void start(Context context) {
        Intent intent = new Intent(context, VideoViewActivity.class);
        context.startActivity(intent);
    }
}

MainActivity.java 中点击之后开启视频 

     case 1:
                        VideoViewActivity.start(MainActivity.this);
                        break;

效果:视频可以正常播放 还有系统自带的暂停、快进等控件,

但是点击home 键再回来之后 就黑屏了(没有画面,其实是surfcaseView被销毁重建了) 怎么解决? 

 \"\"

在onResume的时候执行start();方法

  @Override
    protected void onResume() {
        super.onResume();
        mVideoView.start();

    }

解决了上面的一个问题,新问题:按home键后视频会自动重启播放,但是不会保存之前播放的状态 

因为点击home键之后再进来SurfaceView会销毁重建 所以视频是被初始化了一遍

1.点击暂停之后 home键回来还是暂停的状态

2.并跳转到之前记录的进度值


  //记录当前视频播放的进度值
    private int mCurrentPos;
    //判断是否处于暂停状态
    private boolean mIsPause;

    ....

    @Override
    protected void onResume() {
        super.onResume();
        //跳转到当前进度值
        mVideoView.seekTo(mCurrentPos);
        if (!mIsPause) {
            mVideoView.start();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        //记录暂停时视频的进度值
        mCurrentPos = mVideoView.getCurrentPosition();
        mIsPause = !mVideoView.isPlaying();
    }

目前效果: 

 \"\"

新问题:当我们旋转屏幕,视频又重头播放了 ,因为Activity被销毁重建了

状态的存储和恢复

  //bundle的key值
    private static final String KEY_CUR_POS = \"key_cur_pos\";
    private static final String KEY_IS_PAUSE = \"key_is_pause\";
 

/**
     * 状态存储
     * @param outState
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt(KEY_CUR_POS, mCurrentPos);
        outState.putBoolean(KEY_IS_PAUSE, mIsPause);
    }

    /**
     * 状态恢复
     * @param savedInstanceState
     */
    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        mCurrentPos = savedInstanceState.getInt(KEY_CUR_POS);
        mIsPause = savedInstanceState.getBoolean(KEY_IS_PAUSE);
    }

3、SurfaceView配合MediaP

  • 1、MediaP 播放视频
  • 2、SurfaceView显示视频画面
  • 3、自定义控制器实现:快进、快退、暂停等
  • 4、考虑点击Home键视频的暂停与恢复

MediaP Activity.java

实现视频播放和点击home键后可以继续播放


/**
 * VideoView mMediaP 是和SurfaceView绑定的
 * 现在这个 mMediaP 是和SurfaceView 分开的
 */
public class MediaP Activity extends AppCompatActivity {
    private RelativeLayout mRlContainer;
    private SurfaceView mSurfaceView;
    private MediaP  mMediaP ;
    //标识符
    private boolean mIsprepared;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_media_p );
        initViews();
        initMediaP ();
        initEvents();
    }

    //暂停
    @Override
    protected void onPause() {
        super.onPause();
        mMediaP .pause();
    }

    //销毁 释放
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mMediaP .release();
    }

    /**
     * 初始化视图
     */
    private void initViews() {
        mRlContainer = findViewById(R.id.id_rl_container);
        mSurfaceView = findViewById(R.id.id_surface_view);
    }

    private void initMediaP () {
        mMediaP  = new MediaP ();
        //找到内存卡里面的视频 参数1:文件路径  参数2:文件名称  /storage/emulated/0/video的.mp4文件
        File file = new File(Environment.getExternalStorageDirectory().getPath() + \"/video\", \"lala.mp4\");
        try {
            //每一步严格遵守MediaP 的状态
            mMediaP .setDataSource(file.getAbsolutePath());
            mMediaP .prepareAsync();
            mMediaP .setOnPreparedListener(new MediaP .OnPreparedListener() {
                @Override
                public void onPrepared(MediaP  mp) {
                    mMediaP .start();
                    //更新状态
                    mIsprepared = true;
                }
            });
            //这里拿到视频的宽高
            mMediaP .setOnVideoSizeChangedListener(new MediaP .OnVideoSizeChangedListener() {
                @Override
                public void onVideoSizeChanged(MediaP  mp, int width, int height) {
                    //动态设置RlContainer的高度
                    ViewGroup.LayoutParams lp = mRlContainer.getLayoutParams();
                    //拿到控件的宽度和视频的宽度比值再乘以视频的宽高得到同等比例下控件的高
                    lp.height = (int) (mRlContainer.getWidth() * 1.0f / width * height);
                    mRlContainer.setLayoutParams(lp);
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 初始化事件
     */
    private void initEvents() {
        //将MediaP 和SurfaceView进行绑定
        mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                //在surface创建成功
                mMediaP .setDisplay(holder);
                //如果是prepared之后才能start()
                if (!mIsprepared) {
                    return;
                }
                //如果mMediaP 不是在播放的时候再start
                if (!mMediaP .isPlaying()) {
                    //点击home键回重新销毁创建surfaceView  也会执行这个方法
                    //所以在这里重新开启
                    mMediaP .start();
                }
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {

            }
        });
    }

    /**
     * MainActivity对启用的方法
     *
     * @param context
     */
    public static void start(Context context) {
        Intent intent = new Intent(context, MediaP Activity.class);
        context.startActivity(intent);
    }
}

布局文件:activity_media_p .

<?  version=\"1.0\" encoding=\"utf-8\"?>
<RelativeLayout  ns:android=\"http://schemas.android.com/apk/res/android\"
     ns:app=\"http://schemas.android.com/apk/res-auto\"
     ns:tools=\"http://schemas.android.com/tools\"
    android:layout_width=\"match_parent\"
    android:layout_height=\"match_parent\"
    tools:context=\".MediaP Activity\">

    <RelativeLayout
        android:id=\"@+id/id_rl_container\"
        android:layout_width=\"match_parent\"
        android:layout_height=\"200dp\">

        <SurfaceView
            android:id=\"@+id/id_surface_view\"
            android:layout_width=\"match_parent\"
            android:layout_height=\"match_parent\" />
    </RelativeLayout>

</RelativeLayout>

 效果: 

\"\"

添加SeekBar 进度条暂停按键 和 阻止旋转屏幕销毁重建activity(状态保存恢复的另一种方式):

\"\"

布局文件:

<?  version=\"1.0\" encoding=\"utf-8\"?>
<RelativeLayout  ns:android=\"http://schemas.android.com/apk/res/android\"
     ns:app=\"http://schemas.android.com/apk/res-auto\"
     ns:tools=\"http://schemas.android.com/tools\"
    android:layout_width=\"match_parent\"
    android:layout_height=\"match_parent\"
    tools:context=\".MediaP Activity\">

    <RelativeLayout
        android:id=\"@+id/id_rl_container\"
        android:layout_width=\"match_parent\"
        android:layout_height=\"200dp\">

        <SurfaceView
            android:id=\"@+id/id_surface_view\"
            android:layout_width=\"match_parent\"
            android:layout_height=\"match_parent\" />

        <SeekBar
            android:id=\"@+id/id_seekbar\"
            android:layout_width=\"match_parent\"
            android:layout_height=\"wrap_content\"
            android:layout_alignParentBottom=\"true\"
            android:layout_marginBottom=\"4dp\" />

        <Button
            android:id=\"@+id/id_btn_play\"
            android:layout_width=\"wrap_content\"
            android:layout_height=\"wrap_content\"
            android:layout_alignParentRight=\"true\"
            android:layout_alignParentTop=\"true\"
            android:enabled=\"false\"
            android:minHeight=\"0dp\"
            android:minWidth=\"0dp\"
            android:text=\"暂停\" />
    </RelativeLayout>

</RelativeLayout>

清单文件:

<?  version=\"1.0\" encoding=\"utf-8\"?>
<manifest  ns:android=\"http://schemas.android.com/apk/res/android\"
    package=\"com.demo.videodemo\">

    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />

    <application
        android:allowBackup=\"true\"
        android:icon=\"@mipmap/ic_launcher\"
        android:label=\"@string/app_name\"
        android:roundIcon=\"@mipmap/ic_launcher_round\"
        android:supportsRtl=\"true\"
        android:theme=\"@style/AppTheme\">
        <activity android:name=\".MainActivity\">
            <intent-filter>
                <action android:name=\"android.intent.action.MAIN\" />

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

        <provider
            android:name=\"android.support.v4.content.FileProvider\"
            android:authorities=\"com.demo.videodemo.fileprovider\"
            android:exported=\"false\"
            android:grantUriPermissions=\"true\">
            < -data
                android:name=\"android.support.FILE_PROVIDER_PATHS\"
                android:resource=\"@ /file_paths\" />
        </provider>

        <activity android:name=\".VideoViewActivity\" />
        <!--当发生方向变化时自己处理 阻止屏幕切换 会销毁重建activity了 只会回答java代码中的 onConfigurati d放到-->
        <activity android:name=\".MediaP Activity\"
            android:configChanges=\"orientation|screenSize\"></activity>
    </application>

</manifest>

完整java代码: 

/**
 * VideoView mMediaP 是和SurfaceView绑定的
 * 现在这个 mMediaP 是和SurfaceView 分开的
 */
public class MediaP Activity extends AppCompatActivity {
    private RelativeLayout mRlContainer;
    private SurfaceView mSurfaceView;
    private MediaP  mMediaP ;
    private SeekBar mSeekBar;
    private Button mBtnPlay;
    //标识符
    private boolean mIsprepared;
    private boolean mIsPause;
    ///记录控件的高宽比
    private float mRatioHW;
    //自动更新SeekBar 进度条的位置
    private Handler mHandler = new Handler();
    private Runnable mUpdateProgressRunnable = new Runnable() {
        @Override
        public void run() {
            if (mMediaP  == null) {
                return;
            } else {
                int currentPosition = mMediaP .getCurrentPosition();
                int duration = mMediaP .getDuration();
                if (mSeekBar != null && duration > 0) {
                    //拿到当前的进度值
                    int progress = (int) (currentPosition * 1.0f / duration * 1000);
                    mSeekBar.setProgress(progress);
                    //如果视频处于播放的时候才有改变进度条
                    if (mMediaP .isPlaying()) {
                        mHandler.postDelayed(mUpdateProgressRunnable, 1000);
                    }
                }
            }

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_media_p );
        initViews();
        initMediaP ();
        initEvents();
    }

    @Override
    protected void onPause() {
        super.onPause();
        mMediaP .pause();
        mHandler.removeCallbacks(mUpdateProgressRunnable);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mMediaP .release();
    }

    /**
     * 初始化视图
     */
    private void initViews() {
        mRlContainer = findViewById(R.id.id_rl_container);
        mSurfaceView = findViewById(R.id.id_surface_view);
        mSeekBar = findViewById(R.id.id_seekbar);
        mBtnPlay = findViewById(R.id.id_btn_play);
        mSeekBar.setMax(1000);
    }

    //阻止屏幕切换时,销毁重建activity 在 中 添加
    //android:configChanges=\"orientation|screenSize\"  自己处理 这样系统就不会销毁重建activity了
    @Override
    public void onConfigurati d(Configuration newConfig) {
        super.onConfigurati d(newConfig);
        //收到设置container的高度
        ViewTreeObserver observer = mRlContainer.getViewTreeObserver();
        observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mRlContainer.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                ViewGroup.LayoutParams lp = mRlContainer.getLayoutParams();
                //通过之前记录的高宽比恢复控件的高
                lp.height = (int) (mRatioHW * mRlContainer.getWidth());
                mRlContainer.setLayoutParams(lp);
            }
        });
    }

    private void initMediaP () {
        mMediaP  = new MediaP ();
        //找到内存卡里面的视频 参数1:文件路径  参数2:文件名称  /storage/emulated/0/video的.mp4文件
        File file = new File(Environment.getExternalStorageDirectory().getPath() + \"/video\", \"lala.mp4\");
        try {
            //每一步严格遵守MediaP 的状态
            mMediaP .setDataSource(file.getAbsolutePath());
            mMediaP .prepareAsync();
            mMediaP .setOnPreparedListener(new MediaP .OnPreparedListener() {
                @Override
                public void onPrepared(MediaP  mp) {
                    mBtnPlay.setEnabled(true);
                    //更新状态
                    mIsprepared = true;
                    if (!mMediaP .isPlaying()) {
                        //开启视频播放
                        mMediaP .start();
                        mHandler.post(mUpdateProgressRunnable);
                        mBtnPlay.setText(\"暂停\");
                    }
                }
            });
            //这里拿到视频的宽高
            mMediaP .setOnVideoSizeChangedListener(new MediaP .OnVideoSizeChangedListener() {
                @Override
                public void onVideoSizeChanged(MediaP  mp, int width, int height) {
                    //动态设置RlContainer的高度
                    ViewGroup.LayoutParams lp = mRlContainer.getLayoutParams();
                    //记录高宽比
                    mRatioHW = height * 1.0f / width;
                    //拿到控件的宽度和视频的宽度比值再乘以视频的宽高得到同等比例下控件的高
                    lp.height = (int) (mRlContainer.getWidth() * 1.0f / width * height);
                    mRlContainer.setLayoutParams(lp);
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 初始化事件
     */
    private void initEvents() {
        //将MediaP 和SurfaceView进行绑定
        mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                //在surface创建成功
                mMediaP .setDisplay(holder);
                //如果是prepared之后才能start()
                if (!mIsprepared) {
                    return;
                }
                //如果是暂停状态 点击home键 不需要重新start 直接跳转 return 避免执行下面start的方法
                if (mIsPause) {
                    mMediaP .seekTo(mMediaP .getCurrentPosition());
                    return;
                }
                //如果mMediaP 不是在播放的时候再start
                if (!mMediaP .isPlaying()) {
                    //点击home键回重新销毁创建surfaceView  也会执行这个方法
                    //所以在这里重新开启
                    mMediaP .start();
                    mBtnPlay.setText(\"暂停\");
                }
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {

            }
        });

        //设置进度条的拖动
        mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

            }

            @Override
            public void  TrackingTouch(SeekBar seekBar) {
                mHandler.removeCallbacks(mUpdateProgressRunnable);
            }

            @Override
            public void  TrackingTouch(SeekBar seekBar) {
                //拿到视频的总长度
                long duration = mMediaP .getDuration();
                //拿到当前进度条的比例(0-1000)占视频总长度的位置
                int target = (int) (mSeekBar.getProgress() * 1.0f / 1000 * duration);
                //跳到当前进度
                mMediaP .seekTo(target);
                //拖动进度如果是播放的时候再重新更新进度条
                if (mMediaP .isPlaying()) {
                    mHandler.post(mUpdateProgressRunnable);
                }
            }
        });
        //视频播放完成之后
        mMediaP .setOnCompletionListener(new MediaP .OnCompletionListener() {
            @Override
            public void onCompletion(MediaP  mp) {
                mSeekBar.setProgress(1000);
                mHandler.removeCallbacks(mUpdateProgressRunnable);
                mBtnPlay.setText(\"播放\");
                mIsPause = true;
            }
        });

        //暂停播放的控制
        mBtnPlay.set Listener(new View. Listener() {
            @Override
            public void  (View v) {
                if (mMediaP .isPlaying()) {//如果正在播放
                    mMediaP .pause();
                    mBtnPlay.setText(\"播放\");
                    mHandler.removeCallbacks(mUpdateProgressRunnable);
                    mIsPause = true;
                } else {//如果不是播放
                    mMediaP .start();
                    mBtnPlay.setText(\"暂停\");
                    mHandler.post(mUpdateProgressRunnable);
                    mIsPause = false;
                }
            }
        });
    }

    /**
     * MainActivity对启用的方法
     *
     * @param context
     */
    public static void start(Context context) {
        Intent intent = new Intent(context, MediaP Activity.class);
        context.startActivity(intent);
    }
}

 

收藏 打印