• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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()