• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}