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