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