fluttercommunity / chewie

The video player for Flutter with a heart of gold
MIT License
1.9k stars 983 forks source link

Unable to exit Fullscreen #297

Open OBorce opened 4 years ago

OBorce commented 4 years ago

I am placing the Chewie widget inside a ListView builder, also I am currently caching the videoPlayerController and the ChewieController so that they do not get disposed when scrolling up and down and works great. But as said above, navigating to Full screen does not, I am not sure if caching the Chewie widget is the right way to go?

Pressing the back button works, so can we move the if full screen navigator pop, somewhere in the FullScreen widget not inside Chewie widget?

sathishmscict commented 4 years ago
  • After pressing the full-screen button, chewie navigates to a new route with the full screen widget, passing the Chewie-controller.
  • But depending on where the Chewie widget was placed, the navigate push can trigger a dispose on the Chewie widget, and it will removes the listener.
  • Now pressing on the full screen button can't go back since none is listening.

I am placing the Chewie widget inside a ListView builder, also I am currently caching the videoPlayerController and the ChewieController so that they do not get disposed when scrolling up and down and works great. But as said above, navigating to Full screen does not, I am not sure if caching the Chewie widget is the right way to go?

Pressing the back button works, so can we move the if full screen navigator pop, somewhere in the FullScreen widget not inside Chewie widget?

Same issue here ?

pranavkpr1 commented 4 years ago

Yeah I am also having the same problem when Chewie is placed inside SliverList or ListView. In list view, pressing enter full screen button or exit full screen button triggers dispose function as list is rebuilt and that rebuilding is random as dispose is not called everytime (that's the issue of flutter). This rebuilding causes the chewie and video controller to dispose before one enters the full screen mode. Somehow, I managed to enter full screen mode by checking isFullScreen true inside dispose function and avoiding dispose of the chewie and video controller. But when I exit the full screen mode, the listener is lost sometimes and one can't go back. So one needs to press the back button on phone which causes the video to play in background and not as a member of the list view. Any help on the issue of dealing with Chewie controller in list view will be appreciated :) @brianegan @cbenhagen looking for pointers or suggestions from your side. Thanks again for making one of the best player out there in flutter and for your contributions.

tsandoz commented 4 years ago

So, I had the same, or at least a similar problem. The problem stems from the chewie controller being disposed when entering fullscreen mode. The underlying issue is, that the page from which I started the fullscreen mode, was not being kept alive. Therefore the controller died with it. I managed to create a workaround for this, but I'm not sure how "good" that method really is. Anyway, here is the workaround: In chewie_player.dart find the PageRouteBuilder and add the 'opaque' parameter as 'false.

final TransitionRoute route = PageRouteBuilder( pageBuilder: _fullScreenRoutePageBuilder, opaque: false, );

This prevents the previous page (where your video is located) from being destroyed, since you tell flutter that the new page could be transparent. Side effect beeing, that your chewie controller does not get disposed. But be aware, that this means the previous page stays fully rendered, with all animations or whatever is active there.

pranavkpr1 commented 4 years ago

Hola @tsandoz , Thanks for the solution. I tried making the opaque parameter false in addition to check of chewieController.isFullScreen inside dispose function to not dispose the chewie and video controller while entering the full screen. 7/9 videos it's working fine but giving problem in 2 videos. So basically, there is no problem in entering the full screen but as I exit full screen for these 2 videos, the full screen button stop working (I think there is no listener) and the screen remain full screen and if I press back button on the phone, the video is totally lost in background. And if the same process repeats for 2-3 videos after some time, the phone will notify not enough resources to play video as these video controllers are not getting disposed as these are lost in background and sliver list can't dispose these controllers when I am back to portrait mode. And I observed that this problem is only there when the video state changes from play to pause while entering the full screen. PS: I just mentioned 7/9 as the behavior of controllers in the sliver list/list view is arbitrary depending on whether they are disposed or not while entering/exiting full screen mode.

tsandoz commented 4 years ago

I noticed that when you disable the disposition of the chewie controller, it can lead to problems where you can't exit fullscreen. But I can't say why this only happens sometimes in your case. The combination of a list with arbitrarily self disposing elements and chewie sounds like quite the hassle.

fangshengfy commented 4 years ago

use AutomaticKeepAliveClientMixin

pranavkpr1 commented 4 years ago

@fangshengfy using AutomaticKeepAliveClientMixin solved the issue. Combining AutomaticKeepAliveClientMixin with Visibility Detector did the trick. So in the list, you can mark the keepAlive=true for the video widget and once the visibility is 0, you can mark keepAlive as false which would let the dispose function call and dispose the unused elements. Thanks for the suggestion :)

davidrebollo92 commented 3 years ago

@fangshengfy using AutomaticKeepAliveClientMixin solved the issue. Combining AutomaticKeepAliveClientMixin with Visibility Detector did the trick. So in the list, you can mark the keepAlive=true for the video widget and once the visibility is 0, you can mark keepAlive as false which would let the dispose function call and dispose the unused elements. Thanks for the suggestion :)

Hello @pranavkpr1, I´m triying follow your last comment but I cant apply it in my code, can you posted the solution please?

BesartLaci commented 3 years ago

It seems like at actual state we will have to use Chewie without "FullScreenMode"

because the listener call to pop the fullscreen
gets lost when Flutter disposes the widgets on Navigator.pushToFullScreen

when the user taps the change screen button,
the toogleFullScreen changeNotifier will not call the listeners, becaues the list is now empty from dispose

using AutomaticKeepAliveClientMixin with VisibilityDetector doesn't avoid Chewie to call dispose

the user cann only close the full screen by pushing the native "back button" or by "swipe back gesture"

chewie_on_new_page_route

Example

Flutter 1.22.4 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 1aafb3a8b9 (3 weeks ago) • 2020-11-13 09:59:28 -0800
Engine • revision 2c956a31c0
Tools • Dart 2.10.4
  video_player: ^1.0.1
  chewie: ^0.12.0
  visibility_detector: ^0.1.5
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';

import 'package:chewie/chewie.dart';
import 'package:video_player/video_player.dart';
import 'package:visibility_detector/visibility_detector.dart';

class VideoPreviewPlayer extends StatefulWidget {

  final String videoFilePath;

  VideoPreviewPlayer({@required this.videoFilePath, Key key})
      : super(key: key);

  @override
  _VideoPreviewPlayerState createState() {
    return _VideoPreviewPlayerState();
  }
}

class _VideoPreviewPlayerState extends State<VideoPreviewPlayer>
    with AutomaticKeepAliveClientMixin {
  VideoPlayerController videoPlayerController;
  ChewieController chewieController;

  bool _initialized = false;
  bool _isVisible = false;

  @override
  void initState() {
    super.initState();
    _init();
  }

  _init() async {
    final filePath = widget.videoFilePath.replaceAll(' ', '%20');

    videoPlayerController = VideoPlayerController.file(File(filePath));
    videoPlayerController.initialize().then((value) {
      setChewieController();
    }).catchError((e) {
      print(e.toString());
    });

  }

  setChewieController() {
    chewieController = ChewieController(
      videoPlayerController: videoPlayerController,
      aspectRatio: videoPlayerController.value.aspectRatio,
      autoPlay: false,
      //allowFullScreen: false,
    );

    setState(() {
      _initialized = true;
    });
  }

  @override
  void dispose() {
    if (!chewieController.isFullScreen) {
        chewieController.dispose();
        videoPlayerController.dispose();
        super.dispose();
    }
  }

  @override
  Widget build(BuildContext context) {
    return !_initialized
        //expand to avoid resizing the container while the video controller is not initialized
        ? SizedBox.expand()
        : VisibilityDetector(
            key: Key(widget.videoFilePath),
            onVisibilityChanged: (visibilityInfo) {
              var visiblePercentage = visibilityInfo.visibleFraction * 100;

              setState(() {
                _isVisible = chewieController.isFullScreen
                    ? true
                    : visiblePercentage == 0
                        ? false
                        : true;
              });
            },
            child: RotatedBox(
              quarterTurns: 0,
              child: Chewie(
                controller: chewieController,
              ),
            ),
          );
  }

  @override
  // TODO: implement wantKeepAlive
  bool get wantKeepAlive => _isVisible;
}

Research

yelkamel commented 1 year ago

Hello,

When I use chewie and I go to fullscreen, the dispose() call of the previous screen is called. And that dispose my video and chewie controller.

I found the solution to put opaque: false in the PageRoute() of the fullscreen.

final TransitionRoute<void> route = PageRouteBuilder<void>( pageBuilder: _fullScreenRoutePageBuilder, // This is to prevent the dispose of the previous screen to be called opaque: false, );

But I don't know if that is the best solution. I will have to fork Chewie to use my change....

Regards, Youcef

larmaysee commented 1 year ago

Any update this issue. Unable to exit Fullscreen in IOS.

yakupbaser commented 1 year ago

any update?

diegotori commented 1 year ago

@BesartLaci or any developer that has capacity: if you submit the PR for this issue, I will be able to review it.

Thanks in advance.

MickaelHrndz commented 11 months ago

I had the same issue, which forced me to switch packages. For reference, pod_player doesn't present this issue.

Here's a minimal reproducible example :

import 'package:chewie/chewie.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:video_player/video_player.dart';

final videoPlayerController = VideoPlayerController.networkUrl(Uri.parse('https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4'));

final chewieController = ChewieController(videoPlayerController: videoPlayerController, autoPlay: true, looping: true, deviceOrientationsOnEnterFullScreen: [
  DeviceOrientation.landscapeLeft,
  DeviceOrientation.landscapeRight,
  DeviceOrientation.portraitDown,
  DeviceOrientation.portraitUp,
]);

final playerWidget = Chewie(
  controller: chewieController,
);

/// Displays a list of SampleItems.
class SampleItemListView extends StatefulWidget {
  const SampleItemListView({
    super.key,
  });

  @override
  State<SampleItemListView> createState() => _SampleItemListViewState();
}

class _SampleItemListViewState extends State<SampleItemListView> {
  @override
  void initState() {
    super.initState();
    videoPlayerController.initialize();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: LayoutBuilder(
          builder: (context, contraints) => Row(
            children: [
              Expanded(
                  flex: 1,
                  child: Column(
                    children: [
                      const Text("side panel"),
                      if (MediaQuery.of(context).orientation == Orientation.portrait) Expanded(child: playerWidget),
                    ],
                  )),
              if (MediaQuery.of(context).orientation == Orientation.landscape)
                Expanded(
                  flex: 4,
                  child: Center(
                    child: Column(
                      children: [
                        const Text("landscape view"),
                        Expanded(child: playerWidget),
                      ],
                    ),
                  ),
                ),
            ],
          ),
        ),
      ),
    );
  }
}

(And here is a temporary solution)

ganeshh123 commented 5 months ago

I also had @MickaelHrndz's issue where changing the orientation would make it impossible to exit fullscreen. I solved it by setting my routePageBuilder like this:

routePageBuilder: (BuildContext context, Animation<double> animation,
            Animation<double> secondAnimation, provider) {
          return AnimatedBuilder(
              animation: animation,
              builder: (BuildContext context, Widget? child) {
                return VideoScaffold(
                    key: const ValueKey<String>('video'),
                    child: child ?? provider);
              });
        }

The VideoScaffold component looks like this:

import 'package:auto_orientation/auto_orientation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class VideoScaffold extends StatefulWidget {
  const VideoScaffold({required Key key, required this.child})
      : super(key: key);

  final Widget child;

  @override
  State<StatefulWidget> createState() => _VideoScaffoldState();
}

class _VideoScaffoldState extends State<VideoScaffold> {
  @override
  void initState() {
    SystemChrome.setPreferredOrientations([
      DeviceOrientation.landscapeRight,
      DeviceOrientation.landscapeLeft,
    ]);
    AutoOrientation.landscapeAutoMode();
    super.initState();
  }

  @override
  dispose() {
    SystemChrome.setPreferredOrientations([]);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return PopScope(
        onPopInvoked: (didPop) {
          SystemChrome.setPreferredOrientations([]);
        },
        child: Scaffold(
          body: Container(
            alignment: Alignment.center,
            color: Colors.black,
            child: widget.child,
          ),
        ));
  }
}

When launching full screen, the orientation is locked to landscape mode, and the device is auto rotated to landscape. On exit, the allowed orientations are reset.

The problem was when the orientations were changing in my original widget where the video was full screened from, the video controller would get wiped, and it was impossible to preserve the full screen.

However, when I set a key declared outside the build method for the Chewie player, it persists the video controller, and I am able to exit fullscreen as usual.

My video player widget looks like this:

      Widget videoPlayer(ChewieController? videoController) => AspectRatio(
            aspectRatio: 16 / 9,
            child: videoController != null
                ? Chewie(key: videoPlayerKey, controller: videoController)
                : const Center(child: CircularProgressIndicator()),
          );

Hope this can help somebody.