HarmonyOS分布式娱乐开发实战

周末窝在沙发上用手机看剧,突然想投到电视上继续看;或者在卧室用平板追综艺,走到客厅想无缝切换到大屏——这种"走到哪看到哪"的体验,正是鸿蒙分布式娱乐场景的魅力所在。

一、背景与动机

1.1 传统投屏的痛点

说实话,现在的投屏体验真的很让人抓狂。你肯定遇到过这些情况:

  • 连接繁琐:打开电视、切换输入源、手机找投屏功能、等待连接、祈祷别断开…
  • 状态不同步:投屏后进度条从开头开始,还得手动拖到刚才看的位置
  • 音画不同步:画面在电视上,声音却从手机出来,或者干脆卡成PPT
  • 断线重连难:Wi-Fi一抖动就断开,重新连接又是一番折腾
  • 设备兼容差:这个品牌的电视投不上,那个品牌的盒子不支持

最气人的是,好不容易投上了,突然想上厕所,又得拿着手机——因为电视遥控器上没有暂停按钮,或者根本找不到遥控器在哪。

1.2 鸿蒙分布式娱乐的愿景

鸿蒙的分布式能力让娱乐体验有了质的飞跃:

无缝流转:视频播放状态(进度、音量、字幕、倍速)一键流转到其他设备,无需手动调整。

多屏协同:手机当遥控器、平板当第二屏幕、电视当主屏幕,各司其职又协同配合。

就近发现:设备靠近自动发现,不需要复杂的配对过程。

状态持久:即使流转中断,也能快速恢复到断点位置。

鸿蒙分布式流转

正在观看

点击流转

选择设备

自动恢复

传统投屏流程

打开电视

切换输入源

手机找投屏

等待连接

手动调进度

1.3 核心价值

分布式娱乐场景带来的价值是实实在在的:

  1. 操作步骤减少80%:从5步以上简化到1-2步
  2. 状态同步零延迟:进度、音量、字幕秒级同步
  3. 跨设备无缝切换:任意设备间流转,体验一致
  4. 降低设备门槛:老旧设备也能参与分布式协同

二、核心原理

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 设备能力协商

不同设备的播放能力差异很大,需要在流转前进行协商:

渲染错误: Mermaid 渲染失败: Parse error on line 14: ...lassDef primary fill:#e1f5fe,stroke:#015 -----------------------^ Expecting '()', 'SOLID_OPEN_ARROW', 'DOTTED_OPEN_ARROW', 'SOLID_ARROW', 'SOLID_ARROW_TOP', 'SOLID_ARROW_BOTTOM', 'STICK_ARROW_TOP', 'STICK_ARROW_BOTTOM', 'SOLID_ARROW_TOP_DOTTED', 'SOLID_ARROW_BOTTOM_DOTTED', 'STICK_ARROW_TOP_DOTTED', 'STICK_ARROW_BOTTOM_DOTTED', 'SOLID_ARROW_TOP_REVERSE', 'SOLID_ARROW_BOTTOM_REVERSE', 'STICK_ARROW_TOP_REVERSE', 'STICK_ARROW_BOTTOM_REVERSE', 'SOLID_ARROW_TOP_REVERSE_DOTTED', 'SOLID_ARROW_BOTTOM_REVERSE_DOTTED', 'STICK_ARROW_TOP_REVERSE_DOTTED', 'STICK_ARROW_BOTTOM_REVERSE_DOTTED', 'BIDIRECTIONAL_SOLID_ARROW', 'DOTTED_ARROW', 'BIDIRECTIONAL_DOTTED_ARROW', 'SOLID_CROSS', 'DOTTED_CROSS', 'SOLID_POINT', 'DOTTED_POINT', got 'TXT'

设备能力描述

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;
}

六、总结

分布式娱乐场景让视频播放真正实现了"走到哪看到哪"的无缝体验。通过本文的实战演练,我们掌握了:

核心技术要点

  1. 分布式媒体播放:理解播放状态同步机制,掌握流转时的状态恢复
  2. 设备能力协商:根据设备能力自动适配画质、编解码器等参数
  3. 多设备协同:实现主从模式、镜像模式等多种协同播放方式
  4. 自适应画质:根据网络状况和设备能力动态选择最佳画质

实战经验总结

  • 音画同步需要考虑设备延迟差异,进行精确校准
  • 内存管理要规范,避免播放器资源泄漏
  • 网络切换要有重连机制,保证播放连续性
  • HDR视频需要做好SDR设备的兼容转换

HarmonyOS 6适配要点

  • 新的媒体源配置,支持DRM、自定义请求头
  • 增强的解码器配置,支持硬件加速优先
  • 完善的缓冲配置,支持自适应缓冲
  • 新增HDR检测和音轨/字幕轨变化监听

分布式娱乐场景的实现,让观影体验不再受设备限制,真正实现了"多屏一体、无缝流转"的愿景。无论是追剧、看电影还是听音乐,都能在任何设备上无缝衔接,享受极致的娱乐体验。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐