视频播放器
一个视频和音频播放器,可以从本地资源、本地文件和网络 URL 播放,并具有强大的控件。
如何在 Flutter 中播放视频?
Flutter 团队直接提供了一个名为 `video_player` 的库。但是,这个库非常基础。虽然它可以播放视频,但视频播放控件的添加和样式设计则由您自己完成。有一个更好的选择,它能像您期望的那样在 Android 和 iOS 上都附带 UI——Chewie。 - Chewie 在后台使用了第一方的 `video_player` 包。它只是简化了视频播放过程。
项目设置
导入包,
dependencies:
flutter:
sdk: flutter
chewie: ^0.9.7
播放视频
Chewie(以及 `video_player`)可以从 3 个来源播放视频——资源、文件和网络。它的好处在于,您不需要写太多代码就可以更改数据源。从资源切换到网络视频只需敲几下键盘。我们先来看看资源。
- 资源视频设置
资源是应用程序可以直接使用的文件。在发布时,它们会与您的应用程序文件一起打包。要设置资源,只需在项目根目录中创建一个文件夹,例如 `videos`。然后将您想要的视频文件拖入其中。

为了让 Flutter 知道所有可用的资源,您必须在 `pubspec` 文件中指定它们。这里我添加了项目中所有需要的依赖项。
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.3
chewie: ^0.9.10
file_picker: ^1.13.0+1
audioplayers: ^0.15.1
fluttertoast: ^7.0.2
使用 Chewie 小部件
现在是时候开始播放视频了。为此,Chewie 提供了自己的小部件,正如我之前提到的,它只是 Flutter 团队提供的 `VideoPlayer` 的一个封装。
因为我们要在一个 ListView 中播放多个视频,所以最好将所有与视频播放相关的部分放在它自己的小部件中。另外,由于视频播放器的资源需要释放,您需要创建一个 `StatefulWidget` 来获取 `dispose()` 方法。


让我们创建 `videos_list.dart`,
import 'package:chewie/chewie.dart';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
class VideosList extends StatefulWidget {
final VideoPlayerController videoPlayerController;
final bool looping;
const VideosList(
{Key key, @required this.videoPlayerController, this.looping})
: super(key: key);
@override
_VideosListState createState() => _VideosListState();
}
class _VideosListState extends State<VideosList> {
ChewieController videosController;
@override
void initState() {
super.initState();
videosController = ChewieController(
videoPlayerController: widget.videoPlayerController,
aspectRatio: 16 / 9,
autoInitialize: true,
looping: widget.looping,
errorBuilder: (context, errorMessage) {
return Center(child: progressBar()
);
},
);
}
Widget progressBar() {
return CircularProgressIndicator();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Chewie(
controller: videosController,
),
);
}
@override
void dispose() {
super.dispose();
// IMPORTANT to dispose of all the used resources
// widget.videoPlayerController.dispose();
videosController.dispose();
}
}
要在 ListView 中播放视频,我们不需要做很多额外的工作。将所有 Chewie 相关内容放在一个单独的小部件中后,让我们创建一个 `MyHomePage` StatelessWidget。
在 `main.dart` 中,添加了项目中所有必需的组件
import 'package:audioplayers/audioplayers.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:video_app/videos_list.dart';
import 'package:video_player/video_player.dart';
import 'package:fluttertoast/fluttertoast.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
int status = 0;
AudioPlayer player = AudioPlayer();
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.purple[50],
appBar: AppBar(
leading: Icon(
Icons.play_circle_filled,
size: 45,
),
backgroundColor: Colors.deepPurple,
title: Text("Music Player"),
actions: <Widget>[
IconButton(icon: Icon(Icons.next_week), onPressed: () {}),
IconButton(
icon: Icon(Icons.new_releases),
onPressed: () {
Fluttertoast.showToast(
msg: "New features coming soon!",
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: Colors.deepPurple,
textColor: Colors.white,
fontSize: 16.0);
}),
],
),
body: Stack(children: <Widget>[
ListView(
children: <Widget>[
VideosList(
videoPlayerController: VideoPlayerController.asset(
'videos/Ansible.MP4',
),
looping: true,
),
VideosList(
videoPlayerController: VideoPlayerController.asset(
'videos/Specialist_In_Python.MP4',
),
looping: true,
),
VideosList(
videoPlayerController: VideoPlayerController.asset(
'videos/AnsiblePro.MP4',
),
looping: true,
),
VideosList(
videoPlayerController: VideoPlayerController.network(
'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
),
looping: true,
),
VideosList(
videoPlayerController: VideoPlayerController.asset(
'videos/OpenShift.MP4',
),
looping: true,
),
VideosList(
videoPlayerController: VideoPlayerController.asset(
'videos/Flutter.MP4',
),
looping: true,
),
],
),
]),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.audiotrack),
elevation: 15,
backgroundColor: Colors.deepPurple,
onPressed: () async {
if (status == 1) {
status = await player.stop();
status = 0;
} else {
String filePath = await FilePicker.getFilePath();
status = await player.play(filePath, isLocal: true);
//also can be played from the assets...
//but users must have choices so local file is used!!
}
},
));
}
}
这里我们添加了一些来自资源和一些来自网络的视频
VideosList(
videoPlayerController: VideoPlayerController.asset(
'videos/Ansible.MP4',
),
looping: true,
),
VideosList(
videoPlayerController: VideoPlayerController.asset(
'videos/Specialist_In_Python.MP4',
),
looping: true,
),
VideosList(
videoPlayerController: VideoPlayerController.asset(
'videos/AnsiblePro.MP4',
),
looping: true,
),
VideosList(
videoPlayerController: VideoPlayerController.network(
'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
),
looping: true,
),
VideosList(
videoPlayerController: VideoPlayerController.asset(
'videos/OpenShift.MP4',
),
looping: true,
),
VideosList(
videoPlayerController: VideoPlayerController.asset(
'videos/Flutter.MP4',
),
looping: true,
),
播放音频
您有两个选择
我们使用了 audioplayers 插件。
1) 实现起来很简单,但在这个项目中我们使用了不同的方法来处理 audioplayer。由于我想让这个应用程序成为一个单页应用,我没有使用 Intent 在两个活动之间切换,为了播放和停止音频,我使用了一个带有该级别功能的浮动按钮,这是代码片段:
floatingActionButton: FloatingActionButton(
child: Icon(Icons.audiotrack),
elevation: 15,
backgroundColor: Colors.deepPurple,
onPressed: () async {
if (status == 1) {
status = await player.stop();
status = 0;
} else {
String filePath = await FilePicker.getFilePath();
status = await player.play(filePath, isLocal: true);
//also can be played from the assets...
//but users must have choices so local file is used!!
}
},
)
2) 在这里,我实现了专用的音频 UI 及其功能,如播放、暂停和停止按钮以及持续时间;
对于音频封面,我们目前使用的是静态图片,请将您的图片放在 `images` 根文件夹中,如下所示:

同时将资产路径添加到您的 `pubspec.yml` 文件中:

要实现音频功能和 UI,请参考 `audio.dart`,
import 'package:audioplayers/audioplayers.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
myApp() {
return MaterialApp(
home: HomePage(),
debugShowCheckedModeBanner: false,
);
}
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
AudioPlayer player = AudioPlayer();
bool isPlaying = false;
String currentTime = "0:00:00";
String completeTime = "0:00:00";
@override
void initState() {
super.initState();
player.onAudioPositionChanged.listen((Duration duration) {
setState(() {
currentTime = duration.toString().split(".")[0];
});
});
player.onDurationChanged.listen((Duration duration) {
setState(() {
completeTime = duration.toString().split(".")[0];
});
});
}
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
color: Colors.blueGrey[200],
child: Column(
children: <Widget>[
Card(
shadowColor: Colors.deepPurple[900],
elevation: 20,
margin: EdgeInsets.only(top: 40, left: 30, right: 30),
child: Image.asset("images/wallhaven.jpg"),
),
Container(
margin: EdgeInsets.only(top: 50),
width: 240,
height: 50,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(80),
),
child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceAround,
// mainAxisSize: MainAxisSize.max,
// mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
IconButton(
icon: Icon(
isPlaying
? Icons.pause_circle_filled
: Icons.play_circle_filled,
color: Colors.black,
size: 30,
),
onPressed: () {
if (isPlaying) {
player.pause();
setState(() {
isPlaying = false;
});
} else {
player.resume();
setState(() {
isPlaying = true;
});
}
}),
IconButton(
icon: Icon(
Icons.stop,
color: Colors.black,
size: 25,
),
onPressed: () {
player.stop();
setState(() {
isPlaying = false;
});
},
),
Text(
" " + currentTime,
style: TextStyle(fontWeight: FontWeight.w700),
),
Text(" | "),
Text(
completeTime,
style: TextStyle(fontWeight: FontWeight.w300),
),
],
)),
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.audiotrack),
elevation: 10,
backgroundColor: Colors.deepPurple,
onPressed: () async {
String filePath = await FilePicker.getFilePath();
int status = await player.play(filePath, isLocal: true);
if (status == 1) {
setState(() {
isPlaying = true;
});
}
},
),
);
}
}
我们使用 `audioCache` 流的持续时间和位置,为此必须订阅这些流,
player.onAudioPositionChanged.listen((Duration duration) {
setState(() {
currentTime = duration.toString().split(".")[0];
});
});
player.onDurationChanged.listen((Duration duration) {
setState(() {
completeTime = duration.toString().split(".")[0];
});
});
我们的音频 UI 将如下所示:

Chewie 是一个 Flutter 包,旨在简化视频播放过程。它为您处理了开始、停止和暂停按钮,以及显示视频进度的叠加层。
这是我们项目的最终输出

点击 这里 查看带声音的输出。
未来要添加的功能
- 可以从网络 URL 添加音频/视频(用户输入)。
- 在音频部分添加以分钟为单位的播放/暂停和持续时间。
- 分享应用的截图。
- 通知当前播放的内容。