
基于HarmonyOS的跨设备遥控器开发与媒体同步控制 原创
基于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在设备协同和媒体控制方面的强大能力,为开发者提供了构建跨设备控制类应用的实践参考。
注意事项:
实际开发中需要处理设备权限申请
考虑不同设备的媒体能力差异
生产环境需要添加更完善的错误处理
可根据具体需求扩展更多控制功能
