1/* 2 * Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import media from '@ohos.multimedia.media'; 17import resourceManager from '@ohos.resourceManager'; 18import emitter from '@ohos.events.emitter'; 19import Logger from '../utils/Logger'; 20import common from '@ohos.app.ability.common'; 21import { BusinessError } from '@kit.BasicServicesKit'; 22 23const CASE_ZERO = 0; 24const CASE_ONE = 1; 25const CASE_TWO = 2; 26const CASE_THREE = 3; 27 28export default class AvPlayManager { 29 private tag: string = 'AVPlayManager'; 30 private avPlayer: media.AVPlayer | null = null; 31 private surfaceID: string | null = null; 32 private mgr: resourceManager.ResourceManager | null = null; 33 private currentTime: number = 0; 34 // 视频当前时间 35 private durationTime: number = 0; 36 // 视频总长 37 private speedSelect: number | null = null; 38 // 倍速选择 39 private fileDescriptor: resourceManager.RawFileDescriptor | null = null; 40 private videoSrc: string | null = null; 41 private fileSrc: string | null = null; 42 private default_track_value: string = ''; 43 private track_name_value: string = ''; 44 private currentAudioTrackValue: number = 0 ; 45 public arrList: Array<SelectOption> = []; 46 // 字幕 47 public arrSubTitleList: Array<SelectOption> = [] 48 private currentSubtitleStateValue: number = 0 49 private text: string | null = '' 50 private startTime: number | null = 0 51 private duration: number | null = 0 52 53 /** 54 * 初始化视频 55 */ 56 async initPlayer(ctx: common.UIAbilityContext, surfaceId: string, callback: (avPlayer: media.AVPlayer) => void): Promise<void> { 57 Logger.info(this.tag, `initPlayer==initCamera surfaceId== ${surfaceId}`); 58 this.surfaceID = surfaceId; 59 Logger.info(this.tag, `initPlayer==this.surfaceID surfaceId== ${this.surfaceID}`); 60 try { 61 Logger.info(this.tag, 'initPlayer videoPlay avPlayerDemo'); 62 // 创建avPlayer实例对象 63 this.avPlayer = await media.createAVPlayer(); 64 // 创建状态机变化回调函数 65 await this.setAVPlayerCallback(callback); 66 Logger.info(this.tag, 'initPlayer videoPlay setAVPlayerCallback'); 67 this.mgr = ctx.resourceManager; 68 Logger.info(this.tag, 'initPlayer videoPlay this.mgr'); 69 this.videoSrc = 'test1.mp4' 70 this.fileDescriptor = await this.mgr.getRawFd(this.videoSrc); 71 Logger.info(this.tag, `initPlayer videoPlay fileDescriptor = ${JSON.stringify(this.fileDescriptor)}`); 72 this.avPlayer.fdSrc = this.fileDescriptor; 73 this.addSubtitleFdSrc() 74 this.mgr.getStringValue($r('app.string.default_track')).then((value: string) => { 75 this.default_track_value = value; 76 }).catch((error: BusinessError) => { 77 console.error('getStringValue promise error is ' + error); 78 }); 79 this.mgr.getStringValue($r('app.string.track_name')).then((value: string) => { 80 this.track_name_value = value; 81 }).catch((error: BusinessError) => { 82 console.error('getStringValue promise error is ' + error); 83 }); 84 // 字幕相关 85 await this.getSubtitleStates() 86 } catch (err) { 87 Logger.error(this.tag, `initPlayer initPlayer err:${JSON.stringify(err)}`); 88 } 89 } 90 91 // 注册avplayer回调函数 92 async setAVPlayerCallback(callback: (avPlayer: media.AVPlayer) => void, videoSrc?: string): Promise<void> { 93 // seek操作结果回调函数 94 if (this.avPlayer == null) { 95 Logger.info(this.tag, 'avPlayer has not init'); 96 return; 97 } 98 this.avPlayer.on('seekDone', (seekDoneTime) => { 99 Logger.info(this.tag, `setAVPlayerCallback AVPlayer seek succeeded, seek time is ${seekDoneTime}`); 100 }); 101 // error回调监听函数,当avPlayer在操作过程中出现错误时调用reset接口触发重置流程 102 this.avPlayer.on('error', (err) => { 103 Logger.error(this.tag, `setAVPlayerCallback Invoke avPlayer failed ${JSON.stringify(err)}`); 104 if (this.avPlayer == null) { 105 Logger.info(this.tag, 'avPlayer has not init'); 106 return; 107 } 108 this.avPlayer.reset(); 109 }); 110 // 状态机变化回调函数 111 this.avPlayer.on('stateChange', async (state, reason) => { 112 Logger.info(this.tag, 'stateChange is called >> state = ' + state) 113 if (this.avPlayer == null) { 114 Logger.info(this.tag, 'avPlayer has not init'); 115 return; 116 } 117 switch (state) { 118 case 'idle': // 成功调用reset接口后触发该状态机上报 119 this.avPlayer.release(); 120 this.avPlayerChoose(callback); 121 Logger.info(this.tag, 'setAVPlayerCallback AVPlayer state idle called.'); 122 break; 123 case 'initialized': // avplayer 设置播放源后触发该状态上报 124 Logger.info(this.tag, 'setAVPlayerCallback AVPlayerstate initialized called.'); 125 if(this.surfaceID){ 126 this.avPlayer.surfaceId = this.surfaceID; // 设置显示画面,当播放的资源为纯音频时无需设置 127 Logger.info(this.tag, `setAVPlayerCallback this.avPlayer.surfaceId = ${this.avPlayer.surfaceId}`); 128 this.avPlayer.prepare(); 129 } 130 break; 131 case 'prepared': // prepare调用成功后上报该状态机 132 Logger.info(this.tag, 'setAVPlayerCallback AVPlayer state prepared called.'); 133 this.getAudioTrack(); 134 this.durationTime = this.avPlayer.duration; 135 this.currentTime = this.avPlayer.currentTime; 136 this.avPlayer.play(); // 调用播放接口开始播放 137 callback(this.avPlayer); 138 break; 139 case 'playing': // play成功调用后触发该状态机上报 140 Logger.info(this.tag, 'setAVPlayerCallback AVPlayer state playing called.'); 141 let eventDataTrue: emitter.EventData = { 142 data: { 143 'flag': true 144 } 145 }; 146 let innerEventTrue: emitter.InnerEvent = { 147 eventId: 2, 148 priority: emitter.EventPriority.HIGH 149 }; 150 emitter.emit(innerEventTrue, eventDataTrue); 151 break; 152 case 'completed': // 播放结束后触发该状态机上报 153 Logger.info(this.tag, 'setAVPlayerCallback AVPlayer state completed called.'); 154 let eventDataFalse: emitter.EventData = { 155 data: { 156 'flag': false 157 } 158 }; 159 let innerEvent: emitter.InnerEvent = { 160 eventId: 1, 161 priority: emitter.EventPriority.HIGH 162 }; 163 emitter.emit(innerEvent, eventDataFalse); 164 break; 165 default: 166 Logger.info(this.tag, 'setAVPlayerCallback AVPlayer state unknown called.'); 167 break; 168 } 169 }); 170 // 时间上报监听函数 171 this.avPlayer.on('timeUpdate', (time: number) => { 172 this.currentTime = time; 173 Logger.info(this.tag, `setAVPlayerCallback timeUpdate success,and new time is = ${this.currentTime}`); 174 }); 175 // 字幕回调函数 176 this.avPlayer.on('subtitleUpdate', (info: media.SubtitleInfo) => { 177 if (!!info) { 178 Logger.info(this.tag, `subtitleUpdate info is called text=${info.text} startTime=${info.startTime} duration=${info.duration}`) 179 this.text = (!info.text) ? '' : info.text 180 this.startTime = (!info.startTime) ? 0 : info.startTime 181 this.duration = (!info.duration) ? 0 : info.duration 182 } else { 183 Logger.info(this.tag, 'subtitleUpdate info is null') 184 } 185 }) 186 } 187 188 /** 189 * 获取总时间 190 */ 191 getDurationTime(): number { 192 return this.durationTime; 193 } 194 195 /** 196 * 获取当前时间 197 */ 198 getCurrentTime(): number { 199 return this.currentTime; 200 } 201 202 /** 203 * 视频播放 204 */ 205 videoPlay(): void { 206 if (this.avPlayer) { 207 try { 208 this.avPlayer.play(); 209 } catch (e) { 210 Logger.error(this.tag, `videoPlay = ${JSON.stringify(e)}`); 211 } 212 } 213 } 214 215 /** 216 * 视频暂停 217 */ 218 videoPause(): void { 219 if (this.avPlayer) { 220 try { 221 this.avPlayer.pause(); 222 Logger.info(this.tag, 'videoPause=='); 223 } catch (e) { 224 Logger.info(this.tag, `videoPause== ${JSON.stringify(e)}`); 225 } 226 } 227 } 228 229 /** 230 * 调节1.0倍速 231 */ 232 videoSpeedOne(): void { 233 if (this.avPlayer) { 234 try { 235 this.avPlayer.setSpeed(media.PlaybackSpeed.SPEED_FORWARD_1_00_X); 236 Logger.info(this.tag, 'videoSpeed_1_00'); 237 } catch (e) { 238 Logger.info(this.tag, `videoSpeed_1_00== ${JSON.stringify(e)}`); 239 } 240 } 241 } 242 243 /** 244 * 调节1.25倍速 245 */ 246 videoSpeedOnePointTwentyFive(): void { 247 if (this.avPlayer) { 248 try { 249 this.avPlayer.setSpeed(media.PlaybackSpeed.SPEED_FORWARD_1_25_X); 250 Logger.info(this.tag, 'videoSpeed_1_25'); 251 } catch (e) { 252 Logger.info(this.tag, `videoSpeed_1_25== ${JSON.stringify(e)}`); 253 } 254 } 255 } 256 257 /** 258 * 调节1.75倍速 259 */ 260 videoSpeedOnePointSeventyFive(): void { 261 if (this.avPlayer) { 262 try { 263 this.avPlayer.setSpeed(media.PlaybackSpeed.SPEED_FORWARD_1_75_X); 264 Logger.info(this.tag, 'videoSpeed_1_75'); 265 } catch (e) { 266 Logger.info(this.tag, `videoSpeed_1_75==` + JSON.stringify(e)); 267 } 268 } 269 } 270 271 /** 272 * 调节2.0倍速 273 */ 274 videoSpeedTwo(): void { 275 if (this.avPlayer) { 276 try { 277 this.avPlayer.setSpeed(media.PlaybackSpeed.SPEED_FORWARD_2_00_X); 278 Logger.info(this.tag, 'videoSpeed_2_0'); 279 } catch (e) { 280 Logger.info(this.tag, `videoSpeed_2_0== ${JSON.stringify(e)}`); 281 } 282 } 283 } 284 285 /** 286 * 视频跳转 287 */ 288 async videoSeek(seekTime: number): Promise<void> { 289 if (this.avPlayer) { 290 try { 291 this.avPlayer.seek(seekTime, media.SeekMode.SEEK_PREV_SYNC); 292 this.currentTime = seekTime; 293 Logger.info(this.tag, `videoSeek== ${seekTime}`); 294 } catch (e) { 295 Logger.info(this.tag, `videoSeek== ${JSON.stringify(e)}`); 296 } 297 } 298 } 299 300 /** 301 * 视频重置 302 */ 303 async videoReset(): Promise<void> { 304 if (this.avPlayer == null) { 305 Logger.info(this.tag, 'avPlayer has not init'); 306 return; 307 } 308 this.avPlayer.reset(); 309 this.clearSubtitle() 310 } 311 312 /** 313 * 视频预下载 314 */ 315 async preDownload(url: string): Promise<void> { 316 if (this.avPlayer) { 317 let mediaSource : media.MediaSource = media.createMediaSourceWithUrl(url, {'aa' : 'bb', 'cc' : 'dd'}); 318 let playbackStrategy : media.PlaybackStrategy = {preferredWidth: 1, preferredHeight: 2, preferredBufferDuration: 3, preferredHdr: false}; 319 this.avPlayer.setMediaSource(mediaSource, playbackStrategy) 320 } 321 } 322 323 // 获取视频多音轨列表 324 async getAudioTrack(): Promise<void> { 325 if (this.avPlayer) { 326 this.avPlayer.getTrackDescription().then((arrList: Array<media.MediaDescription>) => { 327 Logger.info('getTrackDescription success'); 328 Logger.info(this.tag, 'get AudioTrackList: ' + arrList.length); 329 if (this.arrList.length == 0 && arrList != null) { 330 let selectOptions: SelectOption[] = []; 331 for (let i = 0; i < arrList.length; i++) { 332 if (i == 0) { 333 selectOptions.push({ value: this.default_track_value}); 334 } else { 335 let property: Object = arrList[i][media.MediaDescriptionKey.MD_KEY_TRACK_INDEX]; 336 selectOptions.push({ value: this.track_name_value + property.toString()}); 337 } 338 } 339 this.arrList = selectOptions 340 } else { 341 Logger.info(this.tag, 'audio getTrackDescription fail'); 342 } 343 }).catch((error: BusinessError) => { 344 Logger.error(`video catchCallback, error:${error}`); 345 }); 346 return; 347 } 348 } 349 350 // 设置视频多音轨轨道 351 async setAudioTrack(audioTrackValue: number): Promise<void> { 352 Logger.info(this.tag, 'selectTrack set AudioTrackType value is:' + audioTrackValue); 353 switch (audioTrackValue) { 354 case 0: 355 if (this.avPlayer != null && this.currentAudioTrackValue != 0) { 356 Logger.info(this.tag, 'deselectTrack start:' + (this.currentAudioTrackValue)); 357 this.avPlayer.deselectTrack(this.currentAudioTrackValue) 358 Logger.info(this.tag, 'deselectTrack end:' + (this.currentAudioTrackValue)); 359 } 360 break; 361 default: 362 if (this.avPlayer != null) { 363 this.currentAudioTrackValue = audioTrackValue; 364 Logger.info(this.tag, 'selectTrack start:' + (audioTrackValue)); 365 this.avPlayer.selectTrack(audioTrackValue) 366 Logger.info(this.tag, 'selectTrack end:' + (audioTrackValue)); 367 } 368 break; 369 } 370 } 371 372 /** 373 * 释放视频资源 374 */ 375 async videoRelease(): Promise<void> { 376 if (this.avPlayer == null) { 377 Logger.info(this.tag, 'avPlayer has not init'); 378 return; 379 } 380 this.avPlayer.release((err) => { 381 if (err == null) { 382 Logger.info(this.tag, 'videoRelease release success'); 383 } else { 384 Logger.error(this.tag, `videoRelease release filed,error message is = ${JSON.stringify(err.message)}`); 385 } 386 }); 387 } 388 389 /** 390 * 视频切换,前台调用 391 */ 392 async videoChoose(videoSrc: string, speedSelect: number, callback: (avPlayer: media.AVPlayer) => void): Promise<void> { 393 try { 394 this.videoSrc = videoSrc; 395 this.speedSelect = speedSelect; 396 Logger.info(this.tag, `videoChoose this.videoSrc = ${this.videoSrc}`); 397 this.currentTime = 0; 398 this.currentAudioTrackValue = 0; 399 this.arrList = [] 400 this.videoReset(); 401 } catch (e) { 402 Logger.info(this.tag, 'videoChoose== ${JSON.stringify(e)}'); 403 } 404 } 405 406 /** 407 * 视频切换,换视频资源 408 */ 409 async avPlayerChoose(callback: (avPlayer: media.AVPlayer) => void): Promise<void> { 410 try { 411 Logger.info(this.tag, 'avPlayerChoose avPlayerDemo'); 412 if (this.avPlayer == null) { 413 return; 414 } 415 // 创建avPlayer实例对象 416 this.avPlayer = await media.createAVPlayer(); 417 // 创建状态机变化回调函数 418 this.fileDescriptor = null; 419 Logger.info(this.tag, `avPlayerChoose this.fileDescriptor = ${this.fileDescriptor}`); 420 await this.setAVPlayerCallback(callback); 421 Logger.info(this.tag, 'avPlayerChoose setAVPlayerCallback'); 422 if (this.videoSrc === 'network.mp4') { 423 this.fileSrc = 'https:\/\/vd3.bdstatic.com\/mda-pdc2kmwtd2vxhiy4\/cae_h264\/1681502407203843413\/mda-pdc2kmwtd2vxhiy4.mp4'; 424 } else if (this.videoSrc === 'pre_download_network.mp4') { 425 this.fileSrc = 'https:\/\/vd3.bdstatic.com\/mda-nmh2004d24kf4bjh\/hd/h264\/1671326683061787710\/mda-nmh2004d24kf4bjh.mp4'; 426 } else if (this.videoSrc === 'pre_download_dash.mpd') { 427 this.fileSrc = 'https:\/\/hwposter-inland.hwcloudtest.cn\/AiMaxEngine\/DASH_LOCAL\/DASH_SDR_H264_LC\/DASH_SDR_H264_LC.mpd'; 428 } else { 429 this.fileSrc = this.videoSrc; 430 } 431 if (this.fileSrc) { 432 let regex = /^(http|https)/i; 433 let bool = regex.test(this.fileSrc); 434 if (bool) { 435 Logger.info(this.tag, `avPlayerChoose avPlayerChoose fileDescriptor = ${JSON.stringify(this.fileDescriptor)}`); 436 if (this.videoSrc === 'pre_download_network.mp4' || this.videoSrc === 'pre_download_dash.mpd') { 437 this.preDownload(this.fileSrc); 438 } else { 439 this.avPlayer.url = this.fileSrc; 440 } 441 } else { 442 if (this.mgr) { 443 this.fileDescriptor = await this.mgr.getRawFd(this.fileSrc); 444 Logger.info(this.tag, `avPlayerChoose avPlayerChoose fileDescriptor = ${JSON.stringify(this.fileDescriptor)}`); 445 this.avPlayer.fdSrc = this.fileDescriptor; 446 // 字幕 447 this.addSubtitleFdSrc() 448 } 449 } 450 } 451 } catch (e) { 452 Logger.info(this.tag, 'avPlayerChoose trycatch avPlayerChoose'); 453 this.videoReset(); 454 } 455 } 456 457 // 字幕相关 458 async getSubtitleStates(): Promise<void> { 459 let selectOptions: SelectOption[] = [] 460 if (this.mgr) { 461 this.mgr.getStringValue($r('app.string.subtitle_on')).then((v: string) => { 462 selectOptions.push({ value: v}) 463 }).catch((error: BusinessError) => { 464 Logger.error('subtitle getStringValue promise error is ' + error) 465 }) 466 this.mgr.getStringValue($r('app.string.subtitle_off')).then((v: string) => { 467 selectOptions.push({ value: v}) 468 }).catch((e: BusinessError) => { 469 Logger.error('subtitle getStringValue promise error is ' + e) 470 }) 471 this.arrSubTitleList = selectOptions 472 } 473 Logger.info(this.tag, 'subtitle getSubtitleStates end:' + `${JSON.stringify(this.arrSubTitleList.length)}`) 474 } 475 476 async setSubtitleState(subtitleState: number): Promise<void> { 477 Logger.info(this.tag, 'subtitle setSubtitleState value is:' + subtitleState) 478 this.currentSubtitleStateValue = subtitleState 479 } 480 481 getSubTitle(): string { 482 if (this.currentSubtitleStateValue == 0 && !!this.startTime && !!this.duration && !!this.text) { 483 if (this.startTime > 0 && this.duration > 0 && this.currentTime > 0) { 484 let dis = this.currentTime - this.startTime 485 if (0 <= dis && dis <= this.duration) { 486 return this.text 487 } else { 488 Logger.info(this.tag, 'getSubTitle there is no subtitle at current time') 489 } 490 } 491 } 492 return '' 493 } 494 495 clearSubtitle(): void { 496 this.text = '' 497 this.startTime = 0 498 this.duration = 0 499 Logger.info(this.tag, 'subtitle clearSubtitle') 500 } 501 502 async addSubtitleFdSrc(): Promise<void> { 503 if (!this.videoSrc) { 504 Logger.info(this.tag, 'subtitle addSubtitleFdSrc videoSrc is null') 505 return 506 } 507 if (!this.mgr) { 508 Logger.info(this.tag, 'subtitle addSubtitleFdSrc this.mgr is null') 509 return 510 } 511 let subtitleSource = this.videoSrc.split('.')[0] + '.srt' 512 Logger.info(this.tag, 'subtitle addSubtitleFdSrc ' + subtitleSource) 513 this.fileDescriptor = await this.mgr.getRawFd(subtitleSource) 514 if (!this.fileDescriptor) { 515 Logger.info(this.tag, 'subtitle subtitleSource maybe not exists') 516 return 517 } 518 if (!this.avPlayer) { 519 Logger.info(this.tag, 'subtitle addSubtitleFdSrc avPlayer has not init') 520 return 521 } 522 Logger.info(this.tag, `subtitle fd=${this.fileDescriptor.fd}, offset=${this.fileDescriptor.offset}, length=${this.fileDescriptor.length}`) 523 this.avPlayer.addSubtitleFromFd(this.fileDescriptor.fd, this.fileDescriptor.offset, this.fileDescriptor.length) 524 } 525}