介绍
本示例使用fileIo获取指定音频文件,并通过AudioPlayer完成了音乐的播放完成了基本的音乐播放、暂停、上一曲、下一曲功能;并使用DeviceManager完成了分布式设备列表的显示和分布式能力完成了音乐播放状态的跨设备分享。
本示例用到了与用户进行交互的Ability的能力接口[@ohos.ability.featureAbility]
文件存储管理能力接口[@ohos.fileio]
屏幕属性接口[@ohos.display]
媒体查询接口[@ohos.mediaquery]
分布式数据管理接口[@ohos.data.distributedData]
音视频相关媒体业务能力接口[@ohos.multimedia.media]
分布式设备管理能力接口(设备管理),实现设备之间的kvStore对象的数据传输交互[@ohos.distributedDeviceManager]
效果预览
首页 |
---|
使用说明
1.音乐播放,点击播放、暂停、上一曲、下一曲按钮可以对音乐进行操作。
2.跨设备分享,组网并且双端均已授权条件下,点击分享按钮,选择设备,拉起对端设备上的音乐,并将本端的播放状态同步到对端上。
3.跨设备停止分享,分享成功前提条件下,点击停止分享按钮,将对端设备拉起的音乐应用停止退出。
相关概念
音频播放:媒体子系统包含了音视频相关媒体业务,通过AudioPlayer实现音频播放的能力。
数据分享:分布式数据管理为应用程序提供不同设备间数据库的分布式协同能力。通过调用分布式数据各个接口,应用程序可将数据保存到分布式数据库中,并可对分布式数据库中的数据进行增/删/改/查等各项操作。
资料文档参考
鸿蒙OS开发 | 更多内容↓点击 《鸿蒙NEXT星河版开发学习文档》 | HarmonyOS与OpenHarmony技术 |
---|
具体实现
鸿蒙NEXT文档可以
+mau12379是v喔直接领取!
在分布式音乐播放器中,分布式设备管理包含了分布式设备搜索、分布式设备列表弹窗、远端设备拉起三部分。
首先在分布式组网内搜索设备,然后把设备展示到分布式设备列表弹窗中,最后根据用户的选择拉起远端设备。
分布式设备搜索
通过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组件重新加载播放器状态和资源。