• 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 UIAbility from '@ohos.app.ability.UIAbility';
17import type Window from '@ohos.window';
18import commonEvent from '@ohos.commonEventManager';
19import backgroundTaskManager from '@ohos.resourceschedule.backgroundTaskManager';
20import wantAgent from '@ohos.wantAgent';
21import avSession from '@ohos.multimedia.avsession';
22import rpc from '@ohos.rpc';
23import PlayerModel from '../feature/BackgroundPlayerFeature';
24import Logger from '../util/Logger';
25
26const TAG: string = 'EntryAbility';
27const ONE_HUNDRED: number = 100; // Convert Milliseconds
28const ONE_THOUSAND: number = 1000; // Convert second
29const SIXTY: number = 60; // Convert minute
30const MIN_TIME: number = 10; // Convert min time
31const MUSIC_SIZE: number = 2; // Convert music size
32
33export default class EntryAbility extends UIAbility {
34  private currentSession = null;
35  private isSwitching = false;
36  private currentTimeText: string = '';
37  private currentProgress: number = 0; // Current progress
38  private totalMs: number = 0; // Total time
39  private title: string = '';
40  private totalTimeText: string = '00:00';
41  private albumSrc: Resource = $r('app.media.album');
42  private backgroundShow: boolean = false;
43  private subscriberContext = null;
44
45  onPlayClick(): void {
46    if (this.isSwitching) {
47      Logger.info(TAG, 'onPlayClick ignored, isSwitching');
48      return null;
49    }
50
51    this.isSwitching = true;
52    Logger.info(TAG, 'onPlayClick isPlaying= ${PlayerModel.isPlaying}');
53    if (!PlayerModel.isPlaying) {
54      // start continuous task
55      PlayerModel.preLoad(PlayerModel.playerIndex, () => {
56        PlayerModel.playMusic(-1, true);
57      });
58      this.startContinuousTask();
59    }
60    this.isSwitching = false;
61    return null;
62  }
63
64  onPauseClick(): void {
65    if (this.isSwitching) {
66      Logger.info(TAG, 'onPauseClick ignored, isSwitching');
67      return null;
68    }
69    this.isSwitching = true;
70    Logger.info(TAG, 'onPauseClick isPlaying= ${PlayerModel.isPlaying}');
71    if (PlayerModel.isPlaying) {
72      PlayerModel.pauseMusic();
73      // cancel continuous task
74      this.stopContinuousTask();
75    }
76    this.isSwitching = false;
77    return null;
78  }
79
80  onPreviousClick(): void {
81    if (this.isSwitching) {
82      Logger.info(TAG, 'onPreviousClick ignored, isSwitching');
83      return null;
84    }
85    Logger.info(TAG, 'onPreviousClick');
86    PlayerModel.playerIndex--;
87    if (PlayerModel.playerIndex < 0 && PlayerModel.playlist.audioFiles.length >= 1) {
88      PlayerModel.playerIndex = PlayerModel.playlist.audioFiles.length - 1;
89    }
90    this.currentProgress = 0;
91    this.isSwitching = true;
92    PlayerModel.preLoad(PlayerModel.playerIndex, () => {
93      this.refreshSongInfo(PlayerModel.playerIndex as number);
94      PlayerModel.playMusic(0, true);
95      this.isSwitching = false;
96    });
97    this.startContinuousTask();
98    return null;
99  }
100
101  // next
102  onNextClick(): void {
103    if (this.isSwitching) {
104      Logger.info(TAG, 'onNextClick ignored, isSwitching');
105      return null;
106    }
107    Logger.info(TAG, 'onNextClick');
108    PlayerModel.playerIndex++;
109    if (PlayerModel.playerIndex >= PlayerModel.playlist.audioFiles.length) {
110      PlayerModel.playerIndex = 0;
111    }
112    this.currentProgress = 0;
113    this.isSwitching = true;
114    PlayerModel.preLoad(PlayerModel.playerIndex, () => {
115      this.refreshSongInfo(PlayerModel.playerIndex as number);
116      PlayerModel.playMusic(0, true);
117      this.isSwitching = false;
118    });
119    this.startContinuousTask();
120    return null;
121  }
122
123  onTime(): number {
124    return (new Date()).getTime();
125  }
126
127  // start continuous task
128  startContinuousTask(): void {
129    let wantAgentInfo = {
130      wants: [
131        {
132          bundleName: 'com.samples.musiccontrol',
133          abilityName: 'EntryAbility',
134        }
135      ],
136      operationType: wantAgent.OperationType.START_ABILITY,
137      requestCode: 0,
138      wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
139    };
140
141    wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj) => {
142      try {
143        backgroundTaskManager.startBackgroundRunning(this.context,
144          backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK, wantAgentObj).then(() => {
145          Logger.info(TAG, 'startBackgroundRunning succeeded');
146        }).catch((err) => {
147          Logger.info(TAG, 'startBackgroundRunning failed Cause:  ${JSON.stringify(err)}');
148        });
149      } catch (error) {
150        Logger.error(TAG, 'Operation startBackgroundRunning failed. code is ${error.code} message is ${error.message}');
151      }
152    });
153  }
154
155  // cancel continuous task
156  stopContinuousTask(): void {
157    try {
158      backgroundTaskManager.stopBackgroundRunning(this.context).then(() => {
159        Logger.info(TAG, 'stopBackgroundRunning succeeded');
160      }).catch((err) => {
161        Logger.info(TAG, 'stopBackgroundRunning failed Cause:  ${JSON.stringify(err)}');
162      });
163    } catch (error) {
164      Logger.error(TAG, 'stopBackgroundRunning failed. code is ${error.code} message is ${error.message}');
165    }
166  }
167
168  getShownTimer(ms: number): string {
169    let minStr: string;
170    let secStr: string;
171    let seconds = Math.floor(ms / ONE_THOUSAND);
172    let sec = seconds % SIXTY;
173    Logger.info(TAG, 'getShownTimer sec = ${sec}');
174    let min = (seconds - sec) / SIXTY;
175    Logger.info(TAG, 'getShownTimer min = ${min}');
176    if (sec < MIN_TIME) {
177      secStr = '0' + sec;
178    } else {
179      secStr = sec.toString(MIN_TIME);
180    }
181    if (min < MIN_TIME) {
182      minStr = '0' + min;
183    } else {
184      minStr = min.toString(MIN_TIME);
185    }
186    Logger.warn(TAG, 'getShownTimer = ${minStr}:${secStr}');
187    return minStr + ':' + secStr;
188  }
189
190  // refresh song
191  refreshSongInfo(index: number): number {
192    Logger.info(TAG, 'refreshSongInfo ${index}/${PlayerModel.playlist.audioFiles.length}');
193    if (index >= PlayerModel.playlist.audioFiles.length) {
194      Logger.warn(TAG, 'refreshSongInfo ignored');
195      return 0;
196    }
197    // update song title
198    this.title = PlayerModel.playlist.audioFiles[index].name;
199    this.albumSrc = (index % MUSIC_SIZE === 0) ? $r('app.media.album') : $r('app.media.album2');
200    // update duration
201    this.totalMs = PlayerModel.getDuration();
202    this.totalTimeText = this.getShownTimer(this.totalMs);
203    this.currentTimeText = this.getShownTimer(PlayerModel.getCurrentMs() as number);
204    Logger.info(TAG, 'refreshSongInfo this.title= ${this.title}, this.totalMs= ${this.totalMs}' +
205      'this.totalTimeText= ${this.totalTimeText},this.currentTimeText= ${this.currentTimeText}');
206    return 0;
207  }
208
209  createSubscriber(): void {
210    let subscriberInfo = {
211      events: ['music.event']
212    };
213
214    commonEvent.createSubscriber(subscriberInfo, (err, subscriber) => {
215      if (err) {
216        Logger.error(TAG, 'createSubscriber error. Cause:' + JSON.stringify(err));
217      } else {
218        Logger.info(TAG, 'createSubscriber success');
219        this.subscriberContext = subscriber;
220        this.subscriber();
221      }
222    });
223  }
224
225  subscriber(): void {
226    if (this.subscriberContext !== null) {
227      commonEvent.subscribe(this.subscriberContext, (err, data) => {
228        if (err) {
229          Logger.error(TAG, 'subscribe error. Cause:' + JSON.stringify(err));
230        } else {
231          Logger.info(TAG, 'subscribe success');
232          if (data.data === 'delete') {
233            this.stopContinuousTask();
234            PlayerModel.stopMusic();
235          }
236        }
237      });
238    }
239  }
240
241  onCreate(want, launchParam): void {
242
243    Logger.info(TAG, 'onCreate');
244    this.backgroundShow = true;
245    try {
246      this.callee.on('play', (data: rpc.MessageSequence)=>{
247        this.onPlayClick();
248        return null;
249      });
250      this.callee.on('pause', (data: rpc.MessageSequence)=>{
251        this.onPauseClick();
252        return null;
253      });
254      this.callee.on('prev', (data: rpc.MessageSequence)=>{
255        this.onPreviousClick();
256        return null;
257      });
258      this.callee.on('next', (data: rpc.MessageSequence)=>{
259        this.onNextClick();
260        return null;
261      });
262    } catch (error) {
263      console.error('Failed to register callee on. Cause:' + JSON.stringify(error));
264    }
265  }
266
267  onDestroy(): void {
268    Logger.info(TAG, 'onDestroy');
269    try {
270      this.callee.off('play');
271      this.callee.off('pause');
272      this.callee.off('prev');
273      this.callee.off('next');
274    } catch (error) {
275      console.error('Failed to register callee off. Cause:' + JSON.stringify(error));
276    }
277  }
278
279  onWindowStageCreate(windowStage: Window.WindowStage): void {
280    // Main window is created, set main page for this ability
281    Logger.info(TAG, 'onWindowStageCreate');
282    this.backgroundShow = false;
283    windowStage.loadContent('pages/Index', (err, data) => {
284      if (err.code) {
285        Logger.info(TAG, 'Failed to load the content. Cause: ${JSON.stringify(err)}');
286        return;
287      }
288      Logger.info(TAG, 'Succeeded in loading the content. Data:  ${JSON.stringify(data)}');
289    });
290  }
291
292  onWindowStageDestroy(): void {
293    // Main window is destroyed, release UI related resources
294    Logger.info(TAG, 'onWindowStageDestroy');
295  }
296
297  onForeground(): void {
298    // Ability has brought to foreground
299    Logger.info(TAG, 'onForeground');
300  }
301
302  onBackground(): void {
303    Logger.info(TAG, 'onBackground');
304    this.createSubscriber();
305    avSession.createAVSession(this.context, 'AVSessionPlayer', 'audio').then(async (session) => {
306      Logger.info(TAG, 'createAVSession success');
307      this.currentSession = session;
308      await this.currentSession.activate(() => {
309        Logger.info(TAG, 'activate success');
310      });
311      Logger.info(TAG, 'begin');
312      this.currentTimeText = this.getShownTimer(0);
313      PlayerModel.setOnStatusChangedListener((isPlaying: boolean) => {
314        Logger.info(TAG, 'on player status changed, isPlaying= ${isPlaying} refresh ui');
315        PlayerModel.setOnPlayingProgressListener((currentTimeMs: number) => {
316          this.currentTimeText = this.getShownTimer(currentTimeMs);
317          this.currentProgress = Math.floor(currentTimeMs / this.totalMs * ONE_HUNDRED);
318        });
319      });
320      PlayerModel.getPlaylist(() => {
321        Logger.info(TAG, 'on playlist generated, refresh ui');
322        PlayerModel.preLoad(0, () => {
323          this.refreshSongInfo(0);
324        });
325      });
326    });
327  }
328}
329