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