diff --git a/README.md b/README.md index 72884a3be..a6bcb1e4b 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,8 @@ Chewie uses the `video_player` under the hood and wraps it in a friendly Materia 8. πŸ§ͺ [Example](#-example) 9. βͺ [Migrating from Chewie < 0.9.0](#-migrating-from-chewie--090) 10. πŸ—ΊοΈ [Roadmap](#%EF%B8%8F-roadmap) -11. πŸ“± [iOS warning](#-ios-warning-) +11. ⚠️ [Android warning](#%EF%B8%8F-android-warning) +12. πŸ“± [iOS warning](#-ios-warning) ## 🚨 IMPORTANT!!! (READ THIS FIRST) @@ -286,6 +287,42 @@ final playerWidget = Chewie( - [ ] Screen-Mirroring / Casting (Google Chromecast) +## ⚠️ Android warning + +There is an open [issue](https://github.com/flutter/flutter/issues/165149) that the buffering state of a video is not reported correctly. With this, the loading state is always triggered, hiding controls to play, pause or seek the video. A workaround was implemented until this is fixed, however it can't be perfect and still hides controls if seeking backwards while the video is paused, as a result of lack of correct buffering information (see #912). + +Add the following to partly fix this behavior: + +```dart + // Your init code can be above + videoController.addListener(yourListeningMethod); + + // ... + + bool wasPlayingBefore = false; + void yourListeningMethod() { + if (!videoController.value.isPlaying && !wasPlayingBefore) { + // -> Workaround if seekTo another position while it was paused before. + // On Android this might lead to infinite loading, so just play the + // video again. + videoController.play(); + } + + wasPlayingBefore = videoController.value.isPlaying; + + // ... + } +``` + +You can also disable the loading spinner entirely to fix this problem in a more _complete_ way, however will remove the loading indicator if a video is buffering. + +```dart +_chewieController = ChewieController( + videoPlayerController: _videoPlayerController, + progressIndicatorDelay: Platform.isAndroid ? const Duration(days: 1) : null, +); +``` + ## πŸ“± iOS warning The video_player plugin used by chewie will only work in iOS simulators if you are on flutter 1.26.0 or above. You may need to switch to the beta channel `flutter channel beta` diff --git a/lib/src/cupertino/cupertino_controls.dart b/lib/src/cupertino/cupertino_controls.dart index db7267836..b0488a275 100644 --- a/lib/src/cupertino/cupertino_controls.dart +++ b/lib/src/cupertino/cupertino_controls.dart @@ -810,9 +810,11 @@ class _CupertinoControlsState extends State void _updateState() { if (!mounted) return; + final bool buffering = getIsBuffering(controller); + // display the progress bar indicator only after the buffering delay if it has been set if (chewieController.progressIndicatorDelay != null) { - if (controller.value.isBuffering) { + if (buffering) { _bufferingDisplayTimer ??= Timer( chewieController.progressIndicatorDelay!, _bufferingTimerTimeout, @@ -823,7 +825,7 @@ class _CupertinoControlsState extends State _displayBufferingIndicator = false; } } else { - _displayBufferingIndicator = controller.value.isBuffering; + _displayBufferingIndicator = buffering; } setState(() { diff --git a/lib/src/helpers/utils.dart b/lib/src/helpers/utils.dart index 0f3a2e128..17ae2161a 100644 --- a/lib/src/helpers/utils.dart +++ b/lib/src/helpers/utils.dart @@ -1,3 +1,6 @@ +import 'package:flutter/foundation.dart'; +import 'package:video_player/video_player.dart'; + String formatDuration(Duration position) { final ms = position.inMilliseconds; @@ -30,3 +33,39 @@ String formatDuration(Duration position) { return formattedTime; } + +/// Gets the current buffering state of the video player. +/// +/// For Android, it will use a workaround due to a [bug](https://github.com/flutter/flutter/issues/165149) +/// affecting the `video_player` plugin, preventing it from getting the +/// actual buffering state. This currently results in the `VideoPlayerController` always buffering, +/// thus breaking UI elements. +/// +/// For this, the actual buffer position is used to determine if the video is +/// buffering or not. See Issue [#912](https://github.com/fluttercommunity/chewie/pull/912) for more details. +bool getIsBuffering(VideoPlayerController controller) { + final VideoPlayerValue value = controller.value; + + if (defaultTargetPlatform == TargetPlatform.android) { + if (value.isBuffering) { + // -> Check if we actually buffer, as android has a bug preventing to + // get the correct buffering state from this single bool. + final int position = value.position.inMilliseconds; + + // Special case, if the video is finished, we don't want to show the + // buffering indicator anymore + if (position >= value.duration.inMilliseconds) { + return false; + } else { + final int buffer = value.buffered.lastOrNull?.end.inMilliseconds ?? -1; + + return position >= buffer; + } + } else { + // -> No buffering + return false; + } + } + + return value.isBuffering; +} diff --git a/lib/src/material/material_controls.dart b/lib/src/material/material_controls.dart index 2d813e05a..3ea67f31d 100644 --- a/lib/src/material/material_controls.dart +++ b/lib/src/material/material_controls.dart @@ -645,9 +645,11 @@ class _MaterialControlsState extends State void _updateState() { if (!mounted) return; + final bool buffering = getIsBuffering(controller); + // display the progress bar indicator only after the buffering delay if it has been set if (chewieController.progressIndicatorDelay != null) { - if (controller.value.isBuffering) { + if (buffering) { _bufferingDisplayTimer ??= Timer( chewieController.progressIndicatorDelay!, _bufferingTimerTimeout, @@ -658,7 +660,7 @@ class _MaterialControlsState extends State _displayBufferingIndicator = false; } } else { - _displayBufferingIndicator = controller.value.isBuffering; + _displayBufferingIndicator = buffering; } setState(() { diff --git a/lib/src/material/material_desktop_controls.dart b/lib/src/material/material_desktop_controls.dart index 4b0ba0fe8..ebffd9560 100644 --- a/lib/src/material/material_desktop_controls.dart +++ b/lib/src/material/material_desktop_controls.dart @@ -581,9 +581,11 @@ class _MaterialDesktopControlsState extends State void _updateState() { if (!mounted) return; + final bool buffering = getIsBuffering(controller); + // display the progress bar indicator only after the buffering delay if it has been set if (chewieController.progressIndicatorDelay != null) { - if (controller.value.isBuffering) { + if (buffering) { _bufferingDisplayTimer ??= Timer( chewieController.progressIndicatorDelay!, _bufferingTimerTimeout, @@ -594,7 +596,7 @@ class _MaterialDesktopControlsState extends State _displayBufferingIndicator = false; } } else { - _displayBufferingIndicator = controller.value.isBuffering; + _displayBufferingIndicator = buffering; } setState(() {