当前位置: 首页 > news >正文

鸿蒙HarmonyOS开发实战:【分布式音乐播放】

介绍

本示例使用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技术

搜狗高速浏览器截图20240326151450.png

具体实现

鸿蒙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';@CustomDialogexport 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 : 0if (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@Componentstruct 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 titlethis.title = PlayerModel.playlist.audioFiles[index].name;this.albumSrc = (index % 2 === 0) ? $r('app.media.album') : $r('app.media.album2');// update durationthis.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 ratiothis.risw = screenWidth * this.riscale;this.rish = screenHeight;} else {// The screen ratio is longer than design ratiothis.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 = nullKvStoreModel.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@Componentstruct 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 titlethis.title = PlayerModel.playlist.audioFiles[index].name;this.albumSrc = (index % 2 === 0) ? $r('app.media.album') : $r('app.media.album2');// update durationthis.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 ratiothis.risw = screenWidth * this.riscale;this.rish = screenHeight;} else {// The screen ratio is longer than design ratiothis.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 = nullKvStoreModel.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组件重新加载播放器状态和资源。

相关文章:

鸿蒙HarmonyOS开发实战:【分布式音乐播放】

介绍 本示例使用fileIo获取指定音频文件&#xff0c;并通过AudioPlayer完成了音乐的播放完成了基本的音乐播放、暂停、上一曲、下一曲功能&#xff1b;并使用DeviceManager完成了分布式设备列表的显示和分布式能力完成了音乐播放状态的跨设备分享。 本示例用到了与用户进行交…...

【iOS ARKit】App 中嵌入 AR Quick Look

AR Quick Look 功能强大&#xff0c;但在应用中嵌入并使用它实现 AR体验却非常简单&#xff0c;如其他所有QuickLook使用一样&#xff0c;简单到只需要提供一个文件名就可以达到目标。 AR Quick Look 支持.usdz 和.reality 两种格式文件&#xff0c;如果在 Xcode 工程中引入了 …...

【Web开发】jquery图片放大镜效果制作变焦镜头图片放大

jquery图片放大镜效果制作变焦镜头图片放大实现 整体步骤流程&#xff1a; 1. 前端html实现 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns"…...

RTC实时显示时间(备份电源 备份域的作用)

RTC初始化配置 系统复位后&#xff0c;可通过 PWR 电源控制寄存器 (PWR_CR) 的 DBP 位保护 RTC 寄存器以防止 非正常的写访问。必须将 DBP 位置 1 才能使能 RTC 寄存器的写访问。 上电复位后&#xff0c;所有 RTC 寄存器均受到写保护。通过向写保护寄存器 (RTC_WPR) 写入一个…...

【YOLOv9】完胜V8的SOTA模型Yolov9(论文阅读笔记)

官方论文地址: 论文地址点击即可跳转 官方代码地址: GitCode - 开发者的代码家园 官方代码地址点击即可跳转 1 总述 当输入数据经过各层的特征提取和变换的时候,都会丢失一定的信息。针对这一问题:...

学生管理系统详细需求文档

文章目录 1. 引言1.1 目的1.2 范围 2. 功能性需求2.1 用户认证2.1.1 登录 2.2 学生信息管理2.2.1 学生档案2.2.2 学籍管理 2.3 课程管理2.3.1 课程信息2.3.2 选课系统 2.4 成绩管理2.4.1 成绩录入2.4.2 成绩查询 2.5 课程进度和通知2.5.1 课程日历2.5.2 通知和提醒 2.6 学生活动…...

产品经理功法修炼(4)之产品管理

点击下载《产品经理功法修炼(4)之产品管理》 产品经理功法修炼(1)之自我管理 产品经理功法修炼(2)之专业技能 产品经理功法修炼(3)之产品设计 产品经理功法修炼(4)之产品管理 产品经理功法修炼(5)之团队管理 1. 前言 产品经理的能力修炼并非局限于某一技能的…...

【LeetCode热题100】【二叉树】二叉树展开为链表

题目链接&#xff1a;114. 二叉树展开为链表 - 力扣&#xff08;LeetCode&#xff09; 就先序遍历的顺序&#xff0c;其实就是简单的深度遍历顺序&#xff0c;装进一个容器里面再前一个后一个串连起来&#xff0c;注意容器的size是个无符号数&#xff0c;无符号数和有符号运行…...

云原生__K8S

createrepo --update /var/localrepo/# 禁用 firewall 和 swap [rootmaster ~]# sed /swap/d -i /etc/fstab [rootmaster ~]# swapoff -a [rootmaster ~]# dnf remove -y firewalld-*[rootmaster ~]# vim /etc/hosts 192.168.1.30 harbor 192.168.1.50 master 192.168.1.…...

nginx配置证书和私钥进行SSL通信验证

文章目录 一、背景1.1 秘钥和证书是两个东西吗&#xff1f;1.2 介绍下nginx配置文件中参数ssl_certificate和ssl_certificate_key1.3介绍下nginx支持的证书类型1.4 目前nginx支持哪种证书格式&#xff1f;1.5 nginx修改配置文件目前方式也会有所不同1.6 介绍下不通格式的证书哪…...

【面试题】微博、百度等大厂的排行榜如何实现?

背景 现如今每个互联网平台都会提供一个排行版的功能&#xff0c;供人们预览最新最有热度的一些消息&#xff0c;比如百度&#xff1a; 再比如微博&#xff1a; 我们要知道&#xff0c;这些互联网平台每天产生的数据是非常大&#xff0c;如果我们使用MySQL的话&#xff0c;db实…...

com.intellij.diagnostic.PluginException 问题

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、商业变现、人工智能等&#xff0c;希望大家多多支持。 未经允许不得转载 目录 一、导读二、 推荐阅读 一、导读 遇到…...

Altair® (澳汰尔)Inspire™ Render —— 强大的 3D 渲染和动画工具

Inspire Render 是一种全新 3D 渲染和动画工具&#xff0c;可供创新设计师、建筑师和数字艺术家以前所未有的速度快速制作精美的产品演示。 借助基于物理特性的内置高品质全局照明渲染引擎 Thea Render&#xff0c;可以快速创建、修改和拖放各种材质并添加照明环境&#xff0c…...

虚幻引擎启动报错记录

0x00007FFEF0C8917C (UnrealEditor-CoreUObject.dll)处(位于 UnrealEditor.exe 中)引发的异常: 0xC0000005: 写入位置 0x0000000000000030 时发生访问冲突。 解决办法&#xff1a;首先查看堆栈信息&#xff0c;我的项目启动是因为默认场景编译不过&#xff0c;进到编辑器配置文…...

最祥解决python 将Dataframe格式数据上传数据库所碰到的问题

碰到的问题 上传Datafrane格式的数据到数据库 会碰见很多错误 举几个很普遍遇到的问题(主要以SqlServer举例) 这里解释下 将截断字符串或二进制数据 这个是字符长度超过数据库设置的长度 然后还有字符转int失败 或者字符串转换日期/或时间失败 这个是碰到的需要解决的最多的问…...

【汇编语言实战】统计个数

已知10个分布在0至100内的正整数&#xff0c;统计大于等于60的数的个数和小于60的数的个数 C语言描述该程序流程&#xff1a; #include <stdio.h> int main() {int arr1[]{11,33,73,52,93,84,67,56,64,75};int num10;for(int i1;i<10;i){if(arr1[i]>60){num1;}}p…...

SQLite数据库概述及在Java中的应用

## 什么是SQLite数据库&#xff1f; SQLite是一种轻量级的数据库管理系统&#xff0c;它不需要一个独立的服务器进程或操作系统的运行&#xff0c;而是将整个数据库&#xff0c;包括定义、表、索引以及数据本身&#xff0c;全部存储在一个独立的磁盘文件中。SQLite的设计理念是…...

嵌入式单片机补光灯项目操作实现

1.【实验目的】 用于直播效果的补光 2.【实验原理】 原理框架图2.各部分原理及主要功能 1.充电和供电:采用5V2A tepy_c接口充电,3.7V锂电池供电, 2.功能:产品主要是用于直播或拍照时的补光。分为三个模式:白光/暧光&#x...

【3GPP】【核心网】核心网/蜂窝网络重点知识面试题二(超详细)

1. 欢迎大家订阅和关注&#xff0c;3GPP通信协议精讲&#xff08;2G/3G/4G/5G/IMS&#xff09;知识点&#xff0c;专栏会持续更新中.....敬请期待&#xff01; 目录 1. 对于主要的LTE核心网接口&#xff0c;给出运行在该接口上数据的协议栈&#xff0c;并给出协议特征 2. 通常…...

R语言记录过程

如何使用这个函数as.peakData 函数构造过程 出现问题是缺少函数的问题 up不告诉我&#xff0c;这里是代表c,h,o的值&#xff0c;你从里面获取把值&#xff0c;设置成c,h,o就可以了 现在开始测试参数 第一次 startRow : 开始查找数据的第一行。不管startRow的值是多少&#xff…...

基于算法竞赛的c++编程(28)结构体的进阶应用

结构体的嵌套与复杂数据组织 在C中&#xff0c;结构体可以嵌套使用&#xff0c;形成更复杂的数据结构。例如&#xff0c;可以通过嵌套结构体描述多层级数据关系&#xff1a; struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...

前端倒计时误差!

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

遍历 Map 类型集合的方法汇总

1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

UE5 学习系列(三)创建和移动物体

这篇博客是该系列的第三篇&#xff0c;是在之前两篇博客的基础上展开&#xff0c;主要介绍如何在操作界面中创建和拖动物体&#xff0c;这篇博客跟随的视频链接如下&#xff1a; B 站视频&#xff1a;s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

工程地质软件市场:发展现状、趋势与策略建议

一、引言 在工程建设领域&#xff0c;准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具&#xff0c;正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...

C++ 基础特性深度解析

目录 引言 一、命名空间&#xff08;namespace&#xff09; C 中的命名空间​ 与 C 语言的对比​ 二、缺省参数​ C 中的缺省参数​ 与 C 语言的对比​ 三、引用&#xff08;reference&#xff09;​ C 中的引用​ 与 C 语言的对比​ 四、inline&#xff08;内联函数…...

3403. 从盒子中找出字典序最大的字符串 I

3403. 从盒子中找出字典序最大的字符串 I 题目链接&#xff1a;3403. 从盒子中找出字典序最大的字符串 I 代码如下&#xff1a; class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...

ios苹果系统,js 滑动屏幕、锚定无效

现象&#xff1a;window.addEventListener监听touch无效&#xff0c;划不动屏幕&#xff0c;但是代码逻辑都有执行到。 scrollIntoView也无效。 原因&#xff1a;这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作&#xff0c;从而会影响…...

docker 部署发现spring.profiles.active 问题

报错&#xff1a; org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...