sarbagyastha / youtube_player_flutter

A Flutter plugin for inline playback or streaming of YouTube videos using the official iFrame Player API.
https://youtube.sarbagyastha.com.np
BSD 3-Clause "New" or "Revised" License
708 stars 820 forks source link

[BUG] Memory leak when using flutter_inappwebview and youtube_player_iframe on the same screen #477

Open JavonneM opened 3 years ago

JavonneM commented 3 years ago

Describe the bug When using flutter_inappwebview: ^5.3.2 and youtube_player_iframe: ^2.0.0 the inappwebview does not render correctly, while the devices memory usage increases until it runs out of memory. Previously (in flutter 1.22) we had no issue with this (using flutter_inappwebview: ^4.0.0+4 and youtube_player_iframe: ^1.1.0) and we are currently upgrading our project to flutter 2.0. You will notice the InAppWebView does not render correctly unless you constantly scroll the screen (ie, you force the InAppWebView to repaint itself). I do not see this being a issue in the flutter_inappwebview library since I can easily have multiple webviews in a widget, the issue occurs when I add a YoutubePlayerIFrame.

To Reproduce Repo - https://github.com/JavonneM/flutter_youtube_memory_leak

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( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } }

class MyHomePage extends StatefulWidget { MyHomePage({Key? key, this.title}) : super(key: key);

final String? title;

@override _MyHomePageState createState() => _MyHomePageState(); }

class _MyHomePageState extends State { int _counter = 0; late YoutubePlayerController _controller; @override void initState() { _controller = YoutubePlayerController( initialVideoId: 'By_Cn5ixYLg', params: const YoutubePlayerParams( autoPlay: false, showControls: true, showFullscreenButton: true, enableCaption: false, strictRelatedVideos: true, desktopMode: false, ), // ignore: always_specify_types )..listen((value) { if (value.isReady && !value.hasPlayed) { _controller ..hidePauseOverlay() ..hideTopMenu(); } }); _controller.onEnterFullscreen = () { SystemChrome.setPreferredOrientations([ DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight, ]); debugPrint('Entered Fullscreen'); };

_controller.onExitFullscreen = () {
  SystemChrome.setPreferredOrientations(<DeviceOrientation>[
    DeviceOrientation.portraitUp,
  ]);

  Future<void>.delayed(const Duration(seconds: 5), () {
    SystemChrome.setPreferredOrientations(DeviceOrientation.values);
  });

  debugPrint('Exited Fullscreen');
};
super.initState();

}

void _incrementCounter() { setState(() { _counter++; }); }

@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title!), ), body: SingleChildScrollView( child: Column( children: [ AspectRatio( aspectRatio: 16 / 9, child: Container( width: double.infinity, child: Stack( children: [ InAppWebView( initialOptions: InAppWebViewGroupOptions( // android: AndroidInAppWebViewOptions(), ios: IOSInAppWebViewOptions( allowsInlineMediaPlayback: true, ), crossPlatform: InAppWebViewOptions( disableHorizontalScroll: true, disableVerticalScroll: true, mediaPlaybackRequiresUserGesture: false, ), ), initialUrlRequest: URLRequest( url: Uri.parse( 'https://media3.giphy.com/media/BZity0rWNCMSsSIRXK/giphy.gif?cid=ecf05e4734d03cab8d2ad84b73d1001ebc3104457eefd116&rid=giphy.gif&ct=s'), ), onLoadStop: (InAppWebViewController controller, Uri? url) async {}, ), ], ), ), ), Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), SizedBox( height: 150, ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), YoutubePlayerIFrame( controller: _controller, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); } }

Pubspec

name: flutter_webview_memory_test description: A new Flutter project.

publish_to: 'none' # Remove this line if you wish to publish to pub.dev

version: 1.0.0+1

environment: sdk: '>=2.12.0 <3.0.0'

dependencies: flutter: sdk: flutter

cupertino_icons: ^1.0.2 flutter_inappwebview: ^5.3.2 youtube_player_iframe: ^2.0.0

dev_dependencies: flutter_test: sdk: flutter

flutter: uses-material-design: true


**Expected behavior**
The InAppWebView to render correctly and the device does not run out of memory.

**Screenshots**
This video illustrates using two InAppWebViews rendering correctly (the issue does not occur here)
https://user-images.githubusercontent.com/4679874/114154446-b46d5780-9920-11eb-84ce-f048bd69406d.mp4

Video below shows the issue that is occuring, Notice how the gif only renders while scrolling.
https://user-images.githubusercontent.com/4679874/114154542-cea73580-9920-11eb-8228-fb5ab1fd39e3.mp4

Shows the memory usage of the app
![Screenshot 2021-04-09 at 09 54 48](https://user-images.githubusercontent.com/4679874/114155141-71f84a80-9921-11eb-8ceb-24f05e949e42.png)
Graphics seems to be using 2.4GB of memory
![Screenshot 2021-04-09 at 12 33 50](https://user-images.githubusercontent.com/4679874/114168035-023d8c00-9930-11eb-983b-f99109527027.png)

**Technical Details:**
 - Device: Pixel 3
 - OS: Android
 - Version Android 11 (SDK 30)
 -  `flutter_inappwebview: ^5.3.2` and `youtube_player_iframe: ^2.0.0`

**Additional context**
Add any other context about the problem here.
*** Flutter doctor ***

[✓] Flutter (Channel stable, 2.0.3, on Mac OS X 10.15.7 19H2 darwin-x64, locale en-GB) • Flutter version 2.0.3 at /Users//Library/Flutter • Framework revision 4d7946a68d (3 weeks ago), 2021-03-18 17:24:33 -0700 • Engine revision 3459eb2436 • Dart version 2.12.2

[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3) • Android SDK at /Users//Library/Android/sdk • Platform android-30, build-tools 30.0.3 • ANDROID_HOME = /Users//Library/Android/sdk • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495) • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS • Xcode at /Applications/Xcode.app/Contents/Developer • Xcode 12.4, Build version 12D4e • CocoaPods version 1.10.1

[✗] Chrome - develop for the web (Cannot find Chrome executable at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome) ! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable.

[✓] Android Studio (version 4.1) • 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 1.8.0_242-release-1644-b3-6915495)

[✓] VS Code (version 1.55.0) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.21.0

[✓] Connected device (1 available) • Pixel 3 (mobile) • • android-arm64 • Android 11 (API 30)

! Doctor found issues in 1 category.

binish784 commented 3 years ago

same issue here, have you managed to find a workaround or a fix ?

JavonneM commented 3 years ago

Nope, we just removed the youtube player in the mean time.

Williano commented 3 years ago

For the memory leak, from your code, you are not disposing the controller when you exit the screen.

dispose method use to release the memory allocated to variables when state object is removed.

@override
void dispose() {
   _controller?.dispose();
  super.dispose();
}
JavonneM commented 3 years ago

For the memory leak, from your code, you are not disposing the controller when you exit the screen.

dispose method use to release the memory allocated to variables when state object is removed.

@override
void dispose() {
   _controller?.dispose();
  super.dispose();
}

Please read the issue correctly, as stated this is an internal memory leak. Dispose would only execute when dismissing the page, this example literally only has one screen. As demonstrated memory is actively being leaked while viewing the video.

Williano commented 3 years ago

Noted.

PcolBP commented 3 years ago

Wrapping YoutubePlayer in OrientationBuilder like this:

OrientationBuilder(
      builder: (ctx, orientation) => YoutubePlayer(
        key: UniqueKey(),..
    ),
),

Solved problem with memory leak.

Williano commented 3 years ago

What if I am using YoutubePlayerBuilder, should I still wrap with orientationbuilder?

sarbagyastha commented 3 years ago

Might be due to hybrid composition. Please try toggling it.

https://pub.dev/documentation/youtube_player_flutter/latest/youtube_player_flutter/YoutubePlayerFlags/useHybridComposition.html