From 6f85e069d545c3f6eb1c5a5324007a71401c56a8 Mon Sep 17 00:00:00 2001 From: ianko Date: Thu, 2 May 2019 16:20:02 -0400 Subject: [PATCH 1/9] Fix race condition on disposing the VideoController. --- packages/video_player/CHANGELOG.md | 5 +++++ packages/video_player/lib/video_player.dart | 4 ++++ packages/video_player/pubspec.yaml | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/video_player/CHANGELOG.md b/packages/video_player/CHANGELOG.md index e3e9d9d01691..1584fcbb0db8 100644 --- a/packages/video_player/CHANGELOG.md +++ b/packages/video_player/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.10.0+9 + +* Fix race condition while disposing the VideoController. + + ## 0.10.0+8 * iOS: Fix an issue where the player sends initialization message incorrectly. diff --git a/packages/video_player/lib/video_player.dart b/packages/video_player/lib/video_player.dart index ce4ac70cff43..80d78a7c4633 100644 --- a/packages/video_player/lib/video_player.dart +++ b/packages/video_player/lib/video_player.dart @@ -228,6 +228,10 @@ class VideoPlayerController extends ValueNotifier { } void eventListener(dynamic event) { + if (_isDisposed) { + return; + } + final Map map = event; switch (map['event']) { case 'initialized': diff --git a/packages/video_player/pubspec.yaml b/packages/video_player/pubspec.yaml index e469a120afa1..9959d5ef4033 100644 --- a/packages/video_player/pubspec.yaml +++ b/packages/video_player/pubspec.yaml @@ -2,7 +2,7 @@ name: video_player description: Flutter plugin for displaying inline video with other Flutter widgets on Android and iOS. author: Flutter Team -version: 0.10.0+8 +version: 0.10.0+9 homepage: https://github.com/flutter/plugins/tree/master/packages/video_player flutter: From ffedc7ec2a74774fd525f757d19af9b77cfb5191 Mon Sep 17 00:00:00 2001 From: ianko Date: Thu, 13 Jun 2019 13:59:46 -0400 Subject: [PATCH 2/9] revert videoplayer 0.10.1 changes --- packages/video_player/CHANGELOG.md | 5 +++++ packages/video_player/ios/Classes/VideoPlayerPlugin.m | 4 ---- packages/video_player/pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/video_player/CHANGELOG.md b/packages/video_player/CHANGELOG.md index 559456c14908..391c57517804 100644 --- a/packages/video_player/CHANGELOG.md +++ b/packages/video_player/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.10.2 + +* iOS: Revert the changes made in version `0.10.1` that made live streams not able to initialize due to the lack of duration. + + ## 0.10.1+3 * Add missing template type parameter to `invokeMethod` calls. diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index 4aea59f34e8b..759db4c1caf0 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -305,10 +305,6 @@ - (void)sendInitialized { if (height == CGSizeZero.height && width == CGSizeZero.width) { return; } - // The player may be initialized but still needs to determine the duration. - if ([self duration] == 0) { - return; - } _isInitialized = true; _eventSink(@{ diff --git a/packages/video_player/pubspec.yaml b/packages/video_player/pubspec.yaml index 8ed4025501d0..3c18e734c964 100644 --- a/packages/video_player/pubspec.yaml +++ b/packages/video_player/pubspec.yaml @@ -2,7 +2,7 @@ name: video_player description: Flutter plugin for displaying inline video with other Flutter widgets on Android and iOS. author: Flutter Team -version: 0.10.1+3 +version: 0.10.2 homepage: https://github.com/flutter/plugins/tree/master/packages/video_player flutter: From a20c8d229cb31c6c0675f51de118faedd57501a3 Mon Sep 17 00:00:00 2001 From: Duy Bao Nguyen Date: Mon, 1 Jul 2019 14:35:47 +0700 Subject: [PATCH 3/9] handle livestream better --- packages/video_player/example/lib/main.dart | 3 ++- .../video_player/ios/Classes/VideoPlayerPlugin.m | 13 +++++++++++++ packages/video_player/lib/video_player.dart | 16 +++++++++++++++- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/video_player/example/lib/main.dart b/packages/video_player/example/lib/main.dart index 320df27c8e3e..9e3fdb2a3e8b 100644 --- a/packages/video_player/example/lib/main.dart +++ b/packages/video_player/example/lib/main.dart @@ -390,7 +390,8 @@ void main() { Container( padding: const EdgeInsets.all(20), child: NetworkPlayerLifeCycle( - 'http://184.72.239.149/vod/smil:BigBuckBunny.smil/playlist.m3u8', + 'https://cbsnewshd-lh.akamaihd.net/i/CBSNHD_7@199302/master.m3u8', + // 'http://184.72.239.149/vod/smil:BigBuckBunny.smil/playlist.m3u8', (BuildContext context, VideoPlayerController controller) => AspectRatioVideo(controller), diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index 4aea59f34e8b..d12bf49e49bc 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -335,6 +335,17 @@ - (int64_t)position { } - (int64_t)duration { + if(CMTIME_IS_INDEFINITE([[_player currentItem] duration])) { + int64_t maxBuffering = 0; + for (NSValue* rangeValue in [_player currentItem].loadedTimeRanges) { + CMTimeRange range = [rangeValue CMTimeRangeValue]; + int64_t start = FLTCMTimeToMillis(range.start); + if (start > maxBuffering) { + maxBuffering = start + FLTCMTimeToMillis(range.duration); + } + } + return maxBuffering; + } return FLTCMTimeToMillis([[_player currentItem] duration]); } @@ -498,6 +509,8 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { result(nil); } else if ([@"position" isEqualToString:call.method]) { result(@([player position])); + } else if ([@"duration" isEqualToString:call.method]) { + result(@([player duration])); } else if ([@"seekTo" isEqualToString:call.method]) { [player seekTo:[argsMap[@"location"] intValue]]; result(nil); diff --git a/packages/video_player/lib/video_player.dart b/packages/video_player/lib/video_player.dart index fb42096d9709..417e22cc4065 100644 --- a/packages/video_player/lib/video_player.dart +++ b/packages/video_player/lib/video_player.dart @@ -331,10 +331,11 @@ class VideoPlayerController extends ValueNotifier { return; } final Duration newPosition = await position; + final Duration newDuration = await duration; if (_isDisposed) { return; } - value = value.copyWith(position: newPosition); + value = value.copyWith(position: newPosition, duration: newDuration); }, ); } else { @@ -369,6 +370,19 @@ class VideoPlayerController extends ValueNotifier { ); } + /// The duration in the current video. + Future get duration async { + if (_isDisposed) { + return null; + } + return Duration( + milliseconds: await _channel.invokeMethod( + 'duration', + {'textureId': _textureId}, + ), + ); + } + Future seekTo(Duration moment) async { if (_isDisposed) { return; From bd6a6ad6514f75791e0f34cfa1d41411d9eea473 Mon Sep 17 00:00:00 2001 From: ianko Date: Fri, 5 Jul 2019 10:39:05 -0400 Subject: [PATCH 4/9] add progress indicator while initializing --- packages/video_player/example/lib/player.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/video_player/example/lib/player.dart b/packages/video_player/example/lib/player.dart index d62ba7a8364a..f90c87fd381b 100644 --- a/packages/video_player/example/lib/player.dart +++ b/packages/video_player/example/lib/player.dart @@ -85,7 +85,10 @@ class _PlayerState extends State { @override Widget build(BuildContext context) { if (!initialized) { - return const SizedBox(); + return const AspectRatio( + aspectRatio: 16.0 / 9.0, + child: Center(child: CircularProgressIndicator()), + ); } final List children = [ @@ -101,7 +104,7 @@ class _PlayerState extends State { } if (controller.value.isBuffering) { - children.add(Center(child: const CircularProgressIndicator())); + children.add(const Center(child: CircularProgressIndicator())); } return Container( From f9a531d18da05ac8054cb16f2f32367c84c4aa0b Mon Sep 17 00:00:00 2001 From: ianko Date: Fri, 5 Jul 2019 11:07:57 -0400 Subject: [PATCH 5/9] set volume on init and dispose --- packages/video_player/example/lib/video_provider.dart | 3 +++ packages/video_player/lib/video_player.dart | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/video_player/example/lib/video_provider.dart b/packages/video_player/example/lib/video_provider.dart index f023beb57bdc..38264c1e70b1 100644 --- a/packages/video_player/example/lib/video_provider.dart +++ b/packages/video_player/example/lib/video_provider.dart @@ -66,10 +66,13 @@ class _VideoProviderState extends State { default: throw Exception('Could not create the VideoPlayerController.'); } + + controller?.setVolume(1.0); } @override void dispose() { + controller?.setVolume(0.0); controller?.dispose(); super.dispose(); } diff --git a/packages/video_player/lib/video_player.dart b/packages/video_player/lib/video_player.dart index c6e9935b8f67..d5ba61adf46f 100644 --- a/packages/video_player/lib/video_player.dart +++ b/packages/video_player/lib/video_player.dart @@ -122,7 +122,7 @@ class VideoPlayerValue { 'buffered: [${buffered.join(', ')}], ' 'isPlaying: $isPlaying, ' 'isLooping: $isLooping, ' - 'isBuffering: $isBuffering' + 'isBuffering: $isBuffering, ' 'volume: $volume, ' 'errorDescription: $errorDescription)'; } From f243d3aaf5faa4b0d42d7472989e0f30d4eab938 Mon Sep 17 00:00:00 2001 From: ianko Date: Fri, 5 Jul 2019 13:20:36 -0400 Subject: [PATCH 6/9] format --- packages/video_player/ios/Classes/VideoPlayerPlugin.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index 87d4af232569..f69cccbf727c 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -331,7 +331,7 @@ - (int64_t)position { } - (int64_t)duration { - if(CMTIME_IS_INDEFINITE([[_player currentItem] duration])) { + if (CMTIME_IS_INDEFINITE([[_player currentItem] duration])) { int64_t maxBuffering = 0; for (NSValue* rangeValue in [_player currentItem].loadedTimeRanges) { CMTimeRange range = [rangeValue CMTimeRangeValue]; From 209313959e07fa7944c62ced83c3a0b070c64448 Mon Sep 17 00:00:00 2001 From: ianko Date: Fri, 5 Jul 2019 13:21:31 -0400 Subject: [PATCH 7/9] update CHANGELOG --- packages/video_player/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/video_player/CHANGELOG.md b/packages/video_player/CHANGELOG.md index e0414f85ddc4..2f19315280e4 100644 --- a/packages/video_player/CHANGELOG.md +++ b/packages/video_player/CHANGELOG.md @@ -2,6 +2,7 @@ * iOS: Revert the changes made in version `0.10.1` that made live streams not able to initialize due to the lack of duration. * Fix race condition while disposing the VideoController. +* Refactor the example app. ## 0.10.1+3 From 138bfd3b2b30d4a0118d698649edc39f7f89fa8d Mon Sep 17 00:00:00 2001 From: ianko Date: Fri, 5 Jul 2019 13:35:20 -0400 Subject: [PATCH 8/9] format --- packages/video_player/ios/Classes/VideoPlayerPlugin.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index f69cccbf727c..7a3f9f2e3c22 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -334,11 +334,11 @@ - (int64_t)duration { if (CMTIME_IS_INDEFINITE([[_player currentItem] duration])) { int64_t maxBuffering = 0; for (NSValue* rangeValue in [_player currentItem].loadedTimeRanges) { - CMTimeRange range = [rangeValue CMTimeRangeValue]; - int64_t start = FLTCMTimeToMillis(range.start); - if (start > maxBuffering) { - maxBuffering = start + FLTCMTimeToMillis(range.duration); - } + CMTimeRange range = [rangeValue CMTimeRangeValue]; + int64_t start = FLTCMTimeToMillis(range.start); + if (start > maxBuffering) { + maxBuffering = start + FLTCMTimeToMillis(range.duration); + } } return maxBuffering; } From c5fdba6ce3faae634638e0a3535ae5235bd8ce4d Mon Sep 17 00:00:00 2001 From: ianko Date: Wed, 10 Jul 2019 14:05:41 -0400 Subject: [PATCH 9/9] separated changes to a single feaure --- packages/video_player/CHANGELOG.md | 4 +- packages/video_player/example/lib/main.dart | 426 ++++++++++++++++-- packages/video_player/example/lib/player.dart | 221 --------- .../example/lib/player_values.dart | 75 --- .../example/lib/tabs/asset_tab.dart | 26 -- .../example/lib/tabs/list_tab.dart | 84 ---- .../example/lib/tabs/live_tab.dart | 23 - .../example/lib/tabs/remote_tab.dart | 26 -- .../example/lib/video_provider.dart | 100 ---- packages/video_player/lib/video_player.dart | 3 - 10 files changed, 397 insertions(+), 591 deletions(-) delete mode 100644 packages/video_player/example/lib/player.dart delete mode 100644 packages/video_player/example/lib/player_values.dart delete mode 100644 packages/video_player/example/lib/tabs/asset_tab.dart delete mode 100644 packages/video_player/example/lib/tabs/list_tab.dart delete mode 100644 packages/video_player/example/lib/tabs/live_tab.dart delete mode 100644 packages/video_player/example/lib/tabs/remote_tab.dart delete mode 100644 packages/video_player/example/lib/video_provider.dart diff --git a/packages/video_player/CHANGELOG.md b/packages/video_player/CHANGELOG.md index 2f19315280e4..0ed4ddcaf7ec 100644 --- a/packages/video_player/CHANGELOG.md +++ b/packages/video_player/CHANGELOG.md @@ -1,8 +1,6 @@ ## 0.10.2 -* iOS: Revert the changes made in version `0.10.1` that made live streams not able to initialize due to the lack of duration. -* Fix race condition while disposing the VideoController. -* Refactor the example app. +* iOS: Revert the changes made in version `0.10.1` that made live streams not able to initialize on the absence of `duration`. ## 0.10.1+3 diff --git a/packages/video_player/example/lib/main.dart b/packages/video_player/example/lib/main.dart index b89e85290074..320df27c8e3e 100644 --- a/packages/video_player/example/lib/main.dart +++ b/packages/video_player/example/lib/main.dart @@ -5,56 +5,422 @@ /// An example of using the plugin, controlling lifecycle and playback of the /// video. +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import './tabs/asset_tab.dart'; -import './tabs/list_tab.dart'; -import './tabs/live_tab.dart'; -import './tabs/remote_tab.dart'; -import './video_provider.dart'; - -// video urls -const String _kAssetPath = 'assets/Butterfly-209.mp4'; -const String _kRemoteUrl = - 'http://184.72.239.149/vod/smil:BigBuckBunny.smil/playlist.m3u8'; -const String _kLiveUrl = - 'https://videos3.earthcam.com/fecnetwork/16560.flv/playlist.m3u8'; +import 'package:video_player/video_player.dart'; + +/// Controls play and pause of [controller]. +/// +/// Toggles play/pause on tap (accompanied by a fading status icon). +/// +/// Plays (looping) on initialization, and mutes on deactivation. +class VideoPlayPause extends StatefulWidget { + VideoPlayPause(this.controller); + + final VideoPlayerController controller; + + @override + State createState() { + return _VideoPlayPauseState(); + } +} + +class _VideoPlayPauseState extends State { + _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 children = [ + 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 + 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 { + 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: [ + ListTile( + leading: const Icon(Icons.airline_seat_flat_angled), + title: Text(title), + ), + ButtonTheme.bar( + child: ButtonBar( + children: [ + 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: [ + 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: [ + Column( + children: [ + const ListTile( + leading: Icon(Icons.cake), + title: Text("Video video"), + ), + Stack( + alignment: FractionalOffset.bottomRight + + const FractionalOffset(-0.1, -0.1), + children: [ + 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 { + 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(); + } + } +} void main() { runApp( MaterialApp( home: DefaultTabController( - length: 4, + length: 3, child: Scaffold( appBar: AppBar( title: const Text('Video player example'), bottom: const TabBar( isScrollable: true, tabs: [ - Tab(icon: Icon(Icons.cloud), text: "Remote"), - Tab(icon: Icon(Icons.live_tv), text: "Live"), + Tab( + icon: Icon(Icons.cloud), + text: "Remote", + ), Tab(icon: Icon(Icons.insert_drive_file), text: "Asset"), Tab(icon: Icon(Icons.list), text: "List example"), ], ), ), body: TabBarView( - children: const [ - VideoControllerProvider.network( - source: _kRemoteUrl, - child: RemoteTab(), - ), - VideoControllerProvider.network( - source: _kLiveUrl, - child: LiveTab(), - ), - VideoControllerProvider.asset( - source: _kAssetPath, - child: AssetTab(), + children: [ + SingleChildScrollView( + child: Column( + children: [ + Container( + padding: const EdgeInsets.only(top: 20.0), + ), + const Text('With remote m3u8'), + Container( + padding: const EdgeInsets.all(20), + child: NetworkPlayerLifeCycle( + 'http://184.72.239.149/vod/smil:BigBuckBunny.smil/playlist.m3u8', + (BuildContext context, + VideoPlayerController controller) => + AspectRatioVideo(controller), + ), + ), + ], + ), ), - VideoControllerProvider.asset( - source: _kAssetPath, - child: ListTab(), + SingleChildScrollView( + child: Column( + children: [ + Container( + padding: const EdgeInsets.only(top: 20.0), + ), + const Text('With assets mp4'), + Container( + padding: const EdgeInsets.all(20), + child: AssetPlayerLifeCycle( + 'assets/Butterfly-209.mp4', + (BuildContext context, + VideoPlayerController controller) => + AspectRatioVideo(controller)), + ), + ], + ), ), + AssetPlayerLifeCycle( + 'assets/Butterfly-209.mp4', + (BuildContext context, VideoPlayerController controller) => + VideoInListOfCards(controller)), ], ), ), diff --git a/packages/video_player/example/lib/player.dart b/packages/video_player/example/lib/player.dart deleted file mode 100644 index f90c87fd381b..000000000000 --- a/packages/video_player/example/lib/player.dart +++ /dev/null @@ -1,221 +0,0 @@ -import 'dart:async'; -import 'package:flutter/material.dart'; -import './video_provider.dart'; - -class Player extends StatefulWidget { - const Player({this.isLive = false}); - - final bool isLive; - - @override - _PlayerState createState() => _PlayerState(); -} - -class _PlayerState extends State { - VideoPlayerController controller; - bool initialized = false; - bool playing = false; - bool buffering = true; - bool active = true; - PlayPauseButton playPauseButton; - - @override - void initState() { - super.initState(); - controller = VideoControllerProvider.of(context); - _initialize(); - } - - @override - void dispose() async { - controller?.removeListener(_listener); - super.dispose(); - } - - Future _initialize() async { - controller.addListener(_listener); - await controller.initialize(); - await controller.play(); - } - - Future _playPause() async { - if (!controller.value.initialized) { - return; - } - - if (controller.value.isPlaying) { - await controller.pause(); - } else { - await controller.play(); - } - } - - void _listener() { - if (!mounted) { - return; - } - - if (!initialized && controller.value.initialized) { - return _refresh(); - } - - if (playing != controller.value.isPlaying) { - return _refresh(); - } - - if (buffering != controller.value.isBuffering) { - return _refresh(); - } - } - - void _refresh() { - if (!active) { - return; - } - - setState(() { - initialized = controller.value.initialized; - buffering = controller.value.isBuffering; - playing = controller.value.isPlaying; - playPauseButton = - playing ? PlayPauseButton.playing() : PlayPauseButton.paused(); - }); - } - - @override - Widget build(BuildContext context) { - if (!initialized) { - return const AspectRatio( - aspectRatio: 16.0 / 9.0, - child: Center(child: CircularProgressIndicator()), - ); - } - - final List children = [ - GestureDetector( - onTap: _playPause, - child: VideoPlayer(controller), - ), - widget.isLive ? _buildLiveIndicator() : _buildProgressIndicator(), - ]; - - if (playPauseButton != null) { - children.add(Center(child: playPauseButton)); - } - - if (controller.value.isBuffering) { - children.add(const Center(child: CircularProgressIndicator())); - } - - return Container( - child: AspectRatio( - aspectRatio: controller.value.aspectRatio, - child: Stack( - fit: StackFit.passthrough, - children: children, - ), - ), - ); - } - - Widget _buildLiveIndicator() { - return Align( - alignment: Alignment.topLeft, - child: Container( - margin: const EdgeInsets.all(8.0), - padding: const EdgeInsets.symmetric(vertical: 3.0, horizontal: 8.0), - decoration: const BoxDecoration( - color: Colors.red, - borderRadius: BorderRadius.all(Radius.circular(4.0)), - ), - child: const Text( - 'LIVE', - style: TextStyle( - color: Colors.white, - fontSize: 12.0, - fontWeight: FontWeight.w600, - ), - ), - ), - ); - } - - Widget _buildProgressIndicator() { - return Align( - alignment: Alignment.bottomCenter, - child: VideoProgressIndicator(controller, allowScrubbing: true), - ); - } -} - -class PlayPauseButton extends StatefulWidget { - PlayPauseButton({ - this.child, - this.duration = const Duration(milliseconds: 500), - }); - - PlayPauseButton.playing() - : child = const Icon(Icons.play_arrow, size: 100.0), - duration = const Duration(milliseconds: 500); - - PlayPauseButton.paused() - : child = const Icon(Icons.pause, size: 100.0), - duration = const Duration(milliseconds: 500); - - final Widget child; - final Duration duration; - - @override - _PlayPauseButtonState createState() => _PlayPauseButtonState(); -} - -class _PlayPauseButtonState extends State - 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(PlayPauseButton 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(); - } -} diff --git a/packages/video_player/example/lib/player_values.dart b/packages/video_player/example/lib/player_values.dart deleted file mode 100644 index 450e2089b427..000000000000 --- a/packages/video_player/example/lib/player_values.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'package:flutter/material.dart'; -import './video_provider.dart'; - -class PlayerValues extends StatefulWidget { - const PlayerValues({Key key}); - - @override - _PlayerValuesState createState() => _PlayerValuesState(); -} - -class _PlayerValuesState extends State { - VideoPlayerController controller; - - @override - void initState() { - super.initState(); - controller = VideoControllerProvider.of(context); - controller.addListener(_listener); - } - - @override - void dispose() { - controller.removeListener(_listener); - super.dispose(); - } - - void _listener() { - if (!mounted) { - return; - } - - setState(() {}); - } - - @override - Widget build(BuildContext context) { - final VideoPlayerValue v = controller.value; - - if (!v.initialized) { - return const SizedBox(); - } - - if (v.errorDescription != null) { - return Text(v.errorDescription, style: TextStyle(color: Colors.red)); - } - - final List children = [ - _row('Status:', Icon(v.isPlaying ? Icons.play_arrow : Icons.pause)), - _row('Size: ', Text('${v.size.width} x ${v.size.height}')), - _row('Volume: ', Text(v.volume.toString())), - _row('Looping? ', Text(v.isLooping.toString())), - _row('Buffering? ', Text(v.isBuffering.toString())), - _row('Position: ', Text(v.position.toString())), - _row('Duration: ', Text(v.duration.toString())), - ]; - - if (v.buffered.isEmpty) { - children.add(_row('Buffered: ', const Text('[]'))); - } else { - children.add(_row('Buffered: ', - Text('[${v.buffered.last.start}, ${v.buffered.last.end}]'))); - } - - return Column(children: children); - } - - Widget _row(String title, Widget child) { - return Row( - children: [ - Text(title, style: TextStyle(fontWeight: FontWeight.w600)), - child, - ], - ); - } -} diff --git a/packages/video_player/example/lib/tabs/asset_tab.dart b/packages/video_player/example/lib/tabs/asset_tab.dart deleted file mode 100644 index 5c555aa78987..000000000000 --- a/packages/video_player/example/lib/tabs/asset_tab.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/material.dart'; -import '../player.dart'; -import '../player_values.dart'; -import '../video_provider.dart'; - -class AssetTab extends StatelessWidget { - const AssetTab({Key key}) : super(key: key); - - @override - Widget build(BuildContext context) { - VideoControllerProvider.of(context)..setLooping(true); - - return SingleChildScrollView( - padding: const EdgeInsets.all(20.0), - child: Column( - children: const [ - Text('With assets mp4'), - SizedBox(height: 20.0), - Player(), - SizedBox(height: 20.0), - PlayerValues(), - ], - ), - ); - } -} diff --git a/packages/video_player/example/lib/tabs/list_tab.dart b/packages/video_player/example/lib/tabs/list_tab.dart deleted file mode 100644 index d938d3ea2023..000000000000 --- a/packages/video_player/example/lib/tabs/list_tab.dart +++ /dev/null @@ -1,84 +0,0 @@ -import 'package:flutter/material.dart'; -import '../player.dart'; -import '../video_provider.dart'; - -class ListTab extends StatelessWidget { - const ListTab({Key key}) : super(key: key); - - @override - Widget build(BuildContext context) { - VideoControllerProvider.of(context)..setLooping(true); - - return ListView( - children: [ - _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: [ - Column( - children: [ - const ListTile( - leading: Icon(Icons.cake), - title: Text("Video video"), - ), - Stack( - alignment: FractionalOffset.bottomRight + - const FractionalOffset(-0.1, -0.1), - children: [ - const Player(), - Image.asset('assets/flutter-mark-square-64.png'), - ], - ), - ], - ), - ], - ), - ), - _buildCard("Item h"), - _buildCard("Item i"), - _buildCard("Item j"), - _buildCard("Item k"), - _buildCard("Item l"), - ], - ); - } - - /// 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: [ - ListTile( - leading: const Icon(Icons.airline_seat_flat_angled), - title: Text(title), - ), - ButtonTheme.bar( - child: ButtonBar( - children: [ - FlatButton( - child: const Text('BUY TICKETS'), - onPressed: () { - /* ... */ - }, - ), - FlatButton( - child: const Text('SELL TICKETS'), - onPressed: () { - /* ... */ - }, - ), - ], - ), - ), - ], - ), - ); - } -} diff --git a/packages/video_player/example/lib/tabs/live_tab.dart b/packages/video_player/example/lib/tabs/live_tab.dart deleted file mode 100644 index 42a06fe52432..000000000000 --- a/packages/video_player/example/lib/tabs/live_tab.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter/material.dart'; -import '../player.dart'; -import '../player_values.dart'; - -class LiveTab extends StatelessWidget { - const LiveTab({Key key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return SingleChildScrollView( - padding: const EdgeInsets.all(20.0), - child: Column( - children: const [ - Text('HLS Live stream'), - SizedBox(height: 20.0), - Player(isLive: true), - SizedBox(height: 20.0), - PlayerValues(), - ], - ), - ); - } -} diff --git a/packages/video_player/example/lib/tabs/remote_tab.dart b/packages/video_player/example/lib/tabs/remote_tab.dart deleted file mode 100644 index 07112affad4d..000000000000 --- a/packages/video_player/example/lib/tabs/remote_tab.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/material.dart'; -import '../player.dart'; -import '../player_values.dart'; -import '../video_provider.dart'; - -class RemoteTab extends StatelessWidget { - const RemoteTab({Key key}) : super(key: key); - - @override - Widget build(BuildContext context) { - VideoControllerProvider.of(context)..setLooping(true); - - return SingleChildScrollView( - padding: const EdgeInsets.all(20.0), - child: Column( - children: const [ - Text('With remote m3u8'), - SizedBox(height: 20.0), - Player(), - SizedBox(height: 20.0), - PlayerValues(), - ], - ), - ); - } -} diff --git a/packages/video_player/example/lib/video_provider.dart b/packages/video_player/example/lib/video_provider.dart deleted file mode 100644 index 38264c1e70b1..000000000000 --- a/packages/video_player/example/lib/video_provider.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'dart:io'; -import 'package:flutter/material.dart'; -import 'package:video_player/video_player.dart'; - -export 'package:video_player/video_player.dart'; - -class VideoControllerProvider extends StatefulWidget { - const VideoControllerProvider.asset({ - Key key, - @required this.source, - @required this.child, - }) : type = DataSourceType.asset, - _file = null, - super(key: key); - - const VideoControllerProvider.network({ - Key key, - @required this.source, - @required this.child, - }) : type = DataSourceType.network, - _file = null, - super(key: key); - - const VideoControllerProvider.file({ - Key key, - File file, - @required this.child, - }) : _file = file, - type = DataSourceType.file, - source = null, - super(key: key); - - final Widget child; - final String source; - final File _file; - final DataSourceType type; - - @override - _VideoProviderState createState() => _VideoProviderState(); - - static VideoPlayerController of(BuildContext context) { - final _VideoProviderInherited provider = context - .ancestorInheritedElementForWidgetOfExactType(_VideoProviderInherited) - ?.widget; - return provider?.controller; - } -} - -class _VideoProviderState extends State { - VideoPlayerController controller; - - @override - void initState() { - super.initState(); - - switch (widget.type) { - case DataSourceType.asset: - controller = VideoPlayerController.asset(widget.source); - break; - case DataSourceType.network: - controller = VideoPlayerController.network(widget.source); - break; - case DataSourceType.file: - controller = VideoPlayerController.file(widget._file); - break; - default: - throw Exception('Could not create the VideoPlayerController.'); - } - - controller?.setVolume(1.0); - } - - @override - void dispose() { - controller?.setVolume(0.0); - controller?.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return _VideoProviderInherited( - controller: controller, - child: widget.child, - ); - } -} - -class _VideoProviderInherited extends InheritedWidget { - const _VideoProviderInherited({ - Key key, - @required Widget child, - @required this.controller, - }) : super(key: key, child: child); - - final VideoPlayerController controller; - - @override - bool updateShouldNotify(_VideoProviderInherited oldWidget) => false; -} diff --git a/packages/video_player/lib/video_player.dart b/packages/video_player/lib/video_player.dart index d5ba61adf46f..c2d8aed21705 100644 --- a/packages/video_player/lib/video_player.dart +++ b/packages/video_player/lib/video_player.dart @@ -223,9 +223,6 @@ class VideoPlayerController extends ValueNotifier { } void eventListener(dynamic event) { - if (_isDisposed) { - return; - } final Map map = event; switch (map['event']) { case 'initialized':