Flutter视频播放器dispose

17

我正在使用providervideo_player来管理状态。

VideoProvider具有以下功能:

videoPlayerController = VideoPlayerController.network(...);

当用户切换到新视频(同一屏幕)时,我经常更改的是VideoPlayerController。如果我直接将一个新的VideoPlayerController.network(...)分配给videoPlayerController,旧视频仍将播放,并且您可以听到声音。解决方法是先调用videoPlayerController.pause(),然后再分配一个新的VideoPlayerCOntroller.network

之前的视频是否被垃圾回收器回收了?这样做是否会带来性能问题?我想在切换到新视频之前摆脱之前的视频和使用的资源。在切换之前无法调用videoPlayerController.dispose(),因为会出现错误。


哪个错误处理引起了问题? - Igor Kharakhordin
2
@IgorKharakhordin videoPlayerController 在被释放后仍然被使用。 - AnsellC
6个回答

27
当你调用dispose时,你的控制器仍然被VideoPlayer小部件使用。首先,你需要确保它不再被使用(将控制器设置为null),然后再调用dispose。
我不确定你是否通过Provider进行状态管理,但我会给你一个使用常规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;
      });
    }
  }

有趣。我原以为 final oldController = _controller 只是创建了一个新变量,即传值方式,这使得 oldController.dispose() 只影响 oldController 而不是 _controller 实例。 - AnsellC
非常感谢您,Igor先生,您的代码完美地运行了。 - mohammad shabani
1
你太棒了Igor,我无法用言语表达我对你的最好祝愿。我已经搜索了4天,想要根据用户请求逐个播放多个视频,而今天你让我开心了。我知道这是我的愚蠢错误,我在函数结尾处设置状态“_controller = null;”,而应该在此函数之外。再次感谢你,继续保持并帮助像我一样的人。Happy Coding :) - Kamlesh
你能告诉我将oldController声明为final的好处是什么吗? - ebg11
3
你好,你的代码中在哪里调用了 _onControllerChange() 函数? - AkbarB

9

我知道现在有点晚了,但这会帮助其他遇到同样问题的人。

我也曾遇到同样的问题,想要在用户从列表中选择新视频时更改视频,经过大量研究后,我最终自己创建了一个解决方案。

遵循以下步骤,你可以在同一屏幕上的视频播放器中查看视频:

  1. 创建一个StatefulWidget类,其中包含VideoPlayer并在init方法中初始化videoPlayerController。 假设您创建了一个名为MyVideoPlayer的StatefulWidget类,然后在构造函数中接受两个变量,即, i> String videoLink ii> UniqueKey()

注意 - 您必须将UniqueKey()传递给此类的super,例如,

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. 在主dart文件中,将播放视频的VideoPlayer实例替换为之前创建的MyVideoPlayer类,同时传递视频链接和UniqueKey()

  2. 现在,无论何时想要更改视频,只需在您的主dart文件的setState(() {})中更新videoLink,然后通过销毁旧的实例来创建新的VideoPlayer实例。

注意 - 这里要依赖于UniqueKey()的工作机制。通过传递UniqueKey(),您告诉Flutter创建该特定类的一个新的唯一实例;


谢谢。这个完美地运作,没有任何麻烦或复杂情况。@Harsh Barnwal - undefined

2
你可以使用这个:
@override
  void dispose() {
    if (_controller.value.isPlaying) _controller.pause();
    _controller.removeListener(_videoListener);
    _controller = null;
    super.dispose();
  }

值得一提的是,这种方法也适用于better_player软件包,只需进行适当的语法修改即可。谢谢。 - Dave

0

这个链接里的某人解决了我的问题,谢谢他。

void dispose() {
        _controller.pause();
        _controller?.dispose();
        //ignore below
        audioPlayer.stop();
        audioPlayer?.dispose();
        super.dispose();
      }

0

我正在制作一个类似Instagram网格视图的东西。每次选择新文件时,我都应该更新我的缩略图。 但是我的Stateful widget没有识别到更新。 dispose方法没有启动。

最后我想出了这个解决方案。 希望能对某些人有所帮助。

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!),
          ),
        ),
      ),
    );
  }
}


0

我知道现在有点晚了,但我想我找到了一种解决方法。

1.创建initPlayer()(我使用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();
    }
  }
}
  • 在小部件中添加Actionsstartpause change)。

          child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
        FlatButton(
          child: Text("播放视频"),
          color: Colors.red,
          onPressed: () {
            musicPlayerProvider.initPlayer();
          },
        ),
        FlatButton(
          child: Text("停止视频"),
          color: Colors.pink,
          onPressed: () {
            musicPlayerProvider.initPlayer(pause: true);
          },
        ),
        FlatButton(
          child: Text("更改视频"),
          color: Colors.yellow,
          onPressed: () {
            musicPlayerProvider.initPlayer(update: true,updateUrl: 
                "url");
          },
        ),
      ],),
    
  • 目前这个可以工作,但当然你可以进行更改。

    稍后我们需要使用addListener方法(我猜)检查视频何时完成


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接