# 4 视频播放

## 1. 播放前准备

### 1.1 部署播放器

`PolyvVideoView` 是视频播放器的主类 View，可以控制视频播放，进度等等。若要实现视频播放功能，只需要添加布局，调用`videoView.setVid(vid)`即可。调用`setVid(vid)`方法视频会自动播放。代码示例如下：

```xml
<RelativeLayout
        android:id="@+id/view_layout"
        android:layout_width="match_parent"
        android:layout_height="@dimen/top_center_player_height"
        android:background="@color/top_text_color_black">

        <com.easefun.polyvsdk.video.PolyvVideoView
            android:id="@+id/polyv_video_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
   
    	...
</RelativeLayout>
```

```java
PolyvVideoView videoView = findViewById(R.id.polyv_video_view);
videoView.setVid(vid);
```

此外还有更多重载的方法，可支持更多的播放设定。如：

```java
videoView.setVid(videoId, isMustFromLocal);//是否播放本地视频。本地存在视频id的文件，并且是完整的就播放
videoView.setVid(videoId, bitRate);//设置视频id，播放对应码率的视频。
videoView.setVidWithStudentId(videoId,studentId);//指定观众的id，播放视频
//播放外部视频
videoView.setVideoPath(path);//设置视频路径，如网址URL。
...
```

### 1.2 外部传入播放凭证

自点播2.17.0版本开始，支持通过外部传入播放凭证的方式进行加密视频的播放，以避免非法盗版者利用漏洞创建无身份信息的播放凭证。

* Token是用于解密播放保利威平台加密视频的，本地传入Token将更好地提升视频安全性。
* 客户在使用此功能的时候，务必要保证传入的Token在自家api传输中的安全性，如接口加密。

首先，您需要集成点播SDK在2.17.0以上版本。

其次，在播放器初始化时，调用`setVideoTokenRequestListener`方法设置回调监听，传入播放凭证。

```java
videoView.setVideoTokenRequestListener(new IPLVVideoTokenRequestListener() {
    @Override
    public String onRequestToken(PolyvVideoVO videoVO, String viewerId, String viewerName, String viewerParam) {
        // 返回外部获取的 token
        // 注意：此处如果为异步请求token，需要将线程等待，待请求结果返回后，再进行return
        // 可参考使用CountDownLatch进行线程等待
        return token;
    }
});
```

播放凭证应通过您自定义的接口获取。后端开发者的接口开发，以及播放加密视频实践全流程，可参考 [播放加密视频最佳实践](https://git.polyv.net/help-center/document-center/-/blob/master/vod/product/mobileDemo/README.md) 。

## 2. 基本设置

在正式使用播放器之前，我们应当先对`PolyvVideoView`进行基本的属性配置，以便让视频播放器更适合开发者的设计与使用。部分设置如下，更多的设置可查看 [API 文档](https://github.com/easefun/polyv-android-sdk-2.0-demo/blob/master/API%E6%96%87%E6%A1%A3%E7%9B%AE%E5%BD%95.md)。

### 2.1 自动播放

设置是否自动播放，默认自动播放。

```java
setAutoPlay(boolean isAutoPlay)
```

### 2.2 自动续播

设置是否自动续播，默认关闭。开启后重新进入视频可从上一次播放位置继续播放。

```java
setAutoContinue(boolean isAutoContinue)
```

### 2.3 开启广告

设置是否开启广告，默认关闭。

```java
setOpenAd(boolean isOpenAd)
```

### 2.4 开启字幕

设置是否开启字幕，默认关闭。

```java
setOpenSRT(boolean isOpenSRT)
```

### 2.5 开启预加载

设置是否开启预加载，默认关闭。

```java
setOpenPreload(boolean isOpenPreload)
```

### 2.6 开启跑马灯

设置是否开启跑马灯，默认关闭。

```java
setOpenMarquee(boolean isOpenMarquee)
```

### 2.7 开启手势

设置是否需要手势，默认关闭。开启后设置手势监听将有回调触发。

```java
setNeedGestureDetector(boolean need)
```

### 2.8 开启防录屏

设置是否开启防录屏开关，默认关闭。如开启防录屏，投屏功能将不可用。

```java
disableScreenCAP(Activity activity, boolean disableScreenCAP)
```

### 2.9 设置加载超时时间

设置加载超时时间，默认关闭，最少25秒。

```java
setLoadTimeoutSecond(int timeoutSecond)//设置后自动开启
setLoadTimeoutSecond(boolean isOpenLoadTimeout, int timeoutSecond)
```

### 2.10 设置缓冲超时时间

设置缓冲超时时间，默认关闭，最少15秒。

```java
setBufferTimeoutSecond(int timeoutSecond)
setBufferTimeoutSecond(boolean isOpenBufferTimeout, int timeoutSecond)
```

### 2.11 设置后台播放

在 `PolyvPlayerActivity` 中有字段 `isBackgroundPlay` 控制是否允许后台播放，默认不允许。

```java
private boolean isBackgroundPlay = false;//默认不允许后台播放
```

需要注意的是我们没有额外地处理后台保活，需要开发者自行增加相关逻辑。

**自 SDK2.11.0 开始，实现了进入后台播放开启 foreground service 的保活逻辑，并默认允许后台播放。**

### 2.12 设置预加载时长

设置视频的预加载时长，提供设置视频的最大缓存字节数，以及设置最大缓存的时间两种方式实现。

```java
setMaxCacheSize(long maxBuffer)
setMaxCacheDuration(int cacheDuration)
```

## 3. 高级设置

### 3.1 设置清晰度

设置码率，也就是设置清晰度。传入的参数为 `PolyvBitRate.java` 中的枚举类型。

```java
/**
* @param bitRate :
* 0-自动， 1-流畅， 2-高清， 3-超清
*/
changeBitRate(int bitRate)
changeBitRate(int bitRate, boolean isMustFromLocal)
changeBitRate(int bitRate, @PolyvPlayStrategy.PlayStrategy int strategy)
```

### 3.2 线路切换

线路切换指切换视频所访问的 CDN 服务商，保利威为加密的点播提供了多种线路切换。可以通过 `getRouteCount()` 知道可切换线路来源数量。`route` 的值大于等于1。

```java
changeRoute(final int route)//切换线路
getRouteCount()//获取线路源数量
```

### 3.3 设置播放速度

播放器支持倍速播放，正常播放速度为 1.0 。开发者可以通过修改该值来改变播放器的播放速度，倍速播放最好不要超过2.0 。

```java
setSpeed(float speed)//设置播放速度
getSpeed()//获取当前播放速度
```

### 3.4 设置播放音量

设置播放音量。取值范围为 \[0,100]。

```java
closeSound()//关闭声音
openSound()//开启声音
setVolume(int volume)//设置音量
getVolume()//获取当前音量
```

### 3.5 播放进度跳转

视频跳转到某个时间点，单位是毫秒。如从视频开始跳转到2秒 `videoView.seekTo(2000)`。

```java
seekTo(int pos)//跳转进度
```

### 3.6 设置视频画面拉伸模式

```java
setAspectRatio(int screen)
```

`PolyvPlayerScreenRatio.java` 中定义了屏幕比的可选模式：

| 拉伸模式                      | 说明        |
| ------------------------- | --------- |
| AR\_ASPECT\_FIT\_PARENT   | 按比例缩放     |
| AR\_ASPECT\_FILL\_PARENT  | 充满父窗      |
| AR\_ASPECT\_WRAP\_CONTENT | 匹配内容      |
| AR\_MATCH\_PARENT         | 适配父窗      |
| AR\_16\_9\_FIT\_PARENT    | 16:9 比例缩放 |
| AR\_4\_3\_FIT\_PARENT     | 4:3 比例缩放  |

### 3.7 音视 / 视频模式切换

控制播放器播放音频还是视频。默认是视频播放模式。可选参数为 `PolyvVideoVO.MODE_VIDEO [视频模式] | PolyvVideoVO.MODE_AUDIO [音频模式]`

```java
changeMode(String priorityMode)
getCurrentMode()//获取当前播放模式，视频模式或音频模式
setPriorityMode(String priorityMode)//设置优先模式
```

音频播放的方式有两种：

一种是视频模式下播放源文件为 MP3 的资源，由于是视频模式，播放器会默认没有画面。demo 中在这种情况显示音频模式的UI。开发者可自行定义。

```java
videoView.setOnPreparedListener(new IPolyvOnPreparedListener2() {
    @Override
    public void onPrepared() {
        if (videoView.getVideo() != null && videoView.getVideo().isMp3Source()) {
            audioSourceCoverView.onlyShowCover(videoView);
        } else {
            audioSourceCoverView.hide();
        }
		//...
    }
});
```

另外一种是切换到音频模式。它是对**支持音视频切换的视频**，直接通过`changeMode()`方法，切换为音频模式播放。生成支持音视频切换的视频需要联系管理员，开通相关功能。开通后会在后台视频设置里面可控制开关，开启后将会在对视频转码时，额外生成一份音频文件，然后播放器就可以进行音频 / 视频的切换。

### 3.8 设置Seek精准模式与Seek策略

播放器**Seek模式**分为两种，控制了Seek时候是否准确滑动到对应时间点：

* PolyvSeekType.SEEKTYPE\_NORMAL:普通模式，根据关键帧Seek。播放器内部会获取视频信息，提取关键帧，Seek的位置视关键帧位置所定。
* PolyvSeekType.SEEKTYPE\_ACCURATE:精准模式。播放器内部会处理分析，Seek时候精确到秒，比普通模式更为精确。同时消耗资源也会有所增加。

```java
videoView.setSeekType(PolyvSeekType.SEEKTYPE_NORMAL);
```

此外自 2.12.1 开始，在 demo 增加了 Seek 策略配置控制，开发者可以参考或者直接引用实现，对Seek进度条禁止拖拽等实现。开发者只需通过控制dragSeekStrategy为所需的配置即可。默认为DRAG\_SEEK\_PLAYED，只允许在已播放进度区域拖动跳转播放进度。

```java
//PolyvPlayerMediaController.java
//进度条拖拽跳转播放进度策略
public static final int DRAG_SEEK_ALLOW = 0;//允许拖动进度条跳转进度
public static final int DRAG_SEEK_BAN = 1;//禁止拖动进度条跳转进度
public static final int DRAG_SEEK_PLAYED = 2;//只允许在已播放进度区域拖动跳转播放进度
private int dragSeekStrategy = DRAG_SEEK_PLAYED;
```

## 4. 播放状态

### 4.1 暂停与继续播放

通过`start()` 方法可以把播放状态改变为继续播放。注意要先调用 `setVid(vid)` 方法，因为`start()`方法没有设置视频源，直接调用 `start()`方法是无效的。它只是修改播放状态为继续播放。配合`pause()` 方法可以实现暂停继续功能。

```java
start()//开始播放
pause()//暂停播放
pause(boolean isNotShowAdvert)//暂停的时候是否显示广告
isPlaying()//是否正在播放
```

此外，在基本设置中，开启自动续播，可以在再次进入同一个视频的时候自动跳到上一次的进度。

```java
setAutoContinue(true)//开启自动续播
```

### 4.2 媒体时长与缓冲进度

媒体时长的返回单位是毫秒。通过`getCurrentPosition()` 可以获取当前播放进度。如一个视频已经播放了2秒，则`videoView.getCurrentPosition() = 2000`。

```java
getCurrentPosition()//取得视频播放进度当前位置
getDuration()//获取总时长
```

通过`getBufferPercentage()` 可以获取当前缓冲进度的百分比。此外还可以设置最大缓存字节，默认为15M。

```java
getBufferPercentage()//当前缓冲百分比
setMaxBuffer(long maxBuffer)//设置最大缓存字节，默认15M
```

### 4.3 获取当前时刻截图

```java
screenshot()
```

### 4.4 手势交互

播放器支持识别8种手势，并把识别的手势类型与响应事件回调开放出来。开发者可以按照实际需求实现手势事件。回调的方法都在主线程中回调。具体实现可以参考 demo 的 `PolyvPlayerActivity`。

```java
interface IPolyvOnGestureRightUpListener;//手势右向上滑动
interface IPolyvOnGestureRightDownListener;//手势右向下滑动
interface IPolyvOnGestureLeftUpListener;//手势左向上滑动
interface IPolyvOnGestureLeftDownListener;//手势左向下滑动
interface IPolyvOnGestureSwipeLeftListener;//手势向左滑
interface IPolyvOnGestureSwipeRightListener;//手势向右滑
interface IPolyvOnGestureClickListener;//单击
interface IPolyvOnGestureDoubleClickListener;//双击
```

以下例子为设置手势左向上的时候，调高亮度：

```java
videoView.setOnGestureLeftUpListener(new IPolyvOnGestureLeftUpListener() {
    @Override
    public void callback(boolean start, boolean end) {
        if(mediaController.isLocked()){
            return;
        }
        int brightness = videoView.getBrightness(PolyvPlayerActivity.this) + 5;
        if (brightness > 100) {
            brightness = 100;
        }
        videoView.setBrightness(PolyvPlayerActivity.this, brightness);
        lightView.setViewLightValue(brightness, end);
    }
});
```

### 4.5 状态回调

在 `com.easefun.polyvsdk.video.listener` 包下，还开放了更多的回调监听，都可以设置给播放器，监听播放器状态。这里只截取部分回调展示，详情请查看[API 文档](https://github.com/easefun/polyv-android-sdk-2.0-demo/blob/master/API%E6%96%87%E6%A1%A3%E7%9B%AE%E5%BD%95.md)。

*   视频已经准备好马上进入播放回调

    ```java
    public interface IPolyvOnPreparedListener2 {
        @MainThread
        void onPrepared();
    }
    ```
*   视频完成回调

    ```java
    public interface IPolyvOnCompletionListener2 {
        @MainThread
        void onCompletion();
    }
    ```
*   视频播放器出错回调

    ```java
    public interface IPolyvOnVideoPlayErrorListener2 {
        @MainThread
        boolean onVideoPlayError(@PolyvPlayErrorReason.PlayErrorReason int errorReason);
    }
    ```
*   缓冲更新回调

    ```java
    public interface IPolyvOnBufferingUpdateListener2 {
        @MainThread
        void onBufferingUpdate(int percent);
    }
    ```
*   seek 完成回调

    ```java
    public interface IPolyvOnSeekCompleteListener2 {
        @MainThread
        void onSeekComplete();
    }
    ```
* ...

## 5. 自定义播放器控件

### 5.1 播放器控制栏

播放器控制栏，可以方便我们控制播放器播放，切换播放器状态。如选择清晰度，控制音量等。自定义的播放器控制栏界面，需要继承自 `PolyvBaseMediaController` 类，原理和系统播放器一样。初始化完成后作为参数传递给 `PolyvVideoView`即可。以下摘以 demo 的例子作为展示：

```java
//PolyvPlayerMediaController 是demo中自定义的播放器控制栏，具体实现请查看demo
PolyvPlayerMediaController mediaController = findViewById(R.id.polyv_player_media_controller);
...
videoView.setMediaController(mediaController);
```

建议有一定能力的开发者才自定义播放器控制栏界面，demo 中有完整播放器控制栏界面示例。

### 5.2 设置辅助视频

辅助视频可以用于播放广告，片头等。`PolyvAuxiliaryVideoView` 是其实现主类。其基本使用与 `PolyvVideoView` 类似。初始化完成后作为参数传递给 `PolyvVideoView`即可。注意设置需要开启广告设置才有效`videoView.setOpenAd(true)`。

```xml
<com.easefun.polyvsdk.video.auxiliary.PolyvAuxiliaryVideoView
    android:id="@+id/polyv_auxiliary_video_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/black" />
```

```java
PolyvAuxiliaryVideoView auxiliaryVideoView = findViewById(R.id.polyv_auxiliary_video_view);
videoView.setAuxiliaryVideoView(auxiliaryVideoView);
```

相关的更多设置可以查看[API 文档](https://github.com/easefun/polyv-android-sdk-2.0-demo/blob/master/API%E6%96%87%E6%A1%A3%E7%9B%AE%E5%BD%95.md)`com.easefun.polyvsdk.video.auxiliary` 部分。具体实现可参考 demo `PolyvPlayerActivity`相关设置。

### 5.3 跑马灯

跑马灯可以让视频播放的时候滚动显示文本，通常用于防盗水印的作用。Polyv SDK 提供了内置的跑马灯实现。主要通过 `PolyvMarqueeView` 作为显示的 View，通过 `PolyvMarqueeItem` 设置跑马灯属性。

```xml
<RelativeLayout
        ...>

        <com.easefun.polyvsdk.video.PolyvVideoView
            android:id="@+id/polyv_video_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    
        <com.easefun.polyvsdk.marquee.PolyvMarqueeView
            android:id="@+id/polyv_marquee_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
   
    	...
</RelativeLayout>
```

```java
//开启跑马灯
videoView.setOpenMarquee(true);
// 设置跑马灯
videoView.setMarqueeView(marqueeView, marqueeItem = new PolyvMarqueeItem()
                         .setStyle(PolyvMarqueeItem.STYLE_ROLL) //样式
                         .setDuration(10000) //时长
                         .setText("POLYV Android SDK") //文本
                         .setSize(16) //字体大小
                         .setColor(Color.YELLOW) //字体颜色
                         .setTextAlpha(70) //字体透明度
                         .setInterval(1000) //隐藏时间
                         .setLifeTime(1000) //显示时间
                         .setTweenTime(1000) //渐隐渐现时间
                         .setHasStroke(true) //是否有描边
                         .setBlurStroke(true) //是否模糊描边
                         .setStrokeWidth(3) //描边宽度
                         .setStrokeColor(Color.MAGENTA) //描边颜色
                         .setReappearTime(3000) // 设置跑马灯再次出现的间隔
                         .setStrokeAlpha(70)); //描边透明度
```

## 6. 半屏 / 全屏切换

在 SDK 中并没有提供相关接口直接进行半屏/全屏切换，但是我们在 Demo 中实现了半屏/全屏切换的功能。其基本原理是通过设置 `PolyvVideoView` 和`PolyvPlayerMediaController` 的宽高为 `match_parent`，然后改变`Activity` 的横竖屏状态，最后设置公共的父布局的宽高适应横竖屏，以此来实现半屏/全屏切换功能。以下实例代码摘自 demo：

1、布局实现：

```xml
<根布局...>
    <!--控制parent_view的高度-->
    <RelativeLayout
        android:id="@+id/parent_view"
        android:layout_width="match_parent"
        android:layout_height="300dp">

        <!--让 PolyvVideoView 和PolyvMediaPlayer 宽高跟随父布局-->
        <com.easefun.polyvsdk.video.PolyvVideoView
            android:id="@+id/polyv_video_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#aaa" />

        <com.shawn.testsdk.PolyvMediaPlayer
            android:id="@+id/media_player"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
		<其他.../>
    </RelativeLayout>
    
    <其他.../>
</根布局...>
```

2、在`AndroidManifest.xml`中设置 `Activity` 的 `configChanges` 属性：

```xml
//...AndroidManifest.xml
<activity
     android:name="com.easefun.polyvsdk.activity.PolyvPlayerActivity"
     android:configChanges="orientation|screenSize|keyboardHidden"
     ...>
	...
</activity>
```

3、改变 `parent_view` 宽高属性以此实现半屏/全屏切换：

* 切换全屏

```java
//...PolyvPlayerMediaController.java
public void changeToLandscape() {
    PolyvScreenUtils.setLandscape(videoActivity);
    //初始为横屏时，状态栏需要隐藏
    PolyvScreenUtils.hideStatusBar(videoActivity);
    //初始为横屏时，导航栏需要隐藏
    PolyvScreenUtils.hideNavigationBar(videoActivity);
    //初始为横屏时，控制栏的宽高需要设置
    initLandScapeWH();
}

private void initLandScapeWH() {
    ViewGroup.LayoutParams vlp = parentView.getLayoutParams();
    vlp.width = ViewGroup.LayoutParams.MATCH_PARENT;
    vlp.height = ViewGroup.LayoutParams.MATCH_PARENT;
	...
}
```

`PolyvScreenUtils` 是 Demo 中定义的工具类。在这里主要是设置 `Activity` 为横屏模式。

* 切换半屏

```java
//...polyvPlayerActivity
protected void onCreate(Bundle saveInstanceState){
    ...
    PolyvScreenUtils.generateHeight16_9(this);//记录高度
}
```

```java
//...PolyvPlayerMediaController.java
public void changeToPortrait() {
    PolyvScreenUtils.setPortrait(videoActivity);
    initPortraitWH();
}

private void initPortraitWH() {
    ViewGroup.LayoutParams vlp = parentView.getLayoutParams();
    vlp.width = ViewGroup.LayoutParams.MATCH_PARENT;
    vlp.height = PolyvScreenUtils.getHeight16_9();
    ...
}
```
