• 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 { backgroundTaskManager } from '@kit.BackgroundTasksKit';
18import { BusinessError } from '@kit.BasicServicesKit';
19import { wantAgent, WantAgent } from '@kit.AbilityKit';
20import { image } from '@kit.ImageKit';
21import { logger } from '../common/utils/Logger';
22import { MusicModel } from './MusicModel';
23import CommonConstants from '../common/constants/CommonConstants';
24import { util } from '@kit.ArkTS';
25
26
27/**
28 * AVSession管理模块,负责以下业务功能:
29 * 1. 创建/销毁AVSession实例
30 * 2. 注册AVSession实例业务回调功能
31 * 3. 启动/关闭于AVSession共同使用的音频后台任务
32 */
33export class AVSessionModel {
34  private bindContext?: Context;
35  private session?: AVSessionManager.AVSession;
36  private avSessionTag: string = 'MUSIC_PLAYER';
37  private avSessionType: AVSessionManager.AVSessionType = 'audio';
38  private curState: AVSessionManager.AVPlaybackState = {
39    state: AVSessionManager.PlaybackState.PLAYBACK_STATE_INITIAL,
40    position: {
41      elapsedTime: 0,
42      updateTime: 0,
43    },
44    loopMode:AVSessionManager.LoopMode.LOOP_MODE_LIST,
45  };
46  private isBackgroundTaskRunning: boolean = false;
47
48  // AVPlayer状态同AVSession状态业务对应关系
49  private playerState2PlaybackStateMap: Map<string, number> = new Map([
50    [CommonConstants.AVPLAYER_STATE_IDLE, AVSessionManager.PlaybackState.PLAYBACK_STATE_IDLE],
51    [CommonConstants.AVPLAYER_STATE_INITIALIZED, AVSessionManager.PlaybackState.PLAYBACK_STATE_INITIAL],
52    [CommonConstants.AVPLAYER_STATE_PREPARED, AVSessionManager.PlaybackState.PLAYBACK_STATE_PREPARE],
53    [CommonConstants.AVPLAYER_STATE_PLAYING, AVSessionManager.PlaybackState.PLAYBACK_STATE_PLAY],
54    [CommonConstants.AVPLAYER_STATE_PAUSED, AVSessionManager.PlaybackState.PLAYBACK_STATE_PAUSE],
55    [CommonConstants.AVPLAYER_STATE_COMPLETED, AVSessionManager.PlaybackState.PLAYBACK_STATE_COMPLETED],
56    [CommonConstants.AVPLAYER_STATE_STOPPED, AVSessionManager.PlaybackState.PLAYBACK_STATE_STOP],
57    [CommonConstants.AVPLAYER_STATE_RELEASED, AVSessionManager.PlaybackState.PLAYBACK_STATE_RELEASED],
58    [CommonConstants.AVPLAYER_STATE_ERROR, AVSessionManager.PlaybackState.PLAYBACK_STATE_ERROR],
59  ]);
60
61  constructor(context: Context) {
62    this.bindContext = context;
63  }
64
65  /**
66   * 创建AVSession实例
67   * @param eventListener AVSession事件回调
68   * @returns {Promise<void>}
69   */
70  async createSession(eventListener: AVSessionEventListener): Promise<void> {
71    // TODO:知识点:创建AVSession实例
72    this.session = await AVSessionManager.createAVSession(this.bindContext!, this.avSessionTag, this.avSessionType);
73    // TODO:知识点:注册AVSession事件
74    this.registerSessionListener(eventListener);
75    // TODO:知识点:激活AVSession实例
76    await this.session.activate().catch((error: BusinessError) => {
77      logger.error('activate error: ', error.code.toString(), error.message)
78    });
79
80    logger.info(`session create done : sessionId : ${this.session.sessionId}`);
81  }
82
83  /**
84   * 销毁AVSession实例
85   * @returns {void}
86   */
87  destroySession(): void {
88    // TODO:知识点:注销AVSession事件
89    this.unRegisterListener();
90    // TODO:知识点:销毁AVSession实例
91    this.session?.destroy((err) => {
92      if (err) {
93        logger.error(`Destroy BusinessError: code: ${err.code}, message: ${err.message}`);
94      } else {
95        logger.info('Destroy : SUCCESS');
96      }
97    });
98  }
99
100  /**
101   * 注册AVSession实例事件
102   * 播控中心有多种操作,播放、暂停、停止、下一首、上一首、拖进度、标记喜好、播放循环模式切换、快进、快退
103   * @returns {void}
104   */
105  registerSessionListener(eventListener: AVSessionEventListener): void {
106    // 播放
107    this.session?.on('play', () => {
108      logger.info('avsession on play');
109      eventListener.onPlay();
110    });
111
112    // 暂停
113    this.session?.on('pause', () => {
114      logger.info('avsession on pause');
115      eventListener.onPause();
116    });
117
118    // 停止
119    this.session?.on('stop', () => {
120      logger.info('avsession on stop');
121      eventListener.onStop();
122    });
123
124    // 下一首
125    this.session?.on('playNext', async () => {
126      logger.info('avsession on playNext');
127      eventListener.onPlayNext();
128    });
129
130    // 上一首
131    this.session?.on('playPrevious', async () => {
132      logger.info('avsession on playPrevious');
133      eventListener.onPlayPrevious();
134    });
135
136    // 拖进度
137    this.session?.on('seek', (position) => {
138      logger.info('avsession on seek', position.toString());
139      eventListener.onSeek(position);
140    });
141
142    // 标记喜好
143    this.session?.on('toggleFavorite', (assetId) => {
144      logger.info('avsession on toggleFavorite', assetId);
145    });
146
147    // 播放循环模式切换
148    this.session?.on('setLoopMode', (mode) => {
149      logger.info('avsession on setLoopMode', mode.toString());
150      eventListener.onSetLoopMode();
151    });
152
153    // 快进
154    this.session?.on('fastForward', (skipInterval?: number) => {
155      logger.info('avsession on fastForward', skipInterval ? skipInterval?.toString() : 'no skipInterval');
156    });
157
158    // 快退
159    this.session?.on('rewind', (skipInterval?: number) => {
160      logger.info('avsession on rewind', skipInterval ? skipInterval?.toString() : 'no skipInterval');
161    });
162  }
163
164  /**
165   * 注销AVSession实例事件
166   * @returns {void}
167   */
168  unRegisterListener(): void {
169    this.session?.off('play');
170    this.session?.off('pause');
171    this.session?.off('stop');
172    this.session?.off('playNext');
173    this.session?.off('playPrevious');
174    this.session?.off('fastForward');
175    this.session?.off('rewind');
176    this.session?.off('seek');
177    this.session?.off('setLoopMode');
178    this.session?.off('toggleFavorite');
179  }
180
181  /**
182   * 设置AVSession实例初始化数据和状态
183   * @param {MusicModel | undefined} musicModel 歌曲数据
184   * @returns {Promise<void>}
185   */
186  async setSessionInfo(musicModel?: MusicModel): Promise<void> {
187    const resourceManager = this.bindContext!.resourceManager;
188    const coverUInt8Arr: Uint8Array = await resourceManager.getRawFileContent(musicModel?.cover!);
189    const imageBuffer: ArrayBuffer = coverUInt8Arr.buffer as ArrayBuffer;
190    const imageSource: image.ImageSource = image.createImageSource(imageBuffer);
191    const imagePixel: image.PixelMap = await imageSource.createPixelMap({
192      desiredSize: {
193        width: CommonConstants.MUSIC_MEDIA_IMAGE_WIDTH,
194        height: CommonConstants.MUSIC_MEDIA_IMAGE_HEIGHT
195      }
196    });
197
198    const lrcUInt8Arr: Uint8Array = await resourceManager.getRawFileContent(musicModel?.lrcRes!);
199    const textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true });
200    const lrcStr: string = textDecoder.decodeToString(lrcUInt8Arr, { stream: false });
201
202    // 设置必要的媒体信息
203    let metadata: AVSessionManager.AVMetadata = {
204      assetId: 'testMusic', // 由应用指定,用于标识应用媒体库里的媒体
205      title: musicModel?.title,
206      mediaImage: imagePixel,
207      artist: musicModel?.singer,
208      duration: musicModel?.totalTime,
209      lyric: lrcStr
210    }
211    // TODO:知识点:设置AVSession元信息
212    this.session?.setAVMetadata(metadata).then(() => {
213      logger.info(`SetAVMetadata successfully`);
214    }).catch((err: BusinessError) => {
215      logger.error(`Failed to set AVMetadata. Code: ${err.code}, message: ${err.message}`);
216    });
217
218    // 设置状态: 播放状态,进度位置,播放倍速,缓存的时间
219    this.curState.state = AVSessionManager.PlaybackState.PLAYBACK_STATE_PREPARE;
220    this.curState.position = {
221      elapsedTime: 0,
222      updateTime: new Date().getTime()
223    }
224    this.setAVPlaybackState();
225  }
226
227  /**
228   * 更新AVSession实例状态
229   * @param {AVSessionUpdateState} updateState 更新状态数据
230   * @returns {void}
231   */
232  updateCurState(updateState: AVSessionUpdateState): void {
233    const newState: string | undefined = updateState.playerState;
234    const newElapsedTime: number | undefined = updateState.curTime;
235    const newLoopMode: number | undefined = updateState.loopMode;
236    logger.info('avsession updateCurState', newState + '', newElapsedTime + '');
237
238    // 播控中心状态更新
239    if (newState !== undefined) {
240      this.curState.state = this.playerState2PlaybackStateMap.get(newState);
241    }
242
243    // 单曲进度更新
244    if (newElapsedTime !== undefined) {
245      this.curState.position = {
246        elapsedTime: newElapsedTime,
247        updateTime: new Date().getTime()
248      }
249    }
250
251    // 循环播放模式更新
252    if (newLoopMode !== undefined) {
253      this.curState.loopMode = newLoopMode;
254    }
255
256    this.setAVPlaybackState();
257  }
258
259  /**
260   * 设置AVSession实例状态
261   * @returns {void}
262   */
263  setAVPlaybackState(): void {
264    logger.info('avsession setAVPlaybackState', JSON.stringify(this.curState));
265    // TODO:知识点:设置AVSession当前状态
266    this.session?.setAVPlaybackState(this.curState, (err) => {
267      if (err) {
268        logger.error(`Failed to set AVPlaybackState. Code: ${err.code}, message: ${err.message}`);
269      } else {
270        logger.info(`SetAVPlaybackState successfully`);
271      }
272    });
273  }
274
275  /**
276   * 启动后台任务
277   * @returns {void}
278   */
279  startContinuousTask(): void {
280    // 避免重新启动后台任务
281    if (this.isBackgroundTaskRunning) {
282      return;
283    }
284
285    // TODO:知识点:创建WantAgent实例,在后台任务时拉起应用
286    let wantAgentInfo: wantAgent.WantAgentInfo = {
287      // 点击通知后,将要执行的动作列表
288      // 添加需要被拉起应用的bundleName和abilityName
289      wants: [
290        {
291          bundleName: 'com.samples.musicplayer',
292          abilityName: 'com.samples.musicplayer.EntryAbility'
293        }
294      ],
295      // 指定点击通知栏消息后的动作是拉起ability
296      actionType: wantAgent.OperationType.START_ABILITY,
297      // 使用者自定义的一个私有值
298      requestCode: 0,
299      // 点击通知后,动作执行属性
300      actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
301    };
302    // 通过wantAgent模块下getWantAgent方法获取WantAgent对象
303    wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj: WantAgent) => {
304      // TODO:知识点:设置后台任务类型,启动后台任务
305      backgroundTaskManager.startBackgroundRunning(this.bindContext!,
306        backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK, wantAgentObj).then(() => {
307        // 此处执行具体的长时任务逻辑,如放音等。
308        logger.info(`Succeeded in operationing startBackgroundRunning.`);
309        this.isBackgroundTaskRunning = true;
310      }).catch((err: BusinessError) => {
311        logger.error(`Failed to operation startBackgroundRunning. Code is ${err.code}, message is ${err.message}`);
312      });
313    });
314  }
315
316  /**
317   * 结束后台任务
318   * @returns {void}
319   */
320  stopContinuousTask(): void {
321    // 避免重新停止后台任务
322    if (!this.isBackgroundTaskRunning) {
323      return;
324    }
325
326    // TODO:知识点:停止后台任务
327    backgroundTaskManager.stopBackgroundRunning(this.bindContext!).then(() => {
328      logger.info(`Succeeded in operationing stopBackgroundRunning.`);
329      this.isBackgroundTaskRunning = false;
330    }).catch((err: BusinessError) => {
331      logger.error(`Failed to operation stopBackgroundRunning. Code is ${err.code}, message is ${err.message}`);
332    });
333  }
334}
335
336/**
337 * AVSession会话交互事件监听接口
338 */
339export interface AVSessionEventListener {
340  onPlay: Function,
341  onPause: Function,
342  onStop: Function,
343  onSeek: Function,
344  onSetLoopMode: Function,
345  onPlayNext: Function,
346  onPlayPrevious: Function
347}
348
349export interface AVSessionUpdateState {
350  playerState?: string,
351  curTime?: number,
352  loopMode?: number,
353}
354