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