17

I'm using provider with video_player to manage the state.

VideoProvider has:

videoPlayerController = VideoPlayerController.network(...);

which I frequently change when the user switches to a new video (same screen). If I directly assign a new VideoPlayerController.network(...) into the videoPlayerController, the old video will still play and you can hear the sound. The workaround is to videoPlayerController.pause() then assign a new VideoPlayerCOntroller.network afterwards.

Is the previous Video being disposed by GC? Are there any performance issues with this? I want to get rid of the previous video and the resources used before switching to a new one. I can't videoPlayerController.dispose() before switching because it causes an error.

AnsellC
  • 795
  • 3
  • 9
  • 17

6 Answers6

26

When you call dispose your controller is still being used by VideoPlayer widget. First, you need to make sure that it's not used anymore (setting controller in state to null) and AFTER that you call dispose.

I'm not sure about your state management via Provider, but I'll give you an example how to do this with regular State.

  VideoPlayerController _controller;

  void _initController(String link) {
    _controller = VideoPlayerController.network(link)
      ..initialize().then((_) {
        setState(() {});
      });
  }

  Future<void> _onControllerChange(String link) async {
    if (_controller == null) {
      // If there was no controller, just create a new one
      _initController(link);
    } else {
      // If there was a controller, we need to dispose of the old one first
      final oldController = _controller;

      // Registering a callback for the end of next frame
      // to dispose of an old controller
      // (which won't be used anymore after calling setState)
      WidgetsBinding.instance.addPostFrameCallback((_) async {
        await oldController.dispose();

        // Initing new controller
        _initController(link);
      });

      // Making sure that controller is not used by setting it to null
      setState(() {
        _controller = null;
      });
    }
  }
Igor Kharakhordin
  • 9,185
  • 3
  • 40
  • 39
  • Interesting. I thought `final oldController = _controller` just creates a new variable ie: pass by value which makes `oldController.dispose()` only affecting `oldController` and not the `_controller` instance. – AnsellC Nov 20 '19 at 16:47
  • thank you very very much mr igor your code work pefectly – mohammad shabani Jan 30 '20 at 03:21
  • 1
    YOU ARE AWESOME IGOR, I HAVE NO WORDS TO EXPLAIN MY BEST WISHES FOR YOU. I was searching playing multiple videos as per user request one by one for 4 days and today you made my day. I know it was my silly mistake that I was setting state "_controller = null;" inside the "WidgetsBinding.instance.addPostFrameCallback((_) async {" function at the end which should be outside of this function. Once again thanks dear, keep it up and help others same like me. Happy Coding :) – Kamlesh Nov 08 '20 at 13:26
  • Can you tell me what the benefit is of making the oldController final is? – ebg11 Jan 21 '21 at 21:23
  • 3
    Hi, where in your code do you call _onControllerChange() ? – AkbarB Jan 24 '21 at 20:21
8

I know its late but this will help others with same problem.

I had same issue, i wanted to change Video when user selects new video from list, after researching so much i finally created a solution by myself.

Follows the below step and you can video video in Video Player in same screen

  1. Create a StatefulWidget class containing VideoPlayer and initialize the videoPlayerController in init method. Suppose you created a StatefulWidget class named MyVideoPlayer then in constructor accept two variable, i.e, i> String videoLink ii> UniqueKey()

Note - You have to pass UniqueKey() to the super of this class, example,

class MyVideoPlayer extends StatefulWidget {
  final String videoLink;
  final UniqueKey newKey;

  MyVideoPlayer(this.videoLink, this.newKey): super(key: newKey); // passing Unique key to dispose old class instance and create new with new data

  @override
  _MyVideoPlayerState createState() => _MyVideoPlayerState();
} ...
  1. Replace your VideoPlayer instance of main dart file where you are playing video with your previously created class i.e, MyVideoPlayer and pass videoLink and UniqueKey().

  2. Now whenever you want to change the video just update videoLink inside setState(() {}) of your main dart file and after that a new VideoPlayer instance is created by disposing the old one totally.

Note - Here the main work is done by UniqueKey(), by passing UniqueKey() you are saying flutter to create a new unique instance of this particular class;

Harsh Barnwal
  • 101
  • 2
  • 7
2

You can use that:

@override
  void dispose() {
    if (_controller.value.isPlaying) _controller.pause();
    _controller.removeListener(_videoListener);
    _controller = null;
    super.dispose();
  }
  • Worth mentioning that this approach works for better_player package too, with the appropriate syntax modifications. Thanks. – Dave Jun 27 '21 at 19:17
0

I know is later but I think i found one way to fix this.

1.Create initPlayer() (I've created using Provider)

  void initPlayer( {pause = false, update = false, updateUrl = ""} ) async {
  var isPlaying = false;

  if (controller == null) {
    controller = VideoPlayerController.file(File(url))..addListener(() {
    isPlaying = true;
    notifyListeners();
   })..initialize().then((value) => notifyListeners());
   controller.play();
   } else {
     print ("Controller is playing...");
   if (pause) {
    if (controller.value.isPlaying) {
      controller.pause();
    } else {
      controller.play();
    }
   }
   if (update && updateUrl != "") {
    final oldController = controller;
    await oldController.dispose();
    controller = VideoPlayerController.file(File(updateUrl))..addListener(() {
      isPlaying = true;
      notifyListeners();
       })..initialize().then((value) => notifyListeners());
      controller.play();
    }
  }
}
  1. Add Actions (start, pause change) in the widget.

          child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
        FlatButton(
          child: Text("Play Video"),
          color: Colors.red,
          onPressed: () {
            musicPlayerProvider.initPlayer();
          },
        ),
        FlatButton(
          child: Text("Stop Video"),
          color: Colors.pink,
          onPressed: () {
            musicPlayerProvider.initPlayer(pause: true);
          },
        ),
        FlatButton(
          child: Text("Change Video"),
          color: Colors.yellow,
          onPressed: () {
            musicPlayerProvider.initPlayer(update: true,updateUrl: 
                "url");
          },
        ),
      ],),
    

For now this works but of course you can make changes.

Later we need to check when the video has finished using addListener method ( I guess ).

thenewendrit
  • 39
  • 1
  • 3
0

This one fixed my issue thanks to someone from this link

void dispose() {
        _controller.pause();
        _controller?.dispose();
        //ignore below
        audioPlayer.stop();
        audioPlayer?.dispose();
        super.dispose();
      }
Dean Villamia
  • 576
  • 11
  • 24
0

I was making a-la Instagram grid view. Every time when I picked a new file I should update my thumbnail. But my Stateful widget was not recognizing the update. The dispose method wasn't launching.

Finally I came up with this. Hope this will help someone.

class _SelectedVideo extends StatefulWidget {
  const _SelectedVideo({
    Key? key,
    required this.file,
  }) : super(key: key);
  final File file;

  @override
  State<_SelectedVideo> createState() => _SelectedVideoState();
}

class _SelectedVideoState extends State<_SelectedVideo> {
  VideoPlayerController? _controller;

  void _initVideoPlayer(File file) async {
    _controller = VideoPlayerController.file(
      file,
    );
    await _controller!.initialize();
    _controller!.setVolume(0);
    _controller!.play();

    setState(() {});
  }

  @override
  void initState() {
    super.initState();
    _initVideoPlayer(widget.file);
  }

  @override
  void didUpdateWidget(covariant _SelectedVideo oldWidget) {
    // I'm disposing controller all the time when widget updates
    _controller?.dispose();

    _initVideoPlayer(widget.file);
    super.didUpdateWidget(oldWidget);
  }

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

  @override
  Widget build(BuildContext context) {
    if (_controller == null) return Container();
    return Container(
      color: Colors.black,
      height: 375,
      width: double.infinity,
      child: FittedBox(
        fit: BoxFit.cover,
        clipBehavior: Clip.hardEdge,
        child: SizedBox(
          width: _controller!.value.size.width,
          height: _controller!.value.size.height,
          child: AspectRatio(
            aspectRatio: _controller!.value.aspectRatio,
            child: VideoPlayer(_controller!),
          ),
        ),
      ),
    );
  }
}