基于HarmonyOS的跨设备遥控器开发与媒体同步控制 原创

进修的泡芙
发布于 2025-6-18 22:14
浏览
0收藏

基于HarmonyOS的跨设备遥控器开发与媒体同步控制

一、项目概述

本项目基于HarmonyOS的分布式能力和媒体服务,开发一个手机控制平板播放视频的跨设备遥控器应用。参考《鸿蒙跨端U同步:同一局游戏中多设备玩家昵称/头像显示》中的分布式技术,实现播放控制、进度同步和设备协同。

!https://example.com/remote-control-arch.png
图1:跨设备遥控器架构(包含控制器UI层、媒体控制层和分布式同步层)

二、核心功能实现
设备发现与连接管理(ArkTS)

// 设备管理服务
class DeviceManagerService {
private static instance: DeviceManagerService;
private controllerList: deviceManager.DeviceInfo[] = [];
private playerList: deviceManager.DeviceInfo[] = [];

static getInstance(): DeviceManagerService {
if (!DeviceManagerService.instance) {
DeviceManagerService.instance = new DeviceManagerService();
return DeviceManagerService.instance;

constructor() {

this.startDiscovery();

// 开始设备发现

private startDiscovery() {
const DISCOVERY_DURATION = 180 * 1000; // 3分钟

const discovery = deviceManager.createDiscovery({
  mode: deviceManager.DiscoveryMode.DISCOVERY_MODE_ACTIVE,
  filter: {
    deviceType: [deviceManager.DeviceType.PHONE, deviceManager.DeviceType.TABLET]

});

discovery.on('deviceFound', (data: { device: deviceManager.DeviceInfo }) => {
  this.updateDeviceList(data.device);
});

discovery.startDiscovery(DISCOVERY_DURATION, (err) => {
  if (err) {
    console.error('设备发现失败:', err);

});

// 更新设备列表

private updateDeviceList(device: deviceManager.DeviceInfo) {
if (device.deviceType === deviceManager.DeviceType.PHONE) {
if (!this.controllerList.some(d => d.deviceId === device.deviceId)) {
this.controllerList.push(device);
} else if (device.deviceType === deviceManager.DeviceType.TABLET) {

  if (!this.playerList.some(d => d.deviceId === device.deviceId)) {
    this.playerList.push(device);

}

// 获取可控制的播放设备

getAvailablePlayers(): deviceManager.DeviceInfo[] {
return […this.playerList];
// 建立连接

async connectToPlayer(deviceId: string): Promise<boolean> {
try {
const device = this.playerList.find(d => d.deviceId === deviceId);
if (!device) return false;

  const connection = deviceManager.createConnection({
    deviceId: device.deviceId,
    connectionType: deviceManager.ConnectionType.CONNECTION_TYPE_WIFI
  });
  
  await connection.connect();
  return true;

catch (error) {

  console.error('连接设备失败:', error);
  return false;

}

媒体控制与分布式同步(ArkTS)

// 媒体控制服务
class MediaControlService {
private static instance: MediaControlService;
private distObject: distributedDataObject.DataObject;
private currentState: MediaState = {
status: ‘idle’,
position: 0,
duration: 0,
videoUrl: ‘’,
deviceId: ‘’
};

static getInstance(): MediaControlService {
if (!MediaControlService.instance) {
MediaControlService.instance = new MediaControlService();
return MediaControlService.instance;

constructor() {

// 初始化分布式数据对象
this.distObject = distributedDataObject.create({
  mediaState: this.currentState
});

// 监听状态变化
this.distObject.on('change', (fields: string[]) => {
  if (fields.includes('mediaState')) {
    this.handleStateUpdate();

});

// 发送控制命令

sendControlCommand(command: MediaCommand): void {
switch (command.type) {
case ‘play’:
this.currentState.status = ‘playing’;
break;
case ‘pause’:
this.currentState.status = ‘paused’;
break;
case ‘seek’:
this.currentState.position = command.position || 0;
break;
case ‘load’:
this.currentState.videoUrl = command.videoUrl || ‘’;
this.currentState.status = ‘ready’;
this.currentState.position = 0;
break;
this.currentState.deviceId = deviceInfo.deviceId;

this.syncState();

// 更新播放进度

updatePlaybackProgress(position: number, duration: number): void {
this.currentState.position = position;
this.currentState.duration = duration;
this.syncState();
// 同步状态

private syncState(): void {
this.distObject.mediaState = this.currentState;

const targetDevices = deviceManager.getConnectedDevices()
  .map(d => d.deviceId)
  .filter(id => id !== deviceInfo.deviceId);

if (targetDevices.length > 0) {
  this.distObject.setDistributed(targetDevices);

}

// 处理状态更新
private handleStateUpdate(): void {
const remoteState = this.distObject.mediaState as MediaState;

if (remoteState.deviceId !== deviceInfo.deviceId) {
  this.currentState = { ...remoteState };
  this.notifyListeners();

}

// 获取当前状态
getCurrentState(): MediaState {
return { …this.currentState };
}

// 媒体状态接口
interface MediaState {
status: ‘idle’ ‘ready’ ‘playing’
‘paused’;
position: number;
duration: number;
videoUrl: string;
deviceId: string;
// 媒体命令接口

interface MediaCommand {
type: ‘play’ ‘pause’ ‘seek’
‘load’;
position?: number;
videoUrl?: string;

UI界面实现(ArkTS)

控制器端(手机)

// 控制器页面
@Entry
@Component
struct ControllerPage {
@State connectedPlayer: deviceManager.DeviceInfo | null = null;
@State mediaState: MediaState = {
status: ‘idle’,
position: 0,
duration: 0,
videoUrl: ‘’,
deviceId: ‘’
};

private deviceManager = DeviceManagerService.getInstance();
private mediaControl = MediaControlService.getInstance();

build() {
Column() {
// 设备连接面板
if (!this.connectedPlayer) {
DeviceSelectionPanel({
onDeviceSelected: this.connectToDevice.bind(this)
})
else {

    // 媒体控制面板
    Column() {
      Text(正在控制: ${this.connectedPlayer.deviceName})
        .fontSize(18)
        .margin({ bottom: 24 })
      
      // 视频URL输入
      TextInput({ placeholder: '输入视频URL' })
        .width('80%')
        .onSubmit((url: string) => {
          this.mediaControl.sendControlCommand({
            type: 'load',
            videoUrl: url
          });
        })
      
      // 播放控制按钮
      Row() {
        Button('播放')
          .enabled(this.mediaState.status = 'ready' || this.mediaState.status = 'paused')
          .onClick(() => {
            this.mediaControl.sendControlCommand({ type: 'play' });
          })
          
        Button('暂停')
          .enabled(this.mediaState.status === 'playing')
          .onClick(() => {
            this.mediaControl.sendControlCommand({ type: 'pause' });
          })

.margin({ top: 24 })

      // 进度条
      Slider({
        value: this.mediaState.position,
        min: 0,
        max: this.mediaState.duration || 100,
        step: 1
      })
      .width('80%')
      .margin({ top: 24 })
      .onChange((value: number) => {
        this.mediaControl.sendControlCommand({
          type: 'seek',
          position: value
        });
      })

.width(‘100%’)

    .height('100%')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)

}

.onAppear(() => {
  this.mediaControl.addListener(() => {
    this.mediaState = this.mediaControl.getCurrentState();
  });
})

// 连接到设备

private async connectToDevice(device: deviceManager.DeviceInfo) {
const success = await this.deviceManager.connectToPlayer(device.deviceId);
if (success) {
this.connectedPlayer = device;
}

// 设备选择面板组件

@Component
struct DeviceSelectionPanel {
@Prop onDeviceSelected: (device: deviceManager.DeviceInfo) => void;
@State deviceList: deviceManager.DeviceInfo[] = [];

private deviceManager = DeviceManagerService.getInstance();

aboutToAppear() {
this.deviceList = this.deviceManager.getAvailablePlayers();
build() {

Column() {
  Text('选择要控制的设备')
    .fontSize(20)
    .margin({ bottom: 24 })
  
  List({ space: 12 }) {
    ForEach(this.deviceList, (device: deviceManager.DeviceInfo) => {
      ListItem() {
        DeviceItem({ 
          device: device,
          onClick: () => this.onDeviceSelected(device)
        })

})

.layoutWeight(1)

.width(‘100%’)

.height('100%')
.padding(24)

}

// 设备项组件
@Component
struct DeviceItem {
@Prop device: deviceManager.DeviceInfo;
@Prop onClick: () => void;

build() {
Row() {
Image($r(‘app.media.ic_tablet’))
.width(40)
.height(40)
.margin({ right: 16 })

  Column() {
    Text(this.device.deviceName)
      .fontSize(16)
    
    Text(this.device.deviceId)
      .fontSize(12)
      .fontColor('#999999')
      .margin({ top: 4 })

.layoutWeight(1)

.width(‘100%’)

.height(72)
.padding(16)
.backgroundColor('#F5F5F5')
.borderRadius(8)
.onClick(() => this.onClick())

}

播放器端(平板)

// 播放器页面
@Entry
@Component
struct PlayerPage {
@State mediaState: MediaState = {
status: ‘idle’,
position: 0,
duration: 0,
videoUrl: ‘’,
deviceId: ‘’
};

private mediaPlayer: media.MediaPlayer | null = null;
private mediaControl = MediaControlService.getInstance();

build() {
Stack() {
// 视频播放器
if (this.mediaState.videoUrl) {
Video({
src: this.mediaState.videoUrl,
controller: this.createVideoController()
})
.width(‘100%’)
.height(‘100%’)
.autoPlay(this.mediaState.status === ‘playing’)
else {

    Text('等待控制指令...')
      .fontSize(18)
      .fontColor('#999999')

// 状态显示

  Column() {
    Text(状态: ${this.mediaState.status})
      .fontSize(14)
      .fontColor('#FFFFFF')
      .backgroundColor('rgba(0,0,0,0.5)')
      .padding(8)
    
    Text(进度: {formatTime(this.mediaState.position)}/{formatTime(this.mediaState.duration)})
      .fontSize(14)
      .fontColor('#FFFFFF')
      .backgroundColor('rgba(0,0,0,0.5)')
      .padding(8)
      .margin({ top: 8 })

.position({ x: 16, y: 16 })

  .alignItems(HorizontalAlign.Start)

.width(‘100%’)

.height('100%')
.onAppear(() => {
  this.initMediaPlayer();
  this.mediaControl.addListener(() => {
    this.handleMediaStateChange();
  });
})
.onDisappear(() => {
  this.releaseMediaPlayer();
})

// 初始化媒体播放器

private initMediaPlayer() {
this.mediaPlayer = media.createMediaPlayer();

this.mediaPlayer.on('timeUpdate', (currentPos: number) => {
  const duration = this.mediaPlayer?.duration || 0;
  this.mediaControl.updatePlaybackProgress(currentPos, duration);
});

this.mediaPlayer.on('playbackComplete', () => {
  this.mediaControl.sendControlCommand({ type: 'pause' });
});

// 处理媒体状态变化

private handleMediaStateChange() {
const newState = this.mediaControl.getCurrentState();

if (newState.videoUrl !== this.mediaState.videoUrl && newState.videoUrl) {
  this.mediaPlayer?.reset();
  this.mediaPlayer?.setSource(newState.videoUrl);
  this.mediaPlayer?.prepare();

switch (newState.status) {

  case 'playing':
    this.mediaPlayer?.play();
    break;
  case 'paused':
    this.mediaPlayer?.pause();
    break;

if (Math.abs(newState.position - this.mediaState.position) > 1) {

  this.mediaPlayer?.seek(newState.position);

this.mediaState = { …newState };

// 创建视频控制器

private createVideoController(): media.VideoController {
const controller = new media.VideoController();

controller.on('play', () => {
  this.mediaControl.sendControlCommand({ type: 'play' });
});

controller.on('pause', () => {
  this.mediaControl.sendControlCommand({ type: 'pause' });
});

controller.on('seek', (position: number) => {
  this.mediaControl.sendControlCommand({
    type: 'seek',
    position: position
  });
});

return controller;

// 释放媒体播放器

private releaseMediaPlayer() {
this.mediaPlayer?.release();
this.mediaPlayer = null;
}

// 辅助函数:格式化时间
function formatTime(seconds: number): string {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return {mins}:{secs.toString().padStart(2, ‘0’)};

三、关键功能说明
设备发现与连接流程

设备发现:

  discovery.on('deviceFound', (data) => {
 this.updateDeviceList(data.device);

});

建立连接:

  const connection = deviceManager.createConnection({
 deviceId: device.deviceId,
 connectionType: deviceManager.ConnectionType.CONNECTION_TYPE_WIFI

});
await connection.connect();

媒体控制同步机制

控制命令 数据内容 同步方式

播放/暂停 播放状态 即时同步
进度跳转 目标位置 即时同步
加载视频 视频URL 全量同步

分布式数据同步流程

sequenceDiagram
participant 手机
participant 平板
participant 分布式数据管理

手机->>手机: 用户点击播放按钮
手机->>分布式数据管理: 同步播放状态
分布式数据管理->>平板: 分发状态更新
平板->>平板: 执行播放命令

四、项目扩展与优化
功能扩展建议

播放列表管理:

  interface PlaylistItem {
 title: string;
 url: string;
 duration: number;

音量控制:

  sendVolumeCommand(volume: number) {
 // 实现音量控制

手势控制:

  .gesture(
 PanGesture({ direction: PanDirection.Horizontal })
   .onActionUpdate((event: GestureEvent) => {
     // 实现手势控制进度
   })

)

性能优化建议

数据压缩:

  // 使用二进制格式传输控制命令

const buffer = new ArrayBuffer(8);
new DataView(buffer).setFloat64(0, position);

心跳检测:

  // 定期检测连接状态

setInterval(() => {
this.checkConnection();
}, 5000);

五、总结

本项目基于HarmonyOS实现了具有以下特点的跨设备遥控器:
便捷的设备发现:自动发现周围可用设备

精确的媒体控制:支持播放、暂停、进度控制

实时的状态同步:多设备状态保持一致

稳定的连接管理:自动处理连接中断

通过参考《鸿蒙跨端U同步:同一局游戏中多设备玩家昵称/头像显示》的技术方案,我们验证了HarmonyOS在设备协同和媒体控制方面的强大能力,为开发者提供了构建跨设备控制类应用的实践参考。

注意事项:
实际开发中需要处理设备权限申请

考虑不同设备的媒体能力差异

生产环境需要添加更完善的错误处理

可根据具体需求扩展更多控制功能

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
收藏
回复
举报
回复
    相关推荐