Flutter for OpenHarmony三方库适配实战:video_player 视频播放器
视频播放是现代移动应用的核心功能之一,无论是短视频应用、在线教育平台还是娱乐媒体应用,都需要强大的视频播放能力。在 Flutter for OpenHarmony 应用开发中,是官方推荐的视频播放插件,提供了完整的视频播放解决方案。video_player 是 Flutter 官方推荐的视频播放插件,为 OpenHarmony 应用提供了完整的视频播放能力。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
本文基于flutter3.27.5开发

一、video_player 库概述
视频播放是现代移动应用的核心功能之一,无论是短视频应用、在线教育平台还是娱乐媒体应用,都需要强大的视频播放能力。在 Flutter for OpenHarmony 应用开发中,video_player 是官方推荐的视频播放插件,提供了完整的视频播放解决方案。
video_player 库特点
video_player 库基于 Flutter 平台接口实现,提供了以下核心特性:
多格式支持:支持 MP4、WebM、HLS、DASH 等主流视频格式,满足各种场景需求。
多数据源:支持网络视频、本地文件、Asset 资源等多种数据源。
播放控制:提供播放、暂停、停止、跳转、倍速播放等完整的播放控制能力。
状态监听:实时监听播放状态、缓冲状态、播放进度等信息。
跨平台一致:统一的 API 接口,在不同平台上表现一致。
功能支持对比
| 功能 | Android | iOS | OpenHarmony |
|---|---|---|---|
| 网络视频播放 | ✅ | ✅ | ✅ |
| 本地文件播放 | ✅ | ✅ | ✅ |
| Asset 资源播放 | ✅ | ✅ | ✅ |
| 播放控制 | ✅ | ✅ | ✅ |
| 倍速播放 | ✅ | ✅ | ✅ |
| 音量控制 | ✅ | ✅ | ✅ |
| 循环播放 | ✅ | ✅ | ✅ |
| HLS/DASH 流媒体 | ✅ | ✅ | ✅ |
使用场景:短视频应用、在线教育平台、视频会议、媒体播放器等。
二、安装与配置
2.1 添加依赖
在项目的 pubspec.yaml 文件中添加 video_player 依赖:
dependencies:
video_player:
git:
url: https://atomgit.com/openharmony-tpc/flutter_packages.git
path: packages/video_player/video_player
然后执行以下命令获取依赖:
flutter pub get
2.2 权限配置
video_player 需要网络权限才能播放网络视频。在 module.json5 中添加:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.INTERNET",
"reason": "$string:network_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
}
在 string.json 中添加权限说明:
{
"string": [
{
"name": "network_reason",
"value": "用于播放网络视频"
}
]
}
三、核心 API 详解
3.1 VideoPlayerController 类
VideoPlayerController 是视频播放的核心控制器,负责管理视频的加载、播放、暂停等操作。
class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
// 构造方法
VideoPlayerController.networkUrl(Uri url, {VideoFormat? formatHint});
VideoPlayerController.asset(String dataSource, {String? package});
VideoPlayerController.file(File file);
VideoPlayerController.contentUri(Uri contentUri);
// 核心方法
Future<void> initialize();
Future<void> play();
Future<void> pause();
Future<void> seekTo(Duration position);
Future<void> setPlaybackSpeed(double speed);
Future<void> setVolume(double volume);
Future<void> setLooping(bool looping);
void dispose();
}
3.2 构造方法详解
3.2.1 VideoPlayerController.networkUrl
从网络 URL 创建视频控制器。
VideoPlayerController.networkUrl(Uri url, {VideoFormat? formatHint})
参数说明:
url 参数是视频的网络地址,必须是一个有效的 URI。
formatHint 参数是视频格式提示,用于流媒体格式识别。
使用示例:
// 播放普通网络视频
final controller = VideoPlayerController.networkUrl(
Uri.parse('https://example.com/video.mp4'),
);
// 播放 HLS 流媒体
final hlsController = VideoPlayerController.networkUrl(
Uri.parse('https://example.com/video.m3u8'),
formatHint: VideoFormat.hls,
);
// 播放 DASH 流媒体
final dashController = VideoPlayerController.networkUrl(
Uri.parse('https://example.com/video.mpd'),
formatHint: VideoFormat.dash,
);
3.2.2 VideoPlayerController.asset
从 Asset 资源创建视频控制器。
VideoPlayerController.asset(String dataSource, {String? package})
参数说明:
dataSource 参数是 Asset 资源路径。
package 参数是资源所在的包名(用于插件资源)。
使用示例:
// 播放应用内 Asset 视频
final controller = VideoPlayerController.asset('assets/videos/intro.mp4');
// 播放插件包中的视频
final pluginController = VideoPlayerController.asset(
'assets/videos/tutorial.mp4',
package: 'my_plugin',
);
注意:Asset 视频需要在
pubspec.yaml中声明:flutter: assets: - assets/videos/
3.2.3 VideoPlayerController.file
从本地文件创建视频控制器。
VideoPlayerController.file(File file)
参数说明:
file 参数是本地文件对象。
使用示例:
import 'dart:io';
// 播放本地文件
final file = File('/path/to/video.mp4');
final controller = VideoPlayerController.file(file);
3.3 initialize 方法
initialize 方法用于初始化视频控制器,必须在播放前调用。
Future<void> initialize()
返回值:返回一个 Future,初始化完成后完成。
使用示例:
final controller = VideoPlayerController.networkUrl(
Uri.parse('https://example.com/video.mp4'),
);
try {
await controller.initialize();
print('视频初始化成功');
print('视频时长: ${controller.duration}');
print('视频尺寸: ${controller.size}');
} catch (e) {
print('视频初始化失败: $e');
}
重要提示:
initialize方法是异步的,必须使用await等待完成- 初始化完成后才能获取视频时长、尺寸等信息
- 初始化失败时会抛出异常,建议使用 try-catch 处理
3.4 play 方法
play 方法用于开始或恢复播放视频。
Future<void> play()
使用示例:
// 开始播放
await controller.play();
// 播放前检查初始化状态
if (controller.value.isInitialized) {
await controller.play();
}
3.5 pause 方法
pause 方法用于暂停播放视频。
Future<void> pause()
使用示例:
// 暂停播放
await controller.pause();
// 根据当前状态切换播放/暂停
if (controller.value.isPlaying) {
await controller.pause();
} else {
await controller.play();
}
3.6 seekTo 方法
seekTo 方法用于跳转到指定播放位置。
Future<void> seekTo(Duration position)
参数说明:
position 参数是目标播放位置。
使用示例:
// 跳转到 30 秒位置
await controller.seekTo(Duration(seconds: 30));
// 跳转到视频开头
await controller.seekTo(Duration.zero);
// 快进 10 秒
final newPosition = controller.position + Duration(seconds: 10);
await controller.seekTo(newPosition);
// 快退 10 秒
final newPosition = controller.position - Duration(seconds: 10);
if (newPosition < Duration.zero) {
await controller.seekTo(Duration.zero);
} else {
await controller.seekTo(newPosition);
}
3.7 setPlaybackSpeed 方法
setPlaybackSpeed 方法用于设置播放速度。
Future<void> setPlaybackSpeed(double speed)
参数说明:
speed 参数是播放速度倍率,1.0 为正常速度。
使用示例:
// 正常速度
await controller.setPlaybackSpeed(1.0);
// 1.5 倍速
await controller.setPlaybackSpeed(1.5);
// 2 倍速
await controller.setPlaybackSpeed(2.0);
// 0.5 倍速(慢放)
await controller.setPlaybackSpeed(0.5);
OpenHarmony 平台说明:OpenHarmony 支持以下固定倍速值:
- 0.125、0.25、0.5、0.75、1.0、1.25、1.5、1.75、2.0、3.0
设置其他倍速值时,系统会自动选择最接近的支持值。
3.8 setVolume 方法
setVolume 方法用于设置音量。
Future<void> setVolume(double volume)
参数说明:
volume 参数是音量值,范围 0.0(静音)到 1.0(最大音量)。
使用示例:
// 静音
await controller.setVolume(0.0);
// 最大音量
await controller.setVolume(1.0);
// 50% 音量
await controller.setVolume(0.5);
3.9 setLooping 方法
setLooping 方法用于设置是否循环播放。
Future<void> setLooping(bool looping)
参数说明:
looping 参数为 true 时循环播放,为 false 时播放一次后停止。
使用示例:
// 开启循环播放
await controller.setLooping(true);
// 关闭循环播放
await controller.setLooping(false);
// 检查是否循环播放
if (controller.value.isLooping) {
print('当前为循环播放模式');
}
3.10 dispose 方法
dispose 方法用于释放视频控制器资源。
void dispose()
使用示例:
void dispose() {
_controller.dispose();
super.dispose();
}
重要提示:
- 必须在 Widget 销毁时调用
dispose释放资源- 未释放的控制器会导致内存泄漏
- 释放后不能再使用该控制器
3.11 常用属性
OpenHarmony 版本的 video_player 中,播放状态相关属性需要通过 value 访问:
// 当前播放位置(通过 value 访问)
Duration get value.position;
// 视频总时长(通过 value 访问)
Duration get value.duration;
// 视频尺寸(通过 value 访问)
Size get value.size;
// 视频宽高比(通过 value 访问)
double get value.aspectRatio;
// 当前播放状态
VideoPlayerValue get value;
使用示例:
// 获取播放进度百分比
final progress = controller.value.position.inMilliseconds / controller.value.duration.inMilliseconds;
// 获取视频尺寸
final width = controller.value.size.width;
final height = controller.value.size.height;
// 获取宽高比
final ratio = controller.value.aspectRatio;
// 检查是否正在播放
if (controller.value.isPlaying) {
print('视频正在播放');
}
// 检查是否初始化完成
if (controller.value.isInitialized) {
print('视频已初始化');
}
// 检查是否缓冲中
if (controller.value.isBuffering) {
print('视频正在缓冲');
}
重要提示:OpenHarmony 版本的
position、duration、size、aspectRatio等属性需要通过controller.value访问,而不是直接通过controller访问。
四、数据模型详解
4.1 VideoPlayerValue 类
VideoPlayerValue 类包含视频播放器的所有状态信息。
class VideoPlayerValue {
const VideoPlayerValue({
this.duration = Duration.zero,
this.position = Duration.zero,
this.isPlaying = false,
this.isLooping = false,
this.isBuffering = false,
this.volume = 1.0,
this.playbackSpeed = 1.0,
this.errorDescription,
this.size = Size.zero,
this.isInitialized = false,
});
final Duration duration; // 视频总时长
final Duration position; // 当前播放位置
final bool isPlaying; // 是否正在播放
final bool isLooping; // 是否循环播放
final bool isBuffering; // 是否正在缓冲
final double volume; // 当前音量
final double playbackSpeed; // 当前播放速度
final String? errorDescription; // 错误描述
final Size size; // 视频尺寸
final bool isInitialized; // 是否已初始化
}
4.2 VideoFormat 枚举
VideoFormat 枚举定义了视频格式类型。
enum VideoFormat {
dash, // MPEG-DASH 流媒体
hls, // HTTP Live Streaming
ss, // Smooth Streaming
other, // 其他格式
}
4.3 DataSourceType 枚举
DataSourceType 枚举定义了数据源类型。
enum DataSourceType {
asset, // Asset 资源
network, // 网络资源
file, // 本地文件
contentUri, // Content URI(仅 Android)
}
4.4 VideoPlayer 组件
VideoPlayer 是用于显示视频的 Widget。
class VideoPlayer extends StatefulWidget {
const VideoPlayer(this.controller, {super.key});
final VideoPlayerController controller;
}
使用示例:
VideoPlayer(controller);
4.5 VideoProgressIndicator 组件
VideoProgressIndicator 是视频进度条组件。
class VideoProgressIndicator extends StatefulWidget {
const VideoProgressIndicator(
this.controller, {
super.key,
this.colors,
this.allowScrubbing = false,
this.padding = const EdgeInsets.only(top: 5.0),
});
final VideoPlayerController controller;
final VideoProgressColors? colors; // 进度条颜色
final bool allowScrubbing; // 是否允许拖动
final EdgeInsets padding; // 内边距
}
使用示例:
VideoProgressIndicator(
controller,
allowScrubbing: true,
colors: VideoProgressColors(
playedColor: Colors.red,
bufferedColor: Colors.grey,
backgroundColor: Colors.black,
),
);
4.6 VideoProgressColors 类
VideoProgressColors 类定义进度条颜色。
class VideoProgressColors {
const VideoProgressColors({
this.playedColor = const Color.fromRGBO(255, 0, 0, 0.7),
this.bufferedColor = const Color.fromRGBO(50, 50, 200, 0.2),
this.backgroundColor = const Color.fromRGBO(200, 200, 200, 0.5),
});
final Color playedColor; // 已播放部分颜色
final Color bufferedColor; // 已缓冲部分颜色
final Color backgroundColor; // 背景颜色
}
五、OpenHarmony 平台实现原理
5.1 原生 API 映射
video_player 在 OpenHarmony 平台上使用 @ohos.multimedia.media 模块实现:
| Flutter API | OpenHarmony API |
|---|---|
| initialize | media.createAVPlayer |
| play | avPlayer.play |
| pause | avPlayer.pause |
| seekTo | avPlayer.seek |
| setPlaybackSpeed | avPlayer.setSpeed |
| setVolume | avPlayer.setVolume |
| setLooping | avPlayer.setLoop |
5.2 AVPlayer 状态机
OpenHarmony AVPlayer 采用状态机模式管理播放状态:
idle -> initialized -> prepared -> playing -> paused -> completed
↓
stopped
状态说明:
| 状态 | 说明 |
|---|---|
| idle | 空闲状态,初始状态 |
| initialized | 已设置数据源,未准备 |
| prepared | 准备完成,可以播放 |
| playing | 正在播放 |
| paused | 已暂停 |
| completed | 播放完成 |
| stopped | 已停止 |
| error | 发生错误 |
5.3 视频渲染实现
OpenHarmony 使用 XComponent 进行视频渲染:
XComponent({
id: 'videoPlayer',
type: 'surface',
libraryname: 'avplayer',
})
.onLoad(() => {
// 获取 surfaceId 并关联到 AVPlayer
avPlayer.surfaceId = surfaceId;
});
5.4 倍速播放实现
OpenHarmony AVPlayer 支持固定的播放速度:
// 支持的速度值
const speeds = [0.125, 0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0, 3.0];
// 设置播放速度
avPlayer.setSpeed(speed);
六、实战案例
6.1 基础视频播放
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
class BasicVideoPlayer extends StatefulWidget {
const BasicVideoPlayer({super.key});
State<BasicVideoPlayer> createState() => _BasicVideoPlayerState();
}
class _BasicVideoPlayerState extends State<BasicVideoPlayer> {
late VideoPlayerController _controller;
bool _isInitialized = false;
void initState() {
super.initState();
_controller = VideoPlayerController.networkUrl(
Uri.parse('https://media.w3.org/2010/05/sintel/trailer.mp4'),
);
_controller.initialize().then((_) {
setState(() {
_isInitialized = true;
});
_controller.play();
});
}
void dispose() {
_controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('基础视频播放')),
body: Center(
child: _isInitialized
? AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
)
: const CircularProgressIndicator(),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
_controller.value.isPlaying
? _controller.pause()
: _controller.play();
});
},
child: Icon(
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
),
),
);
}
}
6.2 带控制条的视频播放器
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
class ControlledVideoPlayer extends StatefulWidget {
final String videoUrl;
const ControlledVideoPlayer({super.key, required this.videoUrl});
State<ControlledVideoPlayer> createState() => _ControlledVideoPlayerState();
}
class _ControlledVideoPlayerState extends State<ControlledVideoPlayer> {
late VideoPlayerController _controller;
bool _isInitialized = false;
void initState() {
super.initState();
_controller = VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl));
_controller.addListener(() {
setState(() {});
});
_controller.initialize().then((_) {
setState(() {
_isInitialized = true;
});
});
}
void dispose() {
_controller.dispose();
super.dispose();
}
String _formatDuration(Duration duration) {
String twoDigits(int n) => n.toString().padLeft(2, '0');
final minutes = duration.inMinutes.remainder(60);
final seconds = duration.inSeconds.remainder(60);
return '${twoDigits(minutes)}:${twoDigits(seconds)}';
}
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('视频播放器'),
backgroundColor: Colors.transparent,
),
body: _isInitialized
? Column(
children: [
Expanded(
child: Center(
child: AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
),
),
),
_buildControls(),
],
)
: const Center(
child: CircularProgressIndicator(color: Colors.white),
),
);
}
Widget _buildControls() {
return Container(
color: Colors.black87,
padding: const EdgeInsets.all(16),
child: Column(
children: [
Row(
children: [
Text(
_formatDuration(_controller.position),
style: const TextStyle(color: Colors.white),
),
Expanded(
child: Slider(
value: _controller.position.inSeconds.toDouble(),
max: _controller.duration.inSeconds.toDouble(),
onChanged: (value) {
_controller.seekTo(Duration(seconds: value.toInt()));
},
),
),
Text(
_formatDuration(_controller.duration),
style: const TextStyle(color: Colors.white),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: const Icon(Icons.replay_10, color: Colors.white),
onPressed: () {
final newPosition = _controller.position - const Duration(seconds: 10);
_controller.seekTo(newPosition < Duration.zero ? Duration.zero : newPosition);
},
),
IconButton(
icon: Icon(
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
color: Colors.white,
size: 48,
),
onPressed: () {
_controller.value.isPlaying ? _controller.pause() : _controller.play();
},
),
IconButton(
icon: const Icon(Icons.forward_10, color: Colors.white),
onPressed: () {
final newPosition = _controller.position + const Duration(seconds: 10);
_controller.seekTo(newPosition > _controller.duration ? _controller.duration : newPosition);
},
),
],
),
],
),
);
}
}
七、常见问题
7.1 视频无法播放?
原因:
- 网络权限未配置
- URL 无效或无法访问
- 视频格式不支持
- 未等待初始化完成
解决方案:
// 确保初始化完成后再播放
await _controller.initialize();
await _controller.play();
7.2 如何实现全屏播放?
void initState() {
super.initState();
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
}
void dispose() {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
super.dispose();
}
7.3 OpenHarmony 倍速播放限制
OpenHarmony 仅支持固定倍速值:0.125、0.25、0.5、0.75、1.0、1.25、1.5、1.75、2.0、3.0。
设置其他值时系统会自动选择最接近的支持值。
八、完整代码示例
以下是一个完整的可运行示例,展示了 video_player 库的核心功能:
main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:video_player/video_player.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Video Player Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.pink),
useMaterial3: true,
),
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
final List<VideoItem> videos = const [
VideoItem(
title: 'Sintel 预告片',
url: 'https://media.w3.org/2010/05/sintel/trailer.mp4',
thumbnail: 'https://picsum.photos/400/225?random=1',
),
VideoItem(
title: 'Big Buck Bunny',
url: 'https://media.w3.org/2010/05/bunny/trailer.mp4',
thumbnail: 'https://picsum.photos/400/225?random=2',
),
VideoItem(
title: '测试视频',
url: 'https://www.w3schools.com/html/mov_bbb.mp4',
thumbnail: 'https://picsum.photos/400/225?random=3',
),
];
HomePage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('视频播放器'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: videos.length,
itemBuilder: (context, index) {
final video = videos[index];
return Card(
clipBehavior: Clip.antiAlias,
margin: const EdgeInsets.only(bottom: 16),
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => VideoPlayerPage(
videoUrl: video.url,
title: video.title,
),
),
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
alignment: Alignment.center,
children: [
Image.network(
video.thumbnail,
width: double.infinity,
height: 180,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Container(
width: double.infinity,
height: 180,
color: Colors.grey[300],
child: const Icon(Icons.video_library, size: 48),
);
},
),
Container(
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.5),
shape: BoxShape.circle,
),
padding: const EdgeInsets.all(16),
child: const Icon(
Icons.play_arrow,
color: Colors.white,
size: 48,
),
),
],
),
Padding(
padding: const EdgeInsets.all(12),
child: Text(
video.title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
);
},
),
);
}
}
class VideoPlayerPage extends StatefulWidget {
final String videoUrl;
final String title;
const VideoPlayerPage({
super.key,
required this.videoUrl,
required this.title,
});
State<VideoPlayerPage> createState() => _VideoPlayerPageState();
}
class _VideoPlayerPageState extends State<VideoPlayerPage> {
late VideoPlayerController _controller;
bool _isInitialized = false;
bool _showControls = true;
bool _hasError = false;
static const List<double> _playbackSpeeds = [0.5, 0.75, 1.0, 1.25, 1.5, 2.0];
void initState() {
super.initState();
_initPlayer();
}
Future<void> _initPlayer() async {
_controller = VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl));
_controller.addListener(() {
if (_controller.value.hasError) {
setState(() {
_hasError = true;
});
}
});
try {
await _controller.initialize();
setState(() {
_isInitialized = true;
});
} catch (e) {
setState(() {
_hasError = true;
});
}
}
String _formatDuration(Duration duration) {
String twoDigits(int n) => n.toString().padLeft(2, '0');
final hours = duration.inHours;
final minutes = duration.inMinutes.remainder(60);
final seconds = duration.inSeconds.remainder(60);
if (hours > 0) {
return '$hours:${twoDigits(minutes)}:${twoDigits(seconds)}';
}
return '${twoDigits(minutes)}:${twoDigits(seconds)}';
}
void dispose() {
_controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: Text(widget.title),
backgroundColor: Colors.transparent,
foregroundColor: Colors.white,
),
body: _buildBody(),
);
}
Widget _buildBody() {
if (_hasError) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error_outline, color: Colors.red, size: 64),
const SizedBox(height: 16),
const Text(
'视频加载失败',
style: TextStyle(color: Colors.white, fontSize: 18),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
setState(() {
_hasError = false;
_isInitialized = false;
});
_initPlayer();
},
child: const Text('重试'),
),
],
),
);
}
if (!_isInitialized) {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(color: Colors.white),
SizedBox(height: 16),
Text(
'正在加载视频...',
style: TextStyle(color: Colors.white, fontSize: 16),
),
],
),
);
}
return GestureDetector(
onTap: () {
setState(() {
_showControls = !_showControls;
});
},
child: Stack(
alignment: Alignment.bottomCenter,
children: [
Center(
child: AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
),
),
if (_showControls) _buildControls(),
],
),
);
}
Widget _buildControls() {
return Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.transparent, Colors.black87],
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildProgressBar(),
_buildControlButtons(),
],
),
);
}
Widget _buildProgressBar() {
final position = _controller.value.position;
final duration = _controller.value.duration;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
Text(
_formatDuration(position),
style: const TextStyle(color: Colors.white, fontSize: 12),
),
Expanded(
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 6),
trackHeight: 3,
activeTrackColor: Theme.of(context).colorScheme.primary,
inactiveTrackColor: Colors.white30,
thumbColor: Theme.of(context).colorScheme.primary,
),
child: Slider(
value: position.inSeconds.toDouble().clamp(0, duration.inSeconds.toDouble()),
max: duration.inSeconds.toDouble(),
onChanged: (value) {
_controller.seekTo(Duration(seconds: value.toInt()));
},
),
),
),
Text(
_formatDuration(duration),
style: const TextStyle(color: Colors.white, fontSize: 12),
),
],
),
);
}
Widget _buildControlButtons() {
final position = _controller.value.position;
final duration = _controller.value.duration;
return Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
icon: const Icon(Icons.replay_10, color: Colors.white, size: 28),
onPressed: () {
final newPosition = position - const Duration(seconds: 10);
_controller.seekTo(
newPosition < Duration.zero ? Duration.zero : newPosition,
);
},
),
IconButton(
icon: Icon(
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
color: Colors.white,
size: 48,
),
onPressed: () {
_controller.value.isPlaying ? _controller.pause() : _controller.play();
setState(() {});
},
),
IconButton(
icon: const Icon(Icons.forward_10, color: Colors.white, size: 28),
onPressed: () {
final newPosition = position + const Duration(seconds: 10);
_controller.seekTo(
newPosition > duration ? duration : newPosition,
);
},
),
PopupMenuButton<double>(
icon: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.white24,
borderRadius: BorderRadius.circular(4),
),
child: Text(
'${_controller.value.playbackSpeed}x',
style: const TextStyle(color: Colors.white, fontSize: 12),
),
),
onSelected: (speed) {
_controller.setPlaybackSpeed(speed);
setState(() {});
},
itemBuilder: (context) {
return _playbackSpeeds.map((speed) {
return PopupMenuItem(
value: speed,
child: Text(
'${speed}x',
style: TextStyle(
fontWeight: _controller.value.playbackSpeed == speed
? FontWeight.bold
: FontWeight.normal,
),
),
);
}).toList();
},
),
],
),
);
}
}
class VideoItem {
final String title;
final String url;
final String thumbnail;
const VideoItem({
required this.title,
required this.url,
required this.thumbnail,
});
}
运行此示例后,您将看到一个完整的视频播放器演示界面,包含视频列表、播放控制、进度条、倍速播放等功能。点击视频卡片即可进入播放页面。
九、总结
video_player 是 Flutter 官方推荐的视频播放插件,为 OpenHarmony 应用提供了完整的视频播放能力。
优点
- 多格式支持:支持主流视频格式
- 跨平台一致:统一的 API 接口
- 完整控制:播放、暂停、跳转、倍速等
- 状态监听:实时获取播放状态
适用场景
- 短视频应用
- 在线教育平台
- 视频会议应用
- 媒体播放器
最佳实践
- 及时释放不再使用的控制器
- 做好错误处理
- OpenHarmony 使用固定倍速值
- 处理应用生命周期事件
十、参考资料
更多推荐



所有评论(0)