jakky1 / video_player_win

Flutter video player for Windows, lightweight, using Windows built-in Media Foundation API. Windows implementation of the video_player plugin.
BSD 3-Clause "New" or "Revised" License
35 stars 11 forks source link

Looping working? #44

Open ItsVeridian opened 1 week ago

ItsVeridian commented 1 week ago

I swear it worked before, but now it just stops at the end

jakky1 commented 1 week ago

Fixed.

Please try the new version 3.1.1

ItsVeridian commented 1 week ago

I tried the new version, but for some reason, I can only get looping if I implement it manually like this:

_controller.setLooping(false);

_controller.addListener(() {
          if(_controller.value.isCompleted) {
            _controller.play();
          }
        });

Full code of my widget:

import 'dart:io';

import 'package:flutter/material.dart';

import 'package:video_player_win/video_player_win.dart';
import 'package:visibility_detector/visibility_detector.dart';

class VideoView extends StatefulWidget {
  const VideoView(this.file, {super.key, this.controller, this.fit = BoxFit.cover})
      : assert(controller != null || file != null, 'Controller and File cannot both be null!');

  final File? file;
  final BoxFit fit;
  final WinVideoPlayerController? controller;

  @override
  State<VideoView> createState() => _VideoViewState();
}

class _VideoViewState extends State<VideoView> {
  late final WinVideoPlayerController _controller;

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

    if (widget.controller == null) {
      _controller = WinVideoPlayerController.file(widget.file!);
    } else {
      _controller = widget.controller!;
    }

    if (_controller.value.isInitialized) return;

    initializeController(_controller).then(
      (value) {
        _controller.play();
        _controller.setVolume(0);
        _controller.setLooping(false);

        _controller.addListener(() {
          if(_controller.value.isCompleted) {
            _controller.play();
          }
        });

        setState(() {});
      },
    );
  }

  Future<void> initializeController(WinVideoPlayerController controller) async {
    while(!controller.value.isInitialized && !_controller.value.isInitialized) {
      await controller.initialize();
    }
  }

  @override
  Widget build(BuildContext context) {
    return VisibilityDetector(
      key: ValueKey(widget.file?.path),
      onVisibilityChanged: (info) {
        if (!_controller.value.isInitialized) return;

        if (info.visibleFraction == 0) {
          _controller.pause();
        } else {
          _controller.play();
        }
      },
      child: FittedBox(
        fit: widget.fit,
        clipBehavior: Clip.hardEdge,
        child: SizedBox(
          width: _controller.value.size.width,
          height: _controller.value.size.height,
          child: Texture(
            textureId: _controller.textureId_,
            filterQuality: FilterQuality.low,
          ),
        ),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
  }
}
jakky1 commented 1 week ago

In your sample code, you called _controller.setLooping(false); Is there any problem for passing true into _controller.setLooping(true); ?

I think the following code snippet is working, could you give it a try ? Just copy and paste it, without any modification.

_controller = WinVideoPlayerController.file(File(widget.file!));
_controller!.initialize().then((value) {
      if (_controller!.value.isInitialized) {
        _controller!.play();
        _controller!.setLooping(true);
      }
}
ItsVeridian commented 1 week ago

I'm making a gallery app, so I create a controller to be passed to both the gallery and single image view, so video playback continues across the navigation:

if (_images[i].isVideo) {
  _images[i].videoController = WinVideoPlayerController.file(_images[i].file);
}

If I modify the video widget code similar to yours:

_controller.initialize().then(
  (value) {
    _controller.play();
    _controller.setVolume(0);
    _controller.setLooping(true);

    setState(() {});
  },
);

It seems to work, but sometimes videos don't play, or play too fast, before returning to normal. It could have something to do with the VisibilityDetector, or the FlutterListView packages, but I'm not sure.

I think for now I'm going to stick to my original code.

jakky1 commented 1 week ago

I noticed that you are making a gallery app, it may have multiple video files opened at the same time. So, I try to improve the solution, and push to github.

Please try it with the following dependency, and let me know if it works for you:

dependencies:
  video_player_win:
    git:
      url: https://github.com/jakky1/video_player_win.git
      ref: master
ItsVeridian commented 5 days ago

Hi! It seems to be looping normally, but I'm struggling a bit to optimize the videos in the gallery. I'm not sure if it's because of the FlutterListView, but I'm having issues with the videos not being garbage collected properly. Can you double check if this package's dispose is working correctly?

jakky1 commented 4 days ago

I have tested: create -> dispose -> create , loops 300 times, and from the Windows source monitor, the memory usage of the application has not increased significantly.

(But I can only test the situation where there is only one video at the same time.)

Please check whether the following text appears in the log window every time you think the video is disposed:

[video_player_win][native] ~MyPlayer() destroyed

If it appears every time when dispose() called, it means it was garbage collected normally.

Please make sure that when you generate a controller in initState() in a StatefulWidget, you also call controller.dispose in the dispose() function in the widget.

For example:

class MyVideoWidget extends StatefulWidget {
  const MyVideoWidget({
      Key? key, 
      required String path
  }) : super(key: key);

  @override
  State<MyVideoWidget> createState() => _MyVideoWidgetState();
}

class _MyVideoWidgetState extends State<MyVideoWidget> {
  WinVideoPlayerController? controller;

  @override
  void initState() {
    super.initState();
    controller = VideoPlayerController.file(File(widget.path));
  }

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