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 image from '@ohos.multimedia.image'; 17import avSession from '@ohos.multimedia.avsession'; 18import Log from '../common/Log'; 19import MediaData from '../common/MediaData'; 20import resourceManager from '@ohos.resourceManager'; 21import WantAgent from '@ohos.app.ability.wantAgent'; 22import backgroundTaskManager from '@ohos.resourceschedule.backgroundTaskManager'; 23import { BusinessError } from '@ohos.base'; 24 25interface Type {lyrics: boolean } 26 27export class ProviderFeature { 28 private constants = new MediaData(); 29 private session: avSession.AVSession | null = null; 30 private isPlayLink: SubscribedAbstractProperty<boolean> | null = null; 31 private currentPlayItemLink: SubscribedAbstractProperty<avSession.AVQueueItem> | undefined = undefined; 32 private currentAVMetadataLink: SubscribedAbstractProperty<avSession.AVMetadata> | undefined = undefined; 33 private currentImageLink: SubscribedAbstractProperty<PixelMap> | undefined = undefined; 34 private currentLyricLink: SubscribedAbstractProperty<string> | null = null; 35 private queueItems: Array<avSession.AVQueueItem> = [this.constants.queueItemFirst, this.constants.queueItemSecond, this.constants.queueItemThird]; 36 private lyrics: Array<string> = this.constants.lyricsForDemo; 37 private currentLyricLine: number = 0; 38 private isSendLyrics: boolean = false; 39 private pixelMap: PixelMap | null = null; 40 private queueItemPixelMapArray: Array<PixelMap> = []; 41 private MetadataPixelMapArray: Array<PixelMap> = []; 42 private resourceManager: resourceManager.ResourceManager = getContext(this).resourceManager; 43 private avMetadataList: Array<avSession.AVMetadata> = [this.constants.avMetadataFirst, this.constants.avMetadataSecond, this.constants.avMetadataThird]; 44 private currentState: avSession.AVPlaybackState = { 45 state: avSession.PlaybackState.PLAYBACK_STATE_PAUSE 46 } 47 private constantsForControl: MediaData = new MediaData(); 48 49 constructor() { 50 this.isPlayLink = AppStorage.SetAndLink('IsPlaying', false); 51 this.currentPlayItemLink = AppStorage.SetAndLink<avSession.AVQueueItem>('CurrentPlayItem', undefined); 52 this.currentAVMetadataLink = AppStorage.SetAndLink<avSession.AVMetadata>('CurrentAVMetadata', undefined); 53 this.currentImageLink = AppStorage.SetAndLink<PixelMap>('CurrentImage', undefined); 54 this.currentLyricLink = AppStorage.SetAndLink('CurrentLyric', 'No lyric'); 55 this.currentImageLink!.set(null); 56 this.currentAVMetadataLink!.set(this.avMetadataList[0]); 57 this.Init(); 58 59 } 60 61 async Init(): Promise<void> { 62 await this.prepareImageResources(); 63 await this.prepareResourcesForController(); 64 await this.InitFirstMusicState(); 65 await this.startContinuousTask(); 66 } 67 68 async startContinuousTask(): Promise<void> { 69 let wantAgentInfo: WantAgent.WantAgentInfo = { 70 wants: [ 71 { 72 bundleName: "com.samples.mediaprovider", 73 abilityName: "com.samples.mediaprovider.EntryAbility" 74 } 75 ], 76 operationType: WantAgent.OperationType.START_ABILITY, 77 requestCode: 0, 78 wantAgentFlags: [WantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG] 79 }; 80 let want = await WantAgent.getWantAgent(wantAgentInfo); 81 await backgroundTaskManager.startBackgroundRunning(getContext(this), backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK, want); 82 } 83 84 async CreateAVSession(): Promise<boolean> { 85 this.handleLyrics(); 86 87 Log.info(`Start create AVSession`) 88 let ret: boolean = true; 89 this.session = await avSession.createAVSession(getContext(this), "AVSessionDemo", 'audio').catch((err: BusinessError) => { 90 Log.error(`Failed to create AVSession, error info: ${JSON.stringify(err)}`); 91 ret = false; 92 return null; 93 }); 94 await this.session!.activate().catch((err: BusinessError) => { 95 Log.error(`Failed to activate AVSession, error info: ${JSON.stringify(err)}`); 96 ret = false; 97 }); 98 await this.session!.setAVQueueItems(this.queueItems).catch((err: BusinessError) => { 99 Log.error(`Failed to set AVQueue items, error info: ${JSON.stringify(err)}`); 100 ret = false; 101 }); 102 await this.session!.setAVQueueTitle('Queue title').catch((err: BusinessError) => { 103 Log.error(`Failed to set AVQueue title, error info: ${JSON.stringify(err)}`); 104 ret = false; 105 }); 106 return ret; 107 } 108 109 async InitFirstMusicState(): Promise<void> { 110 let that = this; 111 that.isPlayLink!.set(false); 112 that.currentLyricLine = 0; 113 that.currentImageLink!.set(that.MetadataPixelMapArray[0]); 114 that.currentState.state = avSession.PlaybackState.PLAYBACK_STATE_PAUSE; 115 await that.session!.setAVPlaybackState(that.currentState); 116 117 await that.setAVMetadataToController(0); 118 that.currentPlayItemLink!.set(that.queueItems![0]); 119 that.currentAVMetadataLink!.set(that.avMetadataList[0]); 120 } 121 122 async RegisterListener(): Promise<void> { 123 let that = this; 124 this.session!.on('play', async () => { 125 console.info(`on play , do play task`); 126 let that = this; 127 that.isPlayLink!.set(true); 128 that.currentState.state = avSession.PlaybackState.PLAYBACK_STATE_PLAY; 129 await that.session!.setAVPlaybackState(that.currentState); 130 }); 131 this.session!.on('pause', async () => { 132 console.info(`on pause , do pause task`); 133 that.isPlayLink!.set(false); 134 that.currentState.state = avSession.PlaybackState.PLAYBACK_STATE_PAUSE; 135 await that.session!.setAVPlaybackState(that.currentState); 136 }); 137 this.session!.on('stop', async () => { 138 console.info(`on stop , do stop task`); 139 that.isPlayLink!.set(false); 140 that.currentState.state = avSession.PlaybackState.PLAYBACK_STATE_PAUSE; 141 await that.session!.setAVPlaybackState(that.currentState); 142 }); 143 this.session!.on('playNext', async () => { 144 console.info(`on playNext , do playNext task`); 145 let nextId: number = that.currentPlayItemLink!.get().itemId + 1; 146 nextId = that.queueItems!.length > nextId ? nextId : nextId - that.queueItems!.length; 147 await that.handleNewItem(nextId); 148 }); 149 this.session!.on('playPrevious', async () => { 150 console.info(`on playPrevious , do playPrevious task`); 151 let previousId: number = that.currentPlayItemLink!.get().itemId - 1; 152 previousId = previousId < 0 ? previousId + that.queueItems!.length : previousId; 153 await that.handleNewItem(previousId); 154 }); 155 this.session!.on('skipToQueueItem', async (itemId) => { 156 console.info(`on skipToQueueItem , do skip task`); 157 await that.handleNewItem(itemId); 158 }); 159 this.session!.on('commonCommand', (commandString, args: object) => { 160 console.info(`on commonCommand , command is ${commandString}, args are ${JSON.stringify(args)}`); 161 that.handleCommonCommand(commandString, args as Type); 162 }); 163 } 164 165 async play(): Promise<void> { 166 console.info(`Start do play task`); 167 let that = this; 168 that.isPlayLink!.set(true); 169 that.currentState.state = avSession.PlaybackState.PLAYBACK_STATE_PLAY; 170 await that.session!.setAVPlaybackState(that.currentState); 171 } 172 173 async setAVMetadataToController(itemId: number): Promise<void> { 174 let that = this; 175 switch (itemId) { 176 case 0: 177 await that.session!.setAVMetadata(that.constantsForControl.avMetadataList[0]); 178 break; 179 case 1: 180 await that.session!.setAVMetadata(that.constantsForControl.avMetadataList[1]); 181 break; 182 case 2: 183 await that.session!.setAVMetadata(that.constantsForControl.avMetadataList[2]); 184 break; 185 } 186 } 187 188 async pause(): Promise<void> { 189 console.info(`on pause , do pause task`); 190 let that = this; 191 that.isPlayLink!.set(false); 192 that.currentState.state = avSession.PlaybackState.PLAYBACK_STATE_PAUSE; 193 await that.session!.setAVPlaybackState(that.currentState); 194 } 195 196 async previous(): Promise<void> { 197 console.info(`on playPrevious , do playPrevious task`); 198 let that = this; 199 let previousId: number = that.currentPlayItemLink!.get().itemId - 1; 200 previousId = previousId < 0 ? previousId + that.queueItems!.length : previousId; 201 await that.handleNewItem(previousId); 202 } 203 204 async next(): Promise<void> { 205 console.info(`on playNext , do playNext task`); 206 let that = this; 207 let nextId: number = that.currentPlayItemLink!.get().itemId + 1; 208 nextId = that.queueItems!.length > nextId ? nextId : nextId - that.queueItems!.length; 209 await that.handleNewItem(nextId); 210 } 211 212 async handleNewItem(itemId: number): Promise<void> { 213 let that = this; 214 that.isPlayLink!.set(true); 215 that.currentLyricLine = 0; 216 that.currentLyricLink!.set(that.lyrics[that.currentLyricLine]); 217 that.currentImageLink!.set(that.MetadataPixelMapArray[itemId]); 218 that.currentState.state = avSession.PlaybackState.PLAYBACK_STATE_PLAY; 219 await that.session!.setAVPlaybackState(that.currentState); 220 await that.setAVMetadataToController(itemId); 221 that.currentPlayItemLink!.set(that.queueItems![itemId]); 222 that.currentAVMetadataLink!.set(that.avMetadataList[that.currentPlayItemLink!.get().itemId]); 223 } 224 225 async handleCommonCommand(commandString: string, args: Type): Promise<void> { 226 let that = this; 227 switch (commandString) { 228 case 'lyrics': 229 that.isSendLyrics = args!.lyrics; 230 await that.session!.setAVMetadata(that.constantsForControl.avMetadataList[that.currentPlayItemLink!.get() 231 .itemId]); 232 await that.session!.setExtras({ lyricsLineNumber: that.currentLyricLine }); 233 await that.session!.dispatchSessionEvent('lyrics', { lyrics: that.lyrics[that.currentLyricLine] }); 234 break; 235 case 'updateQueueItems': 236 that.updateQueueItems(); 237 break; 238 default: 239 Log.warn(`Unknow command, please check`); 240 break; 241 } 242 } 243 244 async handleLyrics(): Promise<void> { 245 let that = this; 246 that.currentLyricLink!.set(that.lyrics[that.currentLyricLine]); 247 setInterval(async () => { 248 if (that.isPlayLink!.get()) { 249 Log.info('Switch lyrics line for every 10s.' + that.isSendLyrics); 250 if (that.isSendLyrics) { 251 await that.session!.setExtras({ lyricsLineNumber: that.currentLyricLine }); 252 await that.session!.dispatchSessionEvent('lyrics', { lyrics: that.lyrics[that.currentLyricLine] }); 253 } 254 that.currentLyricLine++; 255 if (that.currentLyricLine > 20) { 256 that.next(); 257 that.currentLyricLine = 0; 258 } 259 } 260 that.currentLyricLink!.set(that.lyrics[that.currentLyricLine]); 261 }, 2000); 262 } 263 264 async updateQueueItems(): Promise<void> { 265 let that = this; 266 await that.session!.setAVQueueItems(that.queueItems).catch((err: BusinessError) => { 267 Log.error(`Failed to set AVQueueItems, error info: ${JSON.stringify(err)}`); 268 }); 269 } 270 271 async prepareImageResources(): Promise<void> { 272 let that = this; 273 Log.info(`prepareImageResources in`); 274 that.queueItemPixelMapArray.push(await that.saveRawFileToPixelMap('first.png')); 275 that.queueItemPixelMapArray.push(await that.saveRawFileToPixelMap('second.png')); 276 that.queueItemPixelMapArray.push(await that.saveRawFileToPixelMap('third.png')); 277 that.MetadataPixelMapArray.push(await that.saveRawFileToPixelMap('first_with_background.png')); 278 that.MetadataPixelMapArray.push(await that.saveRawFileToPixelMap('second_with_background.png')); 279 that.MetadataPixelMapArray.push(await that.saveRawFileToPixelMap('third_with_background.png')); 280 for (let i = 0;i < that.queueItemPixelMapArray.length; i++) { 281 that.queueItems[i].description!.mediaImage = that.queueItemPixelMapArray[i]; 282 that.avMetadataList[i].mediaImage = that.MetadataPixelMapArray[i]; 283 } 284 this.currentPlayItemLink!.set(this.queueItems![0]); 285 that.currentImageLink!.set(that.MetadataPixelMapArray[0]); 286 this.currentAVMetadataLink!.set(this.avMetadataList[0]); 287 } 288 289 async saveRawFileToPixelMap(rawFilePath: string): Promise<image.PixelMap> { 290 let that = this; 291 let value: Uint8Array = await that.resourceManager.getRawFileContent(rawFilePath); 292 let imageBuffer: ArrayBuffer = value.buffer as ArrayBuffer; 293 let imageSource: image.ImageSource = image.createImageSource(imageBuffer); 294 let imagePixel: image.PixelMap = await imageSource.createPixelMap({ desiredSize: { width: 900, height: 900 } }); 295 return imagePixel; 296 } 297 298 async prepareResourcesForController(): Promise<void> { 299 let that = this; 300 Log.info(`prepareResourcesForController in`); 301 that.constantsForControl.avMetadataList[0].mediaImage = await that.saveRawFileToPixelMap('first.png'); 302 that.constantsForControl.avMetadataList[1].mediaImage = await that.saveRawFileToPixelMap('second.png'); 303 that.constantsForControl.avMetadataList[2].mediaImage = await that.saveRawFileToPixelMap('third.png'); 304 } 305}