• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2024 Huawei Device 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 { avSession as AVSessionManager } from '@kit.AVSessionKit';
17import { promptAction } from '@kit.ArkUI';
18import { Context } from '@kit.AbilityKit';
19
20import { logger } from '../common/utils/Logger';
21
22import CommonConstants from '../common/constants/CommonConstants';
23import { AVPlayerModel } from '../model/AVPlayerModel';
24import { AVSessionEventListener, AVSessionModel, AVSessionUpdateState } from '../model/AVSessionModel';
25import { MusicInfo, MusicModel } from '../model/MusicModel';
26import { Decimal } from '@kit.ArkTS';
27
28export class MusicPlayerViewModel {
29  constructor() {
30  }
31  // avplayer模块实例
32  private avplayerModel: AVPlayerModel = new AVPlayerModel();
33  // avsession模块实例
34  private avsessionModel?: AVSessionModel;
35  // 歌单列表(歌曲数据实例列表)
36  private musicModelArr: Array<MusicModel> = [];
37  // 当前播放的歌曲索引
38  private curMusicModelIndex: number = 0;
39
40  private curMusicModelRaw: MusicModel | undefined;
41  public get curMusicModel(): MusicModel | undefined {
42    return this.curMusicModelRaw;
43  }
44
45  private totalTimeStrRaw: string = '';
46
47  public get totalTimeStr(): string {
48    return this.totalTimeStrRaw;
49  }
50
51  private curTimeRaw: number = 0;
52
53  public get curTime(): number {
54    return this.curTimeRaw;
55  }
56
57  private curTimeStrRaw: string = '';
58
59  public get curTimeStr(): string {
60    return this.curTimeStrRaw;
61  }
62
63  private curProgressRaw: number = 0;
64
65  public get curProgress(): number {
66    return this.curProgressRaw;
67  }
68
69  private curPlayerStateRaw: string = CommonConstants.AVPLAYER_STATE_IDLE;
70
71  public get curPlayerState(): string {
72    return this.curPlayerStateRaw;
73  }
74
75  private progressLockRaw: boolean = false;
76
77  public get progressLock(): boolean {
78    return this.progressLockRaw;
79  }
80
81  public set progressLock(val: boolean) {
82    this.progressLockRaw = val;
83  }
84
85  private curMusiclyricsLineRaw: number = 0;
86
87  public get curMusiclyricsLine(): number {
88    return this.curMusiclyricsLineRaw;
89  }
90
91  private curLoopModeRaw: number = AVSessionManager.LoopMode.LOOP_MODE_LIST;
92
93  public set curLoopMode(value: number) {
94    this.curLoopModeRaw = value;
95  }
96
97  public get curLoopMode(): number {
98    return this.curLoopModeRaw;
99  }
100
101
102  /**
103   * vm初始化
104   * @param timeMs 跳跃到歌曲的毫秒时间点
105   * @returns {Promise<void>}
106   */
107  async init(context: Context, musicInfoArr: Array<MusicInfo>): Promise<void> {
108    // 初始化歌曲列表数据
109    musicInfoArr.forEach((musicInfo: MusicInfo) => {
110      const musicModel: MusicModel = new MusicModel(musicInfo);
111      this.musicModelArr.push(musicModel);
112    })
113    this.curMusicModelRaw = this.musicModelArr[this.curMusicModelIndex];
114
115    // 初始化计时
116    this.updateTotalTime();
117    this.updateCurTime(0);
118
119    // 初始化AVSession
120    this.avsessionModel = new AVSessionModel(context);
121    const avSessionEventListener: AVSessionEventListener = {
122      onPlay: () => {
123        this.play();
124      },
125      onPause: () => {
126        this.pause();
127      },
128      onStop: () => {
129        this.stop();
130      },
131      onSeek: (timeMs: number) => {
132        this.seek(timeMs);
133      },
134      onSetLoopMode: () => {
135        this.updateLoopMode();
136      },
137      onPlayNext: () => {
138        this.playNext();
139      },
140      onPlayPrevious: () => {
141        this.playPrevious();
142      }
143    }
144    await this.avsessionModel.createSession(avSessionEventListener);
145    await this.avsessionModel.setSessionInfo(this.curMusicModelRaw);
146
147    // 初始化AVPlayer
148    await this.avplayerModel.initAVPlayer(
149      (newTime: number) => {
150        // 如果slider进度正在被用户拖动或点击过程中,则不通过avplayer来同步进度
151        if (this.progressLockRaw) {
152          // 如果本次同步过来的时间与slider当前时间一致,表示用户拖动或点击结束,此次之后开始接收avplayer的同步进度
153          if (Math.abs(this.curTimeRaw - newTime) < 5) {
154            this.progressLockRaw = false;
155          }
156
157          return;
158        }
159
160        this.updateCurTime(newTime);
161      },
162      (state: string) => {
163        this.curPlayerStateRaw = state;
164        this.avsessionModel?.updateCurState({
165          playerState: state,
166          curTime: this.curTimeRaw
167        } as AVSessionUpdateState);
168      },
169      () => {
170        this.playNext();
171      }
172    );
173    this.prepare();
174
175    // 监听应用前后台切换,注册后台任务启动和停止
176    const applicationContext = context.getApplicationContext();
177    const _that = this;
178    applicationContext.on('applicationStateChange', {
179      onApplicationForeground() {
180        logger.info('applicationStateChangeCallback onApplicationForeground');
181        _that.avsessionModel!.stopContinuousTask();
182      },
183      onApplicationBackground() {
184        logger.info('applicationStateChangeCallback onApplicationBackground');
185        if (_that.avplayerModel.getAVPlayerState() === CommonConstants.AVPLAYER_STATE_PLAYING) {
186          _that.avsessionModel!.startContinuousTask();
187        }
188      }
189    })
190  }
191
192  initAVSession() {
193
194  }
195
196  /**
197   * 加载播放资源
198   * @param finishTask prepare完成时的任务
199   * @returns {Promise<void}
200   */
201  async prepare(finishTask?: Function): Promise<void> {
202    if (!this.curMusicModelRaw) {
203      return;
204    }
205
206    await this.avplayerModel.prepare(this.curMusicModelRaw.res, finishTask);
207  }
208
209  /**
210   * 启动播放
211   * @returns {void}
212   */
213  play(): void {
214    this.avplayerModel.play();
215  }
216
217  /**
218   * 暂停播放
219   * @returns {void}
220   */
221  pause(): void {
222    this.avplayerModel.pause();
223  }
224
225  /**
226   * 恢复播放
227   * @returns {void}
228   */
229  resume(): void {
230    this.avplayerModel.resume();
231  }
232
233  /**
234   * 结束播放
235   * @returns {void}
236   */
237  stop(): void {
238    this.avplayerModel.stop();
239  }
240
241  /**
242   * 跳跃播放
243   * @param timeMs 跳跃到歌曲的毫秒时间点
244   * @returns {void}
245   */
246  seek(timeMs: number): void {
247    this.avplayerModel.seek(timeMs);
248    this.avsessionModel?.updateCurState({
249      playerState: this.curPlayerStateRaw,
250      curTime: timeMs
251    } as AVSessionUpdateState);
252  }
253
254  /**
255   * 重置播放器
256   * @returns {Promise<void>}
257   */
258  async reset(): Promise<void> {
259    await this.avplayerModel.resetAVPlayer();
260  }
261
262  /**
263   * 释放播放器资源
264   * @returns {void}
265   */
266  release(): void {
267    this.avplayerModel.releaseAVPlayer();
268
269    this.avsessionModel!.stopContinuousTask();
270    this.avsessionModel!.destroySession();
271  }
272
273  /**
274   * 生成时间字符串
275   * @param timeMs 毫秒数
276   * @returns {string} 时:分:秒 规格的字符串
277   */
278  genTimeStr(timeMs: number): string {
279    const totalTime = timeMs / 1000;
280    const hours = Math.floor(totalTime / CommonConstants.SECONDS_IN_HOUR);
281    const leftTime = totalTime % CommonConstants.SECONDS_IN_HOUR;
282    const minutes = Math.floor(leftTime / CommonConstants.SECONDS_IN_MINUTE);
283    const seconds = Math.floor(leftTime % CommonConstants.SECONDS_IN_MINUTE);
284
285    let hoursStr = '';
286    let minutesStr = '';
287    let secondsStr = '';
288
289    if (hours < 10) {
290      hoursStr = `0${hours}`;
291    } else {
292      hoursStr = hours.toString();
293    }
294
295    if (minutes < 10) {
296      minutesStr = `0${minutes}`;
297    } else {
298      minutesStr = minutes.toString();
299    }
300
301    if (seconds < 10) {
302      secondsStr = `0${seconds}`;
303    } else {
304      secondsStr = seconds.toString();
305    }
306
307    const totalTimeStr = `${hoursStr}:${minutesStr}:${secondsStr}`;
308    return totalTimeStr;
309  }
310
311  /**
312   * 更新播放歌曲总时间
313   * @returns {void}
314   */
315  updateTotalTime(): void {
316    if (!this.curMusicModelRaw) {
317      return;
318    }
319
320    const totalTime = this.curMusicModelRaw.totalTime;
321    this.totalTimeStrRaw = this.genTimeStr(totalTime);
322  }
323
324  /**
325   * 更新播放歌曲当前进度时间
326   * @param curTime 当前进度时间
327   * @returns {void}
328   */
329  updateCurTime(curTime: number): void {
330    if (!this.curMusicModelRaw) {
331      return;
332    }
333
334    this.curTimeRaw = curTime;
335    this.curTimeStrRaw = this.genTimeStr(curTime);
336    this.curProgressRaw = curTime / this.curMusicModelRaw.totalTime * CommonConstants.MUSIC_SLIDER_MAX;
337    this.curMusiclyricsLineRaw = this.curMusicModelRaw.lyricsInfo.checkCurLine(this.curTimeRaw);
338  }
339
340  /**
341   * 更新播放循环模式(0:顺序,1:单曲循环,2:列表循环,3:随机循环)
342   */
343  updateLoopMode() {
344    // 按顺序设置下一个循环模式
345    this.curLoopMode = (this.curLoopMode + 1) % CommonConstants.LOOP_MODE_TOTAL_NUM;
346    // 跳过顺序模式,不启用
347    this.curLoopMode = this.curLoopMode ? this.curLoopMode : AVSessionManager.LoopMode.LOOP_MODE_SINGLE;
348    this.avsessionModel?.updateCurState({
349      loopMode: this.curLoopMode
350    } as AVSessionUpdateState)
351  }
352
353  /**
354   * 播放后一首(根据当前循环模式)
355   */
356  async playNext() {
357    switch (this.curLoopMode) {
358      case AVSessionManager.LoopMode.LOOP_MODE_SINGLE: {
359        this.seek(0);
360        break;
361      }
362
363      case AVSessionManager.LoopMode.LOOP_MODE_LIST: {
364        this.curMusicModelIndex = (this.curMusicModelIndex + 1) % this.musicModelArr.length;
365        this.curMusicModelRaw = this.musicModelArr[this.curMusicModelIndex];
366        await this.reset();
367        await this.avsessionModel?.setSessionInfo(this.curMusicModelRaw);
368        await this.prepare(() => {
369          this.play();
370        });
371        break;
372      }
373
374      case AVSessionManager.LoopMode.LOOP_MODE_SHUFFLE: {
375        const randomVal: number = Decimal.random(1).e;
376        let dieta: number = 1;
377        while (dieta < this.musicModelArr.length - 1) {
378          if (randomVal >= dieta - 1 / this.musicModelArr.length - 1 &&
379            randomVal < dieta / this.musicModelArr.length - 1) {
380            break;
381          }
382          dieta++;
383        }
384        this.curMusicModelIndex = (this.curMusicModelIndex + dieta) % this.musicModelArr.length;
385        this.curMusicModelRaw = this.musicModelArr[this.curMusicModelIndex];
386        await this.reset();
387        await this.avsessionModel?.setSessionInfo(this.curMusicModelRaw);
388        await this.prepare(() => {
389          this.play();
390        });
391        break;
392      }
393    }
394  }
395
396  /**
397   * 播放前一首(根据当前循环模式)
398   */
399  async playPrevious() {
400    switch (this.curLoopMode) {
401      case AVSessionManager.LoopMode.LOOP_MODE_SINGLE: {
402        this.seek(0);
403        break;
404      }
405
406      case AVSessionManager.LoopMode.LOOP_MODE_LIST: {
407        this.curMusicModelIndex = (this.curMusicModelIndex + this.musicModelArr.length - 1) % this.musicModelArr.length;
408        this.curMusicModelRaw = this.musicModelArr[this.curMusicModelIndex];
409        await this.reset();
410        await this.avsessionModel?.setSessionInfo(this.curMusicModelRaw);
411        await this.prepare(() => {
412          this.play();
413        });
414        break;
415      }
416
417      case AVSessionManager.LoopMode.LOOP_MODE_SHUFFLE: {
418        const randomVal: number = Decimal.random(1).e;
419        let dieta: number = 1;
420        while (dieta < this.musicModelArr.length - 1) {
421          if (randomVal >= dieta - 1 / this.musicModelArr.length - 1 &&
422            randomVal < dieta / this.musicModelArr.length - 1) {
423            break;
424          }
425          dieta++;
426        }
427        this.curMusicModelIndex = (this.curMusicModelIndex + dieta) % this.musicModelArr.length;
428        this.curMusicModelRaw = this.musicModelArr[this.curMusicModelIndex];
429        await this.reset();
430        await this.avsessionModel?.setSessionInfo(this.curMusicModelRaw);
431        await this.prepare(() => {
432          this.play();
433        });
434        break;
435      }
436    }
437  }
438
439  /**
440   * 未完成的功能,显示TODO消息提示
441   * @returns {void}
442   */
443  showTodoToast(): void {
444    promptAction.showToast({
445      message: $r('app.string.foldable_screen_cases_toast_todo_show')
446    })
447  }
448}
449
450