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