鸿蒙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';



@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组件重新加载播放器状态和资源。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/531530.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构初阶:二叉树

树概念及结构 树的概念 树是一种 非线性 的数据结构&#xff0c;它是由 n &#xff08; n>0 &#xff09;个有限结点组成一个具有层次关系的集合。 把它叫做树是因 为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的 。 有一个特殊的结点&a…

一体式I/O模块与RS485串口联动,实现工业网络无缝对接

在现代工业自动化领域中&#xff0c;一体化I/O模块和RS485串口的联动应用已经成为实现工业设备高效、稳定通信的关键技术手段之一。这种联动机制能够有效地将各种现场设备的数据实时、准确地传输到上位机系统&#xff0c;从而实现工业网络的无缝对接。 一体化I/O模块&#xff…

tailwindcss+vue3+vite+preline项目搭建

最近原子化样式比较火&#xff0c;用了一下确实还不错&#xff0c;也确实是用一些标准的样式能够使网页看起来比较统一&#xff0c;而且能够极大的减轻起名字的压力&#xff0c;有利有弊&#xff0c;就不一一细说了。 之前开发都是习惯于使用vitevue3来开发的&#xff0c;此次搭…

ClickHouse--17--argMin() 和argMax()函数

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 argMin() 和argMax()函数业务场景使用案例1.准备表和数据&#xff1a;业务场景一&#xff1a;查看salary 最高和最小的user业务场景二&#xff1a;根据更新时间获取…

C++11 数据结构0 什么是 “数据结构“?数据,数据对象,数据元素,数据项 概念。算法的基本概念 和 算法的度量,大O表示法,空间换时间的代码

数据&#xff1a; 是能输入计算机且能被计算机处理的各种符号的集合。数值型的数据&#xff1a;整数和实数。非数值型的数据&#xff1a;文字、图像、图形、声音等。 数据对象&#xff1a; 性质相同的 "数据元素" 的集合 例如一个 int arr[10], Teacher tea[3]; 数…

C++设计模式:单例模式(十)

1、单例设计模式 单例设计模式&#xff0c;使用的频率比较高&#xff0c;整个项目中某个特殊的类对象只能创建一个 并且该类只对外暴露一个public方法用来获得这个对象。 单例设计模式又分懒汉式和饿汉式&#xff0c;同时对于懒汉式在多线程并发的情况下存在线程安全问题 饿汉…

每日OJ题_BFS解决FloodFill②_力扣200. 岛屿数量

目录 力扣200. 岛屿数量 解析代码 力扣200. 岛屿数量 200. 岛屿数量 难度 中等 给你一个由 1&#xff08;陆地&#xff09;和 0&#xff08;水&#xff09;组成的的二维网格&#xff0c;请你计算网格中岛屿的数量。 岛屿总是被水包围&#xff0c;并且每座岛屿只能由水平方…

layui中对table表格内容鼠标移入显示 tips内容

要在Layui中的表格中实现鼠标移入显示Tips&#xff0c;你可以使用Layui的事件监听和Tips组件。 有两种实现方式&#xff01; 第一种是&#xff0c;通过自定义鼠标事件显示 tips。在渲染 table 时&#xff0c;对 filed 进行重构&#xff0c;增加相应的选择器标识&#xff0c;一…

OneForAll安装使用

OneForAll简介 OneForAll是一款功能强大的子域收集工具 原项目地址&#xff1a;GitHub - shmilylty/OneForAll: OneForAll是一款功能强大的子域收集工具 gitee项目地址&#xff1a;OneForAll: OneForAll是一款功能强大的子域收集工具 # 安装Python Windows系统安装python参…

Excel文本内容抽取工具[Python]

#创作灵感# 一堆Excel文件&#xff0c;每个打开看太累了。写个脚本直接显示里面的内容多好。最好这些内容可以直接复制到剪切板&#xff0c;方便以后编辑修改。只需要将文件拖动到全屏置顶的文本框内&#xff0c;就能弹出Excel里的内容。支持一次选取多个文件。 开干&#xff…

react17+18 中 setState是同步还是异步更新

在类组件中使用setState&#xff0c;在函数式组件中使用hooks的useState。 setstate目录 1. 类组件1.1 react 17版本1.2 react 18版本 2、函数式组件 1. 类组件 1.1 react 17版本 参考内容&#xff1a;第十一篇&#xff1a;setState 到底是同步的&#xff0c;还是异步的&…

Unity类银河恶魔城学习记录12-8 p130 Skill Tree UI源代码

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili UI.cs using UnityEngine;public class UI : MonoBehaviour {[SerializeFi…

【精选】发布应用到应用商店的基本介

摘要 本文旨在介绍如何在各大应用商店发布应用&#xff0c;包括市场选择、准备材料、上架步骤以及常见被拒原因及解决方法。通过详细的步骤和经验分享&#xff0c;帮助开发者顺利将应用推向市场。 引言 随着移动应用市场的不断发展&#xff0c;越来越多的开发者希望将他们的…

C++类和对象上

C和C语言本质区别 C语言是面向过程的&#xff0c;面向过程的&#xff0c;分析出求解问题的步骤&#xff0c;然后逐步通过函数调用来逐步解决问题。 C在分析问题是在面对对象的基础上来实现的&#xff0c;即将一件事情拆分为不同的对象&#xff0c;靠的是对象之间的交互来完成的…

OSPF数据报文格式

OSPF协议是跨层封装的协议&#xff0c;跨四层封装&#xff0c;直接将应用层的数据封装在网络层协议后面&#xff0c;IP协议包中协议号字段对应的数值为——89 OSPF的头部信息&#xff1a; ——所有数据包公有的信息 版本&#xff1a;OSPF版本 在IPV4中一般使用OSPFV2&#xf…

c 解数独(通用方法,适用于9×9 数独)

折腾了一周时间&#xff0c;终于搞定99数独通用方法 思路&#xff1a;1.生成每行空位的值&#xff0c;也就是1-9中除去非0的数。 2.用行&#xff0c;列&#xff0c;宫判断每行中每个空位的最小取值范围后再重新生成每行。 3.随机提取生成的9行&#xff0c;判断每列之和是否等…

找不到vcruntime140.dll怎么办,vcruntime140.dll丢失的多种解决方法

在我们日常频繁地与电脑打交道、依赖其处理各种工作、学习乃至娱乐任务的过程中&#xff0c;偶尔会遭遇一些令人困扰的技术问题。其中一种颇为常见的情况便是&#xff0c;当您正全神贯注于某个重要应用的操作&#xff0c;或是满怀期待地试图启动一款新安装的游戏时&#xff0c;…

2万亿训练数据!Stable LM 2-12B加入开源队列

公*众*号&#xff1a;AI疯人院 4月9日&#xff0c;知名大型模型开源平台Stability.ai在其官网上发布了全新的类ChatGPT模型——Stable LM 2 12B。 据了解&#xff0c;Stable LM 2 12B模型拥有120亿个参数&#xff0c;其训练数据涵盖了英语、西班牙语、德语等7种语言的2万亿个…

C++修炼之路之string--标准库中的string

目录 前言 一&#xff1a;标准库的string类简介 1.string是basic_string的一份char类型的类模板 2.basic_string类模板的分类 3.string是表示字符串的字符串类 4.在使用string类时要添加头文件#include 二&#xff1a;string类的常用接口(只介绍常用的) 1.构造析构赋…

今日arXiv最热大模型论文:Dataverse,针对大模型的开源ETL工具,数据清洗不再难!

引言&#xff1a;大数据时代下的ETL挑战 随着大数据时代的到来&#xff0c;数据处理的规模和复杂性不断增加&#xff0c;尤其是在大语言模型&#xff08;LLMs&#xff09;的开发中&#xff0c;对海量数据的需求呈指数级增长。这种所谓的“规模化法则”表明&#xff0c;LLM的性…