分布式音樂播放
介紹
本示例使用fileIo獲取指定音頻文件,并通過AudioPlayer完成了音樂的播放完成了基本的音樂播放、暫停、上一曲、下一曲功能;并使用DeviceManager完成了分布式設備列表的顯示和分布式能力完成了音樂播放狀態的跨設備分享。
本示例用到了與用戶進行交互的Ability的能力接口[@ohos.ability.featureAbility]
文件存儲管理能力接口[@ohos.fileio]
屏幕屬性接口[@ohos.display]
媒體查詢接口[@ohos.mediaquery]
分布式數據管理接口[@ohos.data.distributedData]
音視頻相關媒體業務能力接口[@ohos.multimedia.media]
分布式設備管理能力接口(設備管理),實現設備之間的kvStore對象的數據傳輸交互[@ohos.distributedDeviceManager]
效果預覽
使用說明
- 音樂播放 ,點擊 播放 、 暫停 、上 一曲 、下一曲按鈕可以對音樂進行操作。
- 跨設備分享 ,組網并且雙端均已授權條件下,點擊分享按鈕,選擇設備,拉起對端設備上的音樂,并將本端的播放狀態同步到對端上。
- 跨設備停止分享 ,分享成功前提條件下,點擊停止分享按鈕,將對端設備拉起的音樂應用停止退出。
相關概念
音頻播放:媒體子系統包含了音視頻相關媒體業務,通過AudioPlayer實現音頻播放的能力。
數據分享:分布式數據管理為應用程序提供不同設備間數據庫的分布式協同能力。通過調用分布式數據各個接口,應用程序可將數據保存到分布式數據庫中,并可對分布式數據庫中的數據進行增/刪/改/查等各項操作。
資料文檔參考
具體實現
在分布式音樂播放器中,分布式設備管理包含了分布式設備搜索、分布式設備列表彈窗、遠端設備拉起三部分。
首先在分布式組網內搜索設備,然后把設備展示到分布式設備列表彈窗中,最后根據用戶的選擇拉起遠端設備。
分布式設備搜索
通過SUBSCRIBE_ID搜索分布式組網內的遠端設備,詳見registerDeviceListCallback(callback) {}模塊[源碼參考]。
/*
* Copyright (c) 2022 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import deviceManager from '@ohos.distributedDeviceManager';
import Logger from '../model/Logger';
let SUBSCRIBE_ID: number = 100;
const RANDOM: number = 65536;
const TAG: string = 'RemoteDeviceModel';
export class RemoteDeviceModel {
public deviceLists: Array< deviceManager.DeviceBasicInfo > = [];
public discoverLists: Array< deviceManager.DeviceBasicInfo > = [];
private callback: () = > void = null;
private authCallback: () = > void = null;
private deviceManager: deviceManager.DeviceManager = undefined;
registerDeviceListCallback(callback) {
if (typeof (this.deviceManager) === 'undefined') {
Logger.info(TAG, 'deviceManager.createDeviceManager begin');
try {
this.deviceManager = deviceManager.createDeviceManager('ohos.samples.distributedmusicplayer');
this.registerDeviceList(callback);
Logger.info(TAG, `createDeviceManager callback returned, value= ${JSON.stringify(this.deviceManager)}`);
} catch (error) {
Logger.info(TAG, `createDeviceManager throw error, error=${error} message=${error.message}`);
}
Logger.info(TAG, 'deviceManager.createDeviceManager end');
} else {
this.registerDeviceList(callback);
};
};
registerDeviceList(callback) {
Logger.info(TAG, 'registerDeviceListCallback');
this.callback = callback;
if (this.deviceManager === undefined) {
Logger.error(TAG, 'deviceManager has not initialized');
this.callback();
return;
};
Logger.info(TAG, 'getTrustedDeviceListSync begin');
let list: deviceManager.DeviceBasicInfo[] = [];
try {
list = this.deviceManager.getAvailableDeviceListSync();
} catch (error) {
Logger.info(TAG, `getTrustedDeviceListSync throw error, error=${error} message=${error.message}`);
};
Logger.info(TAG, `getTrustedDeviceListSync end, deviceLists= ${JSON.stringify(list)}`);
if (typeof (list) !== 'undefined' && typeof (list.length) !== 'undefined') {
this.deviceLists = list;
};
this.callback();
Logger.info(TAG, 'callback finished');
try {
this.deviceManager.on('deviceStateChange', (data) = > {
Logger.info(TAG, `deviceStateChange data= ${JSON.stringify(data)}`);
switch (data.action) {
case deviceManager.DeviceStateChange.AVAILABLE:
this.discoverLists = [];
this.deviceLists.push(data.device);
Logger.info(TAG, `reday, updated device list= ${JSON.stringify(this.deviceLists)} `);
let list: deviceManager.DeviceBasicInfo[] = [];
try {
list = this.deviceManager.getAvailableDeviceListSync();
} catch (err) {
Logger.info(TAG, `this err is ${JSON.stringify(err)}`);
}
Logger.info(TAG, `getTrustedDeviceListSync end, deviceList= ${JSON.stringify(list)}`);
if (typeof (list) !== 'undefined' && typeof (list.length) !== 'undefined') {
this.deviceLists = list;
}
this.callback();
break;
case deviceManager.DeviceStateChange.UNAVAILABLE:
if (this.deviceLists.length > 0) {
let list = [];
for (let i = 0; i < this.deviceLists.length; i++) {
if (this.deviceLists[i].deviceId !== data.device.deviceId) {
list[i] = data.device;
};
};
this.deviceLists = list;
};
Logger.info(TAG, `offline, updated device list= ${JSON.stringify(this.deviceLists)}`);
this.callback();
break;
default:
break;
};
});
this.deviceManager.on('discoverSuccess', (data) = > {
Logger.info(TAG, `discoverSuccess data= ${JSON.stringify(data)}`);
Logger.info(TAG, `discoverSuccess this.deviceLists= ${this.deviceLists}, this.deviceLists.length= ${this.deviceLists.length}`);
for (let i = 0;i < this.discoverLists.length; i++) {
if (this.discoverLists[i].deviceId === data.device.deviceId) {
Logger.info(TAG, 'device founded, ignored');
return;
};
};
this.discoverLists[this.discoverLists.length] = data.device;
this.callback();
});
this.deviceManager.on('discoverFailure', (data) = > {
Logger.info(TAG, `discoverFailure data= ${JSON.stringify(data)}`);
});
this.deviceManager.on('serviceDie', () = > {
Logger.error(TAG, 'serviceDie');
});
} catch (error) {
Logger.info(TAG, `on throw error, error=${error} message=${error.message}`);
}
let discoverParam = {
'discoverTargetType': 1
};
let filterOptions = {
'availableStatus': 0
};
Logger.info(TAG, `startDiscovering ${SUBSCRIBE_ID}`);
try {
if (this.deviceManager !== null) {
this.deviceManager.startDiscovering(discoverParam, filterOptions);
};
} catch (error) {
Logger.error(TAG, `startDiscovering throw error, error=${error} message=${error.message}`);
};
};
authDevice(device, callback) {
Logger.info(TAG, `authDevice ${device}`);
if (device !== undefined) {
for (let i = 0; i < this.discoverLists.length; i++) {
if (this.discoverLists[i].deviceId === device.deviceId) {
Logger.info(TAG, 'device founded, ignored');
let bindParam = {
bindType: 1,
targetPkgName: 'ohos.samples.distributedmusicplayer',
appName: 'Music',
};
Logger.info(TAG, `authenticateDevice ${JSON.stringify(this.discoverLists[i])}`);
try {
this.deviceManager.bindTarget(device.deviceId, bindParam, (err, data) = > {
if (err) {
Logger.error(TAG, `authenticateDevice error: ${JSON.stringify(err)}`);
this.authCallback = () = > {
};
return;
};
Logger.info(TAG, `authenticateDevice succeed, data= ${JSON.stringify(data)}`);
this.authCallback = callback;
});
} catch (error) {
Logger.error(TAG, `authenticateDevice throw error, error=${JSON.stringify(error)} message=${error.message}`);
}
}
}
}
};
unregisterDeviceListCallback() {
Logger.info(TAG, `stopDiscovering ${SUBSCRIBE_ID}`);
if (this.deviceManager === undefined) {
return;
};
try {
this.deviceManager.stopDiscovering();
this.deviceManager.off('deviceStateChange');
this.deviceManager.off('discoverSuccess');
this.deviceManager.off('discoverFailure');
this.deviceManager.off('serviceDie');
} catch (error) {
Logger.info(TAG, `stopDeviceDiscovery throw error, error=${error} message=${error.message}`);
}
this.deviceLists = [];
};
}
分布式設備列表彈窗
使用@CustomDialog彈出分布式設備列表彈窗,參考首頁。[源碼參考]。
/*
* Copyright (c) 2022 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import deviceManager from '@ohos.distributedDeviceManager';
import Logger from '../model/Logger';
const TAG: string = 'DeviceDialog';
@CustomDialog
export struct DeviceDialog {
controller?: CustomDialogController;
private deviceLists: Array< deviceManager.DeviceBasicInfo > = [];
private selectedIndex: number = 0;
private selectedIndexChange: (selectedIndex: number) = > void = () = > {
};
build() {
Column() {
Text($r('app.string.choiceDevice'))
.fontSize('32px')
.width('434px')
.fontColor(Color.Black)
.textAlign(TextAlign.Start)
.fontWeight(600)
List() {
ForEach(this.deviceLists, (item: deviceManager.DeviceBasicInfo, index: number | undefined) = > {
ListItem() {
Flex({
direction: FlexDirection.Row,
justifyContent: FlexAlign.SpaceBetween,
alignItems: ItemAlign.Center
}) {
Text(item.deviceName)
.fontSize(16)
.width('86%')
.fontColor(Color.Black)
.textAlign(TextAlign.Start)
Radio({ value: '', group: 'radioGroup' })
.radioStyle({
checkedBackgroundColor: '#ff0d64fb'
})
.width('7%')
.checked(index === this.selectedIndex ? true : false)
}
.height(55)
.onClick(() = > {
Logger.info(TAG, `select device: ${item.deviceId}`)
if (index === this.selectedIndex) {
Logger.info(TAG, 'index === this.selectedIndex')
return
}
this.selectedIndex = index !== undefined ? index : 0
if (this.controller !== undefined) {
this.controller.close()
}
this.selectedIndexChange(this.selectedIndex)
})
}
.width('434px')
.height('80px')
})
}
.margin({ top: 12 })
.width('434px')
.height('18%')
Button() {
Text($r('app.string.cancel'))
.width('90%')
.fontSize(21)
.fontColor('#ff0d64fb')
.textAlign(TextAlign.Center)
}
.margin({ bottom: 16 })
.type(ButtonType.Capsule)
.backgroundColor(Color.White)
.onClick(() = > {
if (this.controller !== undefined) {
this.controller.close()
}
})
}
.margin({ bottom: 36 })
.width('500px')
.padding(10)
.backgroundColor(Color.White)
.border({ color: Color.White, radius: 20 })
}
}
遠端設備拉起
通過startAbility(deviceId)方法拉起遠端設備的包,[源碼參考]。
/*
* Copyright (c) 2022-2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
import display from '@ohos.display';
import common from '@ohos.app.ability.common';
import mediaQuery from '@ohos.mediaquery';
import rpc from '@ohos.rpc';
import Want from '@ohos.app.ability.Want';
import PermissionRequestResult from 'security/PermissionRequestResult';
import KvStoreModel from '../model/KvStoreModel';
import Logger from '../model/Logger';
import PlayerModel from '../model/PlayerModel';
import deviceManager from '@ohos.distributedDeviceManager';
import ability from '@ohos.ability.ability';
import { RemoteDeviceModel } from '../model/RemoteDeviceModel';
import { DeviceDialog } from '../common/DeviceDialog';
import {
APPLICATION_BUNDLE_NAME,
APPLICATION_SERVICE_NAME,
MusicSharedEventCode,
MusicSharedStatus,
MusicConnectEvent
} from '../common/MusicSharedDefinition';
const TAG: string = 'Index';
const DESIGN_WIDTH: number = 720.0;
const SYSTEM_UI_HEIGHT: number = 134;
const DESIGN_RATIO: number = 16 / 9;
const ONE_HUNDRED: number = 100;
const ONE_THOUSAND: number = 1000;
const SIXTY: number = 60;
const REMOTE_ABILITY_STARTED: string = 'remoteAbilityStarted';
const ABILITY_SHARED_BUTTON = 0;
const DEFAULT_NUM = -1;
const PREVIOUS_CLICK = 2;
interface Params {
uri: string,
seekTo: number,
isPlaying: boolean
};
@Entry
@Component
struct Index {
private listener = mediaQuery.matchMediaSync('screen and (min-aspect-ratio: 1.5) or (orientation: landscape)');
@State isLand: boolean = false;
@State currentTimeText: string = '';
@State currentProgress: number = 0;
@State totalMs: number = 0;
@State riscale: number = 1;
@State risw: number = 720;
@State rish: number = 1280;
@State isSwitching: boolean = false;
@State deviceLists: Array< deviceManager.DeviceBasicInfo > = [];
@State isDialogShowing: boolean = false;
@State isDistributed: boolean = false;
@State title: string = '';
@State totalTimeText: string = '00:00';
@State albumSrc: Resource = $r('app.media.album');
@State selectedIndex: number = 0;
@State imageArrays: Array< Resource > = [$r('app.media.ic_hop'), $r('app.media.ic_play_previous'), $r('app.media.ic_play'), $r('app.media.ic_play_next')];
private dialogController: CustomDialogController | null = null;
@StorageLink('exitMusicApp') @Watch('exitMusicApp') isExitMusicApp: boolean = false;
@StorageLink('remoteServiceExtensionConnectEvent') @Watch('remoteServiceExtensionConnectEvent') isRemoteServiceExtensionConnectEvent: boolean = false;
@StorageLink('musicPlay') @Watch('musicPlay') isMusicPlay: boolean = false;
@StorageLink('musicPause') @Watch('musicPause') isMusicPause: boolean = false;
private remoteDeviceModel: RemoteDeviceModel = new RemoteDeviceModel();
private context: common.UIAbilityContext | null = null;
private deviceId: string | null = null;
private clickFlag = MusicSharedStatus.MUSIC_SHARED;
private localExtensionRemote: rpc.IRemoteObject | null = null;
onLand = (mediaQueryResult: mediaQuery.MediaQueryResult) = > {
Logger.info(TAG, `onLand: mediaQueryResult.matches= ${mediaQueryResult.matches}`);
if (mediaQueryResult.matches) {
this.isLand = true;
} else {
this.isLand = false;
};
};
showDialog() {
this.remoteDeviceModel.registerDeviceListCallback(() = > {
Logger.info(TAG, 'registerDeviceListCallback, callback entered');
this.deviceLists = [];
this.deviceLists.push({
deviceId: '0',
deviceName: 'local device',
deviceType: '0',
networkId: ''
});
let deviceTempList = this.remoteDeviceModel.discoverLists.length > 0 ? this.remoteDeviceModel.discoverLists : this.remoteDeviceModel.deviceLists;
for (let i = 0; i < deviceTempList.length; i++) {
Logger.info(TAG, `device ${i}/${deviceTempList.length} deviceId= ${deviceTempList[i].deviceId},
deviceName= ${deviceTempList[i].deviceName}, deviceType= ${deviceTempList[i].deviceType}`);
this.deviceLists.push(deviceTempList[i]);
Logger.info(TAG, 'deviceLists push end');
};
Logger.info(TAG, 'CustomDialogController start');
if (this.dialogController !== null) {
this.dialogController.close();
this.dialogController = null;
}
this.dialogController = new CustomDialogController({
builder: DeviceDialog({
deviceLists: this.deviceLists,
selectedIndex: this.selectedIndex,
selectedIndexChange: this.selectedIndexChange
}),
autoCancel: true,
customStyle: true
});
this.dialogController.open();
Logger.info(TAG, 'CustomDialogController end');
})
};
showPromptDialog(title: ResourceStr, str: ResourceStr) {
AlertDialog.show({
title: title,
message: str,
confirm: {
value: $r('app.string.cancel'),
action: () = > {
Logger.info(TAG, `Button-clicking callback`);
}
},
cancel: () = > {
Logger.info(TAG, `Closed callbacks`);
}
});
};
remoteServiceExtensionConnectEvent(event: string) {
if (typeof (event) === 'string') {
let viewThis = AppStorage.get< Index >('viewThis');
if (viewThis !== undefined) {
if (event === MusicConnectEvent.EVENT_CONNECT) {
viewThis.clickFlag = MusicSharedStatus.MUSIC_STOP_SHARED;
viewThis.imageArrays[ABILITY_SHARED_BUTTON] = $r('app.media.ic_hop');
Logger.info(TAG, 'remote service on connect callbacked');
} else if (event === MusicConnectEvent.EVENT_DISCONNECT) {
viewThis.imageArrays[ABILITY_SHARED_BUTTON] = $r('app.media.ic_hop');
viewThis.clickFlag = MusicSharedStatus.MUSIC_SHARED;
viewThis.showPromptDialog($r('app.string.ConnectRemoteDevices'), $r('app.string.onDisconnectService'));
} else if (event === MusicConnectEvent.EVENT_FAILED) {
viewThis.imageArrays[ABILITY_SHARED_BUTTON] = $r('app.media.ic_hop');
viewThis.clickFlag = MusicSharedStatus.MUSIC_SHARED;
viewThis.showPromptDialog($r('app.string.ConnectRemoteDevices'), $r('app.string.onFailedService'));
} else if (event === MusicConnectEvent.EVENT_TIMEOUT) {
this.imageArrays[ABILITY_SHARED_BUTTON] = $r('app.media.ic_hop');
viewThis.clickFlag = MusicSharedStatus.MUSIC_SHARED;
viewThis.showPromptDialog($r('app.string.ConnectRemoteDevices'), $r('app.string.ConnectionTimeout'));
}
}
} else {
Logger.info(TAG, 'event is not a string');
};
};
musicPause() {
Logger.info(TAG, 'music pause recv');
PlayerModel.pause();
let viewThis = AppStorage.get< Index >('viewThis');
viewThis!.imageArrays[PREVIOUS_CLICK] = $r('app.media.ic_play');
};
musicPlay() {
Logger.info(TAG, 'music play recv');
PlayerModel.play(DEFAULT_NUM, true);
let viewThis = AppStorage.get< Index >('viewThis');
viewThis!.imageArrays[PREVIOUS_CLICK] = $r('app.media.ic_pause');
};
exitMusicApp() {
Logger.info(TAG, `exit music app called`);
if (this.localExtensionRemote !== null && typeof (this.localExtensionRemote) === 'object') {
let option = new rpc.MessageOption();
let data = new rpc.MessageParcel();
let reply = new rpc.MessageParcel();
this.localExtensionRemote.sendRequest(
MusicSharedEventCode.STOP_LOCAL_SERIVCE,
data,
reply,
option);
} else {
Logger.info(TAG, `Remote start type is error or deviceID is empty, typeof= ${typeof (this.localExtensionRemote)}`);
};
};
connectLocalExtension() {
let localServiceWant: Want = {
bundleName: APPLICATION_BUNDLE_NAME,
abilityName: APPLICATION_SERVICE_NAME,
};
let connectOptions: ability.ConnectOptions = {
onConnect: (elementName, remote) = > {
this.localExtensionRemote = remote;
Logger.info(TAG, `onConnect called elementName is ${JSON.stringify(elementName)}`);
},
onDisconnect: (elementName) = > {
if (this.context !== null) {
this.context.terminateSelf();
Logger.info(TAG, `OnDisconnect called elementName is ${JSON.stringify(elementName)}`);
};
},
onFailed: (code) = > {
if (this.context !== null) {
this.context.terminateSelf();
Logger.info(TAG, `OnFailed called code is ${JSON.stringify(code)}`);
}
}
};
if (this.context !== null) {
this.context.connectServiceExtensionAbility(localServiceWant, connectOptions);
};
};
startRemoteExtension(deviceId: string, params: object) {
if (this.localExtensionRemote !== null && typeof (this.localExtensionRemote) === 'object' && typeof (deviceId) === 'string' && deviceId !== '') {
let option = new rpc.MessageOption();
let data = new rpc.MessageParcel();
let reply = new rpc.MessageParcel();
data.writeString(deviceId);
data.writeString(JSON.stringify(params));
this.localExtensionRemote.sendRequest(MusicSharedEventCode.START_DISTRIBUTED_MUSIC_SERVICE, data, reply, option);
this.deviceId = deviceId;
this.imageArrays[ABILITY_SHARED_BUTTON] = $r('app.media.ic_hop');
} else {
Logger.info(TAG, `Remote start type is error or deviceID is empty, typeof= ${typeof (this.localExtensionRemote)}`);
};
};
stopRemoteExtension() {
if (this.localExtensionRemote !== null && typeof (this.localExtensionRemote) === 'object' && typeof (this.deviceId) === 'string' && this.deviceId !== '') {
let option = new rpc.MessageOption();
let data = new rpc.MessageParcel();
let reply = new rpc.MessageParcel();
data.writeString(this.deviceId);
this.localExtensionRemote.sendRequest(MusicSharedEventCode.STOP_DISTRIBUTED_MUSIC_SERVICE, data, reply, option);
this.deviceId = '';
} else {
Logger.info(TAG, `Remote stopped type is wrong or deviceID is empty, typeof= ${typeof (this.localExtensionRemote)}`);
};
};
sendMessagePlay() {
if (this.localExtensionRemote !== null) {
let option = new rpc.MessageOption();
let data = new rpc.MessageParcel();
let reply = new rpc.MessageParcel();
this.localExtensionRemote.sendRequest(MusicSharedEventCode.PLAY_MUSIC_SERVICE, data, reply, option);
Logger.info(TAG, `onPlayClick send mssage success`);
} else {
Logger.info(TAG, `can not get proxy`);
return;
};
};
sendMessagePause() {
if (this.localExtensionRemote === null) {
Logger.info(TAG, `can not get proxy`);
return;
};
let option = new rpc.MessageOption();
let data = new rpc.MessageParcel();
let reply = new rpc.MessageParcel();
this.localExtensionRemote.sendRequest(MusicSharedEventCode.PAUSE_MUSIC_SERVICE, data, reply, option);
Logger.info(TAG, `onPauseClick send mssage success`);
};
onBackPress() {
if (this.isDialogShowing === true) {
this.dismissDialog();
return true;
};
return false;
};
onPageHide() {
if (this.isDialogShowing === true) {
this.dismissDialog();
return true;
};
return false;
};
dismissDialog() {
if (this.dialogController !== null) {
this.dialogController.close();
}
this.remoteDeviceModel.unregisterDeviceListCallback();
this.isDialogShowing = false;
};
startAbilityContinuation(deviceId: string) {
let params: Params = {
uri: '',
seekTo: 0,
isPlaying: false
};
Logger.info(TAG, `startAbilityContinuation PlayerModel.index= ${PlayerModel.index}/${PlayerModel.playlist.audioFiles.length}`);
if (PlayerModel.index >= 0 && PlayerModel.index <= PlayerModel.playlist.audioFiles.length) {
params = {
uri: PlayerModel.playlist.audioFiles[PlayerModel.index].fileUri,
seekTo: PlayerModel.getCurrentMs(),
isPlaying: PlayerModel.isPlaying
};
};
Logger.info(TAG, `context.startAbility deviceId= ${deviceId}`);
if (this.context !== null) {
KvStoreModel.setOnMessageReceivedListener(this.context, REMOTE_ABILITY_STARTED, () = > {
Logger.info(TAG, 'OnMessageReceived, terminateSelf');
});
};
Logger.info(TAG, `context.startAbility start`);
this.clickFlag = MusicSharedStatus.MUSIC_REMOTING;
this.startRemoteExtension(deviceId, params);
this.clearSelectState();
Logger.info(TAG, 'context.startAbility end');
};
selectedIndexChange = (selectedIndex: number) = > {
if (this.context !== null && selectedIndex === 0) {
this.context.startAbility({ bundleName: 'ohos.samples.distributedmusicplayer',
abilityName: 'ohos.samples.distributedmusicplayer.MainAbility',
deviceId: this.deviceLists[selectedIndex].deviceId,
parameters: {
isFA: 'EXIT'
}
}).then(() = > {
Logger.info(TAG, `startAbility finished`);
}).catch((err: Error) = > {
Logger.info(TAG, `startAbility filed error = ${JSON.stringify(err)}`);
});
this.isDistributed = false;
this.selectedIndex = 0;
if (this.dialogController !== null) {
this.dialogController.close();
}
this.deviceLists = [];
return;
};
this.selectedIndex = selectedIndex;
this.selectDevice();
};
selectDevice() {
Logger.info(TAG, 'start ability ......');
if (this.selectedIndex !== undefined && (this.remoteDeviceModel === null || this.remoteDeviceModel.discoverLists.length <= 0)) {
Logger.info(TAG, `start ability device:${JSON.stringify(this.deviceLists)}`);
this.startAbilityContinuation(this.deviceLists[this.selectedIndex].networkId as string);
this.clearSelectState();
return;
};
Logger.info(TAG, 'start ability, needAuth');
if (this.selectedIndex !== undefined){
this.remoteDeviceModel.authDevice(this.deviceLists[this.selectedIndex], (device: deviceManager.DeviceBasicInfo) = > {
Logger.info(TAG, 'auth and online finished');
this.startAbilityContinuation(device.networkId);
});
}
Logger.info(TAG, 'start ability2 ......');
this.clearSelectState();
};
clearSelectState() {
this.deviceLists = [];
if (this.dialogController) {
this.dialogController.close();
this.dialogController = null;
};
};
getShownTimer(ms: number) {
let minStr: string;
let secStr: string;
let seconds = Math.floor(ms / ONE_THOUSAND);
let sec = seconds % SIXTY;
Logger.info(TAG, `getShownTimer sec = ${sec}`);
let min = (seconds - sec) / SIXTY;
Logger.info(TAG, `getShownTimer min = ${min}`);
if (sec < 10) {
secStr = '0' + sec;
} else {
secStr = sec.toString(10);
};
if (min < 10) {
minStr = '0' + min;
} else {
minStr = min.toString(10);
};
Logger.warn(TAG, `getShownTimer = ${minStr}:${secStr}`);
return minStr + ':' + secStr;
};
refreshSongInfo(index: number) {
Logger.info(TAG, `refreshSongInfo ${index}/${PlayerModel.playlist.audioFiles.length}`);
if (index >= PlayerModel.playlist.audioFiles.length) {
Logger.warn(TAG, 'refreshSongInfo ignored');
return;
};
// update song title
this.title = PlayerModel.playlist.audioFiles[index].name;
this.albumSrc = (index % 2 === 0) ? $r('app.media.album') : $r('app.media.album2');
// update duration
this.totalMs = PlayerModel.getDuration();
this.totalTimeText = this.getShownTimer(this.totalMs);
this.currentTimeText = this.getShownTimer(PlayerModel.getCurrentMs());
Logger.info(TAG, `refreshSongInfo this.title= ${this.title}, this.totalMs= ${this.totalMs}, this.totalTimeText= ${this.totalTimeText},this.currentTimeText= ${this.currentTimeText}`);
};
onAppSharedClick() {
if (this.clickFlag === MusicSharedStatus.MUSIC_SHARED) {
Logger.info(TAG, `1start button is ${JSON.stringify(this.imageArrays[ABILITY_SHARED_BUTTON])}`);
this.showDialog();
} else if (this.clickFlag === MusicSharedStatus.MUSIC_STOP_SHARED) {
Logger.info(TAG, `2start button is ${JSON.stringify(this.imageArrays[ABILITY_SHARED_BUTTON])}`);
this.stopRemoteExtension();
this.imageArrays[ABILITY_SHARED_BUTTON] = $r('app.media.ic_hop');
};
};
onPreviousClick() {
if (this.isSwitching) {
Logger.info(TAG, 'onPreviousClick ignored, isSwitching');
return;
};
Logger.info(TAG, 'onPreviousClick');
PlayerModel.index--;
if (PlayerModel.index < 0 && PlayerModel.playlist.audioFiles.length >= 1) {
PlayerModel.index = PlayerModel.playlist.audioFiles.length - 1;
};
this.currentProgress = 0;
this.isSwitching = true;
PlayerModel.preLoad(PlayerModel.index, () = > {
this.refreshSongInfo(PlayerModel.index);
PlayerModel.play(0, true);
if (PlayerModel.isPlaying) {
this.imageArrays[PREVIOUS_CLICK] = $r('app.media.ic_pause');
};
this.isSwitching = false;
});
};
onNextClick() {
if (this.isSwitching) {
Logger.info(TAG, 'onNextClick ignored, isSwitching');
return;
};
Logger.info(TAG, 'onNextClick');
PlayerModel.index++;
if (PlayerModel.index >= PlayerModel.playlist.audioFiles.length) {
PlayerModel.index = 0;
};
this.currentProgress = 0;
this.isSwitching = true;
PlayerModel.preLoad(PlayerModel.index, () = > {
this.refreshSongInfo(PlayerModel.index);
PlayerModel.play(0, true);
if (PlayerModel.isPlaying) {
this.imageArrays[PREVIOUS_CLICK] = $r('app.media.ic_pause');
};
this.isSwitching = false;
});
};
onPlayClick() {
if (this.isSwitching) {
Logger.info(TAG, 'onPlayClick ignored, isSwitching');
return;
};
Logger.info(TAG, `onPlayClick isPlaying= ${PlayerModel.isPlaying}`);
if (PlayerModel.isPlaying) {
PlayerModel.pause();
this.imageArrays[PREVIOUS_CLICK] = $r('app.media.ic_play');
this.sendMessagePause();
} else {
PlayerModel.preLoad(PlayerModel.index, () = > {
PlayerModel.play(DEFAULT_NUM, true);
this.imageArrays[PREVIOUS_CLICK] = $r('app.media.ic_pause');
this.sendMessagePlay();
})
};
};
restoreFromWant() {
Logger.info(TAG, 'restoreFromWant');
let status: Record< string, Object > | undefined = AppStorage.get('status');
if (status !== undefined && status !== null && status.uri !== null) {
KvStoreModel.broadcastMessage(this.context, REMOTE_ABILITY_STARTED);
Logger.info(TAG, 'restorePlayingStatus');
PlayerModel.restorePlayingStatus(status, (index: number) = > {
Logger.info(TAG, `restorePlayingStatus finished, index= ${index}`);
if (index >= 0) {
this.refreshSongInfo(index);
} else {
PlayerModel.preLoad(0, () = > {
this.refreshSongInfo(0);
})
}
if (status !== undefined) {
Logger.info(TAG, `Index PlayerModel.restorePlayingStatus this.totalMs = ${this.totalMs}, status.seekTo = ${status.seekTo}`);
this.currentProgress = Math.floor(Number(status.seekTo) / this.totalMs * ONE_HUNDRED);
}
})
} else {
PlayerModel.preLoad(0, () = > {
this.refreshSongInfo(0);
});
}
};
aboutToAppear() {
Logger.info(TAG, `begin`);
Logger.info(TAG, 'grantPermission');
this.context = getContext(this) as common.UIAbilityContext;
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
let permission: Array< Permissions > = ['ohos.permission.DISTRIBUTED_DATASYNC'];
try {
atManager.requestPermissionsFromUser(this.context, permission).then((data: PermissionRequestResult) = > {
Logger.info(TAG, `data: ${JSON.stringify(data)}`);
}).catch((err: object) = > {
Logger.info(TAG, `err: ${JSON.stringify(err)}`);
})
} catch (err) {
Logger.info(TAG, `catch err- >${JSON.stringify(err)}`);
}
display.getDefaultDisplay().then((dis: display.Display) = > {
Logger.info(TAG, `getDefaultDisplay dis= ${JSON.stringify(dis)}`);
let proportion = DESIGN_WIDTH / dis.width;
let screenWidth = DESIGN_WIDTH;
let screenHeight = (dis.height - SYSTEM_UI_HEIGHT) * proportion;
this.riscale = (screenHeight / screenWidth) / DESIGN_RATIO;
if (this.riscale < 1) {
// The screen ratio is shorter than design ratio
this.risw = screenWidth * this.riscale;
this.rish = screenHeight;
} else {
// The screen ratio is longer than design ratio
this.risw = screenWidth;
this.rish = screenHeight / this.riscale;
}
Logger.info(TAG, `proportion=${proportion} , screenWidth= ${screenWidth},
screenHeight= ${screenHeight} , riscale= ${this.riscale} , risw= ${this.risw} , rish= ${this.rish}`);
})
Logger.info(TAG, 'getDefaultDisplay end');
this.currentTimeText = this.getShownTimer(0);
PlayerModel.setOnStatusChangedListener((isPlaying: string) = > {
Logger.info(TAG, `on player status changed, isPlaying= ${isPlaying} refresh ui`);
PlayerModel.setOnPlayingProgressListener((currentTimeMs: number) = > {
this.currentTimeText = this.getShownTimer(currentTimeMs);
this.currentProgress = Math.floor(currentTimeMs / this.totalMs * ONE_HUNDRED);
});
if (isPlaying) {
this.imageArrays[PREVIOUS_CLICK] = $r('app.media.ic_pause');
} else {
this.imageArrays[PREVIOUS_CLICK] = $r('app.media.ic_play');
}
});
PlayerModel.getPlaylist(() = > {
Logger.info(TAG, 'on playlist generated, refresh ui');
this.restoreFromWant();
});
AppStorage.setOrCreate('viewThis', this);
this.connectLocalExtension();
};
aboutToDisappear() {
Logger.info(TAG, `aboutToDisappear begin`)
if (PlayerModel === undefined) {
return
}
PlayerModel.release()
this.remoteDeviceModel.unregisterDeviceListCallback()
this.dialogController = null
KvStoreModel.deleteKvStore()
Logger.info(TAG, `aboutToDisappear end`)
};
build() {
Column() {
Blank()
.width('100%')
.height(72)
Text(this.title)
.width('100%')
.fontSize(28)
.margin({ top: '10%' })
.fontColor(Color.White)
.textAlign(TextAlign.Center)
Image(this.albumSrc)
.width(this.isLand ? '60%' : '89%')
.objectFit(ImageFit.Contain)
.margin({ top: 50, left: 40, right: 40 })
Row() {
Text(this.currentTimeText)
.fontSize(20)
.fontColor(Color.White)
Blank()
Text(this.totalTimeText)
.fontSize(20)
.fontColor(Color.White)
}
.width('90%')
.margin({ top: '12%' })
Slider({ value: typeof (this.currentProgress) === 'number' ? this.currentProgress : 0 })
.trackColor('#64CCE7FF')
.width('90%')
.selectedColor('#ff0c4ae7')
.onChange((value: number, mode: SliderChangeMode) = > {
this.currentProgress = value;
if (typeof (this.totalMs) !== 'number') {
this.currentProgress = 0;
Logger.info(TAG, `setProgress ignored, totalMs= ${this.totalMs}`);
return;
};
let currentMs = this.currentProgress / ONE_HUNDRED * this.totalMs;
this.currentTimeText = this.getShownTimer(currentMs);
if (mode === SliderChangeMode.End || mode === 3) {
Logger.info(TAG, `player.seek= ${currentMs}`);
PlayerModel.seek(currentMs);
};
})
Row() {
ForEach(this.imageArrays, (item: Resource, index: number | undefined) = > {
Column() {
Image(item)
.size({ width: 74, height: 74 })
.objectFit(ImageFit.Contain)
.onClick(() = > {
switch (index) {
case 0:
this.onAppSharedClick();
break;
case 1:
this.onPreviousClick();
break;
case 2:
this.onPlayClick();
break;
case 3:
this.onNextClick();
break;
default:
break;
}
})
}
.id('image' + (index !== undefined ? (index + 1) : 0))
.width(100)
.height(100)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
})
}
.width('100%')
.margin({ top: '4%' })
.justifyContent(FlexAlign.SpaceEvenly)
}
.width('100%')
.height('100%')
.backgroundImage($r('app.media.bg_blurry'))
.backgroundImageSize({ width: '100%', height: '100%' })
}
}
分布式數據管理
(1) 管理分布式數據庫
創建一個KVManager對象實例,用于管理分布式數據庫對象。通過distributedData.createKVManager(config),并通過指定Options和storeId,創建并獲取KVStore數據庫,并通過Promise方式返回,此方法為異步方法,例如this.kvManager.getKVStore(STORE_ID, options).then((store) => {})
(2) 訂閱分布式數據變化
通過訂閱分布式數據庫所有(本地及遠端)數據變化實現數據協同[源碼參考]。
/*
* Copyright (c) 2022-2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
import display from '@ohos.display';
import common from '@ohos.app.ability.common';
import mediaQuery from '@ohos.mediaquery';
import rpc from '@ohos.rpc';
import Want from '@ohos.app.ability.Want';
import PermissionRequestResult from 'security/PermissionRequestResult';
import KvStoreModel from '../model/KvStoreModel';
import Logger from '../model/Logger';
import PlayerModel from '../model/PlayerModel';
import deviceManager from '@ohos.distributedDeviceManager';
import ability from '@ohos.ability.ability';
import { RemoteDeviceModel } from '../model/RemoteDeviceModel';
import { DeviceDialog } from '../common/DeviceDialog';
import {
APPLICATION_BUNDLE_NAME,
APPLICATION_SERVICE_NAME,
MusicSharedEventCode,
MusicSharedStatus,
MusicConnectEvent
} from '../common/MusicSharedDefinition';
const TAG: string = 'Index';
const DESIGN_WIDTH: number = 720.0;
const SYSTEM_UI_HEIGHT: number = 134;
const DESIGN_RATIO: number = 16 / 9;
const ONE_HUNDRED: number = 100;
const ONE_THOUSAND: number = 1000;
const SIXTY: number = 60;
const REMOTE_ABILITY_STARTED: string = 'remoteAbilityStarted';
const ABILITY_SHARED_BUTTON = 0;
const DEFAULT_NUM = -1;
const PREVIOUS_CLICK = 2;
interface Params {
uri: string,
seekTo: number,
isPlaying: boolean
};
@Entry
@Component
struct Index {
private listener = mediaQuery.matchMediaSync('screen and (min-aspect-ratio: 1.5) or (orientation: landscape)');
@State isLand: boolean = false;
@State currentTimeText: string = '';
@State currentProgress: number = 0;
@State totalMs: number = 0;
@State riscale: number = 1;
@State risw: number = 720;
@State rish: number = 1280;
@State isSwitching: boolean = false;
@State deviceLists: Array< deviceManager.DeviceBasicInfo > = [];
@State isDialogShowing: boolean = false;
@State isDistributed: boolean = false;
@State title: string = '';
@State totalTimeText: string = '00:00';
@State albumSrc: Resource = $r('app.media.album');
@State selectedIndex: number = 0;
@State imageArrays: Array< Resource > = [$r('app.media.ic_hop'), $r('app.media.ic_play_previous'), $r('app.media.ic_play'), $r('app.media.ic_play_next')];
private dialogController: CustomDialogController | null = null;
@StorageLink('exitMusicApp') @Watch('exitMusicApp') isExitMusicApp: boolean = false;
@StorageLink('remoteServiceExtensionConnectEvent') @Watch('remoteServiceExtensionConnectEvent') isRemoteServiceExtensionConnectEvent: boolean = false;
@StorageLink('musicPlay') @Watch('musicPlay') isMusicPlay: boolean = false;
@StorageLink('musicPause') @Watch('musicPause') isMusicPause: boolean = false;
private remoteDeviceModel: RemoteDeviceModel = new RemoteDeviceModel();
private context: common.UIAbilityContext | null = null;
private deviceId: string | null = null;
private clickFlag = MusicSharedStatus.MUSIC_SHARED;
private localExtensionRemote: rpc.IRemoteObject | null = null;
onLand = (mediaQueryResult: mediaQuery.MediaQueryResult) = > {
Logger.info(TAG, `onLand: mediaQueryResult.matches= ${mediaQueryResult.matches}`);
if (mediaQueryResult.matches) {
this.isLand = true;
} else {
this.isLand = false;
};
};
showDialog() {
this.remoteDeviceModel.registerDeviceListCallback(() = > {
Logger.info(TAG, 'registerDeviceListCallback, callback entered');
this.deviceLists = [];
this.deviceLists.push({
deviceId: '0',
deviceName: 'local device',
deviceType: '0',
networkId: ''
});
let deviceTempList = this.remoteDeviceModel.discoverLists.length > 0 ? this.remoteDeviceModel.discoverLists : this.remoteDeviceModel.deviceLists;
for (let i = 0; i < deviceTempList.length; i++) {
Logger.info(TAG, `device ${i}/${deviceTempList.length} deviceId= ${deviceTempList[i].deviceId},
deviceName= ${deviceTempList[i].deviceName}, deviceType= ${deviceTempList[i].deviceType}`);
this.deviceLists.push(deviceTempList[i]);
Logger.info(TAG, 'deviceLists push end');
};
Logger.info(TAG, 'CustomDialogController start');
if (this.dialogController !== null) {
this.dialogController.close();
this.dialogController = null;
}
this.dialogController = new CustomDialogController({
builder: DeviceDialog({
deviceLists: this.deviceLists,
selectedIndex: this.selectedIndex,
selectedIndexChange: this.selectedIndexChange
}),
autoCancel: true,
customStyle: true
});
this.dialogController.open();
Logger.info(TAG, 'CustomDialogController end');
})
};
showPromptDialog(title: ResourceStr, str: ResourceStr) {
AlertDialog.show({
title: title,
message: str,
confirm: {
value: $r('app.string.cancel'),
action: () = > {
Logger.info(TAG, `Button-clicking callback`);
}
},
cancel: () = > {
Logger.info(TAG, `Closed callbacks`);
}
});
};
remoteServiceExtensionConnectEvent(event: string) {
if (typeof (event) === 'string') {
let viewThis = AppStorage.get< Index >('viewThis');
if (viewThis !== undefined) {
if (event === MusicConnectEvent.EVENT_CONNECT) {
viewThis.clickFlag = MusicSharedStatus.MUSIC_STOP_SHARED;
viewThis.imageArrays[ABILITY_SHARED_BUTTON] = $r('app.media.ic_hop');
Logger.info(TAG, 'remote service on connect callbacked');
} else if (event === MusicConnectEvent.EVENT_DISCONNECT) {
viewThis.imageArrays[ABILITY_SHARED_BUTTON] = $r('app.media.ic_hop');
viewThis.clickFlag = MusicSharedStatus.MUSIC_SHARED;
viewThis.showPromptDialog($r('app.string.ConnectRemoteDevices'), $r('app.string.onDisconnectService'));
} else if (event === MusicConnectEvent.EVENT_FAILED) {
viewThis.imageArrays[ABILITY_SHARED_BUTTON] = $r('app.media.ic_hop');
viewThis.clickFlag = MusicSharedStatus.MUSIC_SHARED;
viewThis.showPromptDialog($r('app.string.ConnectRemoteDevices'), $r('app.string.onFailedService'));
} else if (event === MusicConnectEvent.EVENT_TIMEOUT) {
this.imageArrays[ABILITY_SHARED_BUTTON] = $r('app.media.ic_hop');
viewThis.clickFlag = MusicSharedStatus.MUSIC_SHARED;
viewThis.showPromptDialog($r('app.string.ConnectRemoteDevices'), $r('app.string.ConnectionTimeout'));
}
}
} else {
Logger.info(TAG, 'event is not a string');
};
};
musicPause() {
Logger.info(TAG, 'music pause recv');
PlayerModel.pause();
let viewThis = AppStorage.get< Index >('viewThis');
viewThis!.imageArrays[PREVIOUS_CLICK] = $r('app.media.ic_play');
};
musicPlay() {
Logger.info(TAG, 'music play recv');
PlayerModel.play(DEFAULT_NUM, true);
let viewThis = AppStorage.get< Index >('viewThis');
viewThis!.imageArrays[PREVIOUS_CLICK] = $r('app.media.ic_pause');
};
exitMusicApp() {
Logger.info(TAG, `exit music app called`);
if (this.localExtensionRemote !== null && typeof (this.localExtensionRemote) === 'object') {
let option = new rpc.MessageOption();
let data = new rpc.MessageParcel();
let reply = new rpc.MessageParcel();
this.localExtensionRemote.sendRequest(
MusicSharedEventCode.STOP_LOCAL_SERIVCE,
data,
reply,
option);
} else {
Logger.info(TAG, `Remote start type is error or deviceID is empty, typeof= ${typeof (this.localExtensionRemote)}`);
};
};
connectLocalExtension() {
let localServiceWant: Want = {
bundleName: APPLICATION_BUNDLE_NAME,
abilityName: APPLICATION_SERVICE_NAME,
};
let connectOptions: ability.ConnectOptions = {
onConnect: (elementName, remote) = > {
this.localExtensionRemote = remote;
Logger.info(TAG, `onConnect called elementName is ${JSON.stringify(elementName)}`);
},
onDisconnect: (elementName) = > {
if (this.context !== null) {
this.context.terminateSelf();
Logger.info(TAG, `OnDisconnect called elementName is ${JSON.stringify(elementName)}`);
};
},
onFailed: (code) = > {
if (this.context !== null) {
this.context.terminateSelf();
Logger.info(TAG, `OnFailed called code is ${JSON.stringify(code)}`);
}
}
};
if (this.context !== null) {
this.context.connectServiceExtensionAbility(localServiceWant, connectOptions);
};
};
startRemoteExtension(deviceId: string, params: object) {
if (this.localExtensionRemote !== null && typeof (this.localExtensionRemote) === 'object' && typeof (deviceId) === 'string' && deviceId !== '') {
let option = new rpc.MessageOption();
let data = new rpc.MessageParcel();
let reply = new rpc.MessageParcel();
data.writeString(deviceId);
data.writeString(JSON.stringify(params));
this.localExtensionRemote.sendRequest(MusicSharedEventCode.START_DISTRIBUTED_MUSIC_SERVICE, data, reply, option);
this.deviceId = deviceId;
this.imageArrays[ABILITY_SHARED_BUTTON] = $r('app.media.ic_hop');
} else {
Logger.info(TAG, `Remote start type is error or deviceID is empty, typeof= ${typeof (this.localExtensionRemote)}`);
};
};
stopRemoteExtension() {
if (this.localExtensionRemote !== null && typeof (this.localExtensionRemote) === 'object' && typeof (this.deviceId) === 'string' && this.deviceId !== '') {
let option = new rpc.MessageOption();
let data = new rpc.MessageParcel();
let reply = new rpc.MessageParcel();
data.writeString(this.deviceId);
this.localExtensionRemote.sendRequest(MusicSharedEventCode.STOP_DISTRIBUTED_MUSIC_SERVICE, data, reply, option);
this.deviceId = '';
} else {
Logger.info(TAG, `Remote stopped type is wrong or deviceID is empty, typeof= ${typeof (this.localExtensionRemote)}`);
};
};
sendMessagePlay() {
if (this.localExtensionRemote !== null) {
let option = new rpc.MessageOption();
let data = new rpc.MessageParcel();
let reply = new rpc.MessageParcel();
this.localExtensionRemote.sendRequest(MusicSharedEventCode.PLAY_MUSIC_SERVICE, data, reply, option);
Logger.info(TAG, `onPlayClick send mssage success`);
} else {
Logger.info(TAG, `can not get proxy`);
return;
};
};
sendMessagePause() {
if (this.localExtensionRemote === null) {
Logger.info(TAG, `can not get proxy`);
return;
};
let option = new rpc.MessageOption();
let data = new rpc.MessageParcel();
let reply = new rpc.MessageParcel();
this.localExtensionRemote.sendRequest(MusicSharedEventCode.PAUSE_MUSIC_SERVICE, data, reply, option);
Logger.info(TAG, `onPauseClick send mssage success`);
};
onBackPress() {
if (this.isDialogShowing === true) {
this.dismissDialog();
return true;
};
return false;
};
onPageHide() {
if (this.isDialogShowing === true) {
this.dismissDialog();
return true;
};
return false;
};
dismissDialog() {
if (this.dialogController !== null) {
this.dialogController.close();
}
this.remoteDeviceModel.unregisterDeviceListCallback();
this.isDialogShowing = false;
};
startAbilityContinuation(deviceId: string) {
let params: Params = {
uri: '',
seekTo: 0,
isPlaying: false
};
Logger.info(TAG, `startAbilityContinuation PlayerModel.index= ${PlayerModel.index}/${PlayerModel.playlist.audioFiles.length}`);
if (PlayerModel.index >= 0 && PlayerModel.index <= PlayerModel.playlist.audioFiles.length) {
params = {
uri: PlayerModel.playlist.audioFiles[PlayerModel.index].fileUri,
seekTo: PlayerModel.getCurrentMs(),
isPlaying: PlayerModel.isPlaying
};
};
Logger.info(TAG, `context.startAbility deviceId= ${deviceId}`);
if (this.context !== null) {
KvStoreModel.setOnMessageReceivedListener(this.context, REMOTE_ABILITY_STARTED, () = > {
Logger.info(TAG, 'OnMessageReceived, terminateSelf');
});
};
Logger.info(TAG, `context.startAbility start`);
this.clickFlag = MusicSharedStatus.MUSIC_REMOTING;
this.startRemoteExtension(deviceId, params);
this.clearSelectState();
Logger.info(TAG, 'context.startAbility end');
};
selectedIndexChange = (selectedIndex: number) = > {
if (this.context !== null && selectedIndex === 0) {
this.context.startAbility({ bundleName: 'ohos.samples.distributedmusicplayer',
abilityName: 'ohos.samples.distributedmusicplayer.MainAbility',
deviceId: this.deviceLists[selectedIndex].deviceId,
parameters: {
isFA: 'EXIT'
}
}).then(() = > {
Logger.info(TAG, `startAbility finished`);
}).catch((err: Error) = > {
Logger.info(TAG, `startAbility filed error = ${JSON.stringify(err)}`);
});
this.isDistributed = false;
this.selectedIndex = 0;
if (this.dialogController !== null) {
this.dialogController.close();
}
this.deviceLists = [];
return;
};
this.selectedIndex = selectedIndex;
this.selectDevice();
};
selectDevice() {
Logger.info(TAG, 'start ability ......');
if (this.selectedIndex !== undefined && (this.remoteDeviceModel === null || this.remoteDeviceModel.discoverLists.length <= 0)) {
Logger.info(TAG, `start ability device:${JSON.stringify(this.deviceLists)}`);
this.startAbilityContinuation(this.deviceLists[this.selectedIndex].networkId as string);
this.clearSelectState();
return;
};
Logger.info(TAG, 'start ability, needAuth');
if (this.selectedIndex !== undefined){
this.remoteDeviceModel.authDevice(this.deviceLists[this.selectedIndex], (device: deviceManager.DeviceBasicInfo) = > {
Logger.info(TAG, 'auth and online finished');
this.startAbilityContinuation(device.networkId);
});
}
Logger.info(TAG, 'start ability2 ......');
this.clearSelectState();
};
clearSelectState() {
this.deviceLists = [];
if (this.dialogController) {
this.dialogController.close();
this.dialogController = null;
};
};
getShownTimer(ms: number) {
let minStr: string;
let secStr: string;
let seconds = Math.floor(ms / ONE_THOUSAND);
let sec = seconds % SIXTY;
Logger.info(TAG, `getShownTimer sec = ${sec}`);
let min = (seconds - sec) / SIXTY;
Logger.info(TAG, `getShownTimer min = ${min}`);
if (sec < 10) {
secStr = '0' + sec;
} else {
secStr = sec.toString(10);
};
if (min < 10) {
minStr = '0' + min;
} else {
minStr = min.toString(10);
};
Logger.warn(TAG, `getShownTimer = ${minStr}:${secStr}`);
return minStr + ':' + secStr;
};
refreshSongInfo(index: number) {
Logger.info(TAG, `refreshSongInfo ${index}/${PlayerModel.playlist.audioFiles.length}`);
if (index >= PlayerModel.playlist.audioFiles.length) {
Logger.warn(TAG, 'refreshSongInfo ignored');
return;
};
// update song title
this.title = PlayerModel.playlist.audioFiles[index].name;
this.albumSrc = (index % 2 === 0) ? $r('app.media.album') : $r('app.media.album2');
// update duration
this.totalMs = PlayerModel.getDuration();
this.totalTimeText = this.getShownTimer(this.totalMs);
this.currentTimeText = this.getShownTimer(PlayerModel.getCurrentMs());
Logger.info(TAG, `refreshSongInfo this.title= ${this.title}, this.totalMs= ${this.totalMs}, this.totalTimeText= ${this.totalTimeText},this.currentTimeText= ${this.currentTimeText}`);
};
onAppSharedClick() {
if (this.clickFlag === MusicSharedStatus.MUSIC_SHARED) {
Logger.info(TAG, `1start button is ${JSON.stringify(this.imageArrays[ABILITY_SHARED_BUTTON])}`);
this.showDialog();
} else if (this.clickFlag === MusicSharedStatus.MUSIC_STOP_SHARED) {
Logger.info(TAG, `2start button is ${JSON.stringify(this.imageArrays[ABILITY_SHARED_BUTTON])}`);
this.stopRemoteExtension();
this.imageArrays[ABILITY_SHARED_BUTTON] = $r('app.media.ic_hop');
};
};
onPreviousClick() {
if (this.isSwitching) {
Logger.info(TAG, 'onPreviousClick ignored, isSwitching');
return;
};
Logger.info(TAG, 'onPreviousClick');
PlayerModel.index--;
if (PlayerModel.index < 0 && PlayerModel.playlist.audioFiles.length >= 1) {
PlayerModel.index = PlayerModel.playlist.audioFiles.length - 1;
};
this.currentProgress = 0;
this.isSwitching = true;
PlayerModel.preLoad(PlayerModel.index, () = > {
this.refreshSongInfo(PlayerModel.index);
PlayerModel.play(0, true);
if (PlayerModel.isPlaying) {
this.imageArrays[PREVIOUS_CLICK] = $r('app.media.ic_pause');
};
this.isSwitching = false;
});
};
onNextClick() {
if (this.isSwitching) {
Logger.info(TAG, 'onNextClick ignored, isSwitching');
return;
};
Logger.info(TAG, 'onNextClick');
PlayerModel.index++;
if (PlayerModel.index >= PlayerModel.playlist.audioFiles.length) {
PlayerModel.index = 0;
};
this.currentProgress = 0;
this.isSwitching = true;
PlayerModel.preLoad(PlayerModel.index, () = > {
this.refreshSongInfo(PlayerModel.index);
PlayerModel.play(0, true);
if (PlayerModel.isPlaying) {
this.imageArrays[PREVIOUS_CLICK] = $r('app.media.ic_pause');
};
this.isSwitching = false;
});
};
onPlayClick() {
if (this.isSwitching) {
Logger.info(TAG, 'onPlayClick ignored, isSwitching');
return;
};
Logger.info(TAG, `onPlayClick isPlaying= ${PlayerModel.isPlaying}`);
if (PlayerModel.isPlaying) {
PlayerModel.pause();
this.imageArrays[PREVIOUS_CLICK] = $r('app.media.ic_play');
this.sendMessagePause();
} else {
PlayerModel.preLoad(PlayerModel.index, () = > {
PlayerModel.play(DEFAULT_NUM, true);
this.imageArrays[PREVIOUS_CLICK] = $r('app.media.ic_pause');
this.sendMessagePlay();
})
};
};
restoreFromWant() {
Logger.info(TAG, 'restoreFromWant');
let status: Record< string, Object > | undefined = AppStorage.get('status');
if (status !== undefined && status !== null && status.uri !== null) {
KvStoreModel.broadcastMessage(this.context, REMOTE_ABILITY_STARTED);
Logger.info(TAG, 'restorePlayingStatus');
PlayerModel.restorePlayingStatus(status, (index: number) = > {
Logger.info(TAG, `restorePlayingStatus finished, index= ${index}`);
if (index >= 0) {
this.refreshSongInfo(index);
} else {
PlayerModel.preLoad(0, () = > {
this.refreshSongInfo(0);
})
}
if (status !== undefined) {
Logger.info(TAG, `Index PlayerModel.restorePlayingStatus this.totalMs = ${this.totalMs}, status.seekTo = ${status.seekTo}`);
this.currentProgress = Math.floor(Number(status.seekTo) / this.totalMs * ONE_HUNDRED);
}
})
} else {
PlayerModel.preLoad(0, () = > {
this.refreshSongInfo(0);
});
}
};
aboutToAppear() {
Logger.info(TAG, `begin`);
Logger.info(TAG, 'grantPermission');
this.context = getContext(this) as common.UIAbilityContext;
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
let permission: Array< Permissions > = ['ohos.permission.DISTRIBUTED_DATASYNC'];
try {
atManager.requestPermissionsFromUser(this.context, permission).then((data: PermissionRequestResult) = > {
Logger.info(TAG, `data: ${JSON.stringify(data)}`);
}).catch((err: object) = > {
Logger.info(TAG, `err: ${JSON.stringify(err)}`);
})
} catch (err) {
Logger.info(TAG, `catch err- >${JSON.stringify(err)}`);
}
display.getDefaultDisplay().then((dis: display.Display) = > {
Logger.info(TAG, `getDefaultDisplay dis= ${JSON.stringify(dis)}`);
let proportion = DESIGN_WIDTH / dis.width;
let screenWidth = DESIGN_WIDTH;
let screenHeight = (dis.height - SYSTEM_UI_HEIGHT) * proportion;
this.riscale = (screenHeight / screenWidth) / DESIGN_RATIO;
if (this.riscale < 1) {
// The screen ratio is shorter than design ratio
this.risw = screenWidth * this.riscale;
this.rish = screenHeight;
} else {
// The screen ratio is longer than design ratio
this.risw = screenWidth;
this.rish = screenHeight / this.riscale;
}
Logger.info(TAG, `proportion=${proportion} , screenWidth= ${screenWidth},
screenHeight= ${screenHeight} , riscale= ${this.riscale} , risw= ${this.risw} , rish= ${this.rish}`);
})
Logger.info(TAG, 'getDefaultDisplay end');
this.currentTimeText = this.getShownTimer(0);
PlayerModel.setOnStatusChangedListener((isPlaying: string) = > {
Logger.info(TAG, `on player status changed, isPlaying= ${isPlaying} refresh ui`);
PlayerModel.setOnPlayingProgressListener((currentTimeMs: number) = > {
this.currentTimeText = this.getShownTimer(currentTimeMs);
this.currentProgress = Math.floor(currentTimeMs / this.totalMs * ONE_HUNDRED);
});
if (isPlaying) {
this.imageArrays[PREVIOUS_CLICK] = $r('app.media.ic_pause');
} else {
this.imageArrays[PREVIOUS_CLICK] = $r('app.media.ic_play');
}
});
PlayerModel.getPlaylist(() = > {
Logger.info(TAG, 'on playlist generated, refresh ui');
this.restoreFromWant();
});
AppStorage.setOrCreate('viewThis', this);
this.connectLocalExtension();
};
aboutToDisappear() {
Logger.info(TAG, `aboutToDisappear begin`)
if (PlayerModel === undefined) {
return
}
PlayerModel.release()
this.remoteDeviceModel.unregisterDeviceListCallback()
this.dialogController = null
KvStoreModel.deleteKvStore()
Logger.info(TAG, `aboutToDisappear end`)
};
build() {
Column() {
Blank()
.width('100%')
.height(72)
Text(this.title)
.width('100%')
.fontSize(28)
.margin({ top: '10%' })
.fontColor(Color.White)
.textAlign(TextAlign.Center)
Image(this.albumSrc)
.width(this.isLand ? '60%' : '89%')
.objectFit(ImageFit.Contain)
.margin({ top: 50, left: 40, right: 40 })
Row() {
Text(this.currentTimeText)
.fontSize(20)
.fontColor(Color.White)
Blank()
Text(this.totalTimeText)
.fontSize(20)
.fontColor(Color.White)
}
.width('90%')
.margin({ top: '12%' })
Slider({ value: typeof (this.currentProgress) === 'number' ? this.currentProgress : 0 })
.trackColor('#64CCE7FF')
.width('90%')
.selectedColor('#ff0c4ae7')
.onChange((value: number, mode: SliderChangeMode) = > {
this.currentProgress = value;
if (typeof (this.totalMs) !== 'number') {
this.currentProgress = 0;
Logger.info(TAG, `setProgress ignored, totalMs= ${this.totalMs}`);
return;
};
let currentMs = this.currentProgress / ONE_HUNDRED * this.totalMs;
this.currentTimeText = this.getShownTimer(currentMs);
if (mode === SliderChangeMode.End || mode === 3) {
Logger.info(TAG, `player.seek= ${currentMs}`);
PlayerModel.seek(currentMs);
};
})
Row() {
ForEach(this.imageArrays, (item: Resource, index: number | undefined) = > {
Column() {
Image(item)
.size({ width: 74, height: 74 })
.objectFit(ImageFit.Contain)
.onClick(() = > {
switch (index) {
case 0:
this.onAppSharedClick();
break;
case 1:
this.onPreviousClick();
break;
case 2:
this.onPlayClick();
break;
case 3:
this.onNextClick();
break;
default:
break;
}
})
}
.id('image' + (index !== undefined ? (index + 1) : 0))
.width(100)
.height(100)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
})
}
.width('100%')
.margin({ top: '4%' })
.justifyContent(FlexAlign.SpaceEvenly)
}
.width('100%')
.height('100%')
.backgroundImage($r('app.media.bg_blurry'))
.backgroundImageSize({ width: '100%', height: '100%' })
}
}
跨設備播放操作
(1)分布式設備管理器綁定應用包 deviceManager.createDeviceManager('ohos.samples.distributedmusicplayer') [源碼參考]。
/*
* Copyright (c) 2022 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import deviceManager from '@ohos.distributedDeviceManager';
import Logger from '../model/Logger';
let SUBSCRIBE_ID: number = 100;
const RANDOM: number = 65536;
const TAG: string = 'RemoteDeviceModel';
export class RemoteDeviceModel {
public deviceLists: Array< deviceManager.DeviceBasicInfo > = [];
public discoverLists: Array< deviceManager.DeviceBasicInfo > = [];
private callback: () = > void = null;
private authCallback: () = > void = null;
private deviceManager: deviceManager.DeviceManager = undefined;
registerDeviceListCallback(callback) {
if (typeof (this.deviceManager) === 'undefined') {
Logger.info(TAG, 'deviceManager.createDeviceManager begin');
try {
this.deviceManager = deviceManager.createDeviceManager('ohos.samples.distributedmusicplayer');
this.registerDeviceList(callback);
Logger.info(TAG, `createDeviceManager callback returned, value= ${JSON.stringify(this.deviceManager)}`);
} catch (error) {
Logger.info(TAG, `createDeviceManager throw error, error=${error} message=${error.message}`);
}
Logger.info(TAG, 'deviceManager.createDeviceManager end');
} else {
this.registerDeviceList(callback);
};
};
registerDeviceList(callback) {
Logger.info(TAG, 'registerDeviceListCallback');
this.callback = callback;
if (this.deviceManager === undefined) {
Logger.error(TAG, 'deviceManager has not initialized');
this.callback();
return;
};
Logger.info(TAG, 'getTrustedDeviceListSync begin');
let list: deviceManager.DeviceBasicInfo[] = [];
try {
list = this.deviceManager.getAvailableDeviceListSync();
} catch (error) {
Logger.info(TAG, `getTrustedDeviceListSync throw error, error=${error} message=${error.message}`);
};
Logger.info(TAG, `getTrustedDeviceListSync end, deviceLists= ${JSON.stringify(list)}`);
if (typeof (list) !== 'undefined' && typeof (list.length) !== 'undefined') {
this.deviceLists = list;
};
this.callback();
Logger.info(TAG, 'callback finished');
try {
this.deviceManager.on('deviceStateChange', (data) = > {
Logger.info(TAG, `deviceStateChange data= ${JSON.stringify(data)}`);
switch (data.action) {
case deviceManager.DeviceStateChange.AVAILABLE:
this.discoverLists = [];
this.deviceLists.push(data.device);
Logger.info(TAG, `reday, updated device list= ${JSON.stringify(this.deviceLists)} `);
let list: deviceManager.DeviceBasicInfo[] = [];
try {
list = this.deviceManager.getAvailableDeviceListSync();
} catch (err) {
Logger.info(TAG, `this err is ${JSON.stringify(err)}`);
}
Logger.info(TAG, `getTrustedDeviceListSync end, deviceList= ${JSON.stringify(list)}`);
if (typeof (list) !== 'undefined' && typeof (list.length) !== 'undefined') {
this.deviceLists = list;
}
this.callback();
break;
case deviceManager.DeviceStateChange.UNAVAILABLE:
if (this.deviceLists.length > 0) {
let list = [];
for (let i = 0; i < this.deviceLists.length; i++) {
if (this.deviceLists[i].deviceId !== data.device.deviceId) {
list[i] = data.device;
};
};
this.deviceLists = list;
};
Logger.info(TAG, `offline, updated device list= ${JSON.stringify(this.deviceLists)}`);
this.callback();
break;
default:
break;
};
});
this.deviceManager.on('discoverSuccess', (data) = > {
Logger.info(TAG, `discoverSuccess data= ${JSON.stringify(data)}`);
Logger.info(TAG, `discoverSuccess this.deviceLists= ${this.deviceLists}, this.deviceLists.length= ${this.deviceLists.length}`);
for (let i = 0;i < this.discoverLists.length; i++) {
if (this.discoverLists[i].deviceId === data.device.deviceId) {
Logger.info(TAG, 'device founded, ignored');
return;
};
};
this.discoverLists[this.discoverLists.length] = data.device;
this.callback();
});
this.deviceManager.on('discoverFailure', (data) = > {
Logger.info(TAG, `discoverFailure data= ${JSON.stringify(data)}`);
});
this.deviceManager.on('serviceDie', () = > {
Logger.error(TAG, 'serviceDie');
});
} catch (error) {
Logger.info(TAG, `on throw error, error=${error} message=${error.message}`);
}
let discoverParam = {
'discoverTargetType': 1
};
let filterOptions = {
'availableStatus': 0
};
Logger.info(TAG, `startDiscovering ${SUBSCRIBE_ID}`);
try {
if (this.deviceManager !== null) {
this.deviceManager.startDiscovering(discoverParam, filterOptions);
};
} catch (error) {
Logger.error(TAG, `startDiscovering throw error, error=${error} message=${error.message}`);
};
};
authDevice(device, callback) {
Logger.info(TAG, `authDevice ${device}`);
if (device !== undefined) {
for (let i = 0; i < this.discoverLists.length; i++) {
if (this.discoverLists[i].deviceId === device.deviceId) {
Logger.info(TAG, 'device founded, ignored');
let bindParam = {
bindType: 1,
targetPkgName: 'ohos.samples.distributedmusicplayer',
appName: 'Music',
};
Logger.info(TAG, `authenticateDevice ${JSON.stringify(this.discoverLists[i])}`);
try {
this.deviceManager.bindTarget(device.deviceId, bindParam, (err, data) = > {
if (err) {
Logger.error(TAG, `authenticateDevice error: ${JSON.stringify(err)}`);
this.authCallback = () = > {
};
return;
};
Logger.info(TAG, `authenticateDevice succeed, data= ${JSON.stringify(data)}`);
this.authCallback = callback;
});
} catch (error) {
Logger.error(TAG, `authenticateDevice throw error, error=${JSON.stringify(error)} message=${error.message}`);
}
}
}
}
};
unregisterDeviceListCallback() {
Logger.info(TAG, `stopDiscovering ${SUBSCRIBE_ID}`);
if (this.deviceManager === undefined) {
return;
};
try {
this.deviceManager.stopDiscovering();
this.deviceManager.off('deviceStateChange');
this.deviceManager.off('discoverSuccess');
this.deviceManager.off('discoverFailure');
this.deviceManager.off('serviceDie');
} catch (error) {
Logger.info(TAG, `stopDeviceDiscovery throw error, error=${error} message=${error.message}`);
}
this.deviceLists = [];
};
}
(2) 初始化播放器 構造函數中通過'@ohos.multimedia.media'組件對播放器進行實例化,并調用播放器初始化函數,通過播放器的on函數,監聽error、finish、timeUpdate
(3) 同步當前播放數據 播放器通過調用selectedIndexChange(),將當前播放的資源、時間、以及播放狀態同步給選中的設備。
(4) 接收當前播放數據 播放器通過在aboutToAppear()時調用this.restoreFromWant(), KvStoreModel組件獲取播放列表,playerModel組件重新加載播放器狀態和資源。
審核編輯 黃宇
-
分布式
+關注
關注
1文章
879瀏覽量
74466 -
鴻蒙
+關注
關注
57文章
2310瀏覽量
42742 -
HarmonyOS
+關注
關注
79文章
1967瀏覽量
30017
發布評論請先 登錄
相關推薦
評論