diff --git a/README.md b/README.md index d9e23a38c8cd360d8b99d01baa76519e9ce15bb9..bc7d3a1e826b477cd3e473daa536b8e2b4aa6556 100644 --- a/README.md +++ b/README.md @@ -14,16 +14,16 @@ 1. 修改`entry/src/main/ets/common/CustomConfigs.ets`中的配置项,补充hls视频流链接、缓冲区大小等配置,其中不同的hls视频流需要正确配置所有的配置项。 - | 配置参数名称 | 类型 | 说明 | - |--------------------------|----------------------------|---------------------------------------------------------| - | X_COMPONENT_ID | string | XComponent组件自定义id。 | - | URL | string | hls多码率流链接,需要保证链接有效 | - | PREFERRED_BUFFER_SIZE | number | 视频缓冲区大小,范围[5, 20],单位为MB,本示例中为最大值20MB。 | - | CACHED_PERCENT_THRESHOLD | number | 码率调节判决阈值,范围(0, 1),表示视频缓存区满时视频可播放时长的占比,超过则进行判决。本示例中为0.2。 | - | PLAYBACK_STRATEGY | media.PlaybackStrategy | 播放策略配置,默认应用PREFERRED_BUFFER_SIZE。参考AVPlayer文档说明。 | - | MEDIA_SOURCE | media.MediaSource | 播放媒体源配置,默认应用URL。参考AVPlayer文档说明。 | - | SELECTABLE_RESOLUTIONS | Map | 与URL同配置,需保证分辨率高度与码率一一对应 | - | SELECTABLE_NET_PATH | Array | 点击界面右上角查看信息的网络类型,参考Network Boost Kit相关说明 | + | 配置参数名称 | 类型 | 说明 | + |--------------------------|----------------------------|------------------------------------------------------------| + | X_COMPONENT_ID | string | XComponent组件自定义id。 | + | URL | string | hls多码率流链接,需要保证链接有效。 | + | PREFERRED_BUFFER_SIZE | number | 视频缓冲区大小,范围[5, 20],单位为MB,本示例中为最大值20MB。 | + | CACHED_PERCENT_THRESHOLD | number | 码率调节判决阈值占比,范围(0, 1),当可播放时长低于缓冲区最大可播放时长一定占比后则进行判决。本示例中为0.2。 | + | PLAYBACK_STRATEGY | media.PlaybackStrategy | 播放策略配置,默认应用PREFERRED_BUFFER_SIZE。参考AVPlayer文档说明。 | + | MEDIA_SOURCE | media.MediaSource | 播放媒体源配置,默认应用URL。参考AVPlayer文档说明。 | + | SELECTABLE_RESOLUTIONS | Map | 与URL同配置,需保证分辨率高度与码率一一对应。 | + | SELECTABLE_NET_PATH | Array | 点击界面右上角查看信息的网络类型,参考Network Boost Kit相关说明。 | 2. 启动应用后显示视频播放界面,可自由控制播放状态、进度以及分辨率,右上角的“信息”可查看当前视频播放相关信息。 @@ -78,49 +78,32 @@ ```typescript // AVPlayerController.ets avPlayer.on('bufferingUpdate', (infoType: media.BufferingInfoType, value: number) => { - this.currentBufferInfo = [infoType, value]; - if (infoType !== media.BufferingInfoType.CACHED_DURATION || this.isSeeking) { - return; - } - - const bitrateListLength = this.bitrateList.length; - // 码率列表不存在,因此直接退出 - if (bitrateListLength === 0) { - Logger.warn(`No available bitrate`); - return; - } - - // 当视频缓存可播放时长小于设定阈值时,根据当前下载速率调整码率 - if (value < CustomConfigs.CACHED_PERCENT_THRESHOLD * this.maxBufferValueList[this.currentBitrateIndex!]) { - // 匹配小于当前下载速率的码率,未匹配到则选择最低码率 - const targetBitrate = this.bitrateList.find((_value, index) => - index < bitrateListLength - 1 && - this.bitrateList[index] <= this.downloadRate && - this.bitrateList[index + 1] > this.downloadRate - ) ?? this.bitrateList[0]; - // 下调码率 - if (targetBitrate < this.bitrateList[this.currentBitrateIndex!]) { - customToast($r('app.string.resolution_auto_reducing'), ViewConstants.TOAST_TIME); - // 网络场景可能发生变化,重置峰值下载速率 - this.maxDownloadRate = 0; - this.setBitrate(targetBitrate); - } - } else { - // 已经达到最高码率 - if (this.currentBitrateIndex === bitrateListLength - 1) { + this.videoInfo.currentBufferInfoType = infoType; + this.videoInfo.currentBufferInfoValue = value; + + // 对于其他缓存类型信息、处于seek期间、或无法正常获取码率信息的视频源,不进行处理 + if (infoType !== media.BufferingInfoType.CACHED_DURATION || this.isSeeking || !this.isValidVideoStream()) { return; - } - /** - * 当前码率下视频缓存可播放时间达到上一级码率对应的最大缓存可播放时间,考虑切换到更高码率,此处不允许在切换未成功时进一步提高码率, - * 且需要近期网络峰值下载速率大于目前切换码率 - */ - const nextBitrateIndex = this.currentBitrateIndex! + 1; - if (value > this.maxBufferValueList[nextBitrateIndex] && !this.isNewResolutionCaching() && - this.maxDownloadRate >= this.bitrateList[nextBitrateIndex]) { - customToast($r('app.string.resolution_auto_increasing'), ViewConstants.TOAST_TIME); - this.setBitrate(this.bitrateList[nextBitrateIndex]); - } - } + } + + // 当视频缓存可播放时长小于设定阈值时,根据当前下载速率调整码率 + if (value < CustomConfigs.CACHED_PERCENT_THRESHOLD * this.maxBufferValueList[this.currentBitrateIndex!]) { + // 匹配小于当前下载速率的码率,未匹配到则选择最低码率 + const targetBitrate = this.findTargetBitrate(); + if (targetBitrate < this.bitrateList[this.currentBitrateIndex!]) { + this.autoReduceBitrate(targetBitrate); + } + } else { + /** + * 当前码率非最高码率、当前码率下视频缓存可播放时间达到上一级码率对应的最大缓存可播放时间、 + * 近期网络峰值下载速率大于目前切换码率、当前未处于分辨率切换中状态。满足四个条件,考虑上调一级码率 + */ + const nextBitrateIndex = this.currentBitrateIndex! + 1; + if (nextBitrateIndex < this.bitrateList.length && value > this.maxBufferValueList[nextBitrateIndex] && + this.maxDownloadRate >= this.bitrateList[nextBitrateIndex] && !this.isNewResolutionCaching()) { + this.autoIncreaseBitrate(this.bitrateList[nextBitrateIndex]); + } + } }); ``` diff --git a/entry/src/main/ets/viewmodel/AVPlayerController.ets b/entry/src/main/ets/viewmodel/AVPlayerController.ets index 452262eddac8b15ec603f2cc98b564c0362695e9..aad7c6f89e22891fc6bec433df8d572593ef661b 100644 --- a/entry/src/main/ets/viewmodel/AVPlayerController.ets +++ b/entry/src/main/ets/viewmodel/AVPlayerController.ets @@ -14,6 +14,7 @@ */ import { media } from '@kit.MediaKit'; +import { window } from '@kit.ArkUI'; import { BusinessError } from '@kit.BasicServicesKit'; import connection from '@ohos.net.connection'; import { customToast } from '../common/utils/CommonUtils'; @@ -146,6 +147,15 @@ export class AVPlayerController { return this.currentBitrateIndex !== this.currentResolutionIndex; } + private setScreenKeepOnStatus(status: boolean) { + const context = getContext(this); + window.getLastWindow(context).then((windowClass: window.Window) => { + windowClass.setWindowKeepScreenOn(status); + }).catch((err: BusinessError) => { + Logger.error(`Failed to keep screen on, code: ${err.code}`); + }); + } + // [Start GetDownloadRate] private setPlaybackInfoInterval(avPlayer: media.AVPlayer) { this.playbackCallbackInterval = setInterval(async () => { @@ -190,7 +200,6 @@ export class AVPlayerController { return; } // [EndExclude BasicCallback] - // 获取可选码率列表,增序排序并保存 this.bitrateList = bitrateList.sort((a, b) => (a - b)); // [StartExclude BasicCallback] @@ -235,8 +244,8 @@ export class AVPlayerController { this.videoInfo.currentBufferInfoType = infoType; this.videoInfo.currentBufferInfoValue = value; - // 对于无法正常获取码率信息的视频源,其他缓存类型信息,或处于seek期间,不进行处理 - if (!this.isValidVideoStream() || infoType !== media.BufferingInfoType.CACHED_DURATION || this.isSeeking) { + // 对于其他缓存类型信息、处于seek期间、或无法正常获取码率信息的视频源,不进行处理 + if (infoType !== media.BufferingInfoType.CACHED_DURATION || this.isSeeking || !this.isValidVideoStream()) { return; } // [EndExclude CustomBitrateRule] @@ -264,11 +273,11 @@ export class AVPlayerController { Logger.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`); // Seek完成 this.isSeeking = false; - this.currentTime = Math.floor(avPlayer.currentTime / CommonConstants.SECOND_TO_MS); + this.currentTime = Math.ceil(avPlayer.currentTime / CommonConstants.SECOND_TO_MS); }); avPlayer.on('timeUpdate', (timeInMs: number) => { if (this.isPlaying && !this.isSeeking) { - this.currentTime = Math.floor(timeInMs / CommonConstants.SECOND_TO_MS); + this.currentTime = Math.ceil(timeInMs / CommonConstants.SECOND_TO_MS); } }); avPlayer.on('error', (err: BusinessError) => { @@ -285,7 +294,15 @@ export class AVPlayerController { * @param avPlayer */ private setStatusChangeCallback(avPlayer: media.AVPlayer) { - avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => { + avPlayer.on('stateChange', (state: string, reason: media.StateChangeReason) => { + if (state === 'playing') { + this.isPlaying = true; + this.setScreenKeepOnStatus(true); + } else { + this.isPlaying = false; + this.setScreenKeepOnStatus(false); + } + switch (state) { case 'idle': Logger.info('AVPlayer state idle called.'); @@ -299,7 +316,7 @@ export class AVPlayerController { case 'prepared': Logger.info('AVPlayer state prepared called.'); this.setPlaybackInfoInterval(avPlayer); - this.durationTime = Math.floor(avPlayer.duration / CommonConstants.SECOND_TO_MS); + this.durationTime = Math.ceil(avPlayer.duration / CommonConstants.SECOND_TO_MS); if (this.isResumePlay) { this.seek(this.currentTime * CommonConstants.SECOND_TO_MS); avPlayer.play(() => { @@ -309,19 +326,15 @@ export class AVPlayerController { break; case 'playing': Logger.info('AVPlayer state playing called.'); - this.isPlaying = true; break; case 'paused': Logger.info('AVPlayer state paused called.'); - this.isPlaying = false; break; case 'completed': Logger.info('AVPlayer state completed called.'); - this.isPlaying = false; break; case 'stopped': Logger.info('AVPlayer state stopped called.'); - this.isPlaying = false; break; case 'released': Logger.info('AVPlayer state released called.'); @@ -329,7 +342,6 @@ export class AVPlayerController { case 'error': Logger.error(`AVPlayer state error called, reason: ${reason}`); customToast($r('app.string.error_info'), ViewConstants.TOAST_TIME_MS); - this.isPlaying = false; this.isResumePlay = true; avPlayer.reset(); break;