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 try { 148 clearInterval(Number(this.#intervalID)) 149 this.#intervalID = undefined 150 }catch (err){ 151 logger.info(TAG, "clearInterval is error : " + err) 152 } 153 } 154 } 155 156 preLoad(index, callback) { 157 logger.debug(TAG, `preLoad ${index}/${this.playlist.audioFiles.length}`) 158 if (index < 0 || index >= this.playlist.audioFiles.length) { 159 logger.error(TAG, `preLoad ignored`) 160 return 0 161 } 162 this.index = index 163 let uri = this.playlist.audioFiles[index].fileUri 164 fileIO.open(uri, function(err, fdNumber){ 165 let fdPath = 'fd://' 166 let source = fdPath + fdNumber 167 logger.debug(TAG, `preLoad source ${source}`) 168 if (typeof (source) === 'undefined') { 169 logger.error(TAG, `preLoad ignored, source=${source}`) 170 return 171 } 172 logger.debug(TAG, `preLoad ${source} begin state= ${this.#player.state}`) 173 let self = this 174 if (source === this.#player.src && this.#player.state !== 'idle') { 175 logger.info(TAG, `preLoad finished. src not changed`) 176 callback() 177 } else { 178 this.notifyPlayingStatus(false) 179 this.cancelTimer() 180 logger.info(TAG, `player.reset`) 181 self.#player.reset() 182 logger.debug(TAG, `player.reset done, state=${self.#player.state}`) 183 self.#player.on('dataLoad', () => { 184 logger.debug(TAG, `dataLoad callback, state=${self.#player.state}`) 185 callback() 186 }) 187 logger.debug(TAG, `player.src=${source}`) 188 self.#player.src = source 189 } 190 logger.debug(TAG, `preLoad ${source} end`) 191 }.bind(this)) 192 } 193 194 getDuration() { 195 logger.debug(TAG, `getDuration index=${this.index}`) 196 if (this.playlist.audioFiles[this.index].duration > 0) { 197 return this.playlist.audioFiles[this.index].duration 198 } 199 logger.debug(TAG, `getDuration state=${this.#player.state}`) 200 this.playlist.audioFiles[this.index].duration = Math.min(this.#player.duration, 97615) 201 logger.debug(TAG, `getDuration src=${this.#player.src} duration=${this.playlist.audioFiles[this.index].duration}`) 202 return this.playlist.audioFiles[this.index].duration 203 } 204 205 getCurrentMs() { 206 return this.#currentTimeMs 207 } 208 209 play(seekTo, startPlay) { 210 logger.debug(TAG, `play seekTo=${seekTo}, startPlay=${startPlay}`) 211 this.notifyPlayingStatus(startPlay) 212 if (startPlay) { 213 if (seekTo < 0 && this.#currentTimeMs > 0) { 214 logger.debug(TAG, `pop seekTo=${this.#currentTimeMs}`) 215 seekTo = this.#currentTimeMs 216 } 217 let self = this 218 this.#player.on('play', (err, action) => { 219 if (err) { 220 logger.error(TAG, `error returned in play callback`) 221 return 222 } 223 logger.info(TAG, `play() callback entered, player.state=${self.#player.state}`) 224 if (seekTo > 0) { 225 self.seek(seekTo) 226 } 227 }) 228 logger.info(TAG, `call player.play`) 229 this.#player.play() 230 logger.debug(TAG, `player.play called player.state=${this.#player.state}`) 231 } else if (seekTo > 0) { 232 this.#playingProgressListener(seekTo) 233 this.#currentTimeMs = seekTo 234 logger.debug(TAG, `stash seekTo=${this.#currentTimeMs}`) 235 } 236 } 237 238 pause() { 239 if (!this.isPlaying) { 240 logger.debug('MusicPlayer[PlayerModel] pause ignored, isPlaying=' + this.isPlaying) 241 return 242 } 243 this.notifyPlayingStatus(false) 244 logger.info('MusicPlayer[PlayerModel] call player.pause') 245 this.#player.pause() 246 logger.debug('MusicPlayer[PlayerModel] player.pause called, player.state=' + this.#player.state) 247 } 248 249 seek(ms) { 250 this.#currentTimeMs = ms 251 if (this.isPlaying) { 252 logger.debug('MusicPlayer[PlayerModel] player.seek ' + ms) 253 this.#player.seek(ms) 254 } else { 255 logger.debug('MusicPlayer[PlayerModel] stash seekTo=' + ms) 256 } 257 } 258 259 stop() { 260 if (!this.isPlaying) { 261 logger.debug('MusicPlayer[PlayerModel] stop ignored, isPlaying=' + this.isPlaying) 262 return 263 } 264 this.notifyPlayingStatus(false) 265 logger.info('MusicPlayer[PlayerModel] call player.stop') 266 this.#player.stop() 267 logger.debug('MusicPlayer[PlayerModel] player.stop called, player.state=' + this.#player.state) 268 } 269}