实战第12篇:SurfaceView实现视频播放的展示

2,129 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第12天,点击查看活动详情


今天是8月12号,是我写原创专栏《java转android》跟随8月更文活动的第12天。没有了推荐首页,也没有了大家不看好的评论,我反而清净了。

本次实战项目,开发一个视频播放器,可对本地或者网络视频进行播放,支持暂停、继续。

视频.gif

一、所需知识

主要涉及两点知识:1、MediaPlayer处理视频数据;2、SurfaceView呈现视频画面。

1.1 MediaPlayer 媒体播放器

我们采用MediaPlayer进行视频数据的解析。

关于MediaPlayer的介绍,虽然我很想再复制一遍,但是掘金不提倡。掘金作为一个高质量的内容平台,拒绝重复数据,所以大家想要了解可以去昨天的《第11篇:MediaPlayer实现音频播放》查看。一些问题的答案,可能就在那里。

为了便于理解本期的内容,提示大家重点看3处知识:

  1. MediaPlayer的创建。
  2. 加载多媒体文件的方式。
  3. 控制媒体状态的常用方法。

好了,假设MediaPlayer你已经完全掌握了。

1.2 SurfaceView 表面视图

SurfaceView……又遇到一个不好翻译的类。

View我们都不陌生,比如ButtonTextViewImageView都是View。但是,这个View是静态的,你的按钮不怎么更新吧,图片也是赋值一次基本不怎么变。有需要更新时,我们给它set新数据就可以了。

但是,还有一类场景很难搞定,那就是:地图视频游戏

image.png

这些都是高频率刷新的,一秒钟几十帧。这时,传统的View就有心无力了,如果资源都放在刷新上,那么UI主线程会很卡。

所以SurfaceView就出现了,它提供一个在内存中的临时缓冲区,叫做表面视图,我觉得叫它表层视图更贴切。

下面,我们来说它的用法。

我们还是可以像用普通View一样通过xml定义SurfaceView

<SurfaceView
    android:id="@+id/surfaceView"
    android:layout_width="match_parent"
    android:layout_height="240dp"
   />

但是,到逻辑代码面,我们没法直接操作SurfaceView,需要通过SurfaceHolder来对它进行操作。

SurfaceView surfaceView = findViewById(R.id.surfaceView);
SurfaceHolder surfaceHolder = surfaceView.getHolder();
surfaceHolder.addCallback(new SurfaceHolder.Callback() {
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // SurfaceView 创建完成时,你要对它做什么
    }
    …… ……            
});

二、实战开发

再来回顾一下,我们要做的这个视频播放器。

视频.gif

2.1 布局

我们来理一下思路,界面的组成是一个SurfaceView加上2个按钮,SurfaceView用于展示视频的画面。

下面是布局文件activity_main.xml的代码。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <SurfaceView
        android:id="@+id/sfvShow"
        android:layout_width="match_parent"
        android:layout_height="240dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <Button
        android:id="@+id/btnStart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="24dp"
        android:onClick="start"
        android:text="开始"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/sfvShow" />
    <Button
        android:id="@+id/btnStop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:onClick="pause"
        android:text="暂停"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="@+id/btnStart" />
</android.support.constraint.ConstraintLayout>

我们看到SurfaceView就是普通的定义方式。

2.2 逻辑

在Activity里,如何实现MediaPlayer和SurfaceView的结合呢?来,看整体代码!


public class MainActivity extends Activity {

    private MediaPlayer mediaPlayer = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mediaPlayer = MediaPlayer.create(MainActivity.this, R.raw.video_test);
        mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);

        SurfaceView sfvShow = findViewById(R.id.sfvShow);
        //初始化SurfaceHolder类,SurfaceView的控制器
        final SurfaceHolder surfaceHolder = sfvShow.getHolder();
        surfaceHolder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                //设置视频显示在SurfaceView上
                mediaPlayer.setDisplay(surfaceHolder);
            }

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

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {}
        });
    }

    public void start(View view) {
        mediaPlayer.start();
    }

    public void pause(View view) {
        mediaPlayer.pause();
    }

}

首先是创建MediaPlayer,然后加载raw文件夹下的视频文件,我们依然采用上一节中的工厂模式,实现创建加载一条龙。

然后,建立SurfaceView,通过SurfaceView的getHolder()方法获取到SurfaceHolder

下一步,给SurfaceHolder设置回调方法,此方法包含了整个SurfaceView的创建、变化、销毁的周期。也就说,它创建时,你可以拿到它的实例。它销毁时,你也能得到通知,然后做一些事情。那么,我们在它创建时,我们调用了mediaPlayer.setDisplay(surfaceHolder),把播放器的显示权交给surfaceHolder,让它负责视频画面的承载。

点击播放和暂停,正常调用MediaPlayer的start()pause()方法就可以。

写程序要严谨,在Activity的销毁方法里,要加上一段收尾的处理。

@Override
protected void onDestroy() {
    super.onDestroy();
    if (mediaPlayer.isPlaying()) {
        mediaPlayer.stop();
    }
    mediaPlayer.release();
}

如果程序要退出,记得把视频关掉,把播放器销毁。不然会出现,程序关了,视频的声音还在播放,你也关不掉,挺吓人的。

以上代码,注意把视频文件放到raw文件下,并通过R.raw.[xx]正确引用。点击运行,可实现视频的播放和暂停。如果是要播放网络视频,调用setDataSource("http://juejin.cn/ok.mp4")设置一个视频源url就可以了,上一篇也有相关知识点的讲解

三、最后

我们可以做一款自己的视频播放器,只允许看一遍,不允许回退,每天只允许看10分钟,甚至只限于八点到九点之间才能看。

总之,规则由自己定,这就是开发的乐趣。

我是TF男孩,关注我的掘金专栏《Java转Android》。日读800字,30天可入门安卓开发。

本专辑掘金社区独家发布,转载请注明出处。