flutter / flutter

Flutter makes it easy and fast to build beautiful apps for mobile and beyond
https://flutter.dev
BSD 3-Clause "New" or "Revised" License
162.18k stars 26.65k forks source link

[video player] weird behaviour when the download speed is low #147438

Open moham96 opened 2 weeks ago

moham96 commented 2 weeks ago

Steps to reproduce

first simulate slow internet connection or slow server: 1- install some bandwidth limiter, for me i used the official network link conditioner. 2- set the bandwidth limiter to something really low, i have set mine to 300 kbps download

next test the video buffering 1- fire up some video player example, i will attach the example i used. 2- give it some time to buffer some part of the video but not the whole video, theoretically it should be able to play the video once it has buffered a part of it, but in this case, it can't for some reason

Expected results

I expect the player to act the same regardless of user connection speed or server speed, in other words it should be able to play the video as soon as some part of it is buffered

Actual results

Currently the player doesn't play the video until the whole video is buffered, watch the attached below video, it is very important in showing the issue

Code sample

video player example that i used ```dart import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; const mainColor = Color(0xFFF09636); extension SaneFormat on Duration { String saneFormat() { return "${inHours.toString().padLeft(2, '0')}:${inMinutes.remainder(60).toString().padLeft(2, '0')}:${inSeconds.remainder(60).toString().padLeft(2, '0')}"; } } class VideoPlayerScreen extends StatefulWidget { const VideoPlayerScreen({super.key}); @override State createState() => _VideoPlayerScreenState(); } class _VideoPlayerScreenState extends State { bool showcontrols = true; VideoPlayerController? videoController; bool isInitializing = false; bool isPortrait = true; double playerControlSize = 40; List translationNames = []; List resolutionNames = []; videoControllerListener() { setState(() {}); if (videoController!.value.hasError) { print('video error'); initVideo(); } } reloadVideo() async { Uri? videoUrl; videoUrl = Uri.parse( 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4'); videoController = VideoPlayerController.networkUrl(videoUrl); videoController!.addListener(videoControllerListener); initVideo(); } initVideo() { if (isInitializing) return; isInitializing = true; videoController!.initialize().then((_) { videoController!.setVolume(0); videoController!.play(); }).onError((e, st) { log('got error', error: e); initVideo(); }).whenComplete(() { isInitializing = false; }); } @override void initState() { super.initState(); reloadVideo(); } @override void dispose() async { super.dispose(); videoController?.removeListener(videoControllerListener); await videoController?.dispose(); } seekForward() async { videoController!.seekTo( (await videoController!.position)! + const Duration(seconds: 10)); } seekBackwards() async { videoController!.seekTo( (await videoController!.position)! - const Duration(seconds: 10)); } @override Widget build(BuildContext context) { var problem = videoController == null || !videoController!.value.isInitialized; playerControlSize = 40; return SafeArea( child: DefaultTextStyle( style: const TextStyle(color: Colors.white), child: Stack( children: [ videoController != null ? Center( child: AspectRatio( aspectRatio: videoController!.value.aspectRatio, child: VideoPlayer(videoController!), ), ) : const Center(child: CircularProgressIndicator()), GestureDetector( onTap: () { setState(() { showcontrols = !showcontrols; }); }, child: Container( color: Colors.transparent, ), ), Visibility( visible: showcontrols, child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Align( alignment: Alignment.topLeft, child: Padding( padding: const EdgeInsets.all(15), child: Container( color: Colors.black45, child: ValueListenableBuilder( valueListenable: videoController!, builder: (context, value, child) => RichText( text: TextSpan( text: 'Stats', children: [ const TextSpan(text: '\n'), TextSpan( text: 'isBuffering: ${value.isBuffering}\n'), TextSpan( text: 'isCompleted: ${value.isCompleted}\n'), TextSpan( text: 'isInitialized: ${value.isInitialized}\n'), TextSpan( text: 'isLooping: ${value.isLooping}\n'), TextSpan( text: 'isPlaying: ${value.isPlaying}\n'), TextSpan(text: 'hasError: ${value.hasError}\n'), TextSpan( text: 'errorDescription: ${value.errorDescription}\n'), ], ), ), ), ), ), ), Directionality( textDirection: TextDirection.ltr, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Material( color: Colors.transparent, child: IconButton( onPressed: seekBackwards, icon: Icon( Icons.fast_rewind, color: mainColor, size: playerControlSize, )), ), problem ? const Icon( Icons.block, color: Colors.red, size: 30, ) : Material( color: Colors.transparent, child: IconButton( onPressed: () { videoController!.value.isPlaying ? videoController?.pause() : videoController?.play(); setState(() {}); }, icon: Icon( videoController != null ? videoController!.value.isPlaying ? Icons.pause : Icons.play_arrow : Icons.sync_problem, color: mainColor, size: playerControlSize, ), ), ), Material( color: Colors.transparent, child: IconButton( onPressed: seekForward, icon: Icon( Icons.fast_forward, color: mainColor, size: playerControlSize, )), ), ], ), ), Container( margin: const EdgeInsets.symmetric(horizontal: 5), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ videoController != null ? ValueListenableBuilder( valueListenable: videoController!, builder: ((context, value, child) { return Text(value.duration.saneFormat()); }), ) : const SizedBox.shrink(), Directionality( textDirection: TextDirection.ltr, child: Expanded( child: Container( margin: const EdgeInsets.only(left: 10, right: 10), child: videoController != null ? VideoProgressIndicator( videoController!, allowScrubbing: true, colors: const VideoProgressColors( playedColor: mainColor, bufferedColor: Colors.red, ), ) : const SizedBox.shrink(), ), ), ), videoController != null ? ValueListenableBuilder( valueListenable: videoController!, builder: ((context, value, child) { return Text(value.position.saneFormat()); }), ) : const SizedBox.shrink(), ], ), ) ], ), ) ], ), ), ); } } ```

Screenshots or Video

Screenshots / Video demonstration https://streamable.com/y53yra **i couldn't upload the video here because of size limit**

Logs

Logs ```console [Paste your logs here] ```

Flutter Doctor output

Doctor output ```console [✓] Flutter (Channel stable, 3.19.5, on macOS 14.4.1 23E224 darwin-arm64, locale en-IQ) • Flutter version 3.19.5 on channel stable at /Users/rhx/dev/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 300451adae (4 weeks ago), 2024-03-27 21:54:07 -0500 • Engine revision e76c956498 • Dart version 3.3.3 • DevTools version 2.31.1 [✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0) • Android SDK at /Users/rhx/Library/Android/sdk • Platform android-34, build-tools 34.0.0 • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 17.0.9+0-17.0.9b1087.7-11185874) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 15.3) • Xcode at /Applications/Xcode.app/Contents/Developer • Build 15E204a • CocoaPods version 1.15.2 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] Android Studio (version 2023.2) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 17.0.9+0-17.0.9b1087.7-11185874) [✓] VS Code (version 1.88.1) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.86.0 [✓] Connected device (2 available) • macOS (desktop) • macos • darwin-arm64 • macOS 14.4.1 23E224 darwin-arm64 • Chrome (web) • chrome • web-javascript • Google Chrome 124.0.6367.92 [✓] Network resources • All expected network resources are available. • No issues found! ```
moham96 commented 2 weeks ago

tested on MacOS and Android emulator with the same results, don't know about other platforms

huycozy commented 2 weeks ago

Thanks for the report. I can also reproduce this on iOS and macOS app targets. Current package version video_player: ^2.8.6.

For Android platform, this could be related to https://github.com/google/ExoPlayer/issues/7413.

flutter doctor -v (stable and master) ```bash [✓] Flutter (Channel stable, 3.19.6, on macOS 14.1 23B74 darwin-x64, locale en-VN) • Flutter version 3.19.6 on channel stable at /Users/huynq/Documents/GitHub/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 54e66469a9 (31 hours ago), 2024-04-17 13:08:03 -0700 • Engine revision c4cd48e186 • Dart version 3.3.4 • DevTools version 2.31.1 [✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0) • Android SDK at /Users/huynq/Library/Android/sdk • Platform android-34, build-tools 34.0.0 • ANDROID_HOME = /Users/huynq/Library/Android/sdk • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 17.0.9+0-17.0.9b1087.7-11185874) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 15.3) • Xcode at /Applications/Xcode15.3.app/Contents/Developer • Build 15E204a • CocoaPods version 1.15.2 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] Android Studio (version 2023.2) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • android-studio-dir = /Applications/Android Studio.app/ • Java version OpenJDK Runtime Environment (build 17.0.9+0-17.0.9b1087.7-11185874) [✓] VS Code (version 1.88.1) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.86.0 [✓] Connected device (3 available) • RMX2001 (mobile) • EUYTFEUSQSRGDA6D • android-arm64 • Android 11 (API 30) • macOS (desktop) • macos • darwin-x64 • macOS 14.1 23B74 darwin-x64 • Chrome (web) • chrome • web-javascript • Google Chrome 123.0.6312.124 [✓] Network resources • All expected network resources are available. • No issues found! ``` ```bash [!] Flutter (Channel master, 3.22.0-18.0.pre.53, on macOS 14.1 23B74 darwin-x64, locale en-VN) • Flutter version 3.22.0-18.0.pre.53 on channel master at /Users/huynq/Documents/GitHub/flutter_master ! Warning: `flutter` on your path resolves to /Users/huynq/Documents/GitHub/flutter/bin/flutter, which is not inside your current Flutter SDK checkout at /Users/huynq/Documents/GitHub/flutter_master. Consider adding /Users/huynq/Documents/GitHub/flutter_master/bin to the front of your path. ! Warning: `dart` on your path resolves to /Users/huynq/Documents/GitHub/flutter/bin/dart, which is not inside your current Flutter SDK checkout at /Users/huynq/Documents/GitHub/flutter_master. Consider adding /Users/huynq/Documents/GitHub/flutter_master/bin to the front of your path. • Upstream repository https://github.com/flutter/flutter.git • Framework revision 098e7e7ff3 (2 hours ago), 2024-04-29 01:25:19 +0000 • Engine revision 752b146df7 • Dart version 3.5.0 (build 3.5.0-109.0.dev) • DevTools version 2.35.0-dev.16 • If those were intentional, you can disregard the above warnings; however it is recommended to use "git" directly to perform update checks and upgrades. [✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0) • Android SDK at /Users/huynq/Library/Android/sdk • Platform android-34, build-tools 34.0.0 • ANDROID_HOME = /Users/huynq/Library/Android/sdk • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 17.0.9+0-17.0.9b1087.7-11185874) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 15.3) • Xcode at /Applications/Xcode15.3.app/Contents/Developer • Build 15E204a • CocoaPods version 1.15.2 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] Android Studio (version 2023.2) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • android-studio-dir = /Applications/Android Studio.app/ • Java version OpenJDK Runtime Environment (build 17.0.9+0-17.0.9b1087.7-11185874) [✓] VS Code (version 1.88.1) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.86.0 [✓] Connected device (2 available) • macOS (desktop) • macos • darwin-x64 • macOS 14.1 23B74 darwin-x64 • Chrome (web) • chrome • web-javascript • Google Chrome 124.0.6367.92 [✓] Network resources • All expected network resources are available. ! Doctor found issues in 1 category. ```
moham96 commented 2 weeks ago

Just wanted to add something that might not be very clear in the original post;

moham96 commented 2 weeks ago

Tested the issue with media_kit on MacOS which should be using different platform implementation and it also suffers from this issue

stuartmorgan commented 2 weeks ago

This may be underlying ExoPlayer/AVPlayer behavior, rather than something the plugin can control. It will need investigation and comparison to non-Flutter video apps using the same libraries.

huycozy commented 1 week ago

@moham96 Could you help to check this with native players as mentioned above?

I was testing this with AVPlayer using this online example on iOS device, I had to set 500 kbps download because it's really slow to fetch data with 300 kbps; the issue doesn't seem to appear as I still can play/pause video.

moham96 commented 1 week ago

@huycozy i did a quick test using the provided sample and it does show the same issue, but there could be something wrong with my testing since the sample provided doesn't show the amount of buffered data. It could be that when you tested it that the whole video was buffered since you raised the speed to 500kbps and since in my original test video i stated that if you let the whole video to buffer then the weird behavior is gone.

we need the native sample to have a slider that shows the amount of buffered data to be able to do any meaningful tests

moham96 commented 6 days ago

@huycozy do you have native examples that shows the buffered amount so i can test I can test MacOS, Android and ios

huycozy commented 6 days ago

@moham96 I have Android native sample for ExoPlayer here that you can try on: https://github.com/huycozy/AndroidNativeExoPlayer.

I checked this on Android emulator that allows me to set network speed; I went with umts - UMTS/3G (up: 384.0, down: 384.0) and edge - EDGE/EGPRS (up: 473.6, down: 473.6) (see network options here) but the video can't be loaded due to slow network even though I wait a long time for it. The same result occurs with video_player package when running on this Android emulator as well.

moham96 commented 6 days ago

@moham96 I have Android native sample for ExoPlayer here that you can try on: https://github.com/huycozy/AndroidNativeExoPlayer.

I checked this on Android emulator that allows me to set network speed; I went with umts - UMTS/3G (up: 384.0, down: 384.0) and edge - EDGE/EGPRS (up: 473.6, down: 473.6) (see network options here) but the video can't be loaded due to slow network even though I wait a long time for it. The same result occurs with video_player package when running on this Android emulator as well.

So i was able to test the provided demo with the following modifications:

the testing revealed a less buggy player than the flutter version but has partially the same issue:

The player doesn't play the buffered part, but unlike the flutter version it doesn't have to buffer the whole video to play, it was able to play the video when the video was buffered around 70-80% i don't know if this is different behavior or maybe it's the same for flutter but the updating of the buffered amount is incorrect in the flutter implementation.

testing using the bandwidth limiter of the emulator i was not able to play the video, which reveals one truth, the player is practically unusable on low bandwidth