1/* 2 * Copyright (c) 2023 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 media from '@ohos.multimedia.media' 17import avSession from '@ohos.multimedia.avsession'; 18import common from '@ohos.app.ability.common'; 19import fileIo from '@ohos.fileio' 20import Logger from '../util/Logger' 21import { SONG_List } from '../mock/BackgroundPlayerData' 22 23const TAG: string = 'BackgroundPlayerFeature' 24 25class PlayList { 26 public audioFiles: Array<Song> = [] 27} 28 29class Song { 30 public name: string 31 public fileUri: string 32 public duration: number 33 34 constructor(name, fileUri, duration) { 35 this.name = name 36 this.fileUri = fileUri 37 this.duration = duration 38 } 39} 40 41class PlayerModel { 42 public isPlaying: boolean = false 43 public playlist: PlayList = new PlayList() 44 public playerIndex: number 45 public player: media.AudioPlayer 46 public statusChangedListener 47 public playingProgressListener 48 public intervalID = undefined 49 public currentTimeMs: number = 0 // Current time 50 public avSession: avSession.AVSession 51 52 constructor() { 53 Logger.info(TAG, `createAudioPlayer start`) 54 this.player = media.createAudioPlayer() 55 Logger.info(TAG, `createAudioPlayer end and initAudioPlayer`) 56 this.initAudioPlayer() 57 Logger.info(TAG, `createAudioPlayer= ${this.player}`) 58 } 59 60 initAudioPlayer(): void { 61 Logger.info(TAG, 'initAudioPlayer begin') 62 this.player.on('error', () => { 63 Logger.error(TAG, `player error`) 64 }) 65 this.player.on('finish', () => { 66 Logger.info(TAG, `finish() callback is called`) 67 this.seekTimeMs(0) 68 this.notifyPlayingStatus(false) 69 }) 70 this.player.on('timeUpdate', () => { 71 Logger.info(TAG, `timeUpdate() callback is called`) 72 }) 73 Logger.info(TAG, `initAudioPlayer end`) 74 } 75 76 createAVSession(context: common.UIAbilityContext): void { 77 Logger.debug(TAG, 'createAVSession begin'); 78 avSession.createAVSession(context, 'player', 'audio').then((data) => { 79 Logger.debug(TAG, 'createAVSession succeed'); 80 this.avSession = data; 81 }); 82 }; 83 84 destroyAVSession(): void { 85 Logger.debug(TAG, 'destroyAVSession begin'); 86 this.avSession?.destroy(); 87 }; 88 89 getPlaylist(callback: () => void): void { 90 // generate play list 91 Logger.info(TAG, `getAudioAssets begin`) 92 this.playlist = new PlayList() 93 this.playlist.audioFiles = [] 94 this.playlist.audioFiles[0] = new Song(SONG_List[0].name, SONG_List[0].fileUri, SONG_List[0].duration) 95 this.playlist.audioFiles[1] = new Song(SONG_List[1].name, SONG_List[1].fileUri, SONG_List[1].duration) 96 callback() 97 Logger.info(TAG, `getAudioAssets end`) 98 } 99 100 setOnStatusChangedListener(callback: (isPlay: boolean) => void): void { 101 this.statusChangedListener = callback 102 } 103 104 setOnPlayingProgressListener(callback: (currentTimeMs: number) => void): void { 105 this.playingProgressListener = callback 106 } 107 108 // notify status 109 notifyPlayingStatus(isPlaying: boolean): void { 110 this.isPlaying = isPlaying 111 this.statusChangedListener(this.isPlaying) 112 Logger.info(TAG, `notifyPlayingStatus isPlaying= ${isPlaying} intervalId= ${this.intervalID}`) 113 if (isPlaying) { 114 if (typeof (this.intervalID) === 'undefined') { 115 this.intervalID = setInterval(() => { 116 if (typeof (this.playingProgressListener)) { 117 let timeMs = this.player.currentTime 118 this.currentTimeMs = timeMs 119 if (typeof (timeMs) === 'undefined') { 120 timeMs = 0 121 } 122 Logger.info(TAG, `player.currentTime= ${timeMs}`) 123 this.playingProgressListener(timeMs) 124 } 125 }, 500) 126 Logger.info(TAG, `set update interval ${this.intervalID}`) 127 } 128 } else { 129 this.cancelTimer() 130 } 131 } 132 133 cancelTimer(): void { 134 if (typeof (this.intervalID) !== 'undefined') { 135 Logger.info(TAG, `clear update interval ${this.intervalID}`) 136 clearInterval(this.intervalID) 137 this.intervalID = undefined 138 } 139 } 140 141 preLoad(index: number, callback: () => void): number { 142 Logger.info(TAG, `preLoad ${index}/${this.playlist.audioFiles.length}`) 143 if (index < 0 || index >= this.playlist.audioFiles.length) { 144 Logger.error(TAG, `preLoad ignored`) 145 return 0 146 } 147 this.playerIndex = index 148 let uri = this.playlist.audioFiles[index].fileUri 149 fileIo.open(uri, (err, fdNumber) => { 150 let fdPath = 'fd://' 151 let source = fdPath + fdNumber 152 Logger.info(TAG, `preLoad source ${source}`) 153 if (typeof (source) === 'undefined') { 154 Logger.error(TAG, `preLoad ignored source= ${source}`) 155 return 156 } 157 Logger.info(TAG, `preLoad ${source} begin`) 158 Logger.info(TAG, `state= ${this.player.state}`) 159 160 if (source === this.player.src && this.player.state !== 'idle') { 161 Logger.info(TAG, `preLoad finished. src not changed`) 162 callback() 163 } else { 164 this.notifyPlayingStatus(false) 165 this.cancelTimer() 166 Logger.info(TAG, `player.reset`) 167 this.player.reset() 168 Logger.info(TAG, `player.reset done, state= ${this.player.state}`) 169 this.player.on('dataLoad', () => { 170 Logger.info(TAG, `dataLoad callback, state= ${this.player.state}`) 171 callback() 172 }) 173 Logger.info(TAG, `player.src= ${source}`) 174 this.player.src = source 175 } 176 Logger.info(TAG, `preLoad ${source} end`) 177 }) 178 } 179 180 getDuration(): number { 181 Logger.info(TAG, `getDuration playerIndex= ${this.playerIndex}`) 182 if (this.playlist.audioFiles[this.playerIndex].duration > 0) { 183 return this.playlist.audioFiles[this.playerIndex].duration 184 } 185 Logger.info(TAG, `getDuration state= ${this.player.state}`) 186 this.playlist.audioFiles[this.playerIndex].duration = Math.min(this.player.duration, 97615) // 97615表示预置时间 187 Logger.info(TAG, `getDuration player.src= ${this.player.src} player.duration= ${this.playlist.audioFiles[this.playerIndex].duration} `) 188 return this.playlist.audioFiles[this.playerIndex].duration 189 } 190 191 getCurrentMs(): number { 192 return this.currentTimeMs 193 } 194 195 playMusic(flag: number, startPlay: boolean, context: common.UIAbilityContext): void { 196 Logger.info(TAG, `playMusic flag= ${flag} startPlay= ${startPlay}`); 197 this.notifyPlayingStatus(startPlay); 198 if (startPlay) { 199 if (flag < 0 && this.currentTimeMs > 0) { 200 Logger.info(TAG, `pop flag= ${this.currentTimeMs}`); 201 flag = this.currentTimeMs; 202 } 203 this.player.on('play', () => { 204 Logger.info(TAG, `play() callback entered, player.state= ${this.player.state}`); 205 if (flag > 0) { 206 this.seekTimeMs(flag); 207 } 208 }); 209 this.player.play(); 210 this.createAVSession(context); 211 Logger.info(TAG, `player.play called player.state= ${this.player.state}`); 212 } else if (flag > 0) { 213 this.playingProgressListener(flag); 214 this.currentTimeMs = flag; 215 Logger.info(TAG, `stash flag= ${this.currentTimeMs}`); 216 } 217 } 218 219 pauseMusic(): boolean { 220 if (!this.isPlaying) { 221 Logger.info(TAG, `pauseMusic ignored, isPlaying= ${this.isPlaying}`) 222 return 223 } 224 this.notifyPlayingStatus(false) 225 Logger.info(TAG, `call player.pauseMusic`) 226 this.player.pause() 227 Logger.info(TAG, `player.pause called, player.state= ${this.player.state}`) 228 } 229 230 seekTimeMs(time: number): void { 231 this.currentTimeMs = time 232 if (this.isPlaying) { 233 Logger.info(TAG, `player.seek= ${time}`) 234 this.player.seek(time) 235 } else { 236 Logger.info(TAG, `stash flag= ${time}`); 237 } 238 } 239 240 stopMusic(): boolean { 241 if (!this.isPlaying) { 242 Logger.info(TAG, `stopMusic ignored, isPlaying= ${this.isPlaying}`); 243 return; 244 } 245 this.notifyPlayingStatus(false); 246 Logger.info(TAG, 'call player.stop'); 247 this.player.stop(); 248 this.destroyAVSession(); 249 Logger.info(TAG, `player.stop called, player.state= ${this.player.state}`); 250 }; 251} 252 253export default new PlayerModel()