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
29 stars 9 forks source link

[BUG] Garbage collector problem #35

Open Andrekarma opened 2 months ago

Andrekarma commented 2 months ago

if i initialize a video player and dont use it (i want to prepare it for later), this message occur:

[video_player_win] gc free a player that didn't dispose() yet !!!!!

And the player is no more initialized

jakky1 commented 2 months ago

I think the controller instance you created is not pointed by any variable anymore, so dart garbage collection try to clean it.

Could you try to assign the controller instance to a static / global variable (though it is not a good idea), ensure you just create ONE controller instance, and check the message still occurs?

Andrekarma commented 2 months ago

I am creating 2 instances of a class with the video controller. i load 2 different videos at the time, but only one is showed. The second one is ready for the first one to be finished, then i switch them. Sometimes (randomly) the "[video_player_win] gc free a player that didn't dispose() yet !!!! appears" and the instance that is not playing is gone.

`class PHFPlayerVideoPlayer extends PHFPlayer{ PHFPlayerVideoPlayer(index){ this.index = index; }

VideoPlayerController? videoPlayerController;

@override closeVideoPlayer() async{ if(videoPlayerController != null) { await videoPlayerController!.pause(); await videoPlayerController!.dispose(); videoPlayerController = null; } }

@override initializeVideoSlide(path) async{ videoPlayerController = VideoPlayerController.file(File(path)); videoPlayerController!.initialize(); videoPlayerController!.setVolume(PHFConfig.instance.moviesVolume 100 (PHFConfig.instance.disableMovieAudio == 0 ? 1 : 0)); } `

Andrekarma commented 2 months ago

I hope it is related to the fact that after some iterations of this switch beetween different videos, the app crashes with no specific error log

jakky1 commented 2 months ago

I write the following simple test code, always preload the next video every time when clicking "Next" button. I try to click the "Next" button every second about 50 times, and it works well with no gc occurs.

Did this solve your problem?

Note: Please remember to edit the gFileList array in the following code if you try to run it.

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:video_player_win/video_player_win.dart';

void main() {
  runApp(const MyApp());
}

const gFileList = [
  "E:\\test_4k.mp4",
  "E:\\test_align.mp4",
  "E:\\test_av1.mp4",
  "E:\\test_clock.mp4",
  "E:\\test_youtube.mp4",
];

class PHFPlayerVideoPlayer {
  WinVideoPlayerController? controller;

  Future<void> initializeVideoSlide(String path) async {
    controller?.dispose();
    controller = WinVideoPlayerController.file(File(path));
    await controller!.initialize();
  }

  void play() {
    controller!.play();
  }

  void closeVideoPlayer() {
    controller?.dispose();
    controller = null;
  }
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  var nowPlayer = PHFPlayerVideoPlayer();
  var nextPlayer = PHFPlayerVideoPlayer();
  int nowIndex = 0;

  @override
  void initState() {
    super.initState();
    nowIndex = 0;
    nowPlayer.initializeVideoSlide(gFileList[nowIndex]).then((value) {
      nowPlayer.play();
      setState(() {}); // update UI
    });
    prepareNext();
  }

  void prepareNext() {
    int nextIndex = (nowIndex + 1) % gFileList.length; // loop the list
    nextPlayer.initializeVideoSlide(gFileList[nextIndex]);
  }

  void onNextButtonClicked() {
    nowPlayer.closeVideoPlayer();

    // swap 2 player pointer
    var tmp = nextPlayer;
    nextPlayer = nowPlayer;
    nowPlayer = tmp;
    nowIndex = (nowIndex + 1) % gFileList.length; // loop the list

    nowPlayer.play();
    prepareNext();
    setState(() {}); // update UI
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
      body: Stack(children: [
        WinVideoPlayer(nowPlayer.controller!),
        ElevatedButton(
            onPressed: onNextButtonClicked, child: const Text("Next")),
      ]),
    ));
  }
}
Andrekarma commented 2 months ago

It is still happening, and i don't understand why The logic is the same of your example, but the Garbage collector is doing something that should not be doing. I still think is something related to this library, because its not happening with other libraries

jakky1 commented 2 months ago

First, did you try to run my sample code above, WITHOUT any modification, and ensure that the GC still occurs ?

Second, you said it is not happen with other libraries, Could you give me a SIMPLE sample code ( less than 200 lines ), and tell me which library and which platform that no GC occurs. I need more information to clarify issue.

Andrekarma commented 2 months ago

@jakky1 Here you have it

image

import 'dart:io'; import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart';

void main() { runApp(const MyApp()); }

const gFileList = [ "C:\Users\andreacarmagnola\Desktop\VIDEOTEST\4kvert.mp4", "C:\Users\andreacarmagnola\Desktop\VIDEOTEST\bene.mp4", "C:\Users\andreacarmagnola\Desktop\VIDEOTEST\male.mp4", ];

class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key);

@override State createState() => _MyAppState(); }

class _MyAppState extends State { PHFPlayer player1 = PHFPlayer.createVideoPlayer(PHFPlayer.VIDEO_PLAYER_LIBRARY, 1); PHFPlayer player2 = PHFPlayer.createVideoPlayer(PHFPlayer.VIDEO_PLAYER_LIBRARY, 2); late PHFPlayer currentPlayer; bool showPlayer =false; int nowIndex = 0;

@override initState(){ super.initState(); nowIndex = 0; Future.delayed(Duration(seconds: 1), () async { await player1.initializeVideoSlide(gFileList[nowIndex]); int nextIndex = (nowIndex + 1) % gFileList.length; // loop the list await player2.initializeVideoSlide(gFileList[nextIndex]); currentPlayer = player1;

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

}

@override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: Stack(children: [ showPlayer?currentPlayer.getVideoWidget(false):Container()

    ]),
  ),
);

} }

abstract class PHFPlayer { static const int VIDEO_PLAYER_LIBRARY = 2;

late int libraryType; late int index; late Image image;

Future initializeVideoSlide(videoFile);

Widget getVideoWidget(useBlur);

static PHFPlayer createVideoPlayer(int libraryType, index) { return PHFPlayerVideoPlayer(index);

}

}

class PHFPlayerVideoPlayer extends PHFPlayer{ PHFPlayerVideoPlayer(index){ this.index = index; libraryType = PHFPlayer.VIDEO_PLAYER_LIBRARY; }

VideoPlayerController? videoPlayerController;

@override initializeVideoSlide(videoFile) async{ videoPlayerController = VideoPlayerController.file(File(videoFile)); await videoPlayerController!.initialize(); }

@override Widget getVideoWidget(useBlur){ videoPlayerController!.play(); return VideoPlayer(videoPlayerController!); }

}

jakky1 commented 2 months ago

Thanks for reporting this issue, and the sample code.

It seems there is really something wrong in this package. In my sample code last week, I use WinVideoPlayerController, and it really work well. In your sample code, you use official VideoPlayerController, and gc occurs.

It is confirmed that if any VideoPlayerController created but not displayed, then the WinVideoPlayerController which is created by this VideoPlayerController will be GC collection. I have never test this case with official VideoPlayerController before... orz

Now it is fixed, and I only push to github now. Please try it and let me know if the solution works for you.

dependencies:
  video_player_win:
    git:
      url: https://github.com/jakky1/video_player_win.git
      ref: master
Andrekarma commented 2 months ago

The GC issue seems now resolved

But not there is another problem. Randomly after some time playing, i see this error and the app crashes

[ERROR:flutter/shell/platform/windows/external_texture_d3d.cc(107)] Binding D3D surface failed.

jakky1 commented 2 months ago

Because it rarely occurs, again I need a simple sample code (< 300 lines) and a more easy way to duplicate this issue.

I have no way to fix this before I can duplicate this issue on my PC.

Andrekarma commented 1 month ago

import 'dart:async'; import 'dart:io';

import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; import 'package:video_player_win/video_player_win.dart';

void main() { runApp(const MyApp()); }

const gFileList = [ "C:\Users\andreacarmagnola\Desktop\VIDEOTEST\4kvert.mp4", "C:\Users\andreacarmagnola\Desktop\VIDEOTEST\bene.mp4" ];

class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key);

@override State createState() => _MyAppState(); }

class _MyAppState extends State { PHFPlayerVideoPlayer player1 = PHFPlayerVideoPlayer(); PHFPlayerVideoPlayer player2 = PHFPlayerVideoPlayer(); late PHFPlayerVideoPlayer currentPlayer; bool showPlayer =false; int nowIndex = 0;

@override initState(){ super.initState(); nowIndex = 0; Future.delayed(Duration(seconds: 1), () async { await player1.initializeVideoSlide(gFileList[0]); player1.index =1 ; int nextIndex = (nowIndex + 1) % gFileList.length; // loop the list await player2.initializeVideoSlide(gFileList[1]); player2.index = 2; currentPlayer = player1;

  Timer.periodic(Duration(seconds: 5), (Timer) async {
    await switchPlayer();
  });
  setState(() {
    showPlayer = true;
  });
});

}

switchPlayer() async{ if (currentPlayer.index == 1) { currentPlayer = player2; await player1.videoPlayerController!.dispose(); await player1.initializeVideoSlide(gFileList[0]); } else { currentPlayer = player1; await player2.videoPlayerController!.dispose(); await player2.initializeVideoSlide(gFileList[1]); }

setState(() {});

}

@override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: Stack(children: [ showPlayer?currentPlayer.getVideoWidget(false):Container()

    ]),
  ),
);

} }

class PHFPlayerVideoPlayer {

int index = 0; WinVideoPlayerController? videoPlayerController;

initializeVideoSlide(videoFile) async{ videoPlayerController = WinVideoPlayerController.file(File(videoFile)); await videoPlayerController!.initialize(); }

Widget getVideoWidget(useBlur){ videoPlayerController!.play(); return WinVideoPlayer(videoPlayerController!); }

}

Andrekarma commented 1 month ago

@jakky1 do you have any update on this?

jakky1 commented 1 month ago

I have run your code in debug mode (5 hours) and release mode (8 hours) without any error occurs... I have no idea how to produce & fix the error Binding D3D surface failed you mentioned...