justsoft / video_thumbnail

This plugin generates thumbnail from video file or URL. It returns image in memory or writes into a file. It offers rich options to control the image format, resolution and quality. Supports iOS and Android.
MIT License
183 stars 225 forks source link

No implementation found for method file on channel plugins.justsoft.xyz/video_thumbnail #133

Open wlingf opened 1 year ago

wlingf commented 1 year ago

image

Alex-web0 commented 1 year ago

Same problem here

sdkysfzai commented 1 year ago

having same problem.

Alex-web0 commented 1 year ago

I found a solution thanks to chatGBT

class VideoCache {
  late final Directory cacheDir;

  /// Whenever this class is doing a download operation, this will be called
  final Function(int bytesRecieved, int bytesLeft)? onReceiveBytes;

  /// Whenever a download operation is completed, this will be called!
  final VoidCallback? downloadCompleted;

  final VoidCallback? onDownloadOperationCanceled;

  final VoidCallback? onDownloadStart;

  CancelToken cancelToken = CancelToken();
  bool _disposed = false;

  VideoCache({
    this.onReceiveBytes,
    this.downloadCompleted,
    this.onDownloadOperationCanceled,
    this.onDownloadStart,
  });

  /// Must be called ONCE, before using any method!
  init() async {
    cacheDir = await getTemporaryDirectory();
    if (kDebugMode) {
      print("INITIALIZED INSTANCE OF VIDEO CACHE");
    }
  }

  Future<File?> getVideoThumbnail(String outPath, String url) async {
    final arguments = [
      '-i',
      url,
      '-ss',
      '00:00:01.000',
      '-vframes',
      '1',
      '-q:v',
      '50',
      outPath
    ];

    final flutterFFmpeg = FlutterFFmpeg();
    final execution = await flutterFFmpeg.executeWithArguments(arguments);
    if (execution == 0) {
      return File(outPath);
    } else {
      print('Error extracting video thumbnail');
      return null;
    }
  }

  Future<ImageProvider> getCachedVideoThumbnail(String url) async {
    final filename = url.split('/').last;
    final usableFileName = _generateUniqueKeyName(filename);
    const determinedExtension = 'jpg';

    final file = File('${cacheDir.path}/$usableFileName.$determinedExtension');
    print(file.path);

    if (file.existsSync()) {
      return FileImage(file);
    } else {
      try {
        print('Thumbnail not cached, trying to download');
        final thumbnailFile = await getVideoThumbnail(file.path, url);

        return (thumbnailFile != null
                ? FileImage(thumbnailFile)
                : const AssetImage('assets/images/default-fallback-media.png'))
            as ImageProvider;
      } catch (e) {
        print('Error downloading video thumbnail: $e');
        return const AssetImage('assets/images/default-fallback-media.png');
      }
    }
  }

  void clearCache() async {
    try {
      await cacheDir.delete(recursive: true);
      await cacheDir.create();
    } catch (e) {
      print('Error clearing video cache: $e');
    }
  }

  String _determineVideoExtension(String name) {
    if (name.contains('.avi')) {
      return 'avi';
    }
    return 'mp4';
  }

  /// generates a key, from ANY string.
  String _generateUniqueKeyName(String input) {
    var bytes = utf8.encode(input); // Convert the input string to bytes
    var digest = sha256.convert(bytes); // Generate the SHA-256 hash
    var base64Url =
        base64UrlEncode(digest.bytes); // Encode the hash as Base64 URL
    return base64Url.substring(
        0, 32); // Return the first 32 characters of the encoded string
    // Encode the key using URL-safe Base64
  }

  /// fetch the video
  Future<File?> fetchVideoFromCacheIfAny(String url) async {
    File file = _generateFileWithCorrectNamingFromUrl(url);
    print(file.path);

    if (file.existsSync()) {
      return file;
    } else {
      try {
        onDownloadStart?.call();
        final response = await Dio().get(url,
            options: Options(responseType: ResponseType.bytes),
            cancelToken: cancelToken,
            onReceiveProgress: onReceiveBytes);

        final downloadedFile = File(file.path);
        await downloadedFile.writeAsBytes(response.data as List<int>);
        downloadCompleted?.call();
        return file;
      } catch (e) {
        if (e is CancelToken) {}
        if (!_disposed) {
          onDownloadOperationCanceled?.call();
        }
        cancelToken = CancelToken();
        print('Error downloading video: $e');
        return null;
      }
    }
  }

  File _generateFileWithCorrectNamingFromUrl(String url) {
    final filename = url.split('/').last;
    final usableFileName = _generateUniqueKeyName(filename);
    final determinedExtension = _determineVideoExtension(filename);

    final file = File('${cacheDir.path}/$usableFileName.$determinedExtension');
    return file;
  }

  bool doesUrlFileExistInCache(String url) {
    File file = _generateFileWithCorrectNamingFromUrl(url);
    return file.existsSync();
  }

  cancelDownloadOperation() {
    cancelToken.cancel();
  }

  dispose() {
    print("DISPOSED OF: VIDEO CACHE INSTANCE! & CANCELED ON GOING OPERATIONS");
    _disposed = true;
    cancelToken.cancel();
  }
}
sdkysfzai commented 1 year ago

The solution for me was to run in release mode in Android, debug mode did not work. While in iOS debug works fine.

saveKenny commented 1 year ago

Facing the same issue v0.5.3

lucasuracosta commented 1 year ago

Same issue here on v0.5.3

misha-muraly commented 1 year ago

facing the same issue in v0.5.3

akoflacko commented 1 year ago

facing the same issue in v0.5.3

bjmcallister commented 1 year ago

Just adding to the list here.

Same here v0.5.3

AbdullahGaber commented 1 year ago

same here v0.5.3

berkaykurkcu commented 1 year ago

same here. Any workarounds?

AbdullahGaber commented 1 year ago

same here. Any workarounds?

worked with me only in https links

codesculpture commented 1 year ago

Hey Guys, The problem is when u call thumbnailFile requires the thumbnailPath argument. You may ignore it since it was marked as optional. But in native side the code was designed in a way that it expecting the thumbnailPath argument and there is no null check or fallback. So thats why u may get MissingException Error.. I got that and fixed by providing thumbNailPath as getTemporaryDirectory() (you can give other dirs as well)

Piscen commented 11 months ago

Hey Guys, The problem is when u call thumbnailFile requires the thumbnailPath argument. You may ignore it since it was marked as optional. But in native side the code was designed in a way that it expecting the thumbnailPath argument and there is no null check or fallback. So thats why u may get MissingException Error.. I got that and fixed by providing thumbNailPath as getTemporaryDirectory() (you can give other dirs as well)

Even if this parameter “thumbnailPath” is added, an error will still be reported

iOS-Kel commented 11 months ago

same here v0.5.3

WongChanKit commented 10 months ago

android\app\src\main\AndroidManifest.xml, add this in application

this:

android:usesCleartextTraffic="true"

saramand9 commented 7 months ago

android\app\src\main\AndroidManifest.xml,将其添加到应用程序中

这:

android:usesCleartextTraffic="true"

this works for me

BuyMyBeard commented 2 months ago

android\app\src\main\AndroidManifest.xml, add this in application

this:

android:usesCleartextTraffic="true"

Worked for me too. Except that this line is very dangerous. It makes your app use http instead of https. This means that it lowers the security significantly and allows Man in the Middle Attacks, spying, and other security concerns.