Skip to content

Add workaround for invalid buffering info on android #912

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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`
Expand Down
6 changes: 4 additions & 2 deletions lib/src/cupertino/cupertino_controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -810,9 +810,11 @@ class _CupertinoControlsState extends State<CupertinoControls>
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,
Expand All @@ -823,7 +825,7 @@ class _CupertinoControlsState extends State<CupertinoControls>
_displayBufferingIndicator = false;
}
} else {
_displayBufferingIndicator = controller.value.isBuffering;
_displayBufferingIndicator = buffering;
}

setState(() {
Expand Down
35 changes: 35 additions & 0 deletions lib/src/helpers/utils.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:video_player/video_player.dart';

String formatDuration(Duration position) {
final ms = position.inMilliseconds;

Expand Down Expand Up @@ -30,3 +33,35 @@

return formattedTime;
}

/// Get the current buffering state of the video player. Will invoke a
/// Workaround for Android, as a bug in the video_player plugin prevents to get
/// the actual buffering state, always returning true and breaking ui elements.
/// For this, the actual buffer position is used to determine if the video is
/// buffering or not. See #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;

Check warning on line 49 in lib/src/helpers/utils.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/helpers/utils.dart#L49

Added line #L49 was not covered by tests

// Special case, if the video is finished, we don't want to show the
// buffering indicator anymore
if (position >= value.duration.inMilliseconds) {

Check warning on line 53 in lib/src/helpers/utils.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/helpers/utils.dart#L53

Added line #L53 was not covered by tests
return false;
} else {
final int buffer = value.buffered.lastOrNull?.end.inMilliseconds ?? -1;

Check warning on line 56 in lib/src/helpers/utils.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/helpers/utils.dart#L56

Added line #L56 was not covered by tests

return position >= buffer;

Check warning on line 58 in lib/src/helpers/utils.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/helpers/utils.dart#L58

Added line #L58 was not covered by tests
}
} else {
// -> No buffering
return false;
}
}

return value.isBuffering;

Check warning on line 66 in lib/src/helpers/utils.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/helpers/utils.dart#L66

Added line #L66 was not covered by tests
}
6 changes: 4 additions & 2 deletions lib/src/material/material_controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -645,9 +645,11 @@ class _MaterialControlsState extends State<MaterialControls>
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,
Expand All @@ -658,7 +660,7 @@ class _MaterialControlsState extends State<MaterialControls>
_displayBufferingIndicator = false;
}
} else {
_displayBufferingIndicator = controller.value.isBuffering;
_displayBufferingIndicator = buffering;
}

setState(() {
Expand Down
6 changes: 4 additions & 2 deletions lib/src/material/material_desktop_controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -581,9 +581,11 @@ class _MaterialDesktopControlsState extends State<MaterialDesktopControls>
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,
Expand All @@ -594,7 +596,7 @@ class _MaterialDesktopControlsState extends State<MaterialDesktopControls>
_displayBufferingIndicator = false;
}
} else {
_displayBufferingIndicator = controller.value.isBuffering;
_displayBufferingIndicator = buffering;
}

setState(() {
Expand Down