鸿蒙 Flutter 音视频跨设备协同实战:从本地播放到多端同步(2025 进阶版)
本文介绍了基于鸿蒙分布式媒体服务与Flutter框架的音视频跨设备协同开发方案。通过技术融合实现媒体资源共享、播放状态同步和多端控制功能,支持"手机选片、平板续播、智慧屏投屏"的无缝体验。文章详细讲解了从项目初始化、本地播放实现到跨设备投屏与状态同步的完整开发流程,包括核心代码封装、分布式权限配置和性能优化策略。该方案适用于鸿蒙3.2+设备,采用Flutter3.24+和鸿蒙A
鸿蒙 Flutter 音视频跨设备协同实战:从本地播放到多端同步(2025 进阶版)
在鸿蒙全场景生态中,音视频作为核心交互载体,其跨设备协同能力直接决定用户体验。鸿蒙的分布式媒体服务与 Flutter 的跨端 UI 优势结合,可实现 “手机选片、平板续播、智慧屏投屏” 的无缝体验。本文基于 Flutter 3.24+、鸿蒙 API 12 及分布式媒体 SDK,从音视频基础播放、跨设备投屏、进度同步到多端控制,提供一套完整的实战方案,包含可直接复用的代码与官方资源链接,助力开发者快速落地鸿蒙 Flutter 音视频应用。
一、音视频跨设备协同核心认知:技术融合的价值与场景
鸿蒙 Flutter 音视频协同并非简单的 “多端播放”,而是基于分布式软总线实现的 “媒体资源共享、播放状态同步、控制指令互通”,其核心价值在于打破设备边界,构建全场景媒体体验。
1.1 技术融合的核心优势
| 技术能力 | 鸿蒙分布式媒体服务优势 | Flutter 优势 | 融合价值 |
|---|---|---|---|
| 媒体资源管理 | 支持跨设备媒体库共享,统一管理多设备音视频文件 | 跨端统一的媒体列表 UI,适配手机 / 平板 / 智慧屏屏幕尺寸 | 一次开发实现多设备媒体浏览,资源调用无需重复拷贝 |
| 播放状态同步 | 基于分布式数据服务(DDS)实现毫秒级状态同步 | 响应式 UI 框架,状态变更实时刷新播放进度条、控制按钮 | 设备切换时播放进度无缝衔接,无卡顿 / 重复播放问题 |
| 跨设备控制 | 支持分布式控制指令(播放 / 暂停 / 快进)跨设备传输 | 统一的控制组件封装,适配不同设备交互方式(触屏 / 遥控器) | 手机可控制智慧屏播放,平板可接管手机音频,操作逻辑一致 |
| 硬件资源调度 | 自动识别设备硬件能力(如智慧屏音响、手机摄像头) | 自适应渲染框架,根据设备性能调整播放分辨率 | 自动匹配最优硬件资源,如视频在智慧屏渲染、音频在音箱输出 |
1.2 典型应用场景
- 场景 1:多设备续播:用户在手机上观看视频,回家后打开智慧屏,视频自动从手机当前进度续播,音频切换到智慧屏音响输出。
- 场景 2:跨设备投屏:平板上播放 PPT 时,将视频画面投屏到智慧屏,平板保留控制权限,可实时调整播放进度。
- 场景 3:多端协同录制:手机作为摄像头采集画面,平板作为监视器显示预览,智慧屏实时投屏展示,实现多设备协同拍摄。
1.3 技术栈版本要求(2025 必看)
- 开发工具:DevEco Studio 4.3+(支持分布式媒体调试)、VS Code(Flutter 开发)
- 核心框架:Flutter SDK ≥3.24.0(鸿蒙适配版)、鸿蒙 SDK ≥API 12
- 关键依赖:
ohos_flutter_media^2.8.0(鸿蒙音视频核心插件)flutter_ijkplayer^0.8.5(Flutter 音视频播放内核)ohos_distributed_media^1.6.0(分布式媒体资源管理)
- 设备要求:至少 2 台鸿蒙 3.2 + 设备(需支持分布式软总线与媒体服务)
官方资源链接:
二、项目初始化与基础配置:音视频协同的前提
音视频跨设备协同需提前配置媒体权限、分布式服务与播放内核,以下是完整的初始化流程。
2.1 项目结构设计(音视频场景最佳实践)
plaintext
ohos_flutter_media_demo/
├── lib/
│ ├── main.dart # 应用入口
│ ├── pages/ # 核心页面
│ │ ├── media_list_page.dart # 音视频列表页(资源浏览)
│ │ ├── player_page.dart # 播放页(本地+跨设备)
│ │ └── device_control_page.dart # 多设备控制页
│ ├── media/ # 音视频核心模块
│ │ ├── media_player.dart # 播放内核封装(ijkplayer)
│ │ ├── media_manager.dart # 媒体资源管理(分布式)
│ │ └── device_sync.dart # 跨设备状态同步
│ └── widgets/ # 通用组件
│ ├── player_control.dart # 播放控制组件(播放/暂停/快进)
│ └── device_selector.dart # 设备选择组件(投屏目标)
├── ohos/ # 鸿蒙工程目录
│ ├── config.json # 权限与分布式配置
│ └── native/ # 鸿蒙原生媒体能力封装
└── pubspec.yaml # 依赖配置
2.2 依赖配置(pubspec.yaml)
yaml
name: ohos_flutter_media_demo
description: 鸿蒙Flutter音视频跨设备协同演示
version: 1.0.0+1
environment:
sdk: '>=3.24.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
# 鸿蒙音视频核心插件
ohos_flutter_media: ^2.8.0
# 分布式媒体资源管理
ohos_distributed_media: ^1.6.0
# Flutter播放内核
flutter_ijkplayer: ^0.8.5
# 状态管理(播放进度/设备状态)
provider: ^6.1.1
# 序列化工具(状态同步)
json_annotation: ^4.8.1
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.4.4
json_serializable: ^6.7.1
flutter:
uses-material-design: true
# 媒体资源配置
assets:
- assets/videos/
- assets/audios/
2.3 鸿蒙权限与媒体配置(ohos/config.json)
音视频跨设备协同需声明媒体访问、分布式同步、网络等权限,同时配置分布式媒体服务支持:
json
{
"app": {
"bundleName": "com.example.mediademo",
"versionName": "1.0.0",
"versionCode": 1
},
"module": {
"package": "com.example.mediademo",
"name": ".MainAbility",
"mainAbility": "com.example.mediademo.MainAbility",
"deviceType": ["phone", "tablet", "tv"],
"distributedNotificationEnabled": true,
"distributedMediaEnabled": true, # 开启分布式媒体服务
"reqPermissions": [
# 媒体访问权限
{
"name": "ohos.permission.READ_MEDIA",
"reason": "需要读取本地音视频文件",
"usedScene": {"ability": ["MainAbility"], "when": "always"}
},
{
"name": "ohos.permission.WRITE_MEDIA",
"reason": "需要保存音视频文件",
"usedScene": {"ability": ["MainAbility"], "when": "always"}
},
# 分布式权限
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC",
"reason": "需要跨设备同步播放状态",
"usedScene": {"ability": ["MainAbility"], "when": "always"}
},
{
"name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO",
"reason": "需要发现跨设备媒体设备",
"usedScene": {"ability": ["MainAbility"], "when": "always"}
},
# 网络权限(在线媒体播放)
{
"name": "ohos.permission.INTERNET",
"reason": "需要播放在线音视频",
"usedScene": {"ability": ["MainAbility"], "when": "always"}
}
]
}
}
三、核心实战一:本地音视频播放与基础控制

在实现跨设备协同前,需先完成本地音视频播放功能,基于flutter_ijkplayer封装通用播放内核,支持本地文件与在线资源。
3.1 播放内核封装(media/media_player.dart)
dart
import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
import 'package:flutter/services.dart';
/// 音视频播放内核封装(支持本地/在线资源)
class MediaPlayer {
late IjkMediaController _controller;
// 播放状态回调
Function(PlayerStatus status)? onStatusChanged;
// 播放进度回调(每秒更新)
Function(Duration current, Duration total)? onProgressChanged;
MediaPlayer() {
_initController();
_listenPlayerStatus();
_listenProgress();
}
// 初始化播放控制器
void _initController() {
_controller = IjkMediaController();
// 配置播放参数(硬件解码、音量等)
_controller.setOption(IjkMediaOption.playerCategory, "mediacodec", 1); // 开启硬件解码
_controller.setOption(IjkMediaOption.playerCategory, "volume", 100); // 初始音量
}
// 监听播放状态变化
void _listenPlayerStatus() {
_controller.addListener(() {
final status = _controller.value.playerState;
if (onStatusChanged != null) {
switch (status) {
case PlayerState.playing:
onStatusChanged!(PlayerStatus.playing);
break;
case PlayerState.paused:
onStatusChanged!(PlayerStatus.paused);
break;
case PlayerState.completed:
onStatusChanged!(PlayerStatus.completed);
break;
default:
onStatusChanged!(PlayerStatus.idle);
}
}
});
}
// 监听播放进度(每秒更新一次)
void _listenProgress() {
_controller.addListener(() {
final current = _controller.value.position;
final total = _controller.value.duration;
if (current != null && total != null && onProgressChanged != null) {
onProgressChanged!(current, total);
}
});
}
// 加载媒体资源(本地路径/在线URL)
Future<void> loadMedia(String mediaPath, {bool isLocal = true}) async {
try {
if (isLocal) {
// 本地资源:需拼接完整路径
final assetPath = await rootBundle.loadString('assets/path_config.json');
final localPath = "$assetPath/${mediaPath.split('/').last}";
await _controller.setNetworkDataSource(localPath, autoPlay: false);
} else {
// 在线资源:直接设置URL
await _controller.setNetworkDataSource(mediaPath, autoPlay: false);
}
} catch (e) {
throw Exception("加载媒体资源失败:$e");
}
}
// 播放控制:播放/暂停
void togglePlay() {
if (_controller.value.playerState == PlayerState.playing) {
_controller.pause();
} else {
_controller.play();
}
}
// 进度控制:跳转指定时间
void seekTo(Duration duration) {
_controller.seekTo(duration);
}
// 释放资源(页面销毁时调用)
void dispose() {
_controller.dispose();
}
// 获取播放控制器(供UI层使用)
IjkMediaController get controller => _controller;
}
/// 播放状态枚举
enum PlayerStatus { idle, playing, paused, completed, error }
3.2 播放页实现(pages/player_page.dart)
dart
import 'package:flutter/material.dart';
import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
import 'package:ohos_flutter_media_demo/media/media_player.dart';
import 'package:ohos_flutter_media_demo/widgets/player_control.dart';
/// 音视频播放页(本地播放)
class PlayerPage extends StatefulWidget {
final String mediaPath; // 媒体路径(本地/在线)
final bool isLocal; // 是否为本地资源
final String mediaTitle; // 媒体标题
const PlayerPage({
super.key,
required this.mediaPath,
this.isLocal = true,
required this.mediaTitle,
});
@override
State<PlayerPage> createState() => _PlayerPageState();
}
class _PlayerPageState extends State<PlayerPage> {
late MediaPlayer _mediaPlayer;
PlayerStatus _currentStatus = PlayerStatus.idle;
Duration _currentProgress = Duration.zero;
Duration _totalDuration = Duration.zero;
@override
void initState() {
super.initState();
_initPlayer();
}
// 初始化播放器
void _initPlayer() {
_mediaPlayer = MediaPlayer();
// 监听播放状态
_mediaPlayer.onStatusChanged = (status) {
setState(() => _currentStatus = status);
};
// 监听播放进度
_mediaPlayer.onProgressChanged = (current, total) {
setState(() {
_currentProgress = current;
_totalDuration = total;
});
};
// 加载媒体资源
_mediaPlayer.loadMedia(widget.mediaPath, isLocal: widget.isLocal);
}
// 格式化时间(秒转分:秒)
String _formatDuration(Duration duration) {
final minutes = duration.inMinutes.remainder(60).toString().padLeft(2, '0');
final seconds = duration.inSeconds.remainder(60).toString().padLeft(2, '0');
return "$minutes:$seconds";
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.mediaTitle),
backgroundColor: Colors.black87,
titleTextStyle: const TextStyle(color: Colors.white),
iconTheme: const IconThemeData(color: Colors.white),
),
body: Container(
color: Colors.black,
child: Column(
children: [
// 播放视图
Expanded(
child: IjkPlayerView(
controller: _mediaPlayer.controller,
options: IjkPlayerOptions(
initOptions: [
"-i", widget.mediaPath, // 输入媒体路径
"-vol", "100", // 音量
],
),
// 加载中占位图
loadingWidget: const Center(
child: CircularProgressIndicator(color: Colors.blue),
),
// 播放错误占位图
errorWidget: const Center(
child: Text("播放失败,请检查资源路径", color: Colors.white),
),
),
),
// 播放进度条
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Column(
children: [
// 进度条
Slider(
value: _currentProgress.inSeconds.toDouble(),
max: _totalDuration.inSeconds.toDouble(),
min: 0,
activeColor: Colors.blue,
inactiveColor: Colors.grey,
onChanged: (value) {
_mediaPlayer.seekTo(Duration(seconds: value.toInt()));
},
),
// 时间显示(当前/总时长)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_formatDuration(_currentProgress),
style: const TextStyle(color: Colors.white, fontSize: 12),
),
Text(
_formatDuration(_totalDuration),
style: const TextStyle(color: Colors.white, fontSize: 12),
),
],
),
],
),
),
// 播放控制组件
PlayerControlWidget(
status: _currentStatus,
onPlayToggle: () => _mediaPlayer.togglePlay(),
onSeekForward: () => _mediaPlayer.seekTo(
_currentProgress + const Duration(seconds: 10),
),
onSeekBackward: () => _mediaPlayer.seekTo(
_currentProgress - const Duration(seconds: 10),
),
),
],
),
),
);
}
@override
void dispose() {
_mediaPlayer.dispose();
super.dispose();
}
}
3.3 播放控制组件(widgets/player_control.dart)

dart
import 'package:flutter/material.dart';
import 'package:ohos_flutter_media_demo/media/media_player.dart';
/// 播放控制组件(播放/暂停/快进/快退)
class PlayerControlWidget extends StatelessWidget {
final PlayerStatus status;
final VoidCallback onPlayToggle;
final VoidCallback onSeekForward;
final VoidCallback onSeekBackward;
const PlayerControlWidget({
super.key,
required this.status,
required this.onPlayToggle,
required this.onSeekForward,
required this.onSeekBackward,
});
@override
Widget build(BuildContext context) {
return Container(
color: Colors.black87,
padding: const EdgeInsets.symmetric(vertical: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 快退10秒
IconButton(
icon: const Icon(Icons.fast_rewind, color: Colors.white, size: 32),
onPressed: onSeekBackward,
),
const SizedBox(width: 32),
// 播放/暂停
IconButton(
icon: Icon(
status == PlayerStatus.playing
? Icons.pause_circle_filled
: Icons.play_circle_filled,
color: Colors.white,
size: 48,
),
onPressed: onPlayToggle,
),
const SizedBox(width: 32),
// 快进10秒
IconButton(
icon: const Icon(Icons.fast_forward, color: Colors.white, size: 32),
onPressed: onSeekForward,
),
],
),
);
}
}
四、核心实战二:跨设备投屏与播放状态同步
跨设备投屏的核心是 “媒体资源地址共享 + 播放状态实时同步”,基于鸿蒙分布式媒体服务与 DDS 实现。
4.1 分布式媒体资源管理(media/media_manager.dart)
dart
import 'package:ohos_distributed_media/ohos_distributed_media.dart';
import 'package:ohos_flutter_media_demo/media/device_sync.dart';
/// 分布式媒体资源管理器(获取跨设备媒体列表、共享资源地址)
class MediaManager {
static final MediaManager _instance = MediaManager._internal();
factory MediaManager() => _instance;
MediaManager._internal();
// 分布式媒体服务实例
final _distributedMedia = OhosDistributedMedia();
// 设备同步工具
final _deviceSync = DeviceSync();
// 获取所有设备的媒体列表(本地+跨设备)
Future<List<MediaItem>> getDistributedMediaList() async {
try {
// 1. 获取本地媒体列表
final localMedia = await _getLocalMediaList();
// 2. 获取跨设备媒体列表(已连接设备)
final connectedDevices = await _deviceSync.getConnectedDevices();
final crossDeviceMedia = await Future.wait(
connectedDevices.map((device) => _getDeviceMediaList(device.deviceId)),
);
// 3. 合并本地与跨设备媒体列表
final allMedia = localMedia + crossDeviceMedia.expand((e) => e).toList();
return allMedia;
} catch (e) {
throw Exception("获取分布式媒体列表失败:$e");
}
}
// 获取本地媒体列表
Future<List<MediaItem>> _getLocalMediaList() async {
final localFiles = await _distributedMedia.getLocalMedia(
mediaType: MediaType.video, // 筛选视频类型
);
return localFiles.map((file) => MediaItem(
id: file.id,
title: file.name,
path: file.path,
deviceId: "local", // 本地设备标识
deviceName: "本地设备",
isLocal: true,
)).toList();
}
// 获取指定设备的媒体列表
Future<List<MediaItem>> _getDeviceMediaList(String deviceId) async {
final deviceName = await _deviceSync.getDeviceName(deviceId);
final deviceFiles = await _distributedMedia.getRemoteMedia(
deviceId: deviceId,
mediaType: MediaType.video,
);
return deviceFiles.map((file) => MediaItem(
id: file.id,
title: file.name,
path: file.remotePath, // 远程资源路径(分布式访问地址)
deviceId: deviceId,
deviceName: deviceName,
isLocal: false,
)).toList();
}
// 共享本地媒体到指定设备(供投屏使用)
Future<String> shareLocalMediaToDevice(
String localMediaPath,
String targetDeviceId,
) async {
try {
// 通过分布式媒体服务共享本地资源,生成远程访问路径
final remotePath = await _distributedMedia.shareLocalMedia(
localPath: localMediaPath,
targetDeviceId: targetDeviceId,
);
return remotePath;
} catch (e) {
throw Exception("共享媒体到设备失败:$e");
}
}
}
/// 媒体资源模型
class MediaItem {
final String id; // 唯一标识
final String title; // 媒体标题
final String path; // 资源路径(本地路径/远程访问路径)
final String deviceId; // 设备ID(本地为"local")
final String deviceName; // 设备名称
final bool isLocal; // 是否为本地资源
MediaItem({
required this.id,
required this.title,
required this.path,
required this.deviceId,
required this.deviceName,
required this.isLocal,
});
}
/// 媒体类型枚举
enum MediaType { video, audio, image }
4.2 跨设备状态同步(media/device_sync.dart)
dart
import 'package:ohos_flutter_distributed/ohos_flutter_distributed.dart';
import 'package:json_annotation/json_annotation.dart';
part 'device_sync.g.dart';
/// 跨设备状态同步工具(设备发现、连接、播放状态同步)
class DeviceSync {
static final DeviceSync _instance = DeviceSync._internal();
factory DeviceSync() => _instance;
DeviceSync._internal();
// 分布式服务实例
final _distributed = OhosDistributed();
// 已连接设备列表
List<DeviceInfo> _connectedDevices = [];
List<DeviceInfo> get connectedDevices => _connectedDevices;
// 初始化设备发现与连接监听
Future<void> init() async {
// 监听设备发现事件
_distributed.deviceDiscoveryStream.listen((devices) {
_connectedDevices = devices
.where((device) => device.isConnected)
.map((device) => DeviceInfo(
deviceId: device.deviceId,
deviceName: device.deviceName,
deviceType: device.deviceType,
))
.toList();
});
// 启动设备发现
await _distributed.startDeviceDiscovery();
}
// 获取已连接设备列表
Future<List<DeviceInfo>> getConnectedDevices() async {
if (_connectedDevices.isEmpty) {
await _distributed.startDeviceDiscovery();
// 等待设备发现(最多等待3秒)
await Future.delayed(const Duration(seconds: 3));
}
return _connectedDevices;
}
// 获取设备名称
Future<String> getDeviceName(String deviceId) async {
final device = await _distributed.getDeviceInfo(deviceId);
return device.deviceName;
}
// 连接目标设备
Future<bool> connectDevice(String deviceId) async {
try {
return await _distributed.connectDevice(deviceId);
} catch (e) {
print("连接设备失败:$e");
return false;
}
}
// 同步播放状态到目标设备
Future<void> syncPlayerState(
String targetDeviceId,
PlayerStateSync state,
) async {
try {
await _distributed.syncData(
key: "media_player_state_${state.mediaId}",
value: state.toJson(),
syncMode: SyncMode.SPECIFIC_DEVICE,
targetDeviceId: targetDeviceId,
);
} catch (e) {
throw Exception("同步播放状态失败:$e");
}
}
// 监听播放状态变化(接收其他设备的同步数据)
void listenPlayerStateChanges(Function(PlayerStateSync) onStateChanged) {
_distributed.dataSyncStream.listen((data) {
if (data.key.startsWith("media_player_state_")) {
final state = PlayerStateSync.fromJson(data.value);
onStateChanged(state);
}
});
}
}
/// 设备信息模型
class DeviceInfo {
final String deviceId; // 设备唯一ID
final String deviceName; // 设备名称
final String deviceType; // 设备类型(phone/tablet/tv)
DeviceInfo({
required this.deviceId,
required this.deviceName,
required this.deviceType,
});
}
/// 播放状态同步模型(支持序列化)
@JsonSerializable()
class PlayerStateSync {
final String mediaId; // 媒体唯一ID
final String mediaPath; // 媒体路径(远程访问地址)
final Duration currentProgress; // 当前播放进度
final PlayerStatus status; // 播放状态
PlayerStateSync({
required this.mediaId,
required this.mediaPath,
required this.currentProgress,
required this.status,
});
// 序列化
Map<String, dynamic> toJson() => _$PlayerStateSyncToJson(this);
// 反序列化
factory PlayerStateSync.fromJson(Map<String, dynamic> json) =>
_$PlayerStateSyncFromJson(json);
}
4.3 跨设备投屏功能实现(修改 player_page.dart)
在原有播放页基础上添加 “设备选择” 与 “投屏” 功能,支持将本地播放流转到其他设备:
dart
// 在PlayerPage类中添加以下代码
import 'package:ohos_flutter_media_demo/media/media_manager.dart';
import 'package:ohos_flutter_media_demo/media/device_sync.dart';
import 'package:ohos_flutter_media_demo/widgets/device_selector.dart';
class _PlayerPageState extends State<PlayerPage> {
// 新增:分布式工具实例
final _mediaManager = MediaManager();
final _deviceSync = DeviceSync();
// 新增:投屏目标设备
DeviceInfo? _targetDevice;
// 新增:是否处于投屏状态
bool _isCasting = false;
@override
void initState() {
super.initState();
_initPlayer();
// 新增:初始化设备同步
_deviceSync.init();
// 新增:监听其他设备的播放状态同步
_deviceSync.listenPlayerStateChanges((state) {
if (state.mediaId == widget.mediaPath.split('/').last) {
setState(() {
_currentProgress = state.currentProgress;
_currentStatus = state.status;
});
// 同步进度到本地播放器
_mediaPlayer.seekTo(state.currentProgress);
}
});
}
// 新增:打开设备选择弹窗
void _openDeviceSelector() {
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (context) => DeviceSelectorWidget(
onDeviceSelected: (device) {
setState(() => _targetDevice = device);
Navigator.pop(context);
},
),
);
}
// 新增:启动投屏
Future<void> _startCasting() async {
if (_targetDevice == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("请先选择投屏目标设备")),
);
return;
}
// 1. 连接目标设备
final isConnected = await _deviceSync.connectDevice(_targetDevice!.deviceId);
if (!isConnected) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("设备连接失败,请重试")),
);
return;
}
// 2. 共享本地媒体到目标设备(生成远程访问路径)
final remoteMediaPath = await _mediaManager.shareLocalMediaToDevice(
widget.mediaPath,
_targetDevice!.deviceId,
);
// 3. 同步当前播放状态到目标设备
final syncState = PlayerStateSync(
mediaId: widget.mediaPath.split('/').last,
mediaPath: remoteMediaPath,
currentProgress: _currentProgress,
status: _currentStatus,
);
await _deviceSync.syncPlayerState(_targetDevice!.deviceId, syncState);
// 4. 更新投屏状态
setState(() => _isCasting = true);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("已投屏到${_targetDevice!.deviceName}")),
);
}
// 新增:停止投屏
Future<void> _stopCasting() async {
setState(() => _isCasting = false);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("已停止投屏")),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.mediaTitle),
backgroundColor: Colors.black87,
titleTextStyle: const TextStyle(color: Colors.white),
iconTheme: const IconThemeData(color: Colors.white),
// 新增:投屏控制按钮
actions: [
IconButton(
icon: Icon(
_isCasting ? Icons.cast_connected : Icons.cast,
color: Colors.white,
size: 24,
),
onPressed: _isCasting ? _stopCasting : _openDeviceSelector,
),
],
),
// 原有代码不变...
);
}
}
4.4 设备选择组件(widgets/device_selector.dart)

dart
import 'package:flutter/material.dart';
import 'package:ohos_flutter_media_demo/media/device_sync.dart';
/// 设备选择组件(投屏目标选择)
class DeviceSelectorWidget extends StatefulWidget {
final Function(DeviceInfo) onDeviceSelected;
const DeviceSelectorWidget({
super.key,
required this.onDeviceSelected,
});
@override
State<DeviceSelectorWidget> createState() => _DeviceSelectorWidgetState();
}
class _DeviceSelectorWidgetState extends State<DeviceSelectorWidget> {
final _deviceSync = DeviceSync();
List<DeviceInfo> _deviceList = [];
@override
void initState() {
super.initState();
_getDeviceList();
}
// 获取已连接设备列表
Future<void> _getDeviceList() async {
final devices = await _deviceSync.getConnectedDevices();
setState(() => _deviceList = devices);
}
// 获取设备图标
IconData _getDeviceIcon(String deviceType) {
switch (deviceType.toLowerCase()) {
case "phone":
return Icons.phone_android;
case "tablet":
return Icons.tablet_android;
case "tv":
return Icons.tv;
default:
return Icons.device_unknown;
}
}
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
"选择投屏设备",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 24),
if (_deviceList.isEmpty)
const Center(
child: Text("未发现已连接的设备,请确保设备在同一局域网"),
)
else
ListView.builder(
shrinkWrap: true,
itemCount: _deviceList.length,
itemBuilder: (context, index) {
final device = _deviceList[index];
return ListTile(
leading: Icon(
_getDeviceIcon(device.deviceType),
size: 28,
color: Colors.blue,
),
title: Text(device.deviceName),
subtitle: Text("设备类型:${device.deviceType}"),
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
onTap: () => widget.onDeviceSelected(device),
);
},
),
],
),
);
}
}
五、核心实战三:多设备协同控制与进度同步
多设备协同控制支持 “一端控制、多端响应”,如手机暂停播放时,智慧屏同步暂停,核心是通过分布式指令传输实现。
5.1 多设备控制页实现(pages/device_control_page.dart)
dart
import 'package:flutter/material.dart';
import 'package:ohos_flutter_media_demo/media/device_sync.dart';
import 'package:ohos_flutter_media_demo/media/media_player.dart';
import 'package:ohos_flutter_media_demo/widgets/player_control.dart';
/// 多设备协同控制页(控制所有已连接设备的播放状态)
class DeviceControlPage extends StatefulWidget {
final String mediaId; // 媒体唯一ID
final String mediaTitle; // 媒体标题
const DeviceControlPage({
super.key,
required this.mediaId,
required this.mediaTitle,
});
@override
State<DeviceControlPage> createState() => _DeviceControlPageState();
}
class _DeviceControlPageState extends State<DeviceControlPage> {
final _deviceSync = DeviceSync();
final _mediaPlayer = MediaPlayer();
List<DeviceInfo> _connectedDevices = [];
PlayerStatus _currentStatus = PlayerStatus.idle;
Duration _currentProgress = Duration.zero;
@override
void initState() {
super.initState();
_initDeviceList();
_listenPlayerState();
}
// 初始化已连接设备列表
Future<void> _initDeviceList() async {
final devices = await _deviceSync.getConnectedDevices();
setState(() => _connectedDevices = devices);
}
// 监听播放状态同步(来自其他设备)
void _listenPlayerState() {
_deviceSync.listenPlayerStateChanges((state) {
if (state.mediaId == widget.mediaId) {
setState(() {
_currentStatus = state.status;
_currentProgress = state.currentProgress;
});
}
});
}
// 发送控制指令到所有已连接设备
Future<void> _sendControlCommand(PlayerControlCommand command) async {
for (final device in _connectedDevices) {
// 构建控制指令(包含媒体ID、指令类型、进度)
final controlData = {
"mediaId": widget.mediaId,
"commandType": command.type.name,
"progress": _currentProgress.inSeconds,
};
// 发送分布式控制指令
await _deviceSync.syncData(
key: "media_control_command_${widget.mediaId}",
value: controlData,
syncMode: SyncMode.SPECIFIC_DEVICE,
targetDeviceId: device.deviceId,
);
}
}
// 播放/暂停控制
void _togglePlay() {
final newStatus = _currentStatus == PlayerStatus.playing
? PlayerStatus.paused
: PlayerStatus.playing;
setState(() => _currentStatus = newStatus);
_sendControlCommand(PlayerControlCommand(
type: newStatus == PlayerStatus.playing
? ControlCommandType.play
: ControlCommandType.pause,
progress: _currentProgress,
));
}
// 快进/快退控制
void _seek(bool isForward) {
final newProgress = isForward
? _currentProgress + const Duration(seconds: 10)
: _currentProgress - const Duration(seconds: 10);
setState(() => _currentProgress = newProgress);
_sendControlCommand(PlayerControlCommand(
type: ControlCommandType.seek,
progress: newProgress,
));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("多设备控制:${widget.mediaTitle}"),
backgroundColor: Colors.blue,
titleTextStyle: const TextStyle(color: Colors.white),
iconTheme: const IconThemeData(color: Colors.white),
),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
// 已连接设备列表
Text(
"已连接设备(${_connectedDevices.length}台)",
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
Wrap(
spacing: 12,
runSpacing: 8,
children: _connectedDevices.map((device) {
return Chip(
label: Text(device.deviceName),
avatar: Icon(_getDeviceIcon(device.deviceType), size: 16),
backgroundColor: Colors.blue[100],
);
}).toList(),
),
const SizedBox(height: 48),
// 当前播放状态
Text(
"当前状态:${_getStatusText(_currentStatus)}",
style: const TextStyle(fontSize: 18),
),
const SizedBox(height: 8),
Text(
"当前进度:${_formatDuration(_currentProgress)}",
style: const TextStyle(fontSize: 16, color: Colors.grey),
),
const SizedBox(height: 64),
// 播放控制组件
PlayerControlWidget(
status: _currentStatus,
onPlayToggle: _togglePlay,
onSeekForward: () => _seek(true),
onSeekBackward: () => _seek(false),
),
],
),
),
);
}
// 获取设备图标
IconData _getDeviceIcon(String deviceType) {
switch (deviceType.toLowerCase()) {
case "phone":
return Icons.phone_android;
case "tablet":
return Icons.tablet_android;
case "tv":
return Icons.tv;
default:
return Icons.device_unknown;
}
}
// 播放状态文本
String _getStatusText(PlayerStatus status) {
switch (status) {
case PlayerStatus.playing:
return "播放中";
case PlayerStatus.paused:
return "已暂停";
case PlayerStatus.completed:
return "播放完成";
default:
return "未播放";
}
}
// 格式化时间
String _formatDuration(Duration duration) {
final minutes = duration.inMinutes.remainder(60).toString().padLeft(2, '0');
final seconds = duration.inSeconds.remainder(60).toString().padLeft(2, '0');
return "$minutes:$seconds";
}
@override
void dispose() {
_mediaPlayer.dispose();
super.dispose();
}
}
/// 播放控制指令模型
class PlayerControlCommand {
final ControlCommandType type; // 指令类型(播放/暂停/快进)
final Duration progress; // 目标进度(快进/快退时使用)
PlayerControlCommand({
required this.type,
required this.progress,
});
}
/// 控制指令类型枚举
enum ControlCommandType { play, pause, seek }
5.2 播放页接收控制指令(修改 player_page.dart)
在播放页中添加控制指令监听,接收来自其他设备的控制指令并执行相应操作:
dart
class _PlayerPageState extends State<PlayerPage> {
@override
void initState() {
super.initState();
_initPlayer();
_deviceSync.init();
_listenPlayerStateChanges();
// 新增:监听控制指令
_listenControlCommands();
}
// 新增:监听控制指令(来自其他设备)
void _listenControlCommands() {
_distributed.dataSyncStream.listen((data) {
if (data.key.startsWith("media_control_command_")) {
final controlData = data.value as Map<String, dynamic>;
final mediaId = controlData["mediaId"];
// 只处理当前媒体的控制指令
if (mediaId == widget.mediaPath.split('/').last) {
final commandType = controlData["commandType"];
final progress = Duration(seconds: controlData["progress"]);
_handleControlCommand(commandType, progress);
}
}
});
}
// 新增:处理控制指令
void _handleControlCommand(String commandType, Duration progress) {
switch (commandType) {
case "play":
if (_currentStatus != PlayerStatus.playing) {
_mediaPlayer.togglePlay();
}
break;
case "pause":
if (_currentStatus == PlayerStatus.playing) {
_mediaPlayer.togglePlay();
}
break;
case "seek":
_mediaPlayer.seekTo(progress);
setState(() => _currentProgress = progress);
break;
}
}
}
六、性能优化与常见问题解决方案
音视频跨设备协同的性能瓶颈主要集中在 “资源加载速度”“状态同步延迟”“播放卡顿”,以下是针对性优化方案。
6.1 性能优化方案
-
资源加载优化
- 预加载远程资源:在设备连接成功后,提前加载目标设备的媒体资源元数据(如时长、分辨率),减少播放等待时间。
- 自适应码率:根据设备间网络带宽自动调整播放码率,带宽不足时降低分辨率,避免卡顿。
dart
// 自适应码率配置(media_player.dart) void setAdaptiveBitrate() { _controller.setOption(IjkMediaOption.playerCategory, "max-buffer-size", 1024*1024*5); // 5MB缓存 _controller.setOption(IjkMediaOption.playerCategory, "buffer-duration", 3); // 3秒缓存 } -
状态同步优化
- 批量同步:将 “进度更新 + 状态变更” 合并为单次同步,减少分布式通信次数。
- 延迟同步:进度更新间隔从 1 秒调整为 3 秒(非精确场景),降低数据传输压力。
dart
// 延迟同步进度(media/device_sync.dart) void _delayedSyncProgress() { int lastSyncTime = 0; _mediaPlayer.onProgressChanged = (current, total) { final now = DateTime.now().millisecondsSinceEpoch; if (now - lastSyncTime > 3000) { // 每3秒同步一次 syncPlayerState(targetDeviceId, syncState); lastSyncTime = now; } }; } -
播放卡顿优化
- 开启硬件解码:优先使用设备硬件解码能力,减少 CPU 占用。
- 关闭冗余日志:在 Release 模式下关闭播放内核日志输出,减少 IO 开销。
6.2 常见问题与解决方案
| 问题现象 | 解决方案 |
|---|---|
| 跨设备投屏时资源加载失败 | 1. 检查设备是否在同一局域网;2. 确认分布式媒体服务权限已声明;3. 验证远程资源路径是否正确(通过shareLocalMediaToDevice返回值) |
| 播放状态同步延迟超过 1 秒 | 1. 减少同步频率(如 3 秒一次);2. 优先使用 Wi-Fi 连接(蓝牙传输延迟较高);3. 关闭其他占用带宽的应用 |
| 智慧屏投屏时画面比例失调 | 1. 在IjkPlayerView中设置aspectRatio为MediaQuery.of(context).size.aspectRatio;2. 根据设备类型动态调整缩放模式 |
| 多设备控制时部分设备无响应 | 1. 检查设备是否已连接(通过_deviceSync.getConnectedDevices()验证);2. 重新发送控制指令(添加重试机制) |
| 播放大文件(>1GB)时内存溢出 | 1. 增加播放内核缓存(max-buffer-size调整为 10MB);2. 采用分片加载模式(仅加载当前播放片段) |
七、总结与扩展学习资源
鸿蒙 Flutter 音视频跨设备协同,通过分布式媒体服务与 Flutter 跨端 UI 的融合,实现了 “资源共享、状态同步、多端控制” 的全场景体验。本文从本地播放到跨设备协同,提供了完整的实战方案,开发者可基于此快速落地音视频应用。
7.1 扩展学习资源
- 鸿蒙分布式媒体服务 API 文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/distributed-media-api-0000001524213947
- Flutter IjkPlayer 进阶配置:https://github.com/openfresh/flutter_ijkplayer
- 鸿蒙音视频性能优化白皮书:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/media-performance-optimization-0000001524213947
- 跨设备音视频开源示例:https://gitee.com/openharmony-sig/ohos_media_samples
7.2 未来展望
随着鸿蒙生态的发展,音视频跨设备协同将支持更多场景:如多设备音视频通话、分布式直播、AR/VR 媒体协同等。Flutter 也将进一步优化与鸿蒙媒体服务的集成,降低跨设备开发门槛。掌握音视频跨设备协同技术,将成为鸿蒙生态开发者的核心竞争力之一。
如果你在实践中遇到具体问题,欢迎在评论区交流讨论。后续将推出更多进阶内容,如鸿蒙 Flutter 直播开发、AR 媒体协同等,敬请关注!
更多推荐


所有评论(0)