视频播放
安装插件flutter pub add video_player
Android配置
在 android/app/src/main/AndroidManifest.xml 的 <application> 标签下添加网络权限:
ini
<uses-permission android:name="android.permission.INTERNET"/>
iOS配置
在 ios/Runner/Info.plist 中添加以下配置以允许网络加载:
xml
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
(注意:video_player 在 iOS 模拟器上无法运行,必须使用真机进行测试。)
代码
less
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
void main(List<String> args) {
runApp(MaterialApp(home: Scaffold(body: HomePage())));
}
class HomePage extends StatefulWidget {
HomePage({Key? key}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
VideoPlayerController? _controller;
void _onControllerChanged() {
setState(() {});
}
String _fmt(Duration d) {
final mm = (d.inMinutes % 60).toString().padLeft(2, '0');
final ss = (d.inSeconds % 60).toString().padLeft(2, '0');
return '$mm:$ss';
}
Widget _buildVideoView() {
return Container(
color: Colors.blueGrey,
child: _controller?.value.isInitialized == true
? VideoPlayer(_controller!)
: CircularProgressIndicator(),
);
}
Widget _buildVideoControlView() {
return Positioned(
bottom: 0,
left: 0,
right: 0,
height: 40,
child: Container(color: Colors.white54),
);
}
Widget _buildVideoPlayView() {
return AspectRatio(
aspectRatio: 16 / 9,
child: Container(
color: Colors.black,
alignment: Alignment.center,
child: Stack(children: [_buildVideoView(), _buildVideoControlView()]),
),
);
}
final _speeds = const [0.5, 1.0, 1.25, 1.5, 2.0];
Widget _buildSpeedButtons() {
return Wrap(
spacing: 6,
children: _speeds.map((s) {
final selected = _controller?.value.playbackSpeed == s;
return GestureDetector(
onTap: () async {
if (_controller != null) {
await _controller!.setPlaybackSpeed(s);
}
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: selected ? Colors.blue : Colors.white12,
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: selected ? Colors.blueAccent : Colors.white24,
),
),
child: Text(
'${s.toStringAsFixed(s.truncateToDouble() == s ? 1 : s % 1 == 0.25 || s % 1 == 0.5 ? 2 : 1)}x',
style: const TextStyle(color: Colors.white, fontSize: 12),
),
),
);
}).toList(),
);
}
Widget _buildVideoInfoView() {
final v = _controller?.value;
final info = <String>[];
if (v == null) {
info.add('尚未加载视频');
} else {
info.add('状态: ${v.isInitialized ? "已初始化" : "初始化中"}');
info.add('播放: ${v.isPlaying ? "播放中" : "已暂停"}');
info.add('缓冲: ${v.isBuffering ? "缓冲中" : "完成"}');
info.add('进度: ${_fmt(v.position)} / ${_fmt(v.duration)}');
info.add('进度%: ${v.duration.inMilliseconds > 0 ? ((v.position.inMilliseconds / v.duration.inMilliseconds) * 100).toStringAsFixed(1) : "0.0"}%');
info.add('分辨率: ${v.size.width.toInt()} x ${v.size.height.toInt()}');
info.add('倍速: ${v.playbackSpeed.toStringAsFixed(1)}x');
info.add('音量: ${(v.volume * 100).toInt()}%');
info.add('错误: ${v.errorDescription ?? "无"}');
}
return Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
color: Colors.grey.shade900,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...info.map((s) => Text(s,
style: const TextStyle(color: Colors.white, fontSize: 13, height: 1.5))),
const SizedBox(height: 10),
const Text('倍速选择:', style: TextStyle(color: Colors.white, fontSize: 13)),
const SizedBox(height: 6),
_buildSpeedButtons(),
],
),
);
}
Widget _buildVideoSourceView() {
return Container(
color: Colors.greenAccent,
height: 40,
child: Flex(
direction: Axis.horizontal,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
GestureDetector(
onTap: () async {
final old = _controller;
_controller = null;
if (old != null) {
old.removeListener(_onControllerChanged);
await old.dispose();
}
_controller =
VideoPlayerController.asset('film/film.mp4')
..addListener(_onControllerChanged);
_controller?.initialize().then((_) {
setState(() {});
_controller?.play();
});
},
child: Text("播放本地视频", style: TextStyle(color: Colors.white)),
),
GestureDetector(
onTap: () async {
final old = _controller;
_controller = null;
if (old != null) {
old.removeListener(_onControllerChanged);
await old.dispose();
}
_controller = VideoPlayerController.networkUrl(
Uri.parse(
'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4',
),
)..addListener(_onControllerChanged);
_controller?.initialize().then((_) {
setState(() {});
_controller?.play();
});
},
child: Text("播放网络视频", style: TextStyle(color: Colors.white)),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(children: [
_buildVideoPlayView(),
_buildVideoInfoView(),
_buildVideoSourceView(),
]),
);
}
@override
void dispose() {
_controller?.removeListener(_onControllerChanged);
_controller?.dispose();
super.dispose();
}
}