#  关于如何接入SDK回放功能

本文主要介绍如何接入SDK回放功能，包括两大部分，核心功能和其他功能

- [关于如何接入SDK回放功能](#关于如何接入sdk回放功能)
  - [1. 回放核心功能](#1-回放核心功能)
    - [1.1 回放登录](#11-回放登录)
      - [1.1.1 主动调用api](#111-主动调用api)
      - [1.1.2 回调事件api](#112-回调事件api)
    - [1.2 回放播放视频和文档](#12-回放播放视频和文档)
      - [1.2.1 主动调用api](#121-主动调用api)
      - [1.2.2 回调事件api](#122-回调事件api)
      - [1.2.3 关键代码示例](#123-关键代码示例)
      - [1.2.4 集成说明](#124-集成说明)
  - [2. 回放其他功能](#2-回放其他功能)
    - [2.1 聊天功能](#21-聊天功能)
    - [2.2 问答功能](#22-问答功能)
    - [2.3 广播功能](#23-广播功能)
    - [2.4 回放页面信息](#24-回放页面信息)
    - [2.5 切换线路功能 (2021年10月08日之后不再进行维护)](#25-切换线路功能-2021年10月08日之后不再进行维护)
    - [2.6 清晰度切换](#26-清晰度切换)
    - [2.7 线路切换](#27-线路切换)
    - [2.8 音视频播放模式切换](#28-音视频播放模式切换)
    - [2.9 回放打点功能](#29-回放打点功能)
    - [2.10 回放随堂测功能](#30-回放随堂测功能)
    - [2.11 回放缩略图功能](#211-回放缩略图功能)
    - [2.12 回放视频摘要功能](#212-回放视频摘要功能)
    - [2.13 回放投屏功能](#213-回放投屏功能)
    - [2.14 字幕](#214-字幕)

## 1. 回放核心功能

### 1.1 回放登录

#### 1.1.1 主动调用api

回放登录需要主动调用方法在DWLiveReplay.java（DWLiveReplay为单例）中，如下

| 方法                                                         | 说明                               |
| :----------------------------------------------------------- | :--------------------------------- |
| getInstance()                                                | 获取DWLiveReplay单例               |
| setLoginParams(DWLiveLoginListener dwLiveLoginListener, LoginInfo loginInfo) | (过时) 设置回放登录参数            |
| startLogin()                                                 | (过时) 开始登录                    |
| startLogin(ReplayLoginInfo, final DWLiveReplayLoginListener) | （>=3.14.0）设置登录参数并开始登录 |

登录所需参数ReplayLoginInfo如下

```java
// 用户ID  
private String userId;    
// 直播间ID
private String roomId;                 
// 直播ID                                                   
private String liveId;               
// 回放ID                                                 
private String recordId;               
// 观众名称                                                
private String viewerName;              
// 观众密码                                                
private String viewerToken;             
// 用户分组ID                                                
private String groupId;                 
```

#### 1.1.2 回调事件api

DWLiveReplayLoginListener

| 方法                                                | 说明                                       |
| --------------------------------------------------- | ------------------------------------------ |
| onException(DWLiveException exception)              | 登录失败                                   |
| onLogin(TemplateInfo templateInfo)                  | 登录成功                                   |
| onLogin(TemplateInfo templateInfo, Marquee marquee) | 登录成功 附带跑马灯信息Marquee（重写方法） |
| onLogin(TemplateInfo templateInfo, Viewer viewer)   | 登录成功 附个人信息Viewer（重写方法）      |

返回信息同直播相同，登录成功可监听onLogin任意一个互调

onLogin(TemplateInfo templateInfo, Marquee marquee)为定制方法，如需使用到界面跑马灯可单独实现该回调

### 1.2 回放播放视频和文档

#### 1.2.1 主动调用api

回放核心类代码在DWLiveReplay.java（DWLiveReplay为单例）如下

| 方法                                                         | 说明                                                         |
| ------------------------------------------------------------ | ------------------------------------------------------------ |
| setReplayParams(DWLiveReplayListener replayListener, Context context) | 设置回调和上下文                                             |
| setReplayPlayer(DWReplayPlayer player)                       | 设置播放器                                                   |
| setReplayDocView(DocView docView)                            | 设置文档                                                     |
| start()                                                      | 开始播放                                                     |
| stop()                                                       | 停止播放                                                     |
| pause()                                                      | 暂停播放                                                     |
| resume()                                                     | 恢复播放                                                     |
| setLastPosition(long position)                               | 设置开始播放位置，需在start之前调用                          |
| retryReplay(long position, boolean updateStream)             | 重试播放 <br />positon:播放位置updateStream更换流地址        |
| onDestroy()                                                  | 释放资源                                                     |
| setSpeed(float speed)                                        | 设置播放速率                                                 |
| changeLineWithPlayParameter(ReplayLineConfig ,ReplayLineSwitchListener) | (过时) 切换线路                                              |
| changeLineWithNum(int line, ReplayLineSwitchListener changeLineCallback) | (过时) 切换线路                                              |
| changeLine(int lineIndex, ReplayChangeSourceListener changeCallBack) | （>=3.13.0）切换线路                                         |
| changeQuality(int quality, ReplayChangeSourceListener)       | （>=3.13.0）切换清晰度                                       |
| changePlayMode(PlayMode, ReplayChangeSourceListener )        | （>=3.13.0）切换音视频播放模式                               |
| setDocScaleType(DocView.ScaleType type)                      | 设置文档缩放类型                                             |
| changeDocBackgroundColor(String color)                       | 更改文档背景颜色                                             |
| reloadPageChange()                                           | （>=3.17.9）刷新翻页，当文档回调DOC_DP_IMAGE_ERROR(4)错误时，可以调用此方法重试当前翻页。调用间隔需大于3s。 |
| reloadPageData()                                             | （>=3.17.9）刷新数据获取，当回放回调onPageDataError（3.17.9新增）时，可以调用此方法进行基础元数据重试。调用间隔需大于3s。 |

播放器核心代码在DWReplayPlayer.java中，调用方法如下

| 方法                                                         | 说明                         |
| ------------------------------------------------------------ | ---------------------------- |
| DWReplayPlayer(Context context)                              | 构造方法                     |
| updateSurface(Surface surface)                               | 更新Surface                  |
| start()                                                      | 开始播放                     |
| pause()                                                      | 暂停播放                     |
| stop()                                                       | 停止播放                     |
| reset()                                                      | 重置播放器                   |
| release()                                                    | 停止并释放资源，替代stop方法 |
| destroy()                                                    | 完全释放                     |
| seekTo(long progress)                                        | 拖动进度                     |
| setSpeed(float speed)                                        | 设置播放速率                 |
| setOnPreparedListener(OnPreparedListener listener)           | 设置准备回调                 |
| setOnErrorListener(IMediaPlayer.OnErrorListener listener)    | 设置失败回调                 |
| setOnInfoListener(OnInfoListener listener)                   | 设置播放状态信息回调         |
| setOnVideoSizeChangedListener(OnVideoSizeChangedListener listener) | 设置视频尺寸信息回调         |
| setOnSeekCompleteListener(OnSeekCompleteListener listener)   | 设置拖动进度完成回调         |
| setSpeedListener(ReplaySpeedListener speedListener)          | 设置视频缓冲的网速         |

#### 1.2.2 回调事件api

回调事件同直播

#### 1.2.3 关键代码示例

设置视频展示布局

```xml
<TextureView
   android:id="@+id/video_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent" />
```

设置文档展示的控件布局：

```xml
<com.bokecc.sdk.mobile.live.widget.DocView
    android:id="@+id/doc_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
```

初始化播放器并设置播放参数

```java
...
DWLiveReplay dwLiveReplay
TextureView video_view;  
DocView doc_view;        
Surface mSurface;        
DWReplayPlayer mPlayer;  
...
 mPlayer = new DWReplayPlayer(this);  
 // 1. 设置Surface的回调                                                                   
 video_view.setSurfaceTextureListener(surfaceListener);                                
 // 2. 获取单例                                                                               
 dwLiveReplay = DWLiveReplay.getInstance();        
 // 3. 设置播放器回调
 mPlayer.setPlayerEventListener(playerEvent);  
 // 4. 设置回放业务的回调和上下文                                                                     
 dwLiveReplay.setReplayParams(dwLiveListener, DWLiveEngine.getInstance().getContext());   
 // 5. 设置播放器和监听
 dwLiveReplay.setReplayPlayer(mPlayer);      
...
```

获取Surface

下面是通过实现SurfaceTextureListener回调获得

```java
private SurfaceTexture mSurfaceTexture; 
// surface回调                                                                           
TextureView.SurfaceTextureListener surfaceListener = new TextureView.SurfaceTextureListener() {      
     @Override                                                                           
     public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1){ 
         if (mSurfaceTexture != null) {                         
             mVideoContainer.setSurfaceTexture(mSurfaceTexture);
          } else {                                               
             mSurfaceTexture = surfaceTexture;                  
             mSurface = new Surface(surfaceTexture);    
             // 将surface设置给播放器
             mPlayer.setSurface(mSurface);                       
          }                                                                                
     }                                                                                   
     @Override                                                                           
     public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {}                                                                                   
     @Override                                                                           
     public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {           
         mSurface = null;                                                                 
         return false;                                                                   
     }
     @Override                                                                           
     public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {                 
     }                                                                                   
 };                                                                                       
```

#### 1.2.4 集成说明

完成上面的步骤后，基本上我们就能够观看到直播回放的视频画面和文档画面了，也就基本完成了获得场景视频直播回放的核心功能的集成，即观看回放视频和观看回放文档的功能。

## 2. 回放其他功能

功能使用时相关的核心类：DWLiveReplayListener（回放回调接口类），DWLiveReplay（回放核心类）。

### 2.1 聊天功能

回放的聊天功能只包括公共聊天内容，此功能为方法回调功能，相关类DWLiveReplayListener，相关方法如下：

| 方法                                                 | 说明                 |
| ---------------------------------------------------- | -------------------- |
| onChatMessage(TreeSet<ReplayChatMsg> replayChatMsgs) | 全量回调回放聊天信息 |
| onChatMessagePart(TreeSet<ReplayChatMsg> chatMsgs)   | 分段回调回放聊天信息 |

其中回调回来的对象ReplayChatMsg属性信息如下

```java
// 聊天消息内容
private String content; 
// 分组ID
private String groupId;
// 聊天消息时间 秒
private int time;        
// 发送人用户名
private String userName;     
// 发送人userId
private String userId;     
// 发送人头像
private String avatar;
// 用户角色 
// 主讲（publisher）、助教（teacher）、主持人（host）、学生或观众（student）、其他没有角色（unknow）
private String userRole;
//发送人自定义标记
private String userCustomMark;
//是否显示此条消息（0: 不显示 1:显示）
private String status;
//发送人的角色 已过时
private int classRole;
```

主动获取聊天数据 相关类DWLiveReplay

| 方法                                                         | 说明                                     |
| ------------------------------------------------------------ | ---------------------------------------- |
| getAllChats(final BaseFunction<TreeSet<ReplayChatMsg>> func) | 获取回放聊天信息（需在播放成功之后调用） |

### 2.2 问答功能

回放的问答的功能包括已公开回答问答和已发布的答案，此功能为方法回调功能，相关类DWLiveReplayListener相关方法如下：

| 方法                                          | 说明             |
| --------------------------------------------- | ---------------- |
| onQuestionAnswer(TreeSet<ReplayQAMsg> qaMsgs) | 获取回放问答信息 |

返回对象ReplayQAMsg包含问题和回答列表，如何操作该类消息，请查看demo

主动获取问答数据 相关类DWLiveReplay

| 方法                                                     | 说明               |
| -------------------------------------------------------- | ------------------ |
| getAllQAs(final BaseFunction<TreeSet<ReplayQAMsg>> func) | 获取所有的问答数据 |

其中 ReplayQuestionMsg 数据中包含的字段信息如下：

```java
//问答ID
private String questionId;
//问答内容
private String content;
//分组ID
private String groupId;
//问答发布时间 秒 （相对时间）
private int time;
//发送人ID
private String questionUserId;
//发送人昵称
private String questionUserName;
//发送人头像
private String questionUserAvatar;
// 是否是发布的问题
private int isPublish; 
```

其中 ReplayAnswerMsg 数据中包含的字段信息如下：

```java
//回复内容
private String content;
//分组ID
private String groupId;
//发布回复的时间 秒 （相对时间）
private int time;
//发送人昵称
private String userName;
//发送人ID
private String userId;
//发送人头像
private String userAvatar;
//发送人角色 （具体参考 回放用到的 “角色” 字段含义）
private String userRole;

```

### 2.3 广播功能

回放的广播的功能是直播时发布的广播信息列表，此功能为方法回调功能，相关类DWLiveReplayListener，相关方法如下：

| 方法                                                         | 说明         |
| ------------------------------------------------------------ | ------------ |
| onBroadCastMessage(ArrayList<ReplayBroadCastMsg> broadCastMsgList) | 获取广播信息 |

返回对象ReplayBroadCastMsg属性信息如下

```java
// 广播内容   
private String content;                                 
//  广播时间（单位：秒）                                                                     
private int time;                                
// 发广播的人的ID                                                                     
private String publisherId;                           
// 发广播的人的名字                                                                    
private String publisherName;                          
 // 发广播的人的角色                                                                     
//主讲（publisher）、助教（teacher）、主持人（host）、学生或观众（student）、其他没有角色（unknow）
private String publisherRole;                        
```

主动获取广播数据 相关类DWLiveReplay

| 方法                                                         | 说明               |
| ------------------------------------------------------------ | ------------------ |
| getAllBoardCasts(final BaseFunction<ArrayList<ReplayBroadCastMsg>> func) | 获取所有的广播数据 |

### 2.4 回放页面信息

回放页面信息是通过回调返回给上层调用，此功能为回调功能，相关类是DWLiveReplayListener，相关方法如下

| 方法                                                         | 说明               |
| ------------------------------------------------------------ | ------------------ |
| onPageInfoList(ArrayList<ReplayPageInfo> infoList)           | 回放页面信息       |
| onPageChange(String docId, String docName, int docWidth ,int docHeight, int pageNum, int docTotalPage); | 回调当前翻页的信息 |

返回列表对象ReplayPageInfo属性信息如下：

```java
// 当前文档名称               
private String docName;     
// 当前文档页面的标题  
private String pageTitle;      
// 当前文档页面的链接  
private String url;       
// 当前文档页面的翻页时间（单位：S） 
private int time;       
```

### 2.5 切换线路功能 (2021年10月08日之后不再进行维护)

由于该方法不兼容清晰度的切换，故废弃该方法，使用**2.6清晰度切换**和**2.7线路切换**替代

回放可根据返回的线路索引进行线路切换

主动调用方法

| 方法                                                         | 说明               |
| ------------------------------------------------------------ | ------------------ |
| changeLineWithPlayParameter(ReplayLineConfig config, ReplayLineSwitchListener callback) | 切换音视频线路、清晰度方法,已过时 |

监听回调方法在DWLiveReplayListener中

| 方法                                                         | 说明                                 |
| ------------------------------------------------------------ | ------------------------------------ |
| numberOfReceivedLines(int lines)                             | 回放线路回调，已过时                 |
| numberOfReceivedLinesWithVideoAndAudio(List<ReplayVideoLineParams> videoLines, ReplayAudioLineParams audioLines) | 回放线路 清晰度回调,已过时 |

参数属性信息如下：

- 主动调用方法中ReplayLineConfig属性信息如下

  ```java
  //是否启用视频
  private boolean disableVideo = false;
  //线路索引  音频是视频都存在多条线路
  private ReplayLineParams replayLineConfig;
  ```

  ReplayLineParams属性信息如下

  ```java
  //线路 对应ReplayVideoLineParams ReplayAudioLineParams中的lines
  private int lineNum = 0;
  //清晰度  如果是音频模式 设置无效
  private int quality = 0;
  ```

- 回调方法中ReplayVideoLineParams属性信息如下

  **注意**  lines 该属性信息代表的是线路列表，如0，1；0代表线路1，1代表线路2，对应主动调用方法中的lineNum

  ```java
  //线路列表
  private List<Integer> lines ;
  //清晰度
  private int quality = 0;
  //清晰度描述
  private String desp;
  ```

  ReplayAudioLineParams属性信息如下

  ```java
  //线路列表
  private List<Integer> lines;
  ```



### 2.6 清晰度切换

回放可根据返回的清晰度列表进行清晰度切换

主动调用方法

| 方法                                                         | 说明                       |
| ------------------------------------------------------------ | -------------------------- |
| changeQuality(int quality, ReplayChangeSourceListener changeCallBack) | 切换清晰度 |

监听回调方法在DWLiveReplayListener中，该api会存在多次回调  如：切换清晰度、调用开始播放api、从音频切回到视频模式

| 方法                                                         | 说明                                 |
| ------------------------------------------------------------ | ------------------------------------ |
| onHDReceivedVideoQuality(List<ReplayQualityinfo> videoQuality, ReplayQualityinfo currentQuality) | 回调清晰度列表和当前播放的清晰度              |

主动调用方法中ReplayChangeSourceListener类说明如下

```java
 public interface ReplayChangeSourceListener {
    /**
     * 切换回调
     * @param success 是否成功
     *                -2：切换时间间隔小于3s
     *                -1：切换的数据不正确
     *                0 ：切换成功
     */
    void onChange(int success);
}
```

回调方法中ReplayQualityinfo类属性信息如下：

```java
 /**
  * 清晰度的code值，对应主动调用方法中的quality参数
  */
private int quality;
 /**
  * 清晰度描述
  */
private String desc;
```



### 2.7 线路切换

回放可根据返回的线路列表进行线路切换

主动调用方法

| 方法                                                         | 说明     |
| ------------------------------------------------------------ | -------- |
| changeLine(int index,ReplayChangeSourceListener changeCallBack) | 切换线路 |

监听回调方法在DWLiveReplayListener中，该api会存在多次回调  如：切换清晰度、调用开始播放api、音频/视频模式切换

| 方法                                                         | 说明                             |
| ------------------------------------------------------------ | -------------------------------- |
| onHDReceivedVideoAudioLines(List<ReplayLineInfo> lines, int currentLineIndex) | 回调清晰度列表和当前播放的清晰度 |

主动调用方法中ReplayChangeSourceListener类说明如下

```java
 public interface ReplayChangeSourceListener {
    /**
     * 切换回调
     * @param success 是否成功
     *                -2：切换时间间隔小于3s
     *                -1：切换的数据不正确
     *                0 ：切换成功
     */
    void onChange(int success);
}
```

回调方法中ReplayLineInfo参数属性信息如下：

```java
   /**
    * 线路名称，该属性参数表示线路代号，如：line1、line2 ...
    * 主动调用时传入List<ReplayLineInfo>的索引
    */
 private String line;

```

### 2.8 音视频播放模式切换

回放可切换播放模式

主动调用方法

| 方法                                                         | 说明         |
| ------------------------------------------------------------ | ------------ |
| changePlayMode(PlayMode playMode,ReplayChangeSourceListener changeCallBack) | 切换播放模式 |

监听回调方法在DWLiveReplayListener中， 该api会存在多次回调  如：调用开始播放api、音频/视频模式切换

| 方法                          | 说明             |
| ----------------------------- | ---------------- |
| onHDAudioMode(Audio hasAudio) | 是否开启音频模式 |

主动调用方法中PlayMode 参数属性信息如下：

```java
  /**
     * 播放模式
     * VIDEO: 视频播放模式
     * SOUND: 音频播放模式
     */
    public enum PlayMode {
        VIDEO, SOUND
    }
```

ReplayChangeSourceListener详细说明如下

```java
public interface ReplayChangeSourceListener {
    /**
     * 切换回调
     * @param success 是否成功
     *                -2：切换时间间隔小于3s
     *                -1：切换的数据不正确
     *                0 ：切换成功
     */
    void onChange(int success);
}
```

回调方法中Audio类参数信息如下：

```java
   /**
     * 音频模式
     * HAVE_AUDIO_LINE_TURE: 有音频模式
     * HAVE_AUDIO_LINE_FALSE: 无音频模式
     */
    public enum Audio {
        HAVE_AUDIO_LINE_TURE, HAVE_AUDIO_LINE_FALSE
    }
```

### 2.9 回放打点功能

回放打点功能，可根据返回的打点数据展示打点数据，界面显示请参考demo

DWLiveReplayListener类监听回调方法

| 方法                                       | 说明             |
| ------------------------------------------ | ---------------- |
| onHDReplayDotList(List<ReplayDot> dotList) | 回放打点数据列表 |

其中ReplayDot类属性如下

```java
// 对应回放的时间 单位（秒）                  
private int time;    
// 打点描述                
private String desc; 
```

### 2.10 回放随堂测功能

回放随堂测功能

DWLiveReplay类主动获取

| 方法                                                         | 说明               |
| ------------------------------------------------------------ | ------------------ |
| getAllPractices(final BaseFunction<ArrayList<ReplayPracticeInfo>> func) | 回放随堂测数据列表 |

其中ReplayPracticeInfo类属性如下

```java
//随堂测统计数据
private PracticeStatisInfo statisInfo;
//随堂测排名数据
private PracticeRankInfo rankInfo;
//回放随堂测数据
private ReplayPracticeInfoBase replayPracticeInfoBase;

public class PracticeStatisInfo {
    private String id;             // 随堂测ID
    private int type;              // 随堂测类型: 0 判断 1 单选 2 多选
    private int status;            // 随堂测状态  1 发布中 2 停止发布
    private int answerPersonNum;   // 回答该随堂测的人数
    private int correctPersonNum;  // 回答正确的人数
    private String correctRate;    // 正确率
    private int stopTime;          // 停止时间
    private ArrayList<OptionStatis> optionStatis; // 选项的统计信息
  	public static class OptionStatis {
        private String id;  		//  选项ID
        private int index; 			// 选项编号
        private int count; 			// 选择该选项的人数
        private String percent; 	// 选择的比例
        private int isCorrect;  	// 是否为正确选项 1 正确 0 错误
  	}
}
public class PracticeRankInfo {
    // 排名id:暂不可用
    private String id;
    // 排名人员信息
    private ArrayList<ViewerRankInfo> rankList = new ArrayList<>();
  	public class ViewerRankInfo {
            //提交者的用户id
    		private String viewerId;
        	//提交者的名称
    		private String viewerName;
        	//提交答案的时间与随堂测发布的时间的差值
    		private int costTime;				
  	}
}
public static class ReplayPracticeInfoBase {
        private String id;              //随堂测ID
        private int type;               //随堂测类型（判断：0，单选：1，多选：2）
        private int status;             //状态（正在发布：1  已停止：2）
        private String publishTime;     //发布时间
        private long recordId;          //回放ID
        private int recordTime;         //相对于回放开始时长
        private int stopRecordTime;     //相对于回放开始停止时长
        private int closeRecordTime;    //相对于回放开始关闭时长
        private List<Option> options;   //选项
  	public static class Option {
            private double index;       //选项（0,1,2,3）
            private String id;          //选项ID
            private double isCorrect;   //是否正确（1：正确0：错误）
    }
}
```

### 2.11 回放缩略图功能

版本：>=3.23.0

回放支持缩略图功能，请先联系客服开启回放缩略图功能。

回放缩略图将通过以下回调返回：

```
DWLiveReplayListener.onVideoPreviews(ArrayList<String> videoPreviews)
```

返回的数组 videoPreviews 中存有多张雪碧图的链接地址，每张雪碧图（1600x900）包含 10x10 个小图（160x90），每张小图为一帧缩略图，间隔时间6s一帧。

videoPreviews数组中雪碧图是无序的，需要根据雪碧图名称排序，命名规则如下：

```java
// xxx_thumbnail_xxx.jpg
//示例：
//http://img-test212.play.bokecc.com/comimage/FAA3DD658CB47E29/2023-11-24/F978EAA4B676690D9C33DC5901307461_thumbnail_001.jpg
//http://img-test212.play.bokecc.com/comimage/FAA3DD658CB47E29/2023-11-24/F978EAA4B676690D9C33DC5901307461_thumbnail_002.jpg
//http://img-test212.play.bokecc.com/comimage/FAA3DD658CB47E29/2023-11-24/F978EAA4B676690D9C33DC5901307461_thumbnail_000.jpg
```

排序可参考以下代码：

```java
 				String regex = "_thumbnail_([0-9]+)\\.([a-zA-Z]+)$";
        final Pattern pattern = Pattern.compile(regex);
        //根据文件命名进行排序
        Collections.sort(this.videoPreviews, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                Matcher matcher1 = pattern.matcher(o1);
                if (matcher1.find()) {
                    String result1 = matcher1.group(1);
                    Matcher matcher2 = pattern.matcher(o2);
                    if (matcher2.find()) {
                        String result2 = matcher2.group(1);
                        if (result1!=null&&result2!=null){
                            try{
                                return Integer.parseInt(result1)-Integer.parseInt(result2);
                            }catch (Exception e){
                                e.printStackTrace();
                            }
                        }
                    }
                }
                return 0;
            }
        });
```

排序完成后可以下载雪碧图并裁剪，可以使用三方雪碧图加载库加载，也可以自己做裁剪并使用ImageView进行加载。

自行裁剪可以参考以下代码，使用到Glide库：

```java
//处理videoPreviews
        for (int i = 0; i < videoPreviews.size(); i++) {
            String url = videoPreviews.get(i);
            final int index = i;
            if (!TextUtils.isEmpty(url)) {
                Glide.with(getContext()).load(url).asBitmap().into(new SimpleTarget<Bitmap>() {

                    @Override
                    public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
                        // 裁剪并保存
                        cropAndSaveSprites(resource, index);
                    }
                });
            }
        }

private void cropAndSaveSprites(Bitmap spriteSheet, int index) {
        // 定义小图的宽高
        int spriteWidth = 160;
        int spriteHeight = 90;

        // 创建目标文件夹
        File targetDir = new File(getContext().getCacheDir(), "replay_video_preview/"+recordId);
        if (!targetDir.exists()) {
            targetDir.mkdirs();
        }

        // 循环裁剪并保存小图
        for (int row = 0; row < 10; row++) {
            for (int col = 0; col < 10; col++) {
                int x = col * spriteWidth;
                int y = row * spriteHeight;

                // 裁剪小图
                Bitmap sprite = Bitmap.createBitmap(spriteSheet, x, y, spriteWidth, spriteHeight);

                // 保存小图到文件
                File spriteFile = new File(targetDir, "sprite_" + (100 * index + (row * 10 + col)) + ".png");
                //文件不存在，保存
                if (!spriteFile.exists()){
                    try {
                        FileOutputStream fos = new FileOutputStream(spriteFile);
                        sprite.compress(Bitmap.CompressFormat.PNG, 100, fos);
                        fos.flush();
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
```

加载图片：

```java
private void updateVideoPreviews(SeekBar seekBar) {
        if (!isHasVideoPreviews || videoPreviews == null) {
            return;
        }
        long mills = seekBar.getProgress();
        //更新时间戳
        String currentTime = TimeUtil.getFormatTime(seekBar.getProgress());
        String maxTime = TimeUtil.getFormatTime(seekBar.getMax());

        tvCcVideoPreviewTime.setText(currentTime);
        tvCcVideoPreviewTime2.setText("/"+maxTime);

        if (mills /1000/ 6 == lastMills/1000 / 6) {
            return;
        }

        lastMills = mills;
        //videoPreviews集合中存储的是雪碧图（10*10）固定像素：1600x900，6秒截一张图，需要根据mills和max计算当前需要展示的图片，并使用Glide加载到imageview上

        //计算当前mills位于第几张图
        int index = (int) (mills/1000/6);


        String path  = getContext().getCacheDir()+"/replay_video_preview/"+recordId+"/sprite_"+index+".png";

        File file = new File(path);
        if (file.exists()){
            // 使用Glide加载
            Glide.with(getContext()).load(file).placeholder(R.drawable.shape_bg_video_previews).dontAnimate().fitCenter().into(ivCcVideoPreviews);
        }

    }
```

具体实现可以参考 demo

### 2.12 回放视频摘要功能

版本>=3.25.0

在线回放提供对视频进行摘要总结并生成章节概要的能力（开通联系客服）。SDK视频摘要功能使用方式如下：

登录回放房间后，通过`boolean isOpenSummary()`获取视频摘要开关，如果开关为开，通过`void getSummaryInfo(BaseCallback<ReplaySummaryInfo> callback)`获取摘要信息，回调的`ReplaySummaryInfo`如果不为空，则显示摘要信息。

ReplaySummaryInfo说明:

```java
public class ReplaySummaryInfo {
		//摘要描述
    private String summary;
    //目录列表
    private List<ChaptersDTO> chapters;
    
    public static class ChaptersDTO {
      	//开始时间点，单位 ms
        private int start;
        //章节标题
        private String headline;
    }
}
```

### 2.13 回放投屏功能

版本>=3.27.0

在线回放提供相关播放信息以支持投屏，投屏的稳定性受设备以及局域网络状况限制，加密在线回放投屏需要保持手机App在前台运行并且保持投屏设备与手机在同一局域网内并且连接稳定。如果是加密回放（pcm格式），返回的链接格式为：http://127.0.0.1:xxxxx?xxx ，需要替换其中的127.0.0.1为手机所在的局域网ip地址，确保电视端能够通过此ip地址访问到手机。

DWLiveReplayListener新增获取播放地址回调：

```
/**
     * 获取投屏播放地址
     * @param urlInfo 播放地址
     * @since 3.27.0
     */
    public void onGetPlayUrl(PlayUrlInfoBean urlInfo){}
```

收到上述回调时，将播放地址保存，并在投屏时使用

PlayUrlInfoBean：

```
public class PlayUrlInfoBean {
		//视频链接列表
    private List<Video> videos = new ArrayList<>();
    //音频链接列表
    private List<Audio> audios = new ArrayList<>();
}
public static class Video {
		//清晰度index
    private int quality;
    //清晰度说明
    private String desp = "";
    //视频链接地址
    private List<String> videoStream;
}

public static class Audio {
		//音频链接地址
    private String audioStream;
}
```

### 2.14 字幕

版本>=3.28.0

在线回放支持字幕功能，登录在线回放调用start之后，字幕将会从`DWLiveReplayListener`以下方法回调：

```java
/**
     * 获取字幕信息
     * @param subtitles 字幕信息
     * @since 3.28.0
     */
    public void onSubtitles(ReplayUrlInfo.SubtitleData subtitles) {

    }
```

`SubtitleData`

```java
public static class SubtitleData {
    private SubtitleItem subtitle;
    private SubtitleItem subtitle2;
  //0，取subtitle，1，取subtitle2，其他值，取subtitle
    private int defaultSubtitle;
  //字幕自适应模式或固定大小模式
    private int subtitlemodel;
}
```

`SubtitleItem`

```java
 public static class SubtitleItem{
   //srt文件地址
     private String url;
   //字体
     private String font;
   //字体大小
     private String size;
   //字体颜色
     private String color;
   //描边颜色
     private String surroundColor;
   //距离底部比例（以播放器高为基准）
     private String bottom;
   //字幕标题
     private String subtitleName;
   //字母排序（双语模式同时显示两条字幕需要以此值来区分上下）
     private int sort;
}
```

字幕简单实现可参考demo中：SubtitleView.java、Subtitle.java、OutlineTextView.java等类
