## 4.1 广告功能

1. 本功能需要后台开通移动广告；
2. 广告核心类DWMediaAD提供能了获取广告信息的方法，广告详细信息通过DWMediaADListener监听器回调来获取；
3. 广告分为前贴、暂停、片尾，需要分别调用方法来获取，提供了视频和图片广告。

```
……
DWMediaAD mDWMediaAD = new DWMediaAD(dwMediaADlistener, USERID, videoId);
mDWMediaAD.getFrontAD();
mDWMediaAD.getPauseAD();
mDWMediaAD.getEndAD();
……
// 广告监听器
private DWMediaADListener dwMediaADListener = new DWMediaADListener() {
    //回调片头广告信息
    @Override
    public void onFrontAD(FrontADInfo info) {
        
    }
    //回调暂停广告信息
    @Override
    public void onPauseAD(PauseADInfo info) {

    }
    //回调片尾广告信息
    @Override
    public void onEndAD(EndADInfo info) {

    }

    @Override
    public void onFrontADError(HuodeException e) {

    }

    @Override
    public void onPauseADError(HuodeException e) {

    }

    @Override
    public void onEndADError(HuodeException e) {

    }
};
```

注：目前只提供获取广告信息的接口，广告界面需要用户自行实现

## 4.2 用户自定义参数播放统计功能

支持用户自定义参数实现播放的统计，可以通过调用player的setCustomId(String customId)方法来实现，其中customId为用户自己定义的参数值。

```
//设置CustomId
player.setCustomId("Huode");
```

注：需要在prepareAsync()方法前调用才可以生效。


## 4.3 视频打点功能

支持用户自定义视频打点功能的回调，可以通过调用player的setOnHotspotListener(OnHotspotListener onHotspotListener)方法，当视频有打点信息时，在该接口中回调打点信息。

```
//获得视频打点信息        
player.setOnHotspotListener(new OnHotspotListener() {
    /**
     * @param hotspotMap key为时间（秒），value是打点描述
     */
    @Override
    public void onHotspots(TreeMap<Integer, String> hotspotMap) {
        
    }
});          
```

## 4.4 视频问答功能

支持用户自定义视频问答功能的回调，可以通过调用player的setOnQAMsgListener(OnQAMsgListener qaMsgListener)方法，当视频有问答信息时，在该接口中回调问答信息。

```
//获得视频问答信息
player.setOnQAMsgListener(new OnQAMsgListener() {
    /**
     * @param qaJsonArray，单条信息包含有question和answer
     */
    @Override
    public void onQAMessage(JSONArray qaJsonArray) {

    }
});
```

问答信息回调的格式为JSONArray，单条问答信息的内容如下，请用户自行解析。

```
{
    "answers": [
        {
            "content": "A、答案A",
            "id": 1045,
            "right": false
        },
        {
            "content": "B、答案B",
            "id": 1046,
            "right": true
        },
        {
            "content": "C、答案C",
            "id": 1047,
            "right": false
        },
        {
            "content": "D、答案D",
            "id": 1048,
            "right": false
        }
    ],
    "backSecond": -1,
    "keepPlay":true,
    "content": "内容",
    "explainInfo": "解释答案",
    "id": 99,
    "jump": false,
    "showTime": 2
}
```

## 4.5 视频显示字幕功能

### 4.5.1 在线字幕

支持从服务器获取字幕资源并显示，可以通过调用player的setOnSubtitleMsgListener(OnSubtitleMsgListener onSubtitleMsgListener)方法，当有字幕资源时，在该接口中回调字幕信息。

```
//获得字幕信息
player.setOnSubtitleMsgListener(new OnSubtitleMsgListener() {
    /**
     * 第一种字幕，设置这些字幕样式需要在主线程操作
     * @param subtitleName 字幕名称
     * @param sort 字幕位置顺序
     * @param url 字幕资源地址
     * @param font 字体名称，需要在工程的assets（没有请自行创建）
     *             文件夹下有对应的字体文件，自行实现字体显示逻辑
     * @param size 字体大小
     * @param color 字体颜色
     * @param surroundColor 字体环绕色
     * @param bottom 距离底部的边距占手机屏幕宽度的百分比
     * @param code 字幕编码格式，如utf-8
     */
    @Override
    public void onSubtitleMsg(String subtitleName, int sort, String url, String font, int size, String color, String surroundColor, double bottom, String code) {

    }

    /**
     * 第二种字幕，设置这些字幕样式需要在主线程操作
     * @param subtitleName 字幕名称
     * @param sort 字幕位置顺序
     * @param url 字幕资源地址
     * @param font 字体名称，需要在工程的assets（没有请自行创建）
     *             文件夹下有对应的字体文件，自行实现字体显示逻辑
     * @param size 字体大小
     * @param color 字体颜色
     * @param surroundColor 字体环绕色
     * @param bottom 距离底部的边距占手机屏幕宽度的百分比
     * @param code 字幕编码格式，如utf-8
     */
    @Override
    public void onSecSubtitleMsg(String subtitleName, int sort, String url, String font, int size, String color, String surroundColor, double bottom, String code) {

    }

    /**
     * @param defaultSubtitle 0默认显示第一种字幕，1默认显示第二种字幕，2默认显示双语字幕
     */
    @Override
    public void onDefSubtitle(int defaultSubtitle) {

    }
});
```

### 4.5.2 离线字幕

在下载视频时，如果视频存在字幕文件，可以同时下载字幕，通过downloader.setDownloadSubtitle(downloadSubtitlePath,subtitleName)设置下载字幕，只有同时设置了downloadSubtitlePath和subtitleName，才会下载字幕，downloadSubtitlePath的格式如 "/storage/emulated/0/HuodeDownload/"，subtitleName的格式如"huode"，
最终字幕一（如有）的保存路径是downloadSubtitlePath+subtitleName+"subtitle.srt"，字幕二（如有）的保存路径是downloadSubtitlePath+subtitleName+"subtitle2.srt"，
字幕设置（如有）的保存路径是downloadSubtitlePath+subtitleName+"subtitleSet.json"。

```
//设置下载字幕
downloader.setDownloadSubtitle(downloadSubtitlePath, subtitleName);
```

下载字幕回调。

```
//字幕回调
downloader.setOnDownloadSubtitleListener(new OnDownloadSubtitleListener() {

    //字幕数量subtitleNum，0：无字幕 1：一种字幕 2：二种字幕
    @Override
    public void onSubtitleNum(int subtitleNum) {
        
    }
    //视频存在字幕一时才会回调，保存字幕成功，subtitlePath：字幕保存路径
    @Override
    public void onDownloadFirstSubtitleSuccess(String subtitlePath) {

    }
    //视频存在字幕一时才会回调，保存字幕失败，subtitlePath：字幕保存路径 subtitleUrl：字幕资源地址
    @Override
    public void onDownloadFirstSubtitleFail(String subtitlePath, String subtitleUrl) {

    }
    //视频存在字幕二时才会回调，保存字幕成功，subtitlePath：字幕保存路径
    @Override
    public void onDownloadSecondSubtitleSuccess(String subtitlePath) {

    }
    //视频存在字幕二时才会回调，保存字幕失败，subtitlePath：字幕保存路径 subtitleUrl：字幕资源地址
    @Override
    public void onDownloadSecondSubtitleFail(String subtitlePath, String subtitleUrl) {

    }
    //存在字幕设置时回调，保存字幕设置成功，subtitleSetPath：字幕设置文件的保存路径
    @Override
    public void onSaveSubtitleSetSuccess(String subtitleSetPath) {

    }
    //存在字幕设置时回调，保存字幕设置失败，subtitleSetInfo：字幕设置信息
    @Override
    public void onSaveSubtitleSetFail(String subtitleSetInfo) {

    }
});
```

### 4.5.3 字幕自适应

支持从服务器设定当前字幕字体大小显示的方式，固定字号或跟随播放器大小变化的自适应模式。同时支持SDK本地设置字体模式。SDK声明了枚举SubtitleModel类，FIXED（固定）与SELF_ADAPTION（自适应）用于设定当前类型。  
注意:  
1.SDK本地设置的优先级要高于服务器获取。  
2.Demo中，本地缓存视频的字幕模式跟随当前下载时的字幕模式。如有其他需求，可根据自己的业务加以调整。SubtitleView类中同样提供了setSubtitleModel方法。

```
player.setSubtitleModel(SubtitleModel.FIXED);    player.setSubtitleModel(SubtitleModel.SELF_ADAPTION);
```

## 4.6 访客信息收集功能

支持从服务器获取访客信息收集设置并显示，可以通过调用player的setOnVisitMsgListener(OnVisitMsgListener onVisitMsgListener)方法，得到访客信息收集设置。

```
//获得访客信息设置
player.setOnVisitMsgListener(new OnVisitMsgListener() {
    /**
     * @param appearTime     收集器出现的时间(S)
     * @param imageURL       图片地址
     * @param isJump         是否需要跳过 1跳过,0不跳过
     * @param jumpURL        图片跳转的地址
     * @param title          收集器名称
     * @param visitorId      收集器ID
     * @param visitorMessage //要收集的信息
     */
    @Override
    public void onVisitMsg(int appearTime, String imageURL, int isJump, String jumpURL, String title, String visitorId, JSONArray visitorMessage) {

    }
});
```


## 4.7 倍速功能

倍速功能使用第三方库IjkMediaPlayer v0.8.4来实现。倍速播放功能需要使用DWIjkMediaPlayer类，该类继承自第三方库IjkMediaPlayer的IjkMediaPlayer类，该类的使用方法和DWMediaPlayer一样。

3.17.0支持到三倍速

IjkMeidaPlayer的github地址如下：请参考链接：https://github.com/Bilibili/ijkplayer

设置倍速方法如下：

```
player.setSpeed();
```

**ijkspeed文件夹为ijk所需要的so文件和jar，请放到自己的工程目录使用。**

## 4.8 辅助调试工具

SDK提供HTTP通信日志调试功能。在使用SDK进行播放、上传、下载过程中，如果遇到与网络通信相关的问题，可通过设置HTTP日志的级别获取通信信息。使用示例如下：

```
// 设置HTTP通信日志级别HttpUtil.LOG_LEVEL = HttpLogLevel.DETAIL;
```

默认日志级别为GENERAL（记录HTTP通信发生时间、响应状态码）。

## 4.9 问答统计功能

要统计问答的结果，只需在提交答案时调用下面这个方法：

```
/*** @param vid 加密的视频ID* @param qid 问题ID* @param answer 用户选择的选项ID，以逗号分隔多个选项ID。如1345是单选；2067,3092,4789是多选。* @param status 用户答题的结果。1=正确；0=错误。* @param customId 自定义ID*/QaStatistics.reportQaResult(String vid, String qid, String answer, String status, String customId);

注:customId是在3.18.0新增
```

## 4.10 授权验证功能

从服务器获取授权验证信息，可以通过调用player的setOnAuthMsgListener(OnAuthMsgListener onAuthMsgListener)方法，在该接口中回调授权验证信息。

```
//获得授权验证信息
player.setOnAuthMsgListener(new OnAuthMsgListener() {
    /**
     * @param enable   视频是否可以完整播放 0:不允许完整播放 1:允许完整播放
     * @param freetime 视频试看时间,单位:秒
     * @param messaage 不允许播放,或者试看结束时播放器显示的提示内容
     * @param marqueeInfo 跑马灯信息，具体使用方法请参考4.18章节
     */
    @Override
    public void onAuthMsg(int enable, int freetime, String messaage, MarqueeInfo marqueeInfo{
        
    }
});
```

## 4.11 视频压缩功能

可以选择高质量压缩、中质量压缩、低质量压缩，压缩质量越高，压缩出来的文件就相对较大。

```
/**
 * 高质量压缩
 * @param filePath 源文件路径
 * @param compressOutPut 输出文件路径
 * @param VideoCompress.CompressListener 压缩过程监听
 */
VideoCompress.compressVideoHigh(filePath, compressOutPut, new VideoCompress.CompressListener() {
    @Override
    public void onStart() {

    }

    @Override
    public void onSuccess() {

    }

    @Override
    public void onFail() {

    }

    @Override
    public void onProgress(float percent) {

    }
}); 
```

```
/**
 * 中质量压缩
 * @param filePath 源文件路径
 * @param compressOutPut 输出文件路径
 * @param VideoCompress.CompressListener 压缩过程监听
 */
VideoCompress.compressVideoMedium(filePath, compressOutPut, new VideoCompress.CompressListener() {
    @Override
    public void onStart() {

    }

    @Override
    public void onSuccess() {

    }

    @Override
    public void onFail() {

    }

    @Override
    public void onProgress(float percent) {

    }
}); 
```

```
/**
 * 低质量压缩
 * @param filePath 源文件路径
 * @param compressOutPut 输出文件路径
 * @param VideoCompress.CompressListener 压缩过程监听
 */
VideoCompress.compressVideoLow(filePath, compressOutPut, new VideoCompress.CompressListener() {
    @Override
    public void onStart() {

    }

    @Override
    public void onSuccess() {

    }

    @Override
    public void onFail() {

    }

    @Override
    public void onProgress(float percent) {

    }
}); 
```

## 4.12 课堂练习功能

支持用户自定义课堂练习功能的回调，可以通过调用player的setOnExercisesMsgListener(OnExercisesMsgListener onExercisesMsgListener)方法，当视频有课堂练习信息时，在该接口中回调课堂练习信息。

```
//获得视频课堂练习信息
player.setOnExercisesMsgListener(new OnExercisesMsgListener() {
    @Override
    public void onExercisesMessage(JSONArray exArray) {

    }
});
```

课堂练习回调的格式为JSONArray，单条课堂练习的内容如下，请用户自行解析。

```
{
    "id": 86,
    "backSecond": 5,//回看时间
    "isJump": 0,//是否允许跳过   1跳过 ，0不跳过
    "isPlay": 0,//答错是否可以继续  0不能， 1 能
    "questions": [
       
        {
            "answers": [
                {
                    "content": "A、答案A",
                    "id": 608,
                    "right": true
                },
                {
                    "content": "B、答案B",
                    "id": 609,
                    "right": true
                },
                {
                    "content": "C、答案C",
                    "id": 610,
                    "right": true
                },
                {
                    "content": "D、答案D",
                    "id": 615,
                    "right": false
                }
            ],
            "backSecond": 3,//回看时间
            "content": "题目",
            "explainInfo": "试题详解",
            "id": 414,
            "type": 1
        }
        
    ],
    "showTime": 20,
    "title": "课堂练习"
}
```

## 4.13 投屏功能

在播放时可以投射到电视或其他支持投屏的设备，分为非加密视频的投屏和加密视频的投屏。

### 4.13.1 非加密视频的投屏

```
//搜索发现设备
registryListener.setOnDeviceListChangedListener(new ProjectionDeviceListChangedListener() {
    @Override
    public void onDeviceAdded(ProjectionIDevice device) {
        //发现设备
    }

    @Override
    public void onDeviceRemoved(ProjectionIDevice device) {
        //设备移除
    }
});       
```

```
//播放新的视频
projectionPlayControl.playNew(playUrl, new ProjectionControlCallback() {
    @Override
    public void success(ProjectionIResponse response) {
        
    }

    @Override
    public void fail(ProjectionIResponse response) {

    }
});    
```

```
//暂停后恢复播放
projectionPlayControl.play(new ProjectionControlCallback() {
    @Override
    public void success(ProjectionIResponse response) {
        
    }

    @Override
    public void fail(ProjectionIResponse response) {

    }
});      
```

```
//暂停播放
projectionPlayControl.pause(new ProjectionControlCallback() {
    @Override
    public void success(ProjectionIResponse response) {
        
    }

    @Override
    public void fail(ProjectionIResponse response) {

    }
});        
```

### 4.13.2 加密视频的投屏

加密视频不可直接投屏，请打开手机的投屏功能，详情参考Demo。

## 4.14 防录屏

在播放视频时，调用setAntiRecordScreen()方法防录屏。

```
//开启防录屏，会使加密视频投屏功能不能正常使用player.setAntiRecordScreen(activity); 
```

## 4.15 视频水印

上传视频的时候，支持为视频配置水印。

```
//水印位置0,左上 1右上 2左下 3右下，默认3，非必填
videoInfo.setCorner(int corner);
//X轴偏移像素值，要求大于0，默认值5,超出视频大小按默认值，非必填
videoInfo.setOffsetx(int offsetx);
//Y轴偏移像素值，要求大于0，默认值5,超出视频大小按默认值，非必填
videoInfo.setOffsety(int offsety);
//字体类型：0,微软雅黑 1宋体 2黑体，默认0，非必填
videoInfo.setFontfamily(int fontfamily);
//字体大小，[0-100]，默认12
videoInfo.setFontsize(int fontsize);
//16进制字体颜色，如#FFFFFF，不能写#号，默认灰色D3D3D3，非必填
videoInfo.setFontcolor(String fontcolor);
//透明度，[0-100],默认0，100为不透明，非必填
videoInfo.setFontalpha(int fontalpha);
//水印文字内容, 1-50个字符，数字、字母、汉字，不填写则文字水印不生效，填写错误，会导致上传失败
videoInfo.setText(String text);
```

## 4.16 切换到备用线路

调用player.setBackupPlay(true)方法切换到备用线路。

## 4.17 获取播放时长和暂停时长

```
//获取播放时长（单位是秒）
player.getPlayedTime()；
//获取暂停时长（单位是秒）
player.getPausedTime()；
//当切换到另外一个视频时，需要调用player.resetPlayedAndPausedTime()，将上一个视频的播放时长和暂停时长重置为0
player.resetPlayedAndPausedTime();
```

## 4.18 跑马灯

使用跑马灯。

```
<com.bokecc.sdk.mobile.play.MarqueeView
     android:id="@+id/mv_video"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     />
```

获取跑马灯数据。  

1.获取在线视频跑马灯数据

```
player.setOnAuthMsgListener(new OnAuthMsgListener() {
    //marqueeInfo：跑马灯信息
    @Override
    public void onAuthMsg(int enable, int freetime, String messaage, MarqueeInfo marqueeInfo) {
        //获得跑马灯类型，text：文字，image：图片
        String type = marqueeInfo.getType();
        //获得循环次数
        int loop = marqueeInfo.getLoop();
        //获得文字信息
        MarqueeInfo.TextBean textBean = marqueeInfo.getTextBean();
        //获得文字内容
        String content = textBean.getContent();
        //获得字体大小
        int font_size = textBean.getFont_size();
        //获得文字颜色
        String color = textBean.getColor();
        if (!TextUtils.isEmpty(color) && color.length() == 8) {
                String textColor = "#" + color.substring(2, 8);
                if (textColor.length() == 7) {
                     //textColor：文字颜色，格式如：#ffffff
                     mv_video.setTextColor(textColor);
                }
        }
        //获得图片信息
        MarqueeInfo.ImageBean imageBean = marqueeInfo.getImageBean();
        //获得图片url
        String image_url= imageBean.getImage_url();
        //获得图片宽度
        int width= imageBean.getWidth();
        //获得图片高度
        int width = imageBean.getHeight();
        //获得动作节点
        ArrayList<MarqueeAction> action = marqueeInfo.getAction();
    }
});  
```

2.获取离线视频跑马灯数据  
跑马灯的相关数据会跟随视频下载，demo将该数据中保存在了本地数据库object-box中，用户可根据自身逻辑和场景，进行更改和保存。demo中由DownloadInfo.getMarqueeData()方法获取到序列化到本地的String类型的跑马灯数据 marqueeData，之后作为参数传入player的getMarqueeInfo()方法，该方法会返回MarqueeInfo对象，用于MarqueeView使用和展示。具体请参考demo中使用。

```
if (!TextUtils.isEmpty(marqueeData)) {
     MarqueeInfo marqueeInfo=player.getMarqueeInfo(marqueeData);
     setMarqueeView(marqueeInfo);
   }
```

设置跑马灯类型。

```
//MarqueeView.TEXT：文字 MarqueeView.IMAGE:图片
mv_video.setType();
```

设置循环次数。

```
//loop：为-1时是无限循环，为其他正整数时是有限次循环 
mv_video.setLoop(loop);
```

设置跑马灯动作节点，可定义多个节点。

```
//创建动作节点
MarqueeAction marqueeAction = new MarqueeAction();
//index：动作的执行顺序
marqueeAction.setIndex(index);
//duration：动作的执行时间（单位：ms）
marqueeAction.setDuration(duration);
//startXpos：动作起始点距离视频左上角原点水平距离，数据类型float，取值范围0~1，
//具体距离：视频宽度*startXpos
marqueeAction.setStartXpos(startXpos);
//startYpos：动作起始点距离视频左上角原点垂直距离，数据类型float，取值范围0~1
//具体距离：视频高度*startYpos
marqueeAction.setStartYpos(startYpos);
//startAlpha：起始点透明度，数据类型float，取值范围0~1
marqueeAction.setStartAlpha(startAlpha);
//endXpos：动作结束点距离视频左上角原点水平距离，数据类型float，取值范围0~1，
//具体距离：视频宽度*endXpos
marqueeAction.setEndXpos(endXpos);
//endYpos：动作结束点距离视频左上角原点垂直距离，数据类型float，取值范围0~1
//具体距离：视频高度*endYpos
marqueeAction.setEndYpos(endYpos);
//endAlpha：结束点透明度，数据类型float，取值范围0~1
marqueeAction.setEndAlpha(endAlpha);
                

//设置动作节点，marqueeActions：一组动作节点
mv_video.setMarqueeActions(marqueeActions);
```

设置文字内容。

```
//textContent：是文字内容mv_video.setTextContent(textContent);
```

设置文字字体大小。

```
//textSize：文字字体大小（单位px）mv_video.setTextFontSize(textSize);
```

设置文字颜色。

```
//textColor：文字颜色，如#ffffffmv_video.setTextColor(textColor);
```

通过url设置跑马灯图片。

```
  /**
     * @param activity    上下文
     * @param imageUrl    图片url
     * @param imageWidth  图片宽度（单位px）
     * @param imageHeight 图片高度（单位px）
     */
mv_video.setMarqueeImage(activity, imageUrl, imageWidth, imageHeight);

//设置跑马灯图片加载监听
mv_video.setOnMarqueeImgFailListener(new OnMarqueeImgFailListener() {
            @Override
            public void onLoadMarqueeImgFail() {
               
            }
});
```

通过Bitmap设置跑马灯图片。

```
  /**
     * @param bitmap      图片bitmap
     * @param imageWidth  图片宽度（单位px）
     * @param imageHeight 图片高度（单位px）
     */
mv_video.setMarqueeImage(bitmap, imageWidth, imageHeight);
```

运行跑马灯。

```
mv_video.start();
```


## 4.19 小窗播放

Android 8.0及以上系统支持小窗播放,具体使用请参考Demo。

在AndroidManifest.xml文件中给播放Activity配置android:supportsPictureInPicture="true"和android:configChanges="smallestScreenSize|screenLayout|orientation"（避免Activity销毁重建）。

```
<activity
    android:name=".play.MediaPlayActivity"
    android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|screenLayout"
    android:screenOrientation="portrait"
    android:supportsPictureInPicture="true" />
```

进入小窗播放，以下是部分代码示例,详细代码参考Demo：

```
private PictureInPictureParams.Builder builder;

private void useSmallWindowPlay() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (builder == null) {
                builder = new PictureInPictureParams.Builder();
            }
            int width = rl_play_video.getWidth();
            int height = rl_play_video.getHeight();
            // 设置宽高比例值
            if (width > 0 && height > 0) {
                float whRate = MultiUtils.calFloat(2, width, height);
                if (whRate > 0.42 && whRate < 2.39) {
                    Rational aspectRatio = new Rational(width, height);
                    builder.setAspectRatio(aspectRatio);
                }
            }

            if (actions != null && actions.size() > 0) {
                actions.clear();
            }

            if (player.isPlaying()) {
                actions.add(pauseRemoteAction);
            } else {
                actions.add(playRemoteAction);
            }
            if (actions != null && actions.size() > 0) {
                builder.setActions(actions);
            }

            // 进入小窗模式
            enterPictureInPictureMode(builder.build());
        }

}
```

## 4.20 视频截图

通过调用player.getVideoScreenShot(bitmap, videoScreenShotOutPath)获取当前播放视频的截图。

```
private void getVideoScreenShot() {
        String videoScreenShotOutPath = MultiUtils.getVideoScreenShotOutPath();
        if (!TextUtils.isEmpty(videoScreenShotOutPath)) {
            Bitmap bitmap = tv_video.getBitmap();
            //bitmap:视频画面的bitmap  videoScreenShotOutPath：截图输出路径
            boolean videoScreenShot = player.getVideoScreenShot(bitmap, videoScreenShotOutPath);
            if (videoScreenShot) {
                //通知系统相册更新
                sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
                        Uri.fromFile(new File(videoScreenShotOutPath))));
            }
        }
}
```

## 4.21 弹幕功能

发送弹幕。

```
  /**
     * @param videoId             视频ID
     * @param danmuText           弹幕文字
     * @param currentPosition     发送弹幕时的视频播放位置
     * @param danmuColor          弹幕颜色（格式如：0xffffff）
     * @param onSendDanmuListener 发送结果
     */
player.sendDanmu(videoId, danmuText, currentPosition, danmuColor, new OnSendDanmuListener() {
        @Override
        public void onSuccess() {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    isCanSendDanmu = false;
                    sendDanmuInterval = 5;
                    addDanmu(danmuText, danmuColor, currentPosition, true);
                }
            });
        }

        @Override
        public void onFail(final String msg) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    MultiUtils.showToast(activity, "发送弹幕失败：" + msg);
                }
            });
        } 
});     
```

获取弹幕数据。

```
        /**
     * @param videoId  视频ID
     * @param sec      段号
     *每隔1分钟调用1次，段号递增
     */
player.getDanmuList(videoId, sec);

//查询弹幕结果
player.setOnDanmuListListener(new OnDanmuListListener() {
             /**
              * @param danmuInfos  弹幕信息
              *  danmuInfo组成字段:
              *  content：字幕内容 fc：字幕颜色，格式如：0xffffff
              *  pt:字幕相对于视频出现的时间
              */
            @Override
            public void onSuccess(final ArrayList<DanmuInfo> danmuInfos) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        
                    }
                });
            }

            @Override
            public void onFail(String msg) {
                
            }
});
```

弹幕功能的演示请参考Demo。

## 4.22 设置用户标识

```
//setClientId()方法在setVideoPlayInfo()前调用，可以通过此方法设置用户标识，出问题时方便排查player.setClientId("");
```

## 4.23 动感视频功能

支持在关键点震动提醒，详情参考Demo。

```
//添加震动权限<uses-permission android:name="android.permission.VIBRATE"/>
```

```
Vibrator vibrator = vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
if (vibrator.hasVibrator() && isDynamicVideo){
    //震动时长，单位毫秒
    vibrator.vibrate(500);
}

//不用震动的时候，调用cancel()取消
if (vibrator != null) {
    vibrator.cancel();
}
```

## 4.24 防拖拽

启用防拖拽功能，未看部分禁止拖动，将isForbidDragToUnPlayPart的值改为true，详情参考Demo。

```
//未看部分禁止拖动：isForbidDragToUnPlayPart为false时允许拖动到未看部分，为true时不允许拖动到未看部分
private boolean isForbidDragToUnPlayPart = true;
```

## 4.25 自定义Logo

在布局中使用CustomLogoView。

```
<com.bokecc.vod.view.CustomLogoView
     android:id="@+id/clv_logo"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:layout_centerInParent="true" />
```

设置Logo,logo图片不宜太大，控制在100KB内。

```
    /**
     * @param img 图片地址
     * @param xPosRate 相对于左上角的X轴位置偏移量与播放窗口宽度的比例
     * @param yPosRate 相对于左上角的Y轴位置偏移量播放窗口高度的比例
     * @param logoWidthRate Logo宽度相对于播放窗口宽度的比例
     * @param logoHeightRate Logo高度相对于播放窗口高度的比例
     */
clv_logo.setCustomLogoInfo(img, xPosRate, yPosRate, logoWidthRate, logoHeightRate);
```

展示logo，调用clv_logo.show()。

```
clv_logo.show();
```

横竖屏切换时，刷新Logo。

```
//刷新logo视图
clv_logo.refreshView();
```



## 4.26 错误处理

错误信息有两种，一种是播放器触发的错误，另一种是获得场景视频自定义的错误。

### 4.26.1 监听播放器的错误事件

DWMediaPlayer重载了MediaPlayer的setOnErrorListener()方法，如果需要在应用中提示错误信息，可调用此方法设置OnErrorListener。具体实现方式如下:

```
player.setOnErrorListener(new MediaPlayer.OnErrorListener() {
    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        //在这里进行错误处理
        return true;
    }
});
```

### 4.26.2 监听获得场景视频自定义的错误事件

```
player.setOnDreamWinErrorListener(new OnDreamWinErrorListener() {
    @Override
    public void onPlayError(HuodeException e) {
        //在这里进行错误处理
    }
});
```

## 4.27 自定义视频封面  

1.根据后台管理系统上传的自定义封面，用于视频播放前的展示，用户设置的是否自动播放，决定展示时长。当设置为自动播放时，在视频缓冲加载结束之后，将隐藏该封面图，播放视频。当设置为非自动播放时，将持续展示封面图，直到用户点击播放按钮。  

```
 //视频缓冲完成自动播放 
player.setAutoPlay(true);  
 //持续展示视频封面
 player.setAutoPlay(false);
```

2.视频的封面图展示与隐藏逻辑，更多的需要开发人员自行控制，可通过提供的api获取当前配置的封面rul。

```
//在onPrepared()方法中，获取到 playInfo对象
PlayInfo playInfo = player.getPlayInfo();
String coverUrl = playInfo.getCoverImage();
```

3.当音视频模式切换时，需调用api，通知当前模式切换

```
player.playModelChanged();
```

## 4.27 答题器

1.在admin管理系统中配置答题器相关数据，播放器可以通过设置回调获取相关数据，以及相关回调方法

```
player.setOnAnswerSheetListener(new AnswerSheetListener() {
            @Override
            public void onAnswerSheet(List<AnswerSheetInfo> answerSheetInfoList) {
            //获取到返回的数据,将该数据绑定在视图中
                MediaPlayActivity.this.answerSheetInfoList = answerSheetInfoList;
                sheetTimeList = new ArrayList<>();
                for (int i = 0; i < answerSheetInfoList.size(); i++) {
                    sheetTimeList.add(answerSheetInfoList.get(i).getShowTime());
                }
                Collections.sort(sheetTimeList);
            }

            @Override
            public void onAnswerCommitSuccess(final List<AnswerCommitResult> commitResultList) {
            //提交答案成功回调
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        //UI展示，相关逻辑
                    }
                });
            }

            @Override
            public void onAnswerCommitFailed(int errorCode, String errorMessage) {
                //提交答案失败回调
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                         //UI展示，相关逻辑
                    }
                });
            }
        });
```

2.答题器提交答案

```
//这里需要填入当前回答问题的id，以及答案列表player.onSubmitAnswer(int currentSheetInfoId,List<AnswerSheetInfo.Answer> selectedAnswer)
```
4.28.2 答题器相关设置
1.显示开关，SDK提供了答题器显示相关的api给到开发者，以满足用户可以随时控制答题器相关逻辑是否展示的自由度。当配置了答题器的相关内容时，默认为显示答题器。
```
 player.setHideAnswerSheet(boolen hide);
```
2.当进度条拖动时，对于历史进度中未展示的相关答题器，存在有强制依次展示和不展示的两种不同逻辑，SDK以构造的方式进行强绑定,无参构造中，默认为强制依次显示。
```
DWIjkMediaPlayer player = new DWIjkMediaPlayer();
DWIjkMediaPlayer player = new DWIjkMediaPlayer(boolen force);
DWMediaPlayer player = new DWMediaPlayer();
DWMediaPlayer player = new DWMediaPlayer(boolen force);
```
## 4.27 知识点
1.知识点相关功能逻辑在demo中体现，数据源SDK不做相关处理，具体逻辑可根据实际需求进行更改，demo中提供了本地数据源与数据格式。详见assents目录下knowledge.json文件。知识点UI相关体现在KnowledgeDialog中。demo中提供了数据源，UI展示，接口回调的完整逻辑，可按需进行二次更改开发。

具体的展示与逻辑信息，您可参考demo中的相关实现。  

## 4.28 图文打点
1.视频打点信息提供图文展示功能，可在后台配置打点信息视频信息描述文案，点击热点时，会以浮层的形式展示在播放器上，以获取到的当前视频帧截图进行展示。视频帧截图获取需要一定时长，数据源以接口回调为准。视频帧截图会以本地文件的形式保存在本地文件夹中，其路径低版本下为SD卡根目录下com.bokecc/hotspot/视频ID/文件夹下，高版本在Android/data/com.bokecc.vod/files/Documents/com.bokecc/hotspot/视频ID/文件夹下。获取视频帧截图需要按以下逻辑调用API,详情请参考demo实现
```
    1.DWMediaPlayer需要调用在onPrepared回调中调用方法executePortInfo()方法，DWIjkMediaPlayer无需调用。
    @Override
    public void onPrepared(IMediaPlayer iMediaPlayer) {
        player.executePortInfo();
    }
    
    2.数据返回需要注册该监听
    player.setOnHotspotListener(new OnHotspotListener() {
        @Override
        public void onHotspots(TreeMap<Integer, String> hotspotMap) {
         //返回打点的时间点与描述信息
         //do something
        }

        @Override
    public void onHotSpotInfo(LinkedHashMap<Integer, HotSpotInfo> hotSpotInfoList) {
         //返回打点信息相关对象，当前以LinkedHashMap返回，key为时间（秒），value为HotSpotInfo对象
         //do something
        }
    
 });

```
## 4.29 视频帧预览
1.视频帧预览功能是指在进度条滑动过程中，在播放器上方以浮层的形式展示连贯的图片，图片内容为滑动进度的视频帧。该功能需要在vuion后台开启缩略图权限。具体使用可参考demo详情
```
    1.需要设置该监听，返回缩略图图片到此回调中
 player.setThumbnailsCallback(new ThumbnailsCallback() {
     @Override
     public void onThumbnailsInfo(List<String> thumbnailsList) {
                //do something
     }
});

```
## 4.30 自定义跑马灯数据源  
1.添加自定义跑马灯数据源，除在admin配置跑马灯相关数据外，提供自定义数据源入口。数据源需按照固定的json数据格式设置给sdk。其格式可参考demo中assents目录下marquee.json文件。
2.自定义跑马灯数据源优先级高于admin配置，如均有设置，以自定义数据源为准。
```
 //自定义跑马灯数据,该数据优先级高于admin配置，如均存在，则仅展示该数据
 //此处以离线json为例，可按需配置数据源由服务器返回或本地存储，按固定的json格式即可
  MarqueeInfo marqueeInfo = player.setCustomMarqueeData(JsonUtil.getLocalJson(this, "marquee.json"));  
  setMarqueeView(marqueeInfo);
```
##  4.31 admin配置知识点  

1.开发者可自行选择知识点的数据源，admin配置的相关数据或自定义数据源。  
注：自定义数据源请保持demo中示例的json格式。  
2.注册KnowledgeListener监听获取admin配置的数据源。
//使用点播admin配置的知识点相关信息

```
            player.setKnowledgeListener(new KnowledgeListener() {
                @Override
                public void onKnowledge(final KnowledgeBean knowledgeBean) {
                    SpeedPlayActivity.this.knowledgeBean = knowledgeBean;
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            sb_progress.setTimeAxisStatus(knowledgeBean.isTimeAxisStatus());
                            if (!TextUtils.isEmpty(knowledgeBean.getTitle())) {
                                tvKnowledge.setText(knowledgeBean.getTitle());
                                tvKnowledge.setVisibility(View.VISIBLE);
                            } else {
                                tvKnowledge.setVisibility(View.GONE);
                            }
                        }
                    });
                }

                @Override
                public void onError(int errorCode, String errorMessage, String errorDetail) {
                    tvKnowledge.setVisibility(View.GONE);
                    Log.e(TAG, "onKnowledgeError,errorCode:" + errorCode + ",errorMessage:" + errorMessage + ",errorDetail:" + errorDetail);
                }
            });
            
            
    //使用自定义数据源
       String localJson = JsonUtil.getLocalJson(this, "knowledge.json");
            this.knowledgeBean = new Gson().fromJson(localJson, KnowledgeBean.class);
            if (knowledgeBean != null) {
                if (!TextUtils.isEmpty(knowledgeBean.getTitle())) {
                    tvKnowledge.setVisibility(View.VISIBLE);
                    tvKnowledge.setText(knowledgeBean.getTitle());
                } else {
                    tvKnowledge.setVisibility(View.GONE);
                }
                sb_progress.setTimeAxisStatus(knowledgeBean.isTimeAxisStatus());
            }
```

##  4.32 隐形跑马灯

​	3.17.1版本支持

1.在gradle引入组件

```groovy
implementation 'com.bokecc.module:marquee:1.0.0'
```

2.在xml文件中需要添加对应的布局 

示例：

```xml
<com.bokecc.marquee.TransparentMarquee
    android:id="@+id/id_transparent_marquee"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
```

3.根据开关来设置隐形跑马灯数据，设置数据的时机在鉴权监听器回调后

相关api:

| 方法                 | 说明               |
| :------------------- | :----------------- |
| isInvisibleMarquee() | 是否开启透明跑马灯 |

```java
//设置鉴权监听器
player.setOnAuthMsgListener(new OnAuthMsgListener() {
    @Override
    public void onAuthMsg(final int enable, final int freetime, final String messaage, final MarqueeInfo marqueeInfo) {
        runOnUiThread(new Runnable() {

            @SuppressLint("DefaultLocale")
            @Override
            public void run() {
                //如果开启了透明跑马灯，设置透明跑马灯样式以及内容
                if (marqueeInfo!=null&&player.isInvisibleMarquee()){
                    transparentMarquee.setMarqueeAdapter(new TransparentMarqueeAdaper() {
                    		//要展示的内容
                        @Override
                        public String onContent() {
                            return marqueeInfo.getTextBean().getContent();
                        }
                        //是否是深色系 默认是深色系
                        @Override
                        public boolean invisibleMarqueeLightMode() {
                            return super.invisibleMarqueeLightMode();
                        }
                    });
                }
            }
        });
    }
});
```