human: 如何处理七牛云的直播云基础能力使用

gpt: [直播云](https://developer.qiniu.com/pili) > SDK 下载 > 播放端 >[QPlayer2
Android端](https://developer.qiniu.com/pili/sdk/12214/qplayer2-android) >
[基于Core的Qplayer2接入指南](https://developer.qiniu.com/pili/sdk/12215/based-on-the-
access-of-qplayer2-core) > 基础能力使用

#  基础能力使用

最近更新时间: 2024-05-28 11:12:15

在[快速开始 __](http://)中，我们完成了qplayer2播放器的创建\播放\销毁，本文介绍如何使用qplayer2的播放能力

# 播放

## 创建播放数据

以下配置的mediaModel是一个有2个清晰度的播放数据，包含2个清晰度1080 和 720
，同时包含2个srt字幕文件，起播的时候会自动播放1080P的链接和加载中文的字幕

    
    
    //创建MediaModel的构造器
    builder = QMediaModelBuilder()
    //配置视频元素
    builder.addStreamElement(
                "",                                             //userType 预留字段可以填空
                QURLType.QAUDIO_AND_VIDEO,                      //资源的类型，指该元素是包含音频还是视频还是都包含
                1080,                                           //清晰度数值标记
                "http://demo-videos.qnsdk.com/qiniu-1080p.mp4", //视频的地址
                true                                            //是否是默认元素，默认元素是指，起播的时候默认会播放这个元素
    )
    
    builder.addStreamElement(
                "",                                             //userType 预留字段可以填空
                QURLType.QAUDIO_AND_VIDEO,                      //资源的类型，指该元素是包含音频还是视频还是都包含
                720,                                            //清晰度数值标记
                "http://demo-videos.qnsdk.com/qiniu-720p.mp4",  //视频的地址
                false                                           //是否是默认元素，默认元素是指，起播的时候默认会播放这个元素
    )
    
    builder.addSubtitleElement("中文",                                      //字幕名称（唯一的key）
                "http://demo-videos.qnsdk.com/qiniu-2023-chinese.srt",     //srt字幕地址
                true)                                                      //是否是默认字幕 默认元素是指，起播的时候默认加载这个字幕
            builder.addSubtitleElement("英文",
                "http://demo-videos.qnsdk.com/qiniu-2023-english.srt", 
                false)
    
    val mediaModel = builder.build(false) //此处的参数指代该资源是否是一个直播资源，是直播的话就是true,不是直播就是false
    

以下配置的mediaModel是一个音频和视频分成2路url的的播放数据 ，起播的时候会同时播放这两个元素

    
    
    builder = QMediaModelBuilder()
            builder.addStreamElement(
                "",                                              //userType 预留字段可以填空
                QURLType.QAUDIO,                                 //资源的类型，这里的url对应的资源只有音频所以这里用QAUDIO
                100,                                             //清晰度数值标记为100
                "http://demo-videos.qnsdk.com/only-audio.m4s",   //资源地址
                true                                             //是否是默认元素，默认元素是指，起播的时候默认会播放这个元素
            )
            builder.addStreamElement(
                "",                                                           //userType 预留字段可以填空
                QURLType.QVIDEO,                                              //资源的类型，这里的url对应的资源只有视频所以这里用QVIDEO
                1080,                                                         //清晰度数值标记为1080
                "http://demo-videos.qnsdk.com/only-video-1080p-60fps.m4s",    //资源地址
                true                                                          //是否是默认元素，此处也是默认播放，所以也是true
            )
    
    val mediaModel = builder.build(false) //此处的参数指代该资源是否是一个直播资源，是直播的话就是true,不是直播就是false
    

addStreamElement方法的所有参数

参数名 | 参数含义  
---|---  
userType | 用户自定义Type，目前预留，可以填空字符串  
urlType | 媒体的资源属性 只包含视频/只包含音频/同时有视频音频  
quality | 清晰度  
url | 资源地址  
isSelected | 是否起播时播放该流  
backupUrl | 备用地址  
referer | http/https 协议的地址 支持该属性 用于防盗链referer参数  
videoRenderType | 视频的渲染类型，默认是QVideoRenderType.PLANE, 如果是VR视频此处为QVideoRenderType.PANORAMA_EQUIRECT_ANGULAR  
hlsDrmKey | 私有DRM解密的key，用在私有hls加密视频播放的场景中  
mp4DrmKey | mp4 drm 的解密 key 用于 CENC-AES-CTR 加密的视频播放场景中  
mp4QNDrmComKey | mp4 七牛私有 drm 的解密 comkey 用于七牛私有 mp4 加密的视频播放场景中  
mp4QNDrmFileKey | mp4 七牛私有 drm 的解密 filekey 用于七牛私有 mp4 加密的视频播放场景中  
  
## MP4 加密

目前 QPlayer2 仅支持显式的 CENC-AES-CTR 加密，暂不支持隐式加密。CENC-AES-CTR 的初始化向量(IV)
由加密工具随机生成并存储在加密后的 MP4 文件中，QPlayer2 读取 MP4 文件的 IV 和QmediaModel 存储的 mp4DrmKey
进行解密。

### 配置DRM加密视频

    
    
    //七牛私有HLS解密密钥
    builder = QMediaModelBuilder()
            url = "http://80rjmu.com1.z0.glb.clouddn.com/ncyY3ioGAakS-Lg252IscrXNxyo=/Fgny-rvibYqoFP-lPkI53JfmoIx5"
            builder.addStreamElement("", QURLType.QAUDIO_AND_VIDEO, 0, url, true, "", "", QVideoRenderType.PLANE, "testtesttesttest")
            mVideoList.add(Pair(url, builder.build(false)))
    
    //公有MP4解密密钥
    builder = QMediaModelBuilder()
            url = "https://sdk-release.qnsdk.com/CENC_AES-CTR.mp4"
            builder.addStreamElement("", QURLType.QAUDIO_AND_VIDEO, 0, url, true, "", "", QVideoRenderType.PLANE, "", "c7e16c4403654b85847037383f0c2db3")
            mVideoList.add(Pair(url, builder.build(false)))
    
    //私有MP4解密密钥
    builder = QMediaModelBuilder()
            url = "https://kodo.koafun.com/drm-16884581530881688458153078-new.mp4"
            builder.addStreamElement("", QURLType.QAUDIO_AND_VIDEO, 0, url, true, "", "", QVideoRenderType.PLANE, "", "", "comnew", "filenew")
            mVideoList.add(Pair(url, builder.build(false)))
    

### 对 MP4 文件进行 CENC-AES-CTR 加密

可以选择 ffmpeg 对一个 MP4 进行加密。加密命令如下：

    
    
    ffmpeg -i test2.mp4  -vcodec copy -acodec copy -encryption_scheme cenc-aes-ctr -encryption_key c7e16c4403654b85847037383f0c2db3 -encryption_kid a7e61c373e219033c21091fa607bf3b8 encrypted_IV_test.mp4
    

命令说明

test2.mp4 为输入文件

encrypted_IV_test.mp4 为输出文件

-vcodec copy 和 -acodec copy ：指定视频流和音频流的编码方式为原始流的拷贝，即不进行重新编码，保持与原始文件相同的编码格式。

-encryption_scheme cenc-aes-ctr ：设置加密方案为 CENC-AES-CTR，即采用 CTR 模式进行加密。

-encryption_key c7e16c4403654b85847037383f0c2db3 指定加密所使用的密钥，即解密所使用的 key。这里的c7e16c4403654b85847037383f0c2db3 是一个示例密钥，您可以替换为您自己的密钥，格式要求为：16字节的16进制数据。

-encryption_kid a7e61c373e219033c21091fa607bf3b8：指定加密所使用的密钥标识符（KID）。这里的 a7e61c373e219033c21091fa607bf3b8 是一个示例 KID，您可以替换为您自己的密钥标识符, 格式要求为：16字节的16进制数据。

加密后可通过ffplay 4.4 版本进行播放验证，播放正常即可。命令如下：

    
    
    ffplay encrypted_IV_test.mp4 -decryption_key c7e16c4403654b85847037383f0c2db3
    

### QPlayer2 对加密后 MP4 文件进行播放

    
    
    builder = QMediaModelBuilder()
    //配置视频元素
    builder.addStreamElement(
                "",                                             //userType 预留字段可以填空
                QURLType.QAUDIO_AND_VIDEO,                      //资源的类型，指该元素是包含音频还是视频还是都包含
                1080,                                           //清晰度数值标记
                "https://sdk-release.qnsdk.com/CENC_AES-CTR.mp", //视频的地址
                true                                            //是否是默认元素，默认元素是指，起播的时候默认会播放这个元素
                "c7e16c4403654b85847037383f0c2db3"              //mp4 解密使用的秘钥
    )
    
    
    val mediaModel = builder.build(false) //此处的参数指代该资源是否是一个直播资源，是直播的话就是true,不是直播就是false                                                   //预留字段，可以为
    

## 视频URL类型枚举

    
    
    QAUDIO_AND_VIDEO(0),  //包含视频和音频
    QAUDIO(1),            //仅包含音频
    QVIDEO(2),           //仅包含视频
    NONE(5);             //无
    

## 播放媒体资源

上一步中我们创建了一些mediaModel，接下来我们将播放他们

    
    
    mQPlayerView.playerControlHandler.playMediaModel(
                    mediaModel, //要播放的媒体资源
                    0           //起播的位置，如果是直播这里必须为0，不然有不可预见的问题发生
    )
    

## 播放错误监听

当视频起播时，如果遇到网络等问题原因，导致没有和服务器建立链接，那么会导致视频播放失败，总共会有3次重试机会，3次都失败的情况下会回调onOpenFailed方法  
当视频已经起播后，在中途遇到网络等问题原因，进入buffer,
那么内核会一直重试，直到再次连接上服务端继续播放，或者用户通过接口停止播放该视频，在重试的过程中  
onReconnectStart/onReconnectEnd会被回调，那么可以监控回调的次数来做业务层的结束播放的策略，当然在正常播放的情况下，这两个方法也是会被回调的，所以  
最好结合buffer的回调数据一起使用。

    
    
    val listener =  object: QIPlayerMediaNetworkListener {
    
        /***
         * 开始重连
         *
         * @param userType 重连stream element的userType
         * @param urlType 重连stream element的urlType
         * @param url 重连的url
         * @param retryTime 已重试的次数
         */
        override fun onReconnectStart(userType: String, urlType: QURLType, url: String, retryTime: Int) {
        }
    
        /***
         * 重连结束
         *
         * @param userType 重连stream element的userType
         * @param urlType 重连stream element的urlType
         * @param url 重连的url
         * @param retryTime 已重试的次数
         * @param error 错误码
         */
        override fun onReconnectEnd(userType: String, urlType: QURLType, url: String, retryTime: Int, error: OpenError) {
        }
    
        /***
         * 打开失败
         *
         * @param userType 重连stream element的userType
         * @param urlType 重连stream element的urlType
         * @param url 重连的url
         * @param error 错误码
         */
        override fun onOpenFailed(userType: String, urlType: QURLType, url: String, error: OpenError) {
    
             //当非用户中断时，才展示错误面板
             if (error != QIPlayerMediaNetworkListener.OpenError.INTERRUPT) {
                //展示错误面板
             }
        }
    }
    
    //添加监听
    mQPlayerView.playerControlHandler.addPlayerMediaNetworkListener(listener)
    //移除监听
    mQPlayerView.playerControlHandler.removePlayerMediaNetworkListener(listener)
    //移除所有监听
    mQPlayerView.playerControlHandler.removeAllPlayerMediaNetworkListener()
    

## 播放错误码枚举

    
    
    UNKOWN(10000),
    NONE(0),
    IOERROR(-5),
    INTERRUPT(-1414092869),
    URL_INVALID(-875574520),
    FORMAT_INVALID(-1094995529);
    

# SRT字幕

要使用SRT字幕需要满足四个条件

  1. 视频源配置了字幕（配置方式参阅播放条目）
  2. 字幕功能开启`mQPlayerView.playerControlHandler.setSubtitleEnable(true)`
  3. 有生效的字幕（以下方式二选一）

  * 在视频源配置了`isDefault=True`的字幕启动播放
  * 通过接口设置生效字幕 `mQPlayerView.playerControlHandler..setSubtitle(subtitleName)`

  4. 通过字幕回调监听，每当时间轴触发了新的字幕条目，则该监听会回调，并将最新的一条字幕文案传递给监听接口，用户可以以自己的展示方式来展示这条字幕条目

## 字幕回调监听

    
    
    val listener =  object: QIPlayerSubtitleListener {
    
        //text:最新的字幕条目
        override fun on_subtitle_text_change(text: String) {
            //更新字幕文案展示
        }
    
        //name:字幕的名称
        override fun on_subtitle_name_change(name: String) {
           //字幕变更回调
        }
    
        override fun on_subtitle_close() {
            //字幕能力关闭
        }
        //name:字幕的名称
        //result:加载的结果
        override fun on_subtitle_loaded(name: String, result: Boolean) {
            //字幕加载结束
        }
        //name:字幕的名称
        //result:解析的结果
        override fun on_subtitle_decoded(name: String, result: Boolean) {
            //字幕解析结束
        }
    }
    
    //添加监听
    mQPlayerView.playerControlHandler.addPlayerSubtitleListener(listener)
    //移除监听
    mQPlayerView.playerControlHandler.removePlayerSubtitleListener(listener)
    //移除所有监听
    mQPlayerView.playerControlHandler.removeAllPlayerSubtitleListeners()
    

# 预加载

播放预加载实例用于点播（一般用在短视频场景中），不能用在直播上，播放预加载实例能提升首帧的速度。  
预加载的加载时机策略由业务层自行实现

## 预加载的简单使用

    
    
    //创建预加载实例,一般是提前创建
    mediaItem = QMediaItemContext(mediaModel, logLevel, localStorageDir, startPos)
    
    //开始预加载
    mediaItem.playMediaControlHandler.start()
    
    //在需要播放时调用播放预加载实例接口
    mQPlayerView.playerControlHandler.playMediaItem(mediaItem)
    
    //播放完成后
    停止预加载实例
    mediaItem.playMediaControlHandler.stop()
    

## 预加载实例状态枚举

    
    
    NONE(100)         //初始状态
    PREPARE(101)      //开始加载前，准备工作状态
    LOADING(102)      //开始加载
    PAUSED(103)       //暂停加载
    STOPED(104)       //停止加载, 无法恢复
    ERROR(105)        //加载出错
    PREPARE_USE(106)  //准备使用播放
    USED(107)         //已经使用
    DISCARD(108)      //废弃
    

## 预加载实例状态监听

    
    
    val listener =  object: QIMediaItemStateChangeListener {
    
        //状态变更回调时回调，返回变更后的状态
         override fun onStateChanged(state: QMediaItemState) {
    
        }
    }
    
    //添加监听
    mediaItem.playMediaControlHandler.addMediaItemStateChangeListener(listener)
    //移除监听
    mediaItem.playMediaControlHandler.removeMediaItemStateChangeListener(listener)
    //移除所有监听
    mediaItem.playMediaControlHandler.removeAllPlayMediaStateChangeListener()
    

## 预加载的状态枚举

    
    
    NONE(100),//初始状态
    PREPARE(101),
    LOADING(102),
    PAUSED(103),
    STOPED(104),
    ERROR(105),
    PREPARE_USE(106),
    USED(107),
    DISCARD(108);
    

# 设置日志等级

    
    
    mQPlayerView.playerControlHandler.setLogLevel(QLogLevel.LOG_INFO)
    

# 暂停播放

    
    
    mQPlayerView.playerControlHandler.pauseRender()
    

# 恢复播放

    
    
    mQPlayerView.playerControlHandler.resumeRender()
    
    

# 停止当前视频播放

    
    
    mQPlayerView.playerControlHandler.stop()
    
    

# 鉴权

鉴权功能介绍见鉴权[相关文档 __](https://developer.qiniu.io/pili/12223/qplayer2-core-
authentication-related)

## 强制网络鉴权

调用该接口时，会强制进行网络鉴权

    
    
    mQPlayerView.playerControlHandler.forceAuthenticationFromNetwork() 
    

## 鉴权监听

    
    
    val listener =  object: QIPlayerAuthenticationListener {
        /**
         * 鉴权失败回调，同时，会停止当前的视频播放，且状态切换成 @see[QPlayerState.ERROR]
         */
        fun on_authentication_failed(error_type: QAuthenticationErrorType) {
        }
    
        /**
         * 鉴权成功回调
         */
        fun on_authentication_success() {
        }
    
    }
    
    //添加监听
    mQPlayerView.playerControlHandler.addPlayerAuthenticationListener(listener)
    //移除监听
    mQPlayerView.playerControlHandler.removePlayerAuthenticationListener(listener)
    //移除所有监听
    mQPlayerView.playerControlHandler.removeAllPlayerAuthentications()
    
    

## 鉴权错误码枚举

    
    
    NONE(0),         //鉴权出错
    NO_BASE_AUTH(1), //缺少基础功能权限
    NO_VR_AUTH(2),   //缺少VR功能权限
    NO_BLIND_AUTH(3),//缺少色盲功能权限
    NO_SEI_AUTH(4),  //缺少SEI功能权限
    NO_SRT_AUTH(5);  //缺少SRT功能权限
    

# 切换视频进度

    
    
    mQPlayerView.playerControlHandler.seek(2000) //参数为要定位的position 单位毫秒
    
    

## Seek监听

一般情况下
Seek都会成功，如果网络差的情况下，会无限loading（除非调用stop才会停止），直到seek到指定的位置为止。但是如果视频源本身是格式不正确的，比如pts错乱等问题，才会导致seek失败，所以在seek失败回调中，做好提示工作就好，不要尝试再次seek

    
    
    val listener =  object: QIPlayerSeekListener {
    
    	/*seek失败回调*/
    	override fun onSeekFailed() {
        }
    	/*seek成功回调*/
        override fun onSeekSuccess() {
        }
    	
    }
    //添加监听
    mQPlayerView.playerControlHandler.addPlayerSeekListener(listener)
    //移除监听
    mQPlayerView.playerControlHandler.removePlayerSeekListener(listener)
    //移除所有监听
    mQPlayerView.playerControlHandler.removeAllPlayerSeekListener()
    

# 视频解码方式

目前支持的解码方式有:软解、硬解、混解、自动，一般情况下，使用自动，内核会根据手机的硬件情况，要播放的视频资源编码格式等信息，挑选  
合适的解码方式进行解码，各个解码方式的优劣情况如下  
软解: 解码较慢，但是兼容性好，适用于所有手机  
硬解：解码性能高，但是有一定兼容性，比如某些视频资源的编码格式，硬解芯片不支持。并且相对于软解，首帧的速度慢。  
混解：通过软解和硬解相结合的方式，提升首帧速度，加快了解码性能，但是在能耗上略有劣势。

## 设置优先选择哪种解码方式

    
    
    //下一次播放生效
    //最终的解码方式可能不是设置的，在设置的解码方式不能正确解码时，内核会进行降级，选择其他能正确解码的方式
    mQPlayerView.playerControlHandler.setDecodeType(QPlayerSetting.QPlayerDecoder.QPLAYER_DECODER_SETTING_AUTO)
    
    

## 优先选择的解码方式枚举

    
    
    //自动选择
    QPLAYER_DECODER_SETTING_AUTO(0),
    //硬解优先
    QPLAYER_DECODER_SETTING_HARDWARE_PRIORITY(1),
    //软解优先
    QPLAYER_DECODER_SETTING_SOFT_PRIORITY(2),
    //混解优先
    QPLAYER_DECODER_SETTING_FIRST_FRAME_ACCEL_PRIORITY(3),
    

## 播放器视频解码监听相关

    
    
    val listener =  object: QIPlayerVideoDecodeListener {
    
        //setDecodeType 设置了优先使用哪种解码方式，经过内核处理后，该回调将正真的视频解码方式返回
        override fun onVideoDecodeByType(type: QPlayerDecodeType) {
    
        }
    
        
        // 如果当前视频编码 所在设备或者sdk不支持 则回调该方法
        // codecId 视频的编码id
        override fun notSupportCodecFormat(codecId: Int) {
        }
        /**
         * 通知当前视频解码失败，
         * @param retry 底层是否重试
         */
        override fun onDecodeFailed(retry: Boolean) {
        }
    }
    //添加监听
    mQPlayerView.playerControlHandler.addPlayerVideoDecodeTypeListener(listener)
    //移除监听
    mQPlayerView.playerControlHandler.removePlayerVideoDecodeTypeListener(listener)
    //移除所有播放器视频解码监听
    mQPlayerView.playerControlHandler.removeAllPlayerVideoDecodeTypeListener()
    
    

## 解码方式枚举

    
    
    NONE(0),
    //软解
    SOFTWARE(1),
    //surface 硬解
    HARDWARE_SURFACE(10),
    //buffer 硬解
    HARDWARE_BUFFER(11),
    //混解
    FIRST_FRAME_ACCEL(2);
    

# 设置起播方式

    
    
    //下一次播放生效
    mQPlayerView.playerControlHandler.setStartAction(QPlayerSetting.QPlayerStart.QPLAYER_START_SETTING_PLAYING) 
    
    

## 解码方式枚举

    
    
    //起播播放
    QPLAYER_START_SETTING_PLAYING(0),
    //起播暂停在首帧
    QPLAYER_START_SETTING_PAUSE(1),
    

# 设置seek方式

    
    
    //下一次seek即生效
    mQPlayerView.playerControlHandler.setSeekMode(QPlayerSetting.QPlayerSeek.QPLAYER_SEEK_SETTING_NORMAL) 
    
    

## seek方式枚举

    
    
    //关键帧seek，每次seek 都seek到离目标位置向前的最近的一个关键帧，耗时少
    QPLAYER_SEEK_SETTING_NORMAL(0),
    //精准seek，耗时比关键帧seek多，且耗时和视频的gop间隔的大小成正比
    QPLAYER_SEEK_SETTING_ACCURATE(1),
    

# 获取当前播放状态

    
    
    mQPlayerView.playerControlHandler.currentPlayerState
    
    

# 获取当前进度

    
    
    mQPlayerView.playerControlHandler.currentPosition
    
    

# 获取视频时长

    
    
    mQPlayerView.playerControlHandler.duration
    
    

# 获取当前下载速度

    
    
    mQPlayerView.playerControlHandler.downloadSpeed
    
    

# 获取当前缓冲进度

    
    
    mQPlayerView.playerControlHandler.bufferPositon
    
    

# 播放器状态

在整个播放器实例生命周期中，播放会在不同的状态中切换，用户可以通过监听播放器状态，实现上层业务逻辑，  
比如在视频播放完时，展示其他视频的推荐页，那么只需监听播放器状态回调，待回调的状态是COMPLETED，则展示推荐页

## 播放器状态枚举

    
    
    NONE(0),//初始状态
    INIT(1),//播放器开始创建各种对象 
    PREPARE(2),//开始播放一个新视频的时候先进入这个状态，进行一些准备工作
    PLAYING(4),//播放中
    PAUSED_RENDER(6),//暂停
    COMPLETED(7),//播放完成
    SEEKING(8), //SEEK
    
    STOPPED(9),//播放停止
    ERROR(10),//播放出错
    END(11),//播放器释放各种对象完成
    MEDIA_ITEM_PREPARE(12), //预加载的Prepare状态，开始播放一个新视频的时候先进入这个状态，进行一些准备工作
    RELEASE(13); //播放器结束，且释放各类资源
    

## 播放器状态变更监听相关

    
    
    val listener =  object: QIPlayerStateChangeListener {
    
        //状态变更回调时回调，返回变更后的状态
        override fun onStateChanged(state: QPlayerState) {
    
        }
    }
    
    //添加监听
    mQPlayerView.playerControlHandler.addPlayerStateChangeListener(listener)
    //移除监听
    mQPlayerView.playerControlHandler.removePlayerStateChangeListener(listener)
    //移除所有监听
    mQPlayerView.playerControlHandler.removeAllPlayerStateChangeListener()
    
    

# 播放速度

播放速度的调整范围建议在0.25-2.0之间，这样会有一个比较好的效果

## 设置播放速度

    
    
    //即时播放生效
    mQPlayerView.playerControlHandler.setSpeed(1.25f) //参数为倍速值
    
    

## 获取当前速度

    
    
    mQPlayerView.playerControlHandler.speed
    
    

## 速度变更监听相关

    
    
    val listener =  object: QIPlayerSpeedListener {
    
        //播放倍速变更回调
        override fun onSpeedChanged(speed: Float) {
    
        }
    }
    
    //添加监听
    mQPlayerView.playerControlHandler.addPlayerSpeedChangeListener(listener)
    //移除监听
    mQPlayerView.playerControlHandler.removePlayerSpeedChangeListener(listener)
    //移除所有监听
    mQPlayerView.playerControlHandler.removeAllPlayerSpeedChangeListener()
    
    

# 清晰度

QPlayer2提供的清晰度能力是无缝切换清晰度方案，在切换过程中，画面是连续的画面，不会出现类似跳帧的情况。  
使用无缝切换清晰度功能，必须在mediaModel中配置多路清晰度，如果使用非immediately方式(即异步方式)时，同时只能有一次切换会话存在，会话结束后，才能切下一次

## 切换视频清晰度

    
    
    //使用该方法时，mediaModel中需要存在多个StreamElement
    mQPlayerView.playerControlHandler.switchQuality(
                                 "",                          //要切换的StreamElement的userType
                                 QURLType.QAUDIO_AND_VIDEO,   //要切换的StreamElement的urltype
                                 1080,                        //要切换的StreamElement的清晰度数值标记
                                 false                        //是否immediately方式，true:立即切换过程中会出现卡一下，一般用于直播，false:非立即切换下，切换过程无缝切换，只能用于点播。
    ) 
    
    

## 获取当前的清晰度

    
    
    //使用该方法时，mediaModel中需要存在多个StreamElement，返回清晰度数值标记
    mQPlayerView.playerControlHandler.getCurrentQuality(
                                 "",                          //要查询的StreamElement的userType
                                 QURLType.QAUDIO_AND_VIDEO   //要查询的StreamElement的urltype
    ) 
    
    

## 获取当前正在切换的清晰度

    
    
    //获取指定 userType urlType 流正在切换的清晰度（非immediately方式）
    mQPlayerView.playerControlHandler.getSwitchingQuality(
                                 "",                          //要查询的StreamElement的userType
                                 QURLType.QAUDIO_AND_VIDEO   //要查询的StreamElement的urltype
    ) 
    
    

## 播放器清晰度相关监听

    
    
    val listener =  object: QIPlayerQualityListener {
    
        /**
         * 开始清晰度切换
         * @param userType 开始切换清晰度的stream element的userType
         * @param urlType 开始切换清晰度的stream element的 urlType @see[QURLType]
         * @param newQuality 要切到哪路清晰度
         * @param oldQuality 切换前的清晰度
         */
        override fun onQualitySwitchStart(userType: String, urlType: QURLType, newQuality: Int, oldQuality: Int) {
    
        }
    
        /**
         * 清晰度切换完成
         * @param userType 开始切换清晰度的stream element的userType
         * @param urlType 开始切换清晰度的stream element的urlType @see[QURLType]
         * @param newQuality 要切到哪路清晰度
         * @param oldQuality 切换前的清晰度
         */
        override fun onQualitySwitchComplete(userType: String, urlType: QURLType, newQuality: Int, oldQuality: Int) {
        }
    
        /**
         * 清晰度切换取消
         * @param userType 开始切换清晰度的stream element的userType
         * @param urlType 开始切换清晰度的stream element的 urlType @see[QURLType]
         * @param newQuality 要切到哪路清晰度
         * @param oldQuality 切换前的清晰度
         */
        override fun onQualitySwitchCanceled(userType: String, urlType: QURLType, newQuality: Int, oldQuality: Int) {
        }
    
        /**
         * 清晰度切换失败
         * @param userType 开始切换清晰度的url流的userType
         * @param urlType 开始切换清晰度的url流的 urlType @see[QURLType]
         * @param newQuality 要切到哪路清晰度
         * @param oldQuality 切换前的清晰度
         */
        override fun onQualitySwitchFailed(userType: String, urlType: QURLType, newQuality: Int, oldQuality: Int) {
        }
    
        /**
         * 目前仅支持同时有一个清晰度切换，如果前一个还未切换完，再次发起切换 会回调这个函数
         * @param userType 开始切换清晰度的url流的userType
         * @param urlType 开始切换清晰度的url流的 urlType @see[QURLType]
         * @param newQuality 要切到哪路清晰度
         * **/
        override fun onQualitySwitchRetryLater(userType: String, urlType: QURLType, newQuality: Int) {
        }
    }
    
    //添加监听
    mQPlayerView.playerControlHandler.addPlayerQualityListener(listener)
    //移除监听
    mQPlayerView.playerControlHandler.removePlayerQualityListener(listener)
    //移除所有监听
    mQPlayerView.playerControlHandler.removeAllPlayerQualityListener()
    
    

## 直播支持多路清晰度切换

无缝切换清晰度方案需要配置以下三个条件

  1. 首先切换的模式要选择立即切换即：immediately=true
  2. 服务端配置PTS透传，
  3. 对应的清晰度要做主动转码设置

如果无法满足以上3个条件，那么建议用最基础的能力来实现清晰度切换，即每个清晰度都是一个media
model，切换时，调用playMediaModel来播放新的清晰度流。

# 播放进度监听

只有在点播的时候 duration 和 progress 才有意义，直播的话这两个值没有参考意义

    
    
    val listener =  object: QIPlayerProgressListener {
    
        /**
         * 进度变更回调
         * @param duration 总时长 单位毫秒
         * @param progress 当前进度 单位毫秒
         */
        override fun onProgressChanged(duration: Long, progress: Long) {
    
        }
    }
    
    //添加监听
    mQPlayerView.playerControlHandler.addPlayerProgressChangeListener(listener)
    //移除监听
    mQPlayerView.playerControlHandler.removePlayerProgressChangeListener(listener)
    //移除所有监听
    mQPlayerView.playerControlHandler.removeAllPlayerProgressChangeListener()
    
    

# 实时帧率

## 获取当前fps

    
    
    mQPlayerView.playerControlHandler.fps
    
    

## 实时帧率监听

    
    
    val listener =  object: QIPlayerFPSListener {
    
        /**
         * fps 改变的回调
         * @param fps 帧率
         */
        override fun onFPSChanged(fps: Int) {
    
        }
    }
    
    //添加监听
    mQPlayerView.playerControlHandler.addPlayerFPSChangeListener(listener)
    //移除监听
    mQPlayerView.playerControlHandler.removePlayerFPSChangeListener(listener)
    //移除所有监听
    mQPlayerView.playerControlHandler.removeAllPlayerFPSChangeListener()
    
    

# 像素格式或者音频sample格式不支持的监听

    
    
    val listener =  object: QIPlayerFormatListener {
    
        /**
         * 当前有format 不支持，所以视频没法播放
         */
        override fun onFormatNotSupport() {
    
        }
    }
    
    //添加监听
    mQPlayerView.playerControlHandler.addPlayerFormatListener(listener)
    //移除监听
    mQPlayerView.playerControlHandler.removePlayerFormatListener(listener)
    //移除所有监听
    mQPlayerView.playerControlHandler.removeAllPlayerFormatListeners()
    
    

# 缓冲拉流监听

    
    
    val listener =  object: QIPlayerDownloadListener {
    
        /**
         * 拉流速率改变
         * @param speed 单位 b/s（比特每秒）
         * @param bufferProgress 缓冲的进度
         */
        override onDownloadChanged(speed: Long, bufferProgress: Long) {
    
        }
    }
    
    //添加监听
    mQPlayerView.playerControlHandler.addPlayerDownloadChangeListener(listener)
    //移除监听
    mQPlayerView.playerControlHandler.removePlayerDownloadChangeListener(listener)
    //移除所有监听
    mQPlayerView.playerControlHandler.removeAllPlayerDownloadChangeListener()
    
    

# 播放器操作不允许执行监听

在某些状态下，一些操作将不被执行，通过该监听回调上来通知

    
    
    val listener =  object: QIPlayerCommandNotAllowListener {
    
        /**
         * 操作不被允许
         * @param commandName 操作名称
         * @param state 操作被检测时播放器的状态
         */
        override fun onCommandNotAllow(commandName: String, state: QPlayerState) {
    
        }
    }
    
    //添加监听
    mQPlayerView.playerControlHandler.addPlayerCommandNotAllowListener(listener)
    //移除监听
    mQPlayerView.playerControlHandler.removePlayerCommandNotAllowListener(listener)
    //移除所有监听
    mQPlayerView.playerControlHandler.removeAllPlayerCommandNotAllowListener()
    
    

# buffering 监听

    
    
    val listener =  object: QIPlayerBufferingListener {
    
        /**
         * 开始buffering
         */
        override fun onBufferingStart() {
            //展示buffering UI
        }
    
         /**
         * 结束buffering
         */
        override fun onBufferingEnd() {
            //隐藏buffering UI
        }
    }
    
    //添加监听
    mQPlayerView.playerControlHandler.addPlayerBufferingChangeListener(listener)
    //移除监听
    mQPlayerView.playerControlHandler.removePlayerBufferingChangeListener(listener)
    //移除所有监听
    mQPlayerView.playerControlHandler.removeAllPlayerBufferingChangeListener()
    
    

# 码率

## 获取当前码率

    
    
    mQPlayerView.playerControlHandler.biteRate
    
    

## 实时码率监听

    
    
    val listener =  object: QIPlayerBiteRateListener {
    
        /**
         * 码率变换回调
         * @param biteRate 比特率， 单位：bps
         */
        override fun onBiteRateChanged(biteRate: Long) {
        }
    }
    
    //添加监听
    mQPlayerView.playerControlHandler.addPlayerBiteRateChangeListener(listener)
    //移除监听
    mQPlayerView.playerControlHandler.removePlayerBiteRateChangeListener(listener)
    //移除所有监听
    mQPlayerView.playerControlHandler.removeAllPlayerBiteRateChangeListener()
    
    

# SEI 数据回调

用户自定义数据可以带在H264或者H265的编码帧里，用于实现一些基于时间轴的业务场景。  
开启该功能后，如果视频资源内带有SEI数据 会通过监听接口抛给业务层。  
该功能需要单独开通，具体可以联系技术支持咨询。

## 开启 SEI

    
    
    mQPlayerView.playerControlHandler.setSEIEnable(true)
    

## SEI 数据监听

    
    
    val listener =  object: QIPlayerSEIDataListener {
    
        /**
         * SEI 数据回调，且回调时机为SEI数据所在帧的时间
         * @param data SEI 数据
         */
        override fun onSEIData(data: ByteArray) {
        }
    }
    
    //添加监听
    mQPlayerView.playerControlHandler.addPlayerSEIDataListener(listener)
    //移除监听
    mQPlayerView.playerControlHandler.removePlayerSEIDataListener(listener)
    //移除所有监听
    mQPlayerView.playerControlHandler.removeAllPlayerSEIDataListeners()
    
    

# 音频数据上抛相关监听

## 开启音频数据监听

    
    
    mQPlayerView.playerControlHandler.setAudioDataCallbackEnable(true)
    

## 音频数据监听

    
    
    val listener =  object: QIPlayerAudioDataListener {
    
        /**
         * 音频数据回调
         * @param sampleRate 采样率
         * @param format 音频位宽
         * @param channelNum 声道数
         * @param channelLayout 声道布局
         * @param data 音频数据
         */
        override fun onAudioData(sampleRate: Int, format: QSampleFormat, channelNum: Int, channelLayout:QChannelLayout, data: ByteArray){
        }
    }
    
    //添加监听
    mQPlayerView.playerControlHandler.addPlayerAudioDataListener(listener)
    //移除监听
    mQPlayerView.playerControlHandler.removePlayerAudioDataListener(listener)
    //移除所有监听
    mQPlayerView.playerControlHandler.removeAllPlayerAudioDataListeners()
    
    

# 视频数据上抛相关监听

## 开启视频数据监听

    
    
    mQPlayerView.playerControlHandler.setVideoDataCallbackEnable(true)
    

## 音频数据监听

    
    
    val listener =  object: QIPlayerVideoDataListener {
    
        /***
         * 视频数据回调
         * @param width 视频宽度
         * @param height 视频高度
         * @param type 视频数据类型
         * @param data 视频数据
         */
        override fun onVideoData(width: Int, height: Int, type: QVideoDataType, data: ByteArray) {
        }
    }
    
    //添加监听
    mQPlayerView.playerControlHandler.addPlayerVideoDataListener(listener)
    //移除监听
    mQPlayerView.playerControlHandler.removePlayerVideoDataListener(listener)
    //移除所有监听
    mQPlayerView.playerControlHandler.removeAllPlayerVideoDataListeners()
    
    

# 渲染相关监听

    
    
    val listener =  object: QIPlayerRenderListener {
    
        //首帧耗时回调 从play 开始到首帧渲染出来的总耗时 单位毫秒
        override fun onFirstFrameRendered(elapsedTime: Long) {
    
        }
    }
    
    //添加监听
    mQPlayerView.playerRenderHandler.addPlayerRenderListener(listener)
    //移除监听
    mQPlayerView.playerRenderHandler.removePlayerRenderListener(listener)
    //移除所有监听
    mQPlayerView.playerRenderHandler.removeAllPlayerRenderListener()
    
    

# 设置视频渲染比例

    
    
    mQPlayerView.playerRenderHandler.setRenderRatio(QPlayerSetting.QPlayerRenderRatio.QPLAYER_RATIO_SETTING_AUTO) 
    

## 视频比例枚举

    
    
    //自动
    QPLAYER_RATIO_SETTING_AUTO(1),
    //拉升
    QPLAYER_RATIO_SETTING_STRETCH(2),
    //铺满
    QPLAYER_RATIO_SETTING_FULL_SCREEN(3),
    //16:9
    QPLAYER_RATIO_SETTING_16_9(4),
    //4:3
    QPLAYER_RATIO_SETTING_4_3(5),
    

# 设置色觉优化

    
    
    mQPlayerView.playerControlHandler.setBlindType(QPlayerSetting.QPlayerBlind.QPLAYER_BLIND_SETTING_NONE) 
    

## 色觉优化枚举

    
    
    //无
    QPLAYER_BLIND_SETTING_NONE(0),
    //红色盲
    QPLAYER_BLIND_SETTING_RED(1),
    //绿色盲
    QPLAYER_BLIND_SETTING_GREEN(2),
    //蓝色盲
    QPLAYER_BLIND_SETTING_BLUE(3),
    

# VR视频播放

在播放VR视频时，需要在mediaModel中配置vr视频的资源
并且将streamElement的videoRenderType设置为QVideoRenderType.PANORAMA_EQUIRECT_ANGULAR  
然后通过设置vr视频视角改变视频朝向  
该功能需要单独开通，具体可以联系技术支持咨询。

    
    
    /***
    * 设置VR视频的旋转角度
    * @param rotateX 横向角度 （360度制）
    * @param rotateY 纵向角度 （360度制）
    * @return true 设置成功 false 设置失败
    */
    mQPlayerView.playerRenderHandler.setPanoramaViewRotate(mCurrentRotationY, -mCurrentRotationX)
    
    

## 视频类型枚举

    
    
    NONE(-1),
    //普通视频
    PLANE(0),
    //ANGULAR类 VR视频
    PANORAMA_EQUIRECT_ANGULAR(1),
    

# 截图

调用截图接口，就会回调截图时的video画面，并以jpeg的格式返回

    
    
    mQPlayerView.playerControlHandler.shootVideo()
    

## 截图监听

    
    
    val listener =  object: QIPlayerShootVideoListener {
    
    	/**
         * 截图成功
         */
        override fun onShootSuccessful(
            image: ByteArray, //图片的二进制数组
            width: Int,			//图片的宽
            height: Int,		//图片的高
            type: QIPlayerShootVideoListener.ShootVideoType //图片的类型（jpeg）
        ) {
            //保存截图
            //展示截图
        }
    
    	/**
         * 截图失败回调
         */
        override fun onShootFailed() {
            
        }
    
    }
    
    //添加监听
    mQPlayerView.playerControlHandler.addPlayerShootVideoListener(listener)
    //移除监听
    mQPlayerView.playerControlHandler.removePlayerShootVideoListener(listener)
    //移除所有监听
    mQPlayerView.playerControlHandler.removeAllPlayerShootVideoListeners()
    

# 静音

控制播放器静音，且该静音非系统的声音设置，仅仅只是把播放器设置为静音

    
    
    /**
    @param isMute true: 静音 false：非静音
    */
    mQPlayerView.playerControlHandler.setMute(true)
    

## 静音监听

    
    
    val listener =  object: QIPlayerAudioListener {
    
    	//静音改变回调
    	override fun onMuteChanged(is_mute: Boolean) {
        }
    }
    //添加监听
    mQPlayerView.playerControlHandler.addPlayerAudioListener(listener)
    //移除监听
    mQPlayerView.playerControlHandler.removePlayerAudioListener(listener)
    //移除所有监听
    mQPlayerView.playerControlHandler.removeAllPlayerAudioListener()
    

# 视频宽高改变监听

在视频首帧渲染时，会调用该回调，告诉上层当前的视频宽高是多少，如果在这个这个视频播放的生命周期中，宽高改变，那么还会再次调用该回调

    
    
    val listener =  object: QIPlayerVideoFrameSizeChangeListener {
    
    	//宽：width 高：height
    	onVideoFrameSizeChanged(width: Int, height: Int) {
        }
    }
    //添加监听
    mQPlayerView.playerControlHandler.addPlayerVideoFrameSizeChangeListener(listener)
    //移除监听
    mQPlayerView.playerControlHandler.removePlayerVideoFrameSizeChangeListener(listener)
    //移除所有监听
    mQPlayerView.playerControlHandler.removeAllPlayerVideoFrameSizeChangeListener()
    

以上内容是否对您有帮助？