如何在 flutter 中显示我保存在路径中的视频?
How to display a video I saved in a path in flutter?
您好,我尝试在从相机录制视频并将此视频保存在设备中后显示视频 (path: /data/user/0/com.XXXXX.flutter_video_test/app_flutter/Movies/2019-11-08.mp4
)
我使用了flutter.dev中https://pub.dev/packages/video_player的例子。当我完整地写路径时还可以,但是当我使用字符串变量做同样的事情时,我就出错了。
这是问题的一部分
String dirPath;
Future<String> load_path_video() async {
final Directory extDir = await getApplicationDocumentsDirectory();
setState(() {
dirPath = '${extDir.path}/Movies/2019-11-08.mp4';
// if I print ($dirPath) I have /data/user/0/com.XXXXX.flutter_video_test/app_flutter/Movies/2019-11-08.mp4
});
}
@override
void initState() {
// TODO: implement initState
load_path_video();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
child: NetworkPlayerLifeCycle(
'$dirPath', // with the String dirPath I have error but if I use the same path but write like this /data/user/0/com.XXXXX.flutter_video_test/app_flutter/Movies/2019-11-08.mp4 it's ok ... why?
(BuildContext context,
VideoPlayerController controller) =>
AspectRatioVideo(controller)),
),
],
),
);
}
}
这里是我使用字符串变量时的错误
E/ExoPlayerImplInternal(20818): Source error.
E/ExoPlayerImplInternal(20818): com.google.android.exoplayer2.upstream.FileDataSource$FileDataSourceException: java.io.FileNotFoundException: null: open failed: ENOENT (No such file or directory)
E/ExoPlayerImplInternal(20818): at com.google.android.exoplayer2.upstream.FileDataSource.open(FileDataSource.java:73)
E/ExoPlayerImplInternal(20818): at com.google.android.exoplayer2.upstream.DefaultDataSource.open(DefaultDataSource.java:250)
E/ExoPlayerImplInternal(20818): at com.google.android.exoplayer2.upstream.StatsDataSource.open(StatsDataSource.java:83)
E/ExoPlayerImplInternal(20818): at com.google.android.exoplayer2.source.ExtractorMediaPeriod$ExtractingLoadable.load(ExtractorMediaPeriod.java:885)
E/ExoPlayerImplInternal(20818): at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:381)
E/ExoPlayerImplInternal(20818): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
E/ExoPlayerImplInternal(20818): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
E/ExoPlayerImplInternal(20818): at java.lang.Thread.run(Thread.java:776)
E/ExoPlayerImplInternal(20818): Caused by: java.io.FileNotFoundException: null: open failed: ENOENT (No such file or directory)
E/ExoPlayerImplInternal(20818): at libcore.io.IoBridge.open(IoBridge.java:455)
E/ExoPlayerImplInternal(20818): at java.io.RandomAccessFile.<init>(RandomAccessFile.java:247)
E/ExoPlayerImplInternal(20818): at java.io.RandomAccessFile.<init>(RandomAccessFile.java:128)
E/ExoPlayerImplInternal(20818): at com.google.android.exoplayer2.upstream.FileDataSource.open(FileDataSource.java:65)
E/ExoPlayerImplInternal(20818): ... 7 more
E/ExoPlayerImplInternal(20818): Caused by: android.system.ErrnoException: open failed: ENOENT (No such file or directory)
E/ExoPlayerImplInternal(20818): at libcore.io.Posix.open(Native Method)
E/ExoPlayerImplInternal(20818): at libcore.io.BlockGuardOs.open(BlockGuardOs.java:187)
E/ExoPlayerImplInternal(20818): at libcore.io.IoBridge.open(IoBridge.java:441)
E/ExoPlayerImplInternal(20818): ... 10 more
I/flutter (20818): Video player had error com.google.android.exoplayer2.ExoPlaybackException: com.google.android.exoplayer2.upstream.FileDataSource$FileDataSourceException: java.io.FileNotFoundException: null: open failed: ENOENT (No such file or directory)
从错误来看,您似乎正在尝试显示一个不存在的文件 (No such file or directory
),这让我认为您收到错误是因为 dirPath
是 null
.
我建议您在代码中添加一个 if
,如下所示:
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: <Widget>[
if(dirPath != null)
Container(
padding: const EdgeInsets.all(20),
child: NetworkPlayerLifeCycle(
dirPath,
(BuildContext context,
VideoPlayerController controller) =>
AspectRatioVideo(controller)),
),
],
),
);
}
这样,只有在 dirPath
不是 null
时才会显示 Container
小部件。
对您的代码的一个观察:
- 当你的 dirPath
已经是一个字符串时,你不需要使用 '$dirPath'
,你可以直接使用变量
在 initState() await load_path_video() 期间,dirPath 仍然为 null
您需要一个 bool loading 来检查 await getApplicationDocumentsDirectory 是否完成
您可以复制粘贴 运行 下面的完整代码并确保文件在路径
中
代码片段
Future<String> load_path_video() async {
loading = true;
final Directory extDir = await getApplicationDocumentsDirectory();
setState(() {
dirPath = '${extDir.path}/Movies/2019-11-08.mp4';
print(dirPath);
loading = false;
// if I print ($dirPath) I have /data/user/0/com.XXXXX.flutter_video_test/app_flutter/Movies/2019-11-08.mp4
});
}
Container(
padding: const EdgeInsets.all(20),
child: loading
? CircularProgressIndicator()
: NetworkPlayerLifeCycle(
'$dirPath', // with the String dirPath I have error but if I use the same path but write like this /data/user/0/com.XXXXX.flutter_video_test/app_flutter/Movies/2019-11-08.mp4 it's ok ... why ?
(BuildContext context, VideoPlayerController controller) =>
AspectRatioVideo(controller)),
工作演示
完整的工作代码
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
import 'package:video_player/video_player.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
String dirPath;
bool loading = false;
Future<String> load_path_video() async {
loading = true;
final Directory extDir = await getApplicationDocumentsDirectory();
setState(() {
dirPath = '${extDir.path}/Movies/2019-11-08.mp4';
print(dirPath);
loading = false;
// if I print ($dirPath) I have /data/user/0/com.XXXXX.flutter_video_test/app_flutter/Movies/2019-11-08.mp4
});
}
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
@override
void initState() {
// TODO: implement initState
load_path_video();
super.initState();
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
body: ListView(
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
child: loading
? CircularProgressIndicator()
: NetworkPlayerLifeCycle(
'$dirPath', // with the String dirPath I have error but if I use the same path but write like this /data/user/0/com.XXXXX.flutter_video_test/app_flutter/Movies/2019-11-08.mp4 it's ok ... why ?
(BuildContext context, VideoPlayerController controller) =>
AspectRatioVideo(controller)),
),
],
),
);
}
}
class VideoPlayPause extends StatefulWidget {
VideoPlayPause(this.controller);
final VideoPlayerController controller;
@override
State createState() {
return _VideoPlayPauseState();
}
}
class _VideoPlayPauseState extends State<VideoPlayPause> {
_VideoPlayPauseState() {
listener = () {
setState(() {});
};
}
FadeAnimation imageFadeAnim =
FadeAnimation(child: const Icon(Icons.play_arrow, size: 100.0));
VoidCallback listener;
VideoPlayerController get controller => widget.controller;
@override
void initState() {
super.initState();
controller.addListener(listener);
controller.setVolume(1.0);
controller.play();
}
@override
void deactivate() {
controller.setVolume(0.0);
controller.removeListener(listener);
super.deactivate();
}
@override
Widget build(BuildContext context) {
final List<Widget> children = <Widget>[
GestureDetector(
child: VideoPlayer(controller),
onTap: () {
if (!controller.value.initialized) {
return;
}
if (controller.value.isPlaying) {
imageFadeAnim =
FadeAnimation(child: const Icon(Icons.pause, size: 100.0));
controller.pause();
} else {
imageFadeAnim =
FadeAnimation(child: const Icon(Icons.play_arrow, size: 100.0));
controller.play();
}
},
),
Align(
alignment: Alignment.bottomCenter,
child: VideoProgressIndicator(
controller,
allowScrubbing: true,
),
),
Center(child: imageFadeAnim),
Center(
child: controller.value.isBuffering
? const CircularProgressIndicator()
: null),
];
return Stack(
fit: StackFit.passthrough,
children: children,
);
}
}
class FadeAnimation extends StatefulWidget {
FadeAnimation(
{this.child, this.duration = const Duration(milliseconds: 500)});
final Widget child;
final Duration duration;
@override
_FadeAnimationState createState() => _FadeAnimationState();
}
class _FadeAnimationState extends State<FadeAnimation>
with SingleTickerProviderStateMixin {
AnimationController animationController;
@override
void initState() {
super.initState();
animationController =
AnimationController(duration: widget.duration, vsync: this);
animationController.addListener(() {
if (mounted) {
setState(() {});
}
});
animationController.forward(from: 0.0);
}
@override
void deactivate() {
animationController.stop();
super.deactivate();
}
@override
void didUpdateWidget(FadeAnimation oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.child != widget.child) {
animationController.forward(from: 0.0);
}
}
@override
void dispose() {
animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return animationController.isAnimating
? Opacity(
opacity: 1.0 - animationController.value,
child: widget.child,
)
: Container();
}
}
typedef Widget VideoWidgetBuilder(
BuildContext context, VideoPlayerController controller);
abstract class PlayerLifeCycle extends StatefulWidget {
PlayerLifeCycle(this.dataSource, this.childBuilder);
final VideoWidgetBuilder childBuilder;
final String dataSource;
}
/// A widget connecting its life cycle to a [VideoPlayerController] using
/// a data source from the network.
class NetworkPlayerLifeCycle extends PlayerLifeCycle {
NetworkPlayerLifeCycle(String dataSource, VideoWidgetBuilder childBuilder)
: super(dataSource, childBuilder);
@override
_NetworkPlayerLifeCycleState createState() => _NetworkPlayerLifeCycleState();
}
/// A widget connecting its life cycle to a [VideoPlayerController] using
/// an asset as data source
class AssetPlayerLifeCycle extends PlayerLifeCycle {
AssetPlayerLifeCycle(String dataSource, VideoWidgetBuilder childBuilder)
: super(dataSource, childBuilder);
@override
_AssetPlayerLifeCycleState createState() => _AssetPlayerLifeCycleState();
}
abstract class _PlayerLifeCycleState extends State<PlayerLifeCycle> {
VideoPlayerController controller;
@override
/// Subclasses should implement [createVideoPlayerController], which is used
/// by this method.
void initState() {
super.initState();
controller = createVideoPlayerController();
controller.addListener(() {
if (controller.value.hasError) {
print(controller.value.errorDescription);
}
});
controller.initialize();
controller.setLooping(true);
controller.play();
}
@override
void deactivate() {
super.deactivate();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return widget.childBuilder(context, controller);
}
VideoPlayerController createVideoPlayerController();
}
class _NetworkPlayerLifeCycleState extends _PlayerLifeCycleState {
@override
VideoPlayerController createVideoPlayerController() {
return VideoPlayerController.network(widget.dataSource);
}
}
class _AssetPlayerLifeCycleState extends _PlayerLifeCycleState {
@override
VideoPlayerController createVideoPlayerController() {
return VideoPlayerController.asset(widget.dataSource);
}
}
/// A filler card to show the video in a list of scrolling contents.
Widget buildCard(String title) {
return Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: const Icon(Icons.airline_seat_flat_angled),
title: Text(title),
),
// TODO(jackson): Remove when deprecation is on stable branch
// ignore: deprecated_member_use
ButtonTheme.bar(
child: ButtonBar(
children: <Widget>[
FlatButton(
child: const Text('BUY TICKETS'),
onPressed: () {
/* ... */
},
),
FlatButton(
child: const Text('SELL TICKETS'),
onPressed: () {
/* ... */
},
),
],
),
),
],
),
);
}
class VideoInListOfCards extends StatelessWidget {
VideoInListOfCards(this.controller);
final VideoPlayerController controller;
@override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
buildCard("Item a"),
buildCard("Item b"),
buildCard("Item c"),
buildCard("Item d"),
buildCard("Item e"),
buildCard("Item f"),
buildCard("Item g"),
Card(
child: Column(children: <Widget>[
Column(
children: <Widget>[
const ListTile(
leading: Icon(Icons.cake),
title: Text("Video video"),
),
Stack(
alignment: FractionalOffset.bottomRight +
const FractionalOffset(-0.1, -0.1),
children: <Widget>[
AspectRatioVideo(controller),
Image.asset('assets/flutter-mark-square-64.png'),
]),
],
),
])),
buildCard("Item h"),
buildCard("Item i"),
buildCard("Item j"),
buildCard("Item k"),
buildCard("Item l"),
],
);
}
}
class AspectRatioVideo extends StatefulWidget {
AspectRatioVideo(this.controller);
final VideoPlayerController controller;
@override
AspectRatioVideoState createState() => AspectRatioVideoState();
}
class AspectRatioVideoState extends State<AspectRatioVideo> {
VideoPlayerController get controller => widget.controller;
bool initialized = false;
VoidCallback listener;
@override
void initState() {
super.initState();
listener = () {
if (!mounted) {
return;
}
if (initialized != controller.value.initialized) {
initialized = controller.value.initialized;
setState(() {});
}
};
controller.addListener(listener);
}
@override
Widget build(BuildContext context) {
if (initialized) {
return Center(
child: AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: VideoPlayPause(controller),
),
);
} else {
return Container();
}
}
}
您好,我尝试在从相机录制视频并将此视频保存在设备中后显示视频 (path: /data/user/0/com.XXXXX.flutter_video_test/app_flutter/Movies/2019-11-08.mp4
)
我使用了flutter.dev中https://pub.dev/packages/video_player的例子。当我完整地写路径时还可以,但是当我使用字符串变量做同样的事情时,我就出错了。
这是问题的一部分
String dirPath;
Future<String> load_path_video() async {
final Directory extDir = await getApplicationDocumentsDirectory();
setState(() {
dirPath = '${extDir.path}/Movies/2019-11-08.mp4';
// if I print ($dirPath) I have /data/user/0/com.XXXXX.flutter_video_test/app_flutter/Movies/2019-11-08.mp4
});
}
@override
void initState() {
// TODO: implement initState
load_path_video();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
child: NetworkPlayerLifeCycle(
'$dirPath', // with the String dirPath I have error but if I use the same path but write like this /data/user/0/com.XXXXX.flutter_video_test/app_flutter/Movies/2019-11-08.mp4 it's ok ... why?
(BuildContext context,
VideoPlayerController controller) =>
AspectRatioVideo(controller)),
),
],
),
);
}
}
这里是我使用字符串变量时的错误
E/ExoPlayerImplInternal(20818): Source error.
E/ExoPlayerImplInternal(20818): com.google.android.exoplayer2.upstream.FileDataSource$FileDataSourceException: java.io.FileNotFoundException: null: open failed: ENOENT (No such file or directory)
E/ExoPlayerImplInternal(20818): at com.google.android.exoplayer2.upstream.FileDataSource.open(FileDataSource.java:73)
E/ExoPlayerImplInternal(20818): at com.google.android.exoplayer2.upstream.DefaultDataSource.open(DefaultDataSource.java:250)
E/ExoPlayerImplInternal(20818): at com.google.android.exoplayer2.upstream.StatsDataSource.open(StatsDataSource.java:83)
E/ExoPlayerImplInternal(20818): at com.google.android.exoplayer2.source.ExtractorMediaPeriod$ExtractingLoadable.load(ExtractorMediaPeriod.java:885)
E/ExoPlayerImplInternal(20818): at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:381)
E/ExoPlayerImplInternal(20818): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
E/ExoPlayerImplInternal(20818): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
E/ExoPlayerImplInternal(20818): at java.lang.Thread.run(Thread.java:776)
E/ExoPlayerImplInternal(20818): Caused by: java.io.FileNotFoundException: null: open failed: ENOENT (No such file or directory)
E/ExoPlayerImplInternal(20818): at libcore.io.IoBridge.open(IoBridge.java:455)
E/ExoPlayerImplInternal(20818): at java.io.RandomAccessFile.<init>(RandomAccessFile.java:247)
E/ExoPlayerImplInternal(20818): at java.io.RandomAccessFile.<init>(RandomAccessFile.java:128)
E/ExoPlayerImplInternal(20818): at com.google.android.exoplayer2.upstream.FileDataSource.open(FileDataSource.java:65)
E/ExoPlayerImplInternal(20818): ... 7 more
E/ExoPlayerImplInternal(20818): Caused by: android.system.ErrnoException: open failed: ENOENT (No such file or directory)
E/ExoPlayerImplInternal(20818): at libcore.io.Posix.open(Native Method)
E/ExoPlayerImplInternal(20818): at libcore.io.BlockGuardOs.open(BlockGuardOs.java:187)
E/ExoPlayerImplInternal(20818): at libcore.io.IoBridge.open(IoBridge.java:441)
E/ExoPlayerImplInternal(20818): ... 10 more
I/flutter (20818): Video player had error com.google.android.exoplayer2.ExoPlaybackException: com.google.android.exoplayer2.upstream.FileDataSource$FileDataSourceException: java.io.FileNotFoundException: null: open failed: ENOENT (No such file or directory)
从错误来看,您似乎正在尝试显示一个不存在的文件 (No such file or directory
),这让我认为您收到错误是因为 dirPath
是 null
.
我建议您在代码中添加一个 if
,如下所示:
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: <Widget>[
if(dirPath != null)
Container(
padding: const EdgeInsets.all(20),
child: NetworkPlayerLifeCycle(
dirPath,
(BuildContext context,
VideoPlayerController controller) =>
AspectRatioVideo(controller)),
),
],
),
);
}
这样,只有在 dirPath
不是 null
时才会显示 Container
小部件。
对您的代码的一个观察:
- 当你的 dirPath
已经是一个字符串时,你不需要使用 '$dirPath'
,你可以直接使用变量
在 initState() await load_path_video() 期间,dirPath 仍然为 null
您需要一个 bool loading 来检查 await getApplicationDocumentsDirectory 是否完成
您可以复制粘贴 运行 下面的完整代码并确保文件在路径
代码片段
Future<String> load_path_video() async {
loading = true;
final Directory extDir = await getApplicationDocumentsDirectory();
setState(() {
dirPath = '${extDir.path}/Movies/2019-11-08.mp4';
print(dirPath);
loading = false;
// if I print ($dirPath) I have /data/user/0/com.XXXXX.flutter_video_test/app_flutter/Movies/2019-11-08.mp4
});
}
Container(
padding: const EdgeInsets.all(20),
child: loading
? CircularProgressIndicator()
: NetworkPlayerLifeCycle(
'$dirPath', // with the String dirPath I have error but if I use the same path but write like this /data/user/0/com.XXXXX.flutter_video_test/app_flutter/Movies/2019-11-08.mp4 it's ok ... why ?
(BuildContext context, VideoPlayerController controller) =>
AspectRatioVideo(controller)),
工作演示
完整的工作代码
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
import 'package:video_player/video_player.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
String dirPath;
bool loading = false;
Future<String> load_path_video() async {
loading = true;
final Directory extDir = await getApplicationDocumentsDirectory();
setState(() {
dirPath = '${extDir.path}/Movies/2019-11-08.mp4';
print(dirPath);
loading = false;
// if I print ($dirPath) I have /data/user/0/com.XXXXX.flutter_video_test/app_flutter/Movies/2019-11-08.mp4
});
}
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
@override
void initState() {
// TODO: implement initState
load_path_video();
super.initState();
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
body: ListView(
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
child: loading
? CircularProgressIndicator()
: NetworkPlayerLifeCycle(
'$dirPath', // with the String dirPath I have error but if I use the same path but write like this /data/user/0/com.XXXXX.flutter_video_test/app_flutter/Movies/2019-11-08.mp4 it's ok ... why ?
(BuildContext context, VideoPlayerController controller) =>
AspectRatioVideo(controller)),
),
],
),
);
}
}
class VideoPlayPause extends StatefulWidget {
VideoPlayPause(this.controller);
final VideoPlayerController controller;
@override
State createState() {
return _VideoPlayPauseState();
}
}
class _VideoPlayPauseState extends State<VideoPlayPause> {
_VideoPlayPauseState() {
listener = () {
setState(() {});
};
}
FadeAnimation imageFadeAnim =
FadeAnimation(child: const Icon(Icons.play_arrow, size: 100.0));
VoidCallback listener;
VideoPlayerController get controller => widget.controller;
@override
void initState() {
super.initState();
controller.addListener(listener);
controller.setVolume(1.0);
controller.play();
}
@override
void deactivate() {
controller.setVolume(0.0);
controller.removeListener(listener);
super.deactivate();
}
@override
Widget build(BuildContext context) {
final List<Widget> children = <Widget>[
GestureDetector(
child: VideoPlayer(controller),
onTap: () {
if (!controller.value.initialized) {
return;
}
if (controller.value.isPlaying) {
imageFadeAnim =
FadeAnimation(child: const Icon(Icons.pause, size: 100.0));
controller.pause();
} else {
imageFadeAnim =
FadeAnimation(child: const Icon(Icons.play_arrow, size: 100.0));
controller.play();
}
},
),
Align(
alignment: Alignment.bottomCenter,
child: VideoProgressIndicator(
controller,
allowScrubbing: true,
),
),
Center(child: imageFadeAnim),
Center(
child: controller.value.isBuffering
? const CircularProgressIndicator()
: null),
];
return Stack(
fit: StackFit.passthrough,
children: children,
);
}
}
class FadeAnimation extends StatefulWidget {
FadeAnimation(
{this.child, this.duration = const Duration(milliseconds: 500)});
final Widget child;
final Duration duration;
@override
_FadeAnimationState createState() => _FadeAnimationState();
}
class _FadeAnimationState extends State<FadeAnimation>
with SingleTickerProviderStateMixin {
AnimationController animationController;
@override
void initState() {
super.initState();
animationController =
AnimationController(duration: widget.duration, vsync: this);
animationController.addListener(() {
if (mounted) {
setState(() {});
}
});
animationController.forward(from: 0.0);
}
@override
void deactivate() {
animationController.stop();
super.deactivate();
}
@override
void didUpdateWidget(FadeAnimation oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.child != widget.child) {
animationController.forward(from: 0.0);
}
}
@override
void dispose() {
animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return animationController.isAnimating
? Opacity(
opacity: 1.0 - animationController.value,
child: widget.child,
)
: Container();
}
}
typedef Widget VideoWidgetBuilder(
BuildContext context, VideoPlayerController controller);
abstract class PlayerLifeCycle extends StatefulWidget {
PlayerLifeCycle(this.dataSource, this.childBuilder);
final VideoWidgetBuilder childBuilder;
final String dataSource;
}
/// A widget connecting its life cycle to a [VideoPlayerController] using
/// a data source from the network.
class NetworkPlayerLifeCycle extends PlayerLifeCycle {
NetworkPlayerLifeCycle(String dataSource, VideoWidgetBuilder childBuilder)
: super(dataSource, childBuilder);
@override
_NetworkPlayerLifeCycleState createState() => _NetworkPlayerLifeCycleState();
}
/// A widget connecting its life cycle to a [VideoPlayerController] using
/// an asset as data source
class AssetPlayerLifeCycle extends PlayerLifeCycle {
AssetPlayerLifeCycle(String dataSource, VideoWidgetBuilder childBuilder)
: super(dataSource, childBuilder);
@override
_AssetPlayerLifeCycleState createState() => _AssetPlayerLifeCycleState();
}
abstract class _PlayerLifeCycleState extends State<PlayerLifeCycle> {
VideoPlayerController controller;
@override
/// Subclasses should implement [createVideoPlayerController], which is used
/// by this method.
void initState() {
super.initState();
controller = createVideoPlayerController();
controller.addListener(() {
if (controller.value.hasError) {
print(controller.value.errorDescription);
}
});
controller.initialize();
controller.setLooping(true);
controller.play();
}
@override
void deactivate() {
super.deactivate();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return widget.childBuilder(context, controller);
}
VideoPlayerController createVideoPlayerController();
}
class _NetworkPlayerLifeCycleState extends _PlayerLifeCycleState {
@override
VideoPlayerController createVideoPlayerController() {
return VideoPlayerController.network(widget.dataSource);
}
}
class _AssetPlayerLifeCycleState extends _PlayerLifeCycleState {
@override
VideoPlayerController createVideoPlayerController() {
return VideoPlayerController.asset(widget.dataSource);
}
}
/// A filler card to show the video in a list of scrolling contents.
Widget buildCard(String title) {
return Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: const Icon(Icons.airline_seat_flat_angled),
title: Text(title),
),
// TODO(jackson): Remove when deprecation is on stable branch
// ignore: deprecated_member_use
ButtonTheme.bar(
child: ButtonBar(
children: <Widget>[
FlatButton(
child: const Text('BUY TICKETS'),
onPressed: () {
/* ... */
},
),
FlatButton(
child: const Text('SELL TICKETS'),
onPressed: () {
/* ... */
},
),
],
),
),
],
),
);
}
class VideoInListOfCards extends StatelessWidget {
VideoInListOfCards(this.controller);
final VideoPlayerController controller;
@override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
buildCard("Item a"),
buildCard("Item b"),
buildCard("Item c"),
buildCard("Item d"),
buildCard("Item e"),
buildCard("Item f"),
buildCard("Item g"),
Card(
child: Column(children: <Widget>[
Column(
children: <Widget>[
const ListTile(
leading: Icon(Icons.cake),
title: Text("Video video"),
),
Stack(
alignment: FractionalOffset.bottomRight +
const FractionalOffset(-0.1, -0.1),
children: <Widget>[
AspectRatioVideo(controller),
Image.asset('assets/flutter-mark-square-64.png'),
]),
],
),
])),
buildCard("Item h"),
buildCard("Item i"),
buildCard("Item j"),
buildCard("Item k"),
buildCard("Item l"),
],
);
}
}
class AspectRatioVideo extends StatefulWidget {
AspectRatioVideo(this.controller);
final VideoPlayerController controller;
@override
AspectRatioVideoState createState() => AspectRatioVideoState();
}
class AspectRatioVideoState extends State<AspectRatioVideo> {
VideoPlayerController get controller => widget.controller;
bool initialized = false;
VoidCallback listener;
@override
void initState() {
super.initState();
listener = () {
if (!mounted) {
return;
}
if (initialized != controller.value.initialized) {
initialized = controller.value.initialized;
setState(() {});
}
};
controller.addListener(listener);
}
@override
Widget build(BuildContext context) {
if (initialized) {
return Center(
child: AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: VideoPlayPause(controller),
),
);
} else {
return Container();
}
}
}