HarmonyOS分布式娱乐开发实战
HarmonyOS分布式娱乐开发实战
周末窝在沙发上用手机看剧,突然想投到电视上继续看;或者在卧室用平板追综艺,走到客厅想无缝切换到大屏——这种"走到哪看到哪"的体验,正是鸿蒙分布式娱乐场景的魅力所在。
一、背景与动机
1.1 传统投屏的痛点
说实话,现在的投屏体验真的很让人抓狂。你肯定遇到过这些情况:
- 连接繁琐:打开电视、切换输入源、手机找投屏功能、等待连接、祈祷别断开…
- 状态不同步:投屏后进度条从开头开始,还得手动拖到刚才看的位置
- 音画不同步:画面在电视上,声音却从手机出来,或者干脆卡成PPT
- 断线重连难:Wi-Fi一抖动就断开,重新连接又是一番折腾
- 设备兼容差:这个品牌的电视投不上,那个品牌的盒子不支持
最气人的是,好不容易投上了,突然想上厕所,又得拿着手机——因为电视遥控器上没有暂停按钮,或者根本找不到遥控器在哪。
1.2 鸿蒙分布式娱乐的愿景
鸿蒙的分布式能力让娱乐体验有了质的飞跃:
无缝流转:视频播放状态(进度、音量、字幕、倍速)一键流转到其他设备,无需手动调整。
多屏协同:手机当遥控器、平板当第二屏幕、电视当主屏幕,各司其职又协同配合。
就近发现:设备靠近自动发现,不需要复杂的配对过程。
状态持久:即使流转中断,也能快速恢复到断点位置。
1.3 核心价值
分布式娱乐场景带来的价值是实实在在的:
- 操作步骤减少80%:从5步以上简化到1-2步
- 状态同步零延迟:进度、音量、字幕秒级同步
- 跨设备无缝切换:任意设备间流转,体验一致
- 降低设备门槛:老旧设备也能参与分布式协同
二、核心原理
2.1 分布式媒体播放架构
鸿蒙的分布式媒体播放基于以下核心组件:
分布式媒体控制器(Distributed Media Controller):
负责协调多个设备的媒体播放,维护统一的播放状态。
媒体数据传输通道:
- 本地流传输:适合小文件,直接传输媒体数据
- URL共享传输:适合在线视频,只传输播放地址
- DLNA/AirPlay兼容:与传统投屏协议兼容
播放状态同步机制:
播放状态 = {
mediaId: "video_001", // 媒体ID
position: 1234567, // 播放位置(毫秒)
duration: 7200000, // 总时长(毫秒)
playing: true, // 是否播放中
speed: 1.0, // 播放速度
volume: 0.8, // 音量
subtitle: { // 字幕信息
track: 1, // 字幕轨道
offset: 0 // 字幕偏移
},
audioTrack: 0, // 音频轨道
quality: "1080p", // 画质
lastUpdate: 1703836800000 // 最后更新时间
}
2.2 设备能力协商
不同设备的播放能力差异很大,需要在流转前进行协商:
设备能力描述:
interface MediaCapability {
// 解码能力
codecs: string[]; // 支持的编解码器:H.264, H.265, VP9等
maxResolution: string; // 最大分辨率:4K, 1080p, 720p
maxBitrate: number; // 最大码率(bps)
hdrSupport: boolean; // 是否支持HDR
// 音频能力
audioCodecs: string[]; // 音频编解码器:AAC, AC3, EAC3等
maxAudioChannels: number; // 最大声道数
surroundSound: boolean; // 是否支持环绕声
// 字幕能力
subtitleFormats: string[]; // 字幕格式:SRT, ASS, VTT等
// 控制能力
seekable: boolean; // 是否支持拖动
speedControl: boolean; // 是否支持变速播放
pipSupport: boolean; // 是否支持画中画
// 网络能力
bandwidth: number; // 网络带宽
latency: number; // 网络延迟
}
2.3 播放状态同步策略
播放状态的同步需要考虑实时性和准确性:
高频同步状态(实时同步):
- 播放位置(每秒同步一次)
- 播放/暂停状态(立即同步)
- 音量变化(立即同步)
低频同步状态(批量同步):
- 字幕轨道切换
- 音频轨道切换
- 画质切换
一次性同步状态(流转时同步):
- 播放列表
- 历史记录
- 用户偏好设置
2.4 多设备协同模式
分布式娱乐支持多种协同模式:
主从模式:
- 主设备控制播放,从设备显示画面
- 适合:手机控制电视播放
镜像模式:
- 多个设备同步播放相同内容
- 适合:多房间同步播放背景音乐
扩展模式:
- 不同设备显示不同内容
- 适合:手机显示弹幕,电视显示视频
接力模式:
- 设备间传递播放任务
- 适合:走到哪看到哪
三、代码实战
3.1 分布式视频播放器基础实现
首先实现一个支持分布式流转的视频播放器:
// DistributedVideoPlayer.ets
import media from '@ohos.multimedia.media';
import deviceManager from '@ohos.distributedDeviceManager';
import { distributedData } from '@kit.ArkData';
// 播放状态定义
interface PlaybackState {
mediaId: string; // 媒体ID
mediaUrl: string; // 媒体URL
position: number; // 播放位置(毫秒)
duration: number; // 总时长(毫秒)
playing: boolean; // 是否播放中
speed: number; // 播放速度
volume: number; // 音量(0-1)
muted: boolean; // 是否静音
subtitleTrack: number; // 字幕轨道索引
audioTrack: number; // 音频轨道索引
quality: string; // 当前画质
loopMode: LoopMode; // 循环模式
lastUpdate: number; // 最后更新时间戳
}
// 循环模式
enum LoopMode {
OFF = 'off', // 不循环
SINGLE = 'single', // 单曲循环
ALL = 'all' // 列表循环
}
// 设备信息
interface DeviceInfo {
deviceId: string;
deviceName: string;
deviceType: string;
capability: MediaCapability;
online: boolean;
}
@Entry
@Component
struct DistributedVideoPlayer {
// 播放器实例
private avPlayer: media.AVPlayer | null = null;
// 播放状态
@State playbackState: PlaybackState = {
mediaId: '',
mediaUrl: '',
position: 0,
duration: 0,
playing: false,
speed: 1.0,
volume: 1.0,
muted: false,
subtitleTrack: 0,
audioTrack: 0,
quality: 'auto',
loopMode: LoopMode.OFF,
lastUpdate: Date.now()
};
// UI状态
@State showControls: boolean = true;
@State showDeviceList: boolean = false;
@State isBuffering: boolean = false;
@State bufferingProgress: number = 0;
// 分布式相关
@State availableDevices: DeviceInfo[] = [];
@State currentDevice: string = 'local'; // 当前播放设备
private kvStore: distributedData.KvStore | null = null;
// 播放列表
@State playlist: MediaItem[] = [];
@State currentIndex: number = 0;
// 控制栏自动隐藏定时器
private hideControlsTimer: number = -1;
aboutToAppear() {
// 初始化播放器
this.initPlayer();
// 初始化分布式能力
this.initDistributedCapability();
// 加载播放列表
this.loadPlaylist();
}
aboutToDisappear() {
// 释放资源
this.releasePlayer();
// 保存播放状态
this.savePlaybackState();
}
// 初始化播放器
async initPlayer() {
try {
// 创建AVPlayer实例
this.avPlayer = await media.createAVPlayer();
// 设置状态变化监听
this.avPlayer.on('stateChange', (state: string) => {
console.info(`播放器状态变化: ${state}`);
this.handlePlayerStateChange(state);
});
// 设置播放进度监听
this.avPlayer.on('timeUpdate', (time: number) => {
this.playbackState.position = time;
// 定期同步播放位置
this.syncPlaybackPosition();
});
// 设置缓冲更新监听
this.avPlayer.on('bufferingUpdate', (info: media.BufferingInfoType) => {
this.isBuffering = info.isBuffering;
this.bufferingProgress = info.bufferingPercent;
});
// 设置错误监听
this.avPlayer.on('error', (err: Error) => {
console.error(`播放错误: ${err.message}`);
});
console.info('播放器初始化成功');
} catch (err) {
console.error(`播放器初始化失败: ${JSON.stringify(err)}`);
}
}
// 处理播放器状态变化
handlePlayerStateChange(state: string) {
switch (state) {
case 'prepared':
// 准备完成,可以开始播放
this.playbackState.duration = this.avPlayer?.duration || 0;
break;
case 'playing':
this.playbackState.playing = true;
this.syncPlaybackState();
break;
case 'paused':
this.playbackState.playing = false;
this.syncPlaybackState();
break;
case 'completed':
// 播放完成
this.handlePlaybackComplete();
break;
case 'stopped':
this.playbackState.playing = false;
break;
}
}
// 加载媒体源
async loadMedia(mediaItem: MediaItem) {
if (!this.avPlayer) {
return;
}
try {
// 更新播放状态
this.playbackState.mediaId = mediaItem.id;
this.playbackState.mediaUrl = mediaItem.url;
// 设置媒体源
await this.avPlayer.setMediaSource(mediaItem.url);
// 准备播放
await this.avPlayer.prepare();
console.info(`媒体加载成功: ${mediaItem.title}`);
} catch (err) {
console.error(`媒体加载失败: ${JSON.stringify(err)}`);
}
}
// 开始播放
async play() {
if (!this.avPlayer) {
return;
}
try {
await this.avPlayer.play();
} catch (err) {
console.error(`播放失败: ${JSON.stringify(err)}`);
}
}
// 暂停播放
async pause() {
if (!this.avPlayer) {
return;
}
try {
await this.avPlayer.pause();
} catch (err) {
console.error(`暂停失败: ${JSON.stringify(err)}`);
}
}
// 跳转到指定位置
async seek(position: number) {
if (!this.avPlayer) {
return;
}
try {
await this.avPlayer.seek(position);
this.playbackState.position = position;
this.syncPlaybackState();
} catch (err) {
console.error(`跳转失败: ${JSON.stringify(err)}`);
}
}
// 设置播放速度
async setSpeed(speed: number) {
if (!this.avPlayer) {
return;
}
try {
await this.avPlayer.setSpeed(speed);
this.playbackState.speed = speed;
this.syncPlaybackState();
} catch (err) {
console.error(`设置速度失败: ${JSON.stringify(err)}`);
}
}
// 设置音量
async setVolume(volume: number) {
if (!this.avPlayer) {
return;
}
try {
await this.avPlayer.setVolume(volume);
this.playbackState.volume = volume;
this.syncPlaybackState();
} catch (err) {
console.error(`设置音量失败: ${JSON.stringify(err)}`);
}
}
// 处理播放完成
handlePlaybackComplete() {
switch (this.playbackState.loopMode) {
case LoopMode.SINGLE:
// 单曲循环
this.seek(0);
this.play();
break;
case LoopMode.ALL:
// 列表循环
this.playNext();
break;
case LoopMode.OFF:
// 不循环,播放下一个
if (this.currentIndex < this.playlist.length - 1) {
this.playNext();
}
break;
}
}
// 播放下一个
async playNext() {
if (this.playlist.length === 0) {
return;
}
this.currentIndex = (this.currentIndex + 1) % this.playlist.length;
await this.loadMedia(this.playlist[this.currentIndex]);
await this.play();
}
// 播放上一个
async playPrevious() {
if (this.playlist.length === 0) {
return;
}
this.currentIndex = (this.currentIndex - 1 + this.playlist.length) % this.playlist.length;
await this.loadMedia(this.playlist[this.currentIndex]);
await this.play();
}
// 初始化分布式能力
async initDistributedCapability() {
try {
// 创建设备管理器
// const dm = deviceManager.createDeviceManager('com.example.player');
// 发现设备
// const devices = await dm.getAvailableDeviceList();
// this.availableDevices = devices.map(d => this.parseDeviceInfo(d));
console.info('分布式能力初始化成功');
} catch (err) {
console.error(`初始化分布式能力失败: ${JSON.stringify(err)}`);
}
}
// 同步播放状态到分布式数据库
async syncPlaybackState() {
if (!this.kvStore) {
return;
}
this.playbackState.lastUpdate = Date.now();
try {
// await this.kvStore.put('playback_state', JSON.stringify(this.playbackState));
console.info('播放状态已同步');
} catch (err) {
console.error(`同步播放状态失败: ${JSON.stringify(err)}`);
}
}
// 同步播放位置(节流)
private syncPositionTimer: number = -1;
syncPlaybackPosition() {
if (this.syncPositionTimer !== -1) {
return;
}
this.syncPositionTimer = setTimeout(() => {
this.syncPlaybackState();
this.syncPositionTimer = -1;
}, 1000); // 每秒同步一次
}
// 保存播放状态
async savePlaybackState() {
// 保存到本地存储,用于下次打开时恢复
// await Preferences.put('last_playback_state', JSON.stringify(this.playbackState));
}
// 流转到其他设备
async transferToDevice(deviceId: string) {
try {
// 先同步当前播放状态
await this.syncPlaybackState();
// 执行流转
if (deviceId === 'local') {
// 流转回本地
await this.restoreLocalPlayback();
} else {
// 流转到远程设备
await this.transferToRemote(deviceId);
}
this.currentDevice = deviceId;
console.info(`流转到设备 ${deviceId} 成功`);
} catch (err) {
console.error(`流转失败: ${JSON.stringify(err)}`);
}
}
// 流转到远程设备
async transferToRemote(deviceId: string) {
// 暂停本地播放
await this.pause();
// 发送流转请求
// await continuationManager.continue(deviceId, {
// reversible: true,
// continuationExtraParams: {
// playbackState: this.playbackState
// }
// });
}
// 恢复本地播放
async restoreLocalPlayback() {
// 从分布式数据库获取播放状态
// const state = await this.kvStore.get('playback_state');
// if (state) {
// this.playbackState = JSON.parse(state);
// await this.loadMedia({ id: this.playbackState.mediaId, url: this.playbackState.mediaUrl });
// await this.seek(this.playbackState.position);
// if (this.playbackState.playing) {
// await this.play();
// }
// }
}
// 释放播放器资源
async releasePlayer() {
if (this.avPlayer) {
await this.avPlayer.release();
this.avPlayer = null;
}
}
// 加载播放列表
loadPlaylist() {
// 模拟播放列表
this.playlist = [
{
id: 'video_001',
title: '示例视频1',
url: 'https://example.com/video1.mp4',
thumbnail: 'https://example.com/thumb1.jpg',
duration: 7200000
},
{
id: 'video_002',
title: '示例视频2',
url: 'https://example.com/video2.mp4',
thumbnail: 'https://example.com/thumb2.jpg',
duration: 5400000
}
];
}
build() {
Stack() {
// 视频画面
this.buildVideoSurface();
// 播放控制层
if (this.showControls) {
this.buildControls();
}
// 缓冲提示
if (this.isBuffering) {
this.buildBufferingIndicator();
}
// 设备列表弹窗
if (this.showDeviceList) {
this.buildDeviceListDialog();
}
}
.width('100%')
.height('100%')
.backgroundColor('#000000')
.onClick(() => {
// 点击切换控制栏显示
this.showControls = !this.showControls;
if (this.showControls) {
this.startHideControlsTimer();
}
});
}
@Builder
buildVideoSurface() {
// 视频渲染表面
Column() {
// 实际实现需要使用XComponent或Video组件
// 这里用占位符表示
if (this.playbackState.mediaUrl) {
Video({
src: this.playbackState.mediaUrl,
currentProgressRate: this.playbackState.speed
})
.width('100%')
.height('100%')
.autoPlay(false)
.controls(false)
.onPrepared(() => {
console.info('视频准备完成');
})
.onStart(() => {
this.playbackState.playing = true;
})
.onPause(() => {
this.playbackState.playing = false;
})
.onFinish(() => {
this.handlePlaybackComplete();
})
.onError((err) => {
console.error(`视频播放错误: ${err}`);
});
} else {
// 无视频时的占位
Column() {
Image($r('app.media.ic_video_placeholder'))
.width(80)
.height(80)
.opacity(0.5);
Text('选择视频开始播放')
.fontSize(16)
.fontColor('#ffffff')
.opacity(0.5)
.margin({ top: 16 });
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center);
}
}
.width('100%')
.height('100%');
}
@Builder
buildControls() {
Column() {
// 顶部控制栏
this.buildTopControls();
Blank();
// 中间播放/暂停按钮
this.buildCenterControls();
Blank();
// 底部控制栏
this.buildBottomControls();
}
.width('100%')
.height('100%')
.linearGradient({
direction: GradientDirection.Bottom,
colors: [
['rgba(0,0,0,0.7)', 0],
['rgba(0,0,0,0)', 0.3],
['rgba(0,0,0,0)', 0.7],
['rgba(0,0,0,0.7)', 1]
]
});
}
@Builder
buildTopControls() {
Row() {
// 返回按钮
Button({ type: ButtonType.Circle }) {
Image($r('app.media.ic_back'))
.width(24)
.height(24)
.fillColor('#ffffff');
}
.width(40)
.height(40)
.backgroundColor(Color.Transparent);
Blank();
// 视频标题
if (this.playlist.length > 0 && this.currentIndex >= 0) {
Text(this.playlist[this.currentIndex].title)
.fontSize(18)
.fontColor('#ffffff')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width('50%')
.textAlign(TextAlign.Center);
}
Blank();
// 流转按钮
Button({ type: ButtonType.Circle }) {
Image($r('app.media.ic_cast'))
.width(24)
.height(24)
.fillColor('#ffffff');
}
.width(40)
.height(40)
.backgroundColor(Color.Transparent)
.onClick(() => {
this.showDeviceList = true;
});
}
.width('100%')
.padding({ left: 16, right: 16, top: 16 });
}
@Builder
buildCenterControls() {
Row() {
// 上一个
Button({ type: ButtonType.Circle }) {
Image($r('app.media.ic_previous'))
.width(32)
.height(32)
.fillColor('#ffffff');
}
.width(48)
.height(48)
.backgroundColor(Color.Transparent)
.onClick(() => {
this.playPrevious();
});
Blank();
// 播放/暂停
Button({ type: ButtonType.Circle }) {
Image(this.playbackState.playing ? $r('app.media.ic_pause') : $r('app.media.ic_play'))
.width(48)
.height(48)
.fillColor('#ffffff');
}
.width(72)
.height(72)
.backgroundColor('rgba(255,255,255,0.2)')
.onClick(() => {
if (this.playbackState.playing) {
this.pause();
} else {
this.play();
}
});
Blank();
// 下一个
Button({ type: ButtonType.Circle }) {
Image($r('app.media.ic_next'))
.width(32)
.height(32)
.fillColor('#ffffff');
}
.width(48)
.height(48)
.backgroundColor(Color.Transparent)
.onClick(() => {
this.playNext();
});
}
.width('100%')
.padding({ left: 48, right: 48 });
}
@Builder
buildBottomControls() {
Column() {
// 进度条
Row() {
Text(this.formatTime(this.playbackState.position))
.fontSize(12)
.fontColor('#ffffff')
.width(48);
Slider({
value: this.playbackState.position,
min: 0,
max: this.playbackState.duration,
step: 1000
})
.width('100%')
.layoutWeight(1)
.blockColor('#ffffff')
.trackColor('rgba(255,255,255,0.3)')
.selectedColor('#2196F3')
.onChange((value: number) => {
this.seek(value);
});
Text(this.formatTime(this.playbackState.duration))
.fontSize(12)
.fontColor('#ffffff')
.width(48);
}
.width('100%')
.padding({ left: 16, right: 16 });
// 底部按钮栏
Row() {
// 倍速播放
Button({ type: ButtonType.Circle }) {
Text(`${this.playbackState.speed}x`)
.fontSize(12)
.fontColor('#ffffff');
}
.width(40)
.height(40)
.backgroundColor(Color.Transparent)
.onClick(() => {
this.showSpeedSelector();
});
Blank();
// 循环模式
Button({ type: ButtonType.Circle }) {
Image(this.getLoopModeIcon())
.width(24)
.height(24)
.fillColor('#ffffff');
}
.width(40)
.height(40)
.backgroundColor(Color.Transparent)
.onClick(() => {
this.toggleLoopMode();
});
Blank();
// 音量
Button({ type: ButtonType.Circle }) {
Image(this.playbackState.muted ? $r('app.media.ic_mute') : $r('app.media.ic_volume'))
.width(24)
.height(24)
.fillColor('#ffffff');
}
.width(40)
.height(40)
.backgroundColor(Color.Transparent)
.onClick(() => {
this.playbackState.muted = !this.playbackState.muted;
if (this.playbackState.muted) {
this.setVolume(0);
} else {
this.setVolume(this.playbackState.volume);
}
});
}
.width('100%')
.padding({ left: 16, right: 16, bottom: 16 });
}
.width('100%');
}
@Builder
buildBufferingIndicator() {
Column() {
LoadingProgress()
.width(48)
.height(48)
.color('#ffffff');
Text(`缓冲中... ${this.bufferingProgress}%`)
.fontSize(14)
.fontColor('#ffffff')
.margin({ top: 16 });
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.backgroundColor('rgba(0,0,0,0.5)');
}
@Builder
buildDeviceListDialog() {
Column() {
// 标题
Text('选择播放设备')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 });
// 本地设备
Row() {
Image($r('app.media.ic_phone'))
.width(40)
.height(40)
.margin({ right: 16 });
Column() {
Text('本机')
.fontSize(16)
.fontWeight(FontWeight.Medium);
Text('当前设备')
.fontSize(12)
.fontColor('#666666');
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1);
if (this.currentDevice === 'local') {
Image($r('app.media.ic_check'))
.width(24)
.height(24)
.fillColor('#2196F3');
}
}
.width('100%')
.padding(16)
.backgroundColor(this.currentDevice === 'local' ? '#E3F2FD' : '#f5f5f5')
.borderRadius(8)
.margin({ bottom: 12 })
.onClick(() => {
this.transferToDevice('local');
this.showDeviceList = false;
});
// 远程设备列表
List() {
ForEach(this.availableDevices, (device: DeviceInfo) => {
ListItem() {
Row() {
Image(this.getDeviceIcon(device.deviceType))
.width(40)
.height(40)
.margin({ right: 16 });
Column() {
Text(device.deviceName)
.fontSize(16)
.fontWeight(FontWeight.Medium);
Text(device.deviceType)
.fontSize(12)
.fontColor('#666666');
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1);
if (this.currentDevice === device.deviceId) {
Image($r('app.media.ic_check'))
.width(24)
.height(24)
.fillColor('#2196F3');
}
}
.width('100%')
.padding(16)
.backgroundColor(this.currentDevice === device.deviceId ? '#E3F2FD' : '#f5f5f5')
.borderRadius(8)
.margin({ bottom: 8 });
}
.onClick(() => {
this.transferToDevice(device.deviceId);
this.showDeviceList = false;
});
});
}
.width('100%')
.layoutWeight(1);
// 取消按钮
Button('取消')
.width('100%')
.margin({ top: 16 })
.onClick(() => {
this.showDeviceList = false;
});
}
.width('80%')
.padding(20)
.backgroundColor(Color.White)
.borderRadius(12)
.shadow({ radius: 20, color: '#00000020' });
}
// 启动控制栏自动隐藏定时器
startHideControlsTimer() {
if (this.hideControlsTimer !== -1) {
clearTimeout(this.hideControlsTimer);
}
this.hideControlsTimer = setTimeout(() => {
if (this.playbackState.playing) {
this.showControls = false;
}
this.hideControlsTimer = -1;
}, 5000); // 5秒后自动隐藏
}
// 显示倍速选择器
showSpeedSelector() {
// 显示倍速选择对话框
const speeds = [0.5, 0.75, 1.0, 1.25, 1.5, 2.0];
// TODO: 实现选择器UI
}
// 切换循环模式
toggleLoopMode() {
const modes = [LoopMode.OFF, LoopMode.SINGLE, LoopMode.ALL];
const currentIndex = modes.indexOf(this.playbackState.loopMode);
this.playbackState.loopMode = modes[(currentIndex + 1) % modes.length];
}
// 获取循环模式图标
getLoopModeIcon(): Resource {
switch (this.playbackState.loopMode) {
case LoopMode.SINGLE:
return $r('app.media.ic_loop_one');
case LoopMode.ALL:
return $r('app.media.ic_loop_all');
default:
return $r('app.media.ic_loop_off');
}
}
// 获取设备图标
getDeviceIcon(deviceType: string): Resource {
switch (deviceType) {
case 'tv':
return $r('app.media.ic_tv');
case 'tablet':
return $r('app.media.ic_tablet');
case 'pc':
return $r('app.media.ic_pc');
default:
return $r('app.media.ic_device');
}
}
// 格式化时间
formatTime(milliseconds: number): string {
const seconds = Math.floor(milliseconds / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const mm = (minutes % 60).toString().padStart(2, '0');
const ss = (seconds % 60).toString().padStart(2, '0');
if (hours > 0) {
const hh = hours.toString().padStart(2, '0');
return `${hh}:${mm}:${ss}`;
}
return `${mm}:${ss}`;
}
}
// 媒体项定义
interface MediaItem {
id: string;
title: string;
url: string;
thumbnail: string;
duration: number;
}
// 媒体能力定义
interface MediaCapability {
codecs: string[];
maxResolution: string;
maxBitrate: number;
hdrSupport: boolean;
audioCodecs: string[];
maxAudioChannels: number;
surroundSound: boolean;
subtitleFormats: string[];
seekable: boolean;
speedControl: boolean;
pipSupport: boolean;
bandwidth: number;
latency: number;
}
3.2 多设备协同播放实现
实现多设备同步播放,适合多房间背景音乐等场景:
// MultiDeviceSyncPlayer.ets
// 同步播放管理器
class SyncPlaybackManager {
private devices: Map<string, SyncDeviceInfo> = new Map();
private masterDevice: string = '';
private syncInterval: number = 100; // 同步间隔(毫秒)
private maxSyncOffset: number = 500; // 最大同步偏差(毫秒)
// 添加设备
addDevice(deviceId: string, player: media.AVPlayer) {
this.devices.set(deviceId, {
deviceId: deviceId,
player: player,
lastSyncTime: 0,
syncOffset: 0,
latency: 0
});
}
// 移除设备
removeDevice(deviceId: string) {
this.devices.delete(deviceId);
if (this.masterDevice === deviceId) {
// 主设备离线,重新选举
this.electMaster();
}
}
// 选举主设备
electMaster() {
// 选择延迟最低的设备作为主设备
let minLatency = Infinity;
let newMaster = '';
for (const [deviceId, info] of this.devices) {
if (info.latency < minLatency) {
minLatency = info.latency;
newMaster = deviceId;
}
}
this.masterDevice = newMaster;
console.info(`新主设备: ${newMaster}`);
}
// 同步播放
async syncPlay() {
const masterInfo = this.devices.get(this.masterDevice);
if (!masterInfo) {
return;
}
// 获取主设备播放位置
const masterPosition = await masterInfo.player.getCurrentTime();
// 同步所有从设备
for (const [deviceId, info] of this.devices) {
if (deviceId === this.masterDevice) {
continue;
}
// 计算同步位置(考虑网络延迟)
const syncPosition = masterPosition + info.latency;
// 检查是否需要同步
const currentPosition = await info.player.getCurrentTime();
const offset = Math.abs(currentPosition - syncPosition);
if (offset > this.maxSyncOffset) {
// 偏差过大,执行同步
await info.player.seek(syncPosition);
console.info(`设备 ${deviceId} 同步到 ${syncPosition}`);
}
}
}
// 启动同步循环
startSyncLoop() {
setInterval(() => {
this.syncPlay();
}, this.syncInterval);
}
// 测量设备延迟
async measureLatency(deviceId: string): Promise<number> {
const info = this.devices.get(deviceId);
if (!info) {
return 0;
}
// 发送ping消息,测量往返时间
const startTime = Date.now();
// await sendPing(deviceId);
const endTime = Date.now();
info.latency = (endTime - startTime) / 2;
return info.latency;
}
}
interface SyncDeviceInfo {
deviceId: string;
player: media.AVPlayer;
lastSyncTime: number;
syncOffset: number;
latency: number;
}
@Entry
@Component
struct MultiDeviceSyncPlayer {
@State devices: SyncDeviceUI[] = [];
@State isSyncPlaying: boolean = false;
@State masterDevice: string = '';
private syncManager: SyncPlaybackManager = new SyncPlaybackManager();
build() {
Column() {
// 标题
Text('多设备同步播放')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 24 });
// 设备列表
List() {
ForEach(this.devices, (device: SyncDeviceUI) => {
ListItem() {
this.buildDeviceItem(device);
}
});
}
.width('100%')
.layoutWeight(1);
// 控制按钮
Row() {
Button(this.isSyncPlaying ? '停止同步' : '开始同步')
.width('100%')
.onClick(() => {
if (this.isSyncPlaying) {
this.stopSyncPlay();
} else {
this.startSyncPlay();
}
});
}
.width('100%')
.padding(16);
}
.width('100%')
.height('100%')
.padding(16);
}
@Builder
buildDeviceItem(device: SyncDeviceUI) {
Row() {
// 设备图标
Image($r('app.media.ic_speaker'))
.width(48)
.height(48)
.margin({ right: 16 });
// 设备信息
Column() {
Row() {
Text(device.name)
.fontSize(16)
.fontWeight(FontWeight.Medium);
if (device.deviceId === this.masterDevice) {
Text('主设备')
.fontSize(12)
.fontColor('#ffffff')
.backgroundColor('#2196F3')
.borderRadius(4)
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
.margin({ left: 8 });
}
}
Text(`延迟: ${device.latency}ms`)
.fontSize(12)
.fontColor('#666666')
.margin({ top: 4 });
// 同步状态进度条
Progress({ value: device.syncProgress, total: 100, type: ProgressType.Linear })
.width('100%')
.margin({ top: 8 });
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1);
// 音量控制
Column() {
Image($r('app.media.ic_volume'))
.width(24)
.height(24);
Slider({
value: device.volume,
min: 0,
max: 1,
step: 0.1
})
.width(80)
.blockColor('#2196F3')
.trackColor('rgba(33,150,243,0.3)')
.onChange((value: number) => {
device.volume = value;
});
}
.width(100);
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(8)
.margin({ bottom: 8 });
}
async startSyncPlay() {
this.isSyncPlaying = true;
this.syncManager.startSyncLoop();
}
stopSyncPlay() {
this.isSyncPlaying = false;
}
}
interface SyncDeviceUI {
deviceId: string;
name: string;
latency: number;
syncProgress: number;
volume: number;
}
3.3 智能画质适配
根据设备能力和网络状况自动选择最佳画质:
// AdaptiveQualityManager.ets
// 画质等级
enum QualityLevel {
AUTO = 'auto',
LOW = '360p',
MEDIUM = '480p',
HIGH = '720p',
ULTRA = '1080p',
ULTRA_PLUS = '4K'
}
// 画质信息
interface QualityInfo {
level: QualityLevel;
width: number;
height: number;
bitrate: number; // 码率(bps)
framerate: number; // 帧率
codec: string; // 编解码器
hdr: boolean; // 是否HDR
}
// 自适应画质管理器
class AdaptiveQualityManager {
private qualityList: QualityInfo[] = [];
private currentQuality: QualityLevel = QualityLevel.AUTO;
private networkBandwidth: number = 0;
private deviceCapability: MediaCapability | null = null;
// 初始化画质列表
initQualityList(qualities: QualityInfo[]) {
this.qualityList = qualities.sort((a, b) => a.bitrate - b.bitrate);
}
// 设置设备能力
setDeviceCapability(capability: MediaCapability) {
this.deviceCapability = capability;
}
// 更新网络带宽
updateNetworkBandwidth(bandwidth: number) {
this.networkBandwidth = bandwidth;
// 如果是自动模式,重新选择画质
if (this.currentQuality === QualityLevel.AUTO) {
this.selectOptimalQuality();
}
}
// 选择最优画质
selectOptimalQuality(): QualityInfo | null {
if (!this.deviceCapability || this.qualityList.length === 0) {
return null;
}
// 筛选设备支持的画质
const supportedQualities = this.qualityList.filter(q => {
// 检查分辨率
if (!this.checkResolutionSupport(q)) {
return false;
}
// 检查编解码器
if (!this.deviceCapability!.codecs.includes(q.codec)) {
return false;
}
// 检查码率
if (q.bitrate > this.deviceCapability!.maxBitrate) {
return false;
}
// 检查HDR
if (q.hdr && !this.deviceCapability!.hdrSupport) {
return false;
}
return true;
});
// 根据网络带宽选择最佳画质
const networkLimit = this.networkBandwidth * 0.8; // 预留20%余量
const suitableQualities = supportedQualities.filter(q => q.bitrate <= networkLimit);
if (suitableQualities.length === 0) {
// 网络带宽不足,选择最低画质
return supportedQualities[0] || null;
}
// 选择最高画质
const optimal = suitableQualities[suitableQualities.length - 1];
console.info(`选择画质: ${optimal.level}, 码率: ${optimal.bitrate}`);
return optimal;
}
// 检查分辨率支持
private checkResolutionSupport(quality: QualityInfo): boolean {
if (!this.deviceCapability) {
return false;
}
const maxRes = this.deviceCapability.maxResolution;
switch (maxRes) {
case '4K':
return true; // 支持4K,支持所有分辨率
case '1080p':
return quality.height <= 1080;
case '720p':
return quality.height <= 720;
case '480p':
return quality.height <= 480;
default:
return quality.height <= 360;
}
}
// 手动切换画质
async switchQuality(level: QualityLevel): Promise<QualityInfo | null> {
if (level === QualityLevel.AUTO) {
this.currentQuality = level;
return this.selectOptimalQuality();
}
const quality = this.qualityList.find(q => q.level === level);
if (!quality) {
console.error(`画质 ${level} 不存在`);
return null;
}
// 检查设备是否支持
if (!this.deviceCapability) {
return null;
}
if (!this.checkResolutionSupport(quality)) {
console.error(`设备不支持分辨率 ${quality.width}x${quality.height}`);
return null;
}
if (!this.deviceCapability.codecs.includes(quality.codec)) {
console.error(`设备不支持编解码器 ${quality.codec}`);
return null;
}
this.currentQuality = level;
return quality;
}
// 监听网络变化
monitorNetworkChange() {
// connection.getConnectionType((err, type) => {
// // 根据网络类型估算带宽
// switch (type) {
// case connection.ConnectionType.CONNECTION_WIFI:
// this.updateNetworkBandwidth(50 * 1024 * 1024); // 50Mbps
// break;
// case connection.ConnectionType.CONNECTION_CELLULAR:
// this.updateNetworkBandwidth(10 * 1024 * 1024); // 10Mbps
// break;
// default:
// this.updateNetworkBandwidth(1 * 1024 * 1024); // 1Mbps
// }
// });
}
}
// 画质选择UI组件
@Entry
@Component
struct QualitySelector {
@State qualityList: QualityUI[] = [
{ level: QualityLevel.AUTO, name: '自动', selected: true },
{ level: QualityLevel.LOW, name: '流畅 360p', selected: false },
{ level: QualityLevel.MEDIUM, name: '清晰 480p', selected: false },
{ level: QualityLevel.HIGH, name: '高清 720p', selected: false },
{ level: QualityLevel.ULTRA, name: '超清 1080p', selected: false },
{ level: QualityLevel.ULTRA_PLUS, name: '蓝光 4K', selected: false }
];
@State currentBandwidth: string = '50 Mbps';
@State recommendedQuality: string = '超清 1080p';
build() {
Column() {
// 标题
Text('画质选择')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 });
// 网络信息
Row() {
Text('当前网络:')
.fontSize(14)
.fontColor('#666666');
Text(this.currentBandwidth)
.fontSize(14)
.fontWeight(FontWeight.Medium)
.margin({ left: 8 });
Blank();
Text(`推荐: ${this.recommendedQuality}`)
.fontSize(14)
.fontColor('#2196F3');
}
.width('100%')
.padding(16)
.backgroundColor('#f5f5f5')
.borderRadius(8)
.margin({ bottom: 16 });
// 画质列表
List() {
ForEach(this.qualityList, (quality: QualityUI) => {
ListItem() {
Row() {
Radio({ value: quality.level, group: 'quality' })
.checked(quality.selected)
.onChange((isChecked: boolean) => {
if (isChecked) {
this.selectQuality(quality.level);
}
});
Text(quality.name)
.fontSize(16)
.margin({ left: 16 });
Blank();
if (quality.level === QualityLevel.AUTO) {
Text('智能适配')
.fontSize(12)
.fontColor('#999999');
}
}
.width('100%')
.padding(16);
}
});
}
.width('100%')
.layoutWeight(1);
}
.width('100%')
.padding(20)
.backgroundColor(Color.White)
.borderRadius(12);
}
selectQuality(level: QualityLevel) {
this.qualityList.forEach(q => {
q.selected = q.level === level;
});
}
}
interface QualityUI {
level: QualityLevel;
name: string;
selected: boolean;
}
四、踩坑与注意事项
4.1 音画不同步问题
问题现象:
流转到电视后,画面和声音有明显的延迟,口型对不上。
原因分析:
- 不同设备的音视频解码延迟不同
- 网络传输延迟导致数据到达时间不一致
- 音频输出设备和视频输出设备不同步
解决方案:
// 音画同步管理器
class AudioVideoSyncManager {
private audioLatency: number = 0; // 音频延迟(毫秒)
private videoLatency: number = 0; // 视频延迟(毫秒)
private networkLatency: number = 0; // 网络延迟(毫秒)
// 测量音视频延迟
async measureLatency(): Promise<void> {
// 测量音频延迟
this.audioLatency = await this.measureAudioLatency();
// 测量视频延迟
this.videoLatency = await this.measureVideoLatency();
// 测量网络延迟
this.networkLatency = await this.measureNetworkLatency();
console.info(`音频延迟: ${this.audioLatency}ms`);
console.info(`视频延迟: ${this.videoLatency}ms`);
console.info(`网络延迟: ${this.networkLatency}ms`);
}
// 计算同步偏移
calculateSyncOffset(): number {
// 音频需要延迟的时间 = 视频延迟 - 音频延迟
return this.videoLatency - this.audioLatency;
}
// 应用同步偏移
async applySyncOffset(player: media.AVPlayer): Promise<void> {
const offset = this.calculateSyncOffset();
if (offset > 0) {
// 视频延迟更大,音频需要延迟
await player.setAudioDelayOffset(offset);
} else if (offset < 0) {
// 音频延迟更大,视频需要延迟
await player.setVideoDelayOffset(-offset);
}
console.info(`应用同步偏移: ${offset}ms`);
}
// 测量音频延迟
private async measureAudioLatency(): Promise<number> {
// 发送测试音频信号,测量往返时间
// 实际实现需要音频设备配合
return 50; // 示例值
}
// 测量视频延迟
private async measureVideoLatency(): Promise<number> {
// 发送测试视频帧,测量往返时间
// 实际实现需要视频设备配合
return 80; // 示例值
}
// 测量网络延迟
private async measureNetworkLatency(): Promise<number> {
// 发送ping消息,测量往返时间
const startTime = Date.now();
// await sendPing();
const endTime = Date.now();
return endTime - startTime;
}
}
4.2 内存泄漏问题
问题现象:
长时间播放视频后,应用内存持续增长,最终导致OOM崩溃。
原因分析:
- 播放器资源未正确释放
- 缓存数据未及时清理
- 事件监听未移除
解决方案:
// 资源管理器
class PlayerResourceManager {
private players: Map<string, media.AVPlayer> = new Map();
private listeners: Map<string, Function[]> = new Map();
private caches: Map<string, ArrayBuffer> = new Map();
private maxCacheSize: number = 100 * 1024 * 1024; // 最大缓存100MB
// 创建播放器
async createPlayer(id: string): Promise<media.AVPlayer> {
// 如果已存在,先释放
if (this.players.has(id)) {
await this.releasePlayer(id);
}
// 创建新播放器
const player = await media.createAVPlayer();
this.players.set(id, player);
this.listeners.set(id, []);
return player;
}
// 注册事件监听(自动管理)
registerListener(id: string, player: media.AVPlayer, event: string, callback: Function) {
player.on(event, callback);
const listeners = this.listeners.get(id) || [];
listeners.push(() => player.off(event, callback));
this.listeners.set(id, listeners);
}
// 释放播放器
async releasePlayer(id: string): Promise<void> {
// 移除所有事件监听
const listeners = this.listeners.get(id) || [];
listeners.forEach(off => off());
this.listeners.delete(id);
// 释放播放器
const player = this.players.get(id);
if (player) {
await player.release();
this.players.delete(id);
}
console.info(`播放器 ${id} 已释放`);
}
// 缓存数据
cacheData(key: string, data: ArrayBuffer): void {
// 检查缓存大小
const currentSize = this.getCacheSize();
const dataSize = data.byteLength;
if (currentSize + dataSize > this.maxCacheSize) {
// 清理旧缓存
this.cleanCache(dataSize);
}
this.caches.set(key, data);
}
// 获取缓存大小
private getCacheSize(): number {
let size = 0;
for (const data of this.caches.values()) {
size += data.byteLength;
}
return size;
}
// 清理缓存
private cleanCache(neededSize: number): void {
// 简单策略:删除最早的缓存
const keys = Array.from(this.caches.keys());
let freedSize = 0;
for (const key of keys) {
const data = this.caches.get(key);
if (data) {
freedSize += data.byteLength;
this.caches.delete(key);
if (freedSize >= neededSize) {
break;
}
}
}
console.info(`清理缓存: ${freedSize} bytes`);
}
// 释放所有资源
async releaseAll(): Promise<void> {
for (const id of this.players.keys()) {
await this.releasePlayer(id);
}
this.caches.clear();
}
}
4.3 网络切换断流问题
问题现象:
Wi-Fi切换到4G或网络波动时,视频播放中断,需要重新加载。
解决方案:
// 网络自适应播放器
class NetworkAdaptivePlayer {
private player: media.AVPlayer | null = null;
private currentUrl: string = '';
private currentPosition: number = 0;
private isPlaying: boolean = false;
private reconnectAttempts: number = 0;
private maxReconnectAttempts: number = 3;
// 初始化网络监听
initNetworkMonitor() {
// connection.on('connectionChange', (data) => {
// this.handleNetworkChange(data);
// });
}
// 处理网络变化
async handleNetworkChange(data: any) {
console.info(`网络变化: ${JSON.stringify(data)}`);
// 保存当前状态
if (this.player) {
this.currentPosition = await this.player.getCurrentTime();
this.isPlaying = this.playbackState.playing;
}
// 等待网络稳定
await this.waitForNetworkStable();
// 尝试重连
await this.reconnect();
}
// 等待网络稳定
private async waitForNetworkStable(): Promise<void> {
return new Promise((resolve) => {
let stableCount = 0;
const checkInterval = setInterval(() => {
// connection.getConnectionType((err, type) => {
// if (type !== connection.ConnectionType.CONNECTION_NONE) {
// stableCount++;
// if (stableCount >= 3) { // 连续3次检测到网络
// clearInterval(checkInterval);
// resolve();
// }
// } else {
// stableCount = 0;
// }
// });
}, 1000);
});
}
// 重连
async reconnect(): Promise<void> {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('重连次数过多,放弃重连');
return;
}
this.reconnectAttempts++;
try {
// 重新加载媒体
await this.player?.setMediaSource(this.currentUrl);
await this.player?.prepare();
// 恢复播放位置
await this.player?.seek(this.currentPosition);
// 恢复播放状态
if (this.isPlaying) {
await this.player?.play();
}
this.reconnectAttempts = 0;
console.info('重连成功');
} catch (err) {
console.error(`重连失败: ${JSON.stringify(err)}`);
// 延迟后重试
setTimeout(() => {
this.reconnect();
}, 2000 * this.reconnectAttempts);
}
}
// 播放状态
private playbackState = {
playing: false
};
}
4.4 HDR视频兼容问题
问题现象:
HDR视频在SDR设备上播放,画面过暗或过亮,色彩失真。
解决方案:
// HDR转SDR处理器
class HDRProcessor {
// 检测HDR类型
detectHDRType(videoInfo: VideoInfo): HDRType {
if (videoInfo.colorPrimaries === 'bt2020') {
if (videoInfo.transferCharacteristics === 'smpte2084') {
return HDRType.HDR10;
} else if (videoInfo.transferCharacteristics === 'arib-std-b67') {
return HDRType.HLG;
}
} else if (videoInfo.dolbyVision) {
return HDRType.DOLBY_VISION;
}
return HDRType.SDR;
}
// HDR转SDR
async convertHDRToSDR(
inputPath: string,
outputPath: string,
hdrType: HDRType
): Promise<void> {
// 使用FFmpeg或硬件加速进行转换
const convertParams = this.getConvertParams(hdrType);
// await ffmpeg.convert(inputPath, outputPath, convertParams);
console.info(`HDR转SDR完成: ${hdrType}`);
}
// 获取转换参数
private getConvertParams(hdrType: HDRType): string {
switch (hdrType) {
case HDRType.HDR10:
return '-vf "zscale=t=linear:npl=100,tonemap=tonemap=hable:peak=100,zscale=t=bt709"';
case HDRType.HLG:
return '-vf "zscale=t=linear:npl=100,tonemap=tonemap=hable:peak=100,zscale=t=bt709"';
case HDRType.DOLBY_VISION:
return '-vf "zscale=t=linear:npl=100,tonemap=tonemap=bt2390:peak=100,zscale=t=bt709"';
default:
return '';
}
}
}
enum HDRType {
SDR = 'sdr',
HDR10 = 'hdr10',
HLG = 'hlg',
DOLBY_VISION = 'dolby_vision'
}
interface VideoInfo {
colorPrimaries: string;
transferCharacteristics: string;
dolbyVision: boolean;
}
五、HarmonyOS 6适配
5.1 API差异
HarmonyOS 6在媒体播放方面有重要更新:
AVPlayer API变化:
// HarmonyOS 5.x
import media from '@ohos.multimedia.media';
const player = await media.createAVPlayer();
player.setMediaSource(url);
player.prepare();
// HarmonyOS 6
import { media } from '@kit.MediaKit';
const player = await media.createAVPlayer();
// 新增:媒体源配置
const mediaSource: media.MediaSource = {
url: url,
headers: {
'User-Agent': 'HarmonyOS Player',
'Referer': 'https://example.com'
},
// 新增:DRM配置
drmConfig: {
drmSystem: media.DrmSystem.WIDEVINE,
licenseUrl: 'https://license.example.com'
}
};
await player.setMediaSource(mediaSource);
// 新增:解码器配置
const decoderConfig: media.DecoderConfig = {
preferHardware: true, // 优先硬件解码
fallbackToSoftware: true, // 允许软件解码回退
lowLatencyMode: false // 低延迟模式
};
await player.setDecoderConfig(decoderConfig);
分布式流转API变化:
// HarmonyOS 6 流转配置
import { continuationManager } from '@kit.AbilityKit';
const continueOptions: continuationManager.ContinueOptions = {
reversible: true,
// 新增:流转类型
continuationType: continuationManager.ContinuationType.MEDIA_PLAYBACK,
// 新增:媒体流转参数
continuationExtraParams: {
mediaId: 'video_001',
mediaUrl: 'https://example.com/video.mp4',
position: 1234567,
playing: true,
// 新增:画质信息
quality: {
level: '1080p',
bitrate: 5000000
},
// 新增:字幕信息
subtitle: {
track: 1,
offset: 0
}
}
};
// 执行流转
const continueToken = await continuationManager.startContinuation(continueOptions);
5.2 行为变更
播放缓冲行为变化:
// HarmonyOS 6 新增缓冲配置
interface BufferConfigV6 {
// 最小缓冲时长(毫秒)
minBufferDuration: number;
// 最大缓冲时长(毫秒)
maxBufferDuration: number;
// 播放开始时的缓冲时长(毫秒)
startBufferDuration: number;
// 新增:自适应缓冲
adaptiveBuffer: boolean;
// 新增:网络状况权重
networkWeight: number; // 0-1,越大网络状况影响越大
}
// 设置缓冲配置
async function setBufferConfigV6(player: media.AVPlayer) {
const config: BufferConfigV6 = {
minBufferDuration: 2000,
maxBufferDuration: 30000,
startBufferDuration: 5000,
adaptiveBuffer: true,
networkWeight: 0.7
};
// await player.setBufferConfig(config);
}
5.3 适配代码示例
完整的HarmonyOS 6分布式视频播放器适配:
// DistributedVideoPlayerV6.ets
import { media } from '@kit.MediaKit';
import { deviceManager } from '@kit.DistributedService';
import { continuationManager } from '@kit.AbilityKit';
@Entry
@Component
struct DistributedVideoPlayerV6 {
// 播放器实例
private avPlayer: media.AVPlayer | null = null;
// 播放状态
@State playbackState: PlaybackStateV6 = {
mediaId: '',
position: 0,
duration: 0,
playing: false,
speed: 1.0,
volume: 1.0
};
// 设备状态
@State devices: DeviceInfoV6[] = [];
@State currentDevice: string = 'local';
// HarmonyOS 6新特性
@State supportsHDR: boolean = false;
@State currentHDRType: string = 'SDR';
@State audioTracks: AudioTrackInfo[] = [];
@State subtitleTracks: SubtitleTrackInfo[] = [];
aboutToAppear() {
this.initPlayerV6();
this.initDistributedV6();
}
// 初始化播放器(HarmonyOS 6)
async initPlayerV6() {
try {
// 创建AVPlayer
this.avPlayer = await media.createAVPlayer();
// 设置状态监听
this.avPlayer.on('stateChange', (state: string) => {
console.info(`播放器状态: ${state}`);
});
// 设置进度监听
this.avPlayer.on('timeUpdate', (time: number) => {
this.playbackState.position = time;
});
// HarmonyOS 6新增:HDR检测
this.avPlayer.on('hdrInfo', (hdrInfo: media.HDRInfo) => {
this.supportsHDR = hdrInfo.supported;
this.currentHDRType = this.getHDRTypeName(hdrInfo.type);
console.info(`HDR信息: ${this.currentHDRType}`);
});
// HarmonyOS 6新增:音轨变化
this.avPlayer.on('audioTrackChange', (tracks: media.AudioTrack[]) => {
this.audioTracks = tracks.map(t => ({
index: t.index,
language: t.language,
channels: t.channels,
codec: t.codec
}));
});
// HarmonyOS 6新增:字幕轨变化
this.avPlayer.on('subtitleTrackChange', (tracks: media.SubtitleTrack[]) => {
this.subtitleTracks = tracks.map(t => ({
index: t.index,
language: t.language,
format: t.format
}));
});
console.info('播放器初始化成功(HarmonyOS 6)');
} catch (err) {
console.error(`播放器初始化失败: ${JSON.stringify(err)}`);
}
}
// 加载媒体(HarmonyOS 6)
async loadMediaV6(mediaItem: MediaItemV6) {
if (!this.avPlayer) {
return;
}
try {
// 构建媒体源(HarmonyOS 6新配置)
const mediaSource: media.MediaSource = {
url: mediaItem.url,
headers: mediaItem.headers || {},
// DRM配置(如果有)
drmConfig: mediaItem.drmConfig ? {
drmSystem: media.DrmSystem.WIDEVINE,
licenseUrl: mediaItem.drmConfig.licenseUrl
} : undefined
};
// 设置媒体源
await this.avPlayer.setMediaSource(mediaSource);
// 设置解码器配置
const decoderConfig: media.DecoderConfig = {
preferHardware: true,
fallbackToSoftware: true,
lowLatencyMode: false
};
// await this.avPlayer.setDecoderConfig(decoderConfig);
// 设置缓冲配置
const bufferConfig: media.BufferConfig = {
minBufferDuration: 2000,
maxBufferDuration: 30000,
startBufferDuration: 5000
};
// await this.avPlayer.setBufferConfig(bufferConfig);
// 准备播放
await this.avPlayer.prepare();
this.playbackState.mediaId = mediaItem.id;
console.info('媒体加载成功');
} catch (err) {
console.error(`媒体加载失败: ${JSON.stringify(err)}`);
}
}
// 初始化分布式能力(HarmonyOS 6)
async initDistributedV6() {
try {
// 创建设备管理器
const dm = deviceManager.createDeviceManager('com.example.player');
// HarmonyOS 6新增:设备能力查询
dm.on('deviceFound', async (device: deviceManager.DeviceInfo) => {
// 查询设备媒体能力
const capability = await this.queryMediaCapability(device.deviceId);
this.devices.push({
deviceId: device.deviceId,
deviceName: device.deviceName,
deviceType: device.deviceType,
online: true,
capability: capability
});
});
console.info('分布式能力初始化成功(HarmonyOS 6)');
} catch (err) {
console.error(`初始化失败: ${JSON.stringify(err)}`);
}
}
// 查询设备媒体能力
async queryMediaCapability(deviceId: string): Promise<MediaCapabilityV6> {
// 实际实现需要调用设备能力查询API
return {
maxResolution: '4K',
codecs: ['H.264', 'H.265', 'VP9'],
hdrSupport: true,
maxBitrate: 50000000,
surroundSound: true
};
}
// 流转到设备(HarmonyOS 6)
async continueToDeviceV6(deviceId: string) {
try {
// 构建流转配置
const continueOptions: continuationManager.ContinueOptions = {
reversible: true,
continuationType: continuationManager.ContinuationType.MEDIA_PLAYBACK,
continuationExtraParams: {
mediaId: this.playbackState.mediaId,
position: this.playbackState.position,
playing: this.playbackState.playing,
speed: this.playbackState.speed,
volume: this.playbackState.volume
}
};
// 执行流转
const token = await continuationManager.startContinuation(continueOptions);
// 监听流转进度
continuationManager.on('progress', token, (progress: number) => {
console.info(`流转进度: ${progress}%`);
});
// 监听流转完成
continuationManager.on('complete', token, (targetDeviceId: string) => {
console.info(`流转完成: ${targetDeviceId}`);
this.currentDevice = targetDeviceId;
});
} catch (err) {
console.error(`流转失败: ${JSON.stringify(err)}`);
}
}
// 获取HDR类型名称
getHDRTypeName(type: number): string {
switch (type) {
case 1: return 'HDR10';
case 2: return 'HLG';
case 3: return 'Dolby Vision';
default: return 'SDR';
}
}
build() {
Column() {
// 视频画面
this.buildVideoSurface();
// 控制层
this.buildControls();
// HDR指示器
if (this.supportsHDR && this.currentHDRType !== 'SDR') {
this.buildHDRIndicator();
}
}
.width('100%')
.height('100%');
}
@Builder
buildVideoSurface() {
// 视频渲染表面
Column() {
if (this.playbackState.mediaId) {
Video({ src: '' })
.width('100%')
.height('100%');
}
}
.width('100%')
.height('100%');
}
@Builder
buildControls() {
// 控制层UI
Column() {
// 顶部栏
Row() {
Button('返回')
.onClick(() => {});
Blank();
// 流转按钮
Button('流转')
.onClick(() => {
// 显示设备列表
});
}
.width('100%')
.padding(16);
Blank();
// 底部栏
Row() {
Button(this.playbackState.playing ? '暂停' : '播放')
.onClick(() => {
if (this.playbackState.playing) {
this.avPlayer?.pause();
} else {
this.avPlayer?.play();
}
});
// 进度条
Slider({
value: this.playbackState.position,
min: 0,
max: this.playbackState.duration
})
.layoutWeight(1)
.onChange((value: number) => {
this.avPlayer?.seek(value);
});
}
.width('100%')
.padding(16);
}
.width('100%')
.height('100%')
.linearGradient({
direction: GradientDirection.Bottom,
colors: [['rgba(0,0,0,0.7)', 0], ['rgba(0,0,0,0)', 0.5], ['rgba(0,0,0,0)', 0.5], ['rgba(0,0,0,0.7)', 1]]
});
}
@Builder
buildHDRIndicator() {
Row() {
Image($r('app.media.ic_hdr'))
.width(20)
.height(20);
Text(this.currentHDRType)
.fontSize(12)
.fontColor('#ffffff')
.margin({ left: 4 });
}
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.backgroundColor('rgba(0,0,0,0.5)')
.borderRadius(4)
.position({ x: 16, y: 80 });
}
}
// HarmonyOS 6类型定义
interface PlaybackStateV6 {
mediaId: string;
position: number;
duration: number;
playing: boolean;
speed: number;
volume: number;
}
interface DeviceInfoV6 {
deviceId: string;
deviceName: string;
deviceType: string;
online: boolean;
capability: MediaCapabilityV6;
}
interface MediaCapabilityV6 {
maxResolution: string;
codecs: string[];
hdrSupport: boolean;
maxBitrate: number;
surroundSound: boolean;
}
interface MediaItemV6 {
id: string;
url: string;
headers?: Record<string, string>;
drmConfig?: {
licenseUrl: string;
};
}
interface AudioTrackInfo {
index: number;
language: string;
channels: number;
codec: string;
}
interface SubtitleTrackInfo {
index: number;
language: string;
format: string;
}
六、总结
分布式娱乐场景让视频播放真正实现了"走到哪看到哪"的无缝体验。通过本文的实战演练,我们掌握了:
核心技术要点:
- 分布式媒体播放:理解播放状态同步机制,掌握流转时的状态恢复
- 设备能力协商:根据设备能力自动适配画质、编解码器等参数
- 多设备协同:实现主从模式、镜像模式等多种协同播放方式
- 自适应画质:根据网络状况和设备能力动态选择最佳画质
实战经验总结:
- 音画同步需要考虑设备延迟差异,进行精确校准
- 内存管理要规范,避免播放器资源泄漏
- 网络切换要有重连机制,保证播放连续性
- HDR视频需要做好SDR设备的兼容转换
HarmonyOS 6适配要点:
- 新的媒体源配置,支持DRM、自定义请求头
- 增强的解码器配置,支持硬件加速优先
- 完善的缓冲配置,支持自适应缓冲
- 新增HDR检测和音轨/字幕轨变化监听
分布式娱乐场景的实现,让观影体验不再受设备限制,真正实现了"多屏一体、无缝流转"的愿景。无论是追剧、看电影还是听音乐,都能在任何设备上无缝衔接,享受极致的娱乐体验。
更多推荐




所有评论(0)