如何在 flutter web 中创建视频缩略图

Nei*_*fen 6 video video-thumbnails web flutter

出于性能原因,我一直在尝试为上传到 Firebase Firestore 的视频列表创建缩略图。

我想了不同的方法来解决这个问题。但我还没有找到实际可行的解决方案。给我最大希望的一个解决方案是第 5 个,所以请继续关注;)

  1. 有许多缩略图包,例如video_thumbnail,但该包无法在网络上运行。

  2. 我想也许从视频创建一个 gif,然后导出单个帧,但我发现执行此操作的所有包也无法在网络上工作(flutter_video_compressflutter_ffmpeg

  3. 也许我可以制作一个 1 秒版本的视频,所以我考虑修剪视频,结果相同。我发现的软件包不支持网络(video_trimmervideo_editor

  4. 我发现有一个截图包。所以我尝试了 video_player 和 HtmlElementView。两者的结果相同。这是代码:

import 'dart:html';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:screenshot/screenshot.dart';
...
Widget build(BuildContext context) {
    if (widget.item.thumbnail != null) {
      return Image.network(widget.item.thumbnail!);
    }

    //This is my video's path
    String path = widget.path;
    VideoElement video;
    ScreenshotController screenshotController = ScreenshotController();

    // ignore:undefined_prefixed_name
    ui.platformViewRegistry.registerViewFactory(url, (int viewId) {
      // just making sure I don't have some weird issues with crossOrigins
      video = VideoElement()..crossOrigin = 'anonymous';

      // Here I make sure, that the video is ready to play and I seek the first frame
      video.onCanPlay.listen((event) {
        _video.currentTime = 1;
      });

       // Here I make sure, that the video is actually showing a frame
      video.onSeeking.listen((event) async {
        var capture = await screenshotController.capture();
        DaysService.exportThumbnail(capture);
      });

      video.src = path;
      return video;
    });

    return Screenshot(
            controller: screenshotController,
            child: HtmlElementView(
              viewType: path,
           ),
    );
  } 
Run Code Online (Sandbox Code Playgroud)

但我有一个很大的例外,据我了解是因为我无法在网络上截图?(我想这也是为什么这些缩略图包都不能在网络上工作的原因)。这是异常堆栈:

Error: Unexpected null value.
    at Object.throw_ [as throw] (http://localhost:58128/dart_sdk.js:5061:11)
    at Object.nullCheck (http://localhost:58128/dart_sdk.js:5388:30)
    at _engine.PlatformViewLayer.new.preroll (http://localhost:58128/dart_sdk.js:137129:12)
    at _engine.OffsetEngineLayer.new.prerollChildren (http://localhost:58128/dart_sdk.js:136513:15)
    at _engine.OffsetEngineLayer.new.preroll (http://localhost:58128/dart_sdk.js:136788:35)
    at _engine.OffsetEngineLayer.new.prerollChildren (http://localhost:58128/dart_sdk.js:136513:15)
    at _engine.OffsetEngineLayer.new.preroll (http://localhost:58128/dart_sdk.js:136788:35)
    at _engine.TransformEngineLayer.new.prerollChildren (http://localhost:58128/dart_sdk.js:136513:15)
    at _engine.TransformEngineLayer.new.preroll (http://localhost:58128/dart_sdk.js:136788:35)
    at _engine.RootLayer.new.prerollChildren (http://localhost:58128/dart_sdk.js:136513:15)
    at _engine.RootLayer.new.preroll (http://localhost:58128/dart_sdk.js:136508:31)
    at _engine.LayerTree.new.flatten (http://localhost:58128/dart_sdk.js:137408:22)
    at _engine.LayerScene.new.toImage (http://localhost:58128/dart_sdk.js:137170:36)
at layer$.OffsetLayer.new.toImage (http://localhost:58128/packages/flutter/src/rendering/layer.dart.lib.js:1395:30)
    at toImage.next (<anonymous>)
    at runBody (http://localhost:58128/dart_sdk.js:38659:34)
    at Object._async [as async] (http://localhost:58128/dart_sdk.js:38690:7)
at layer$.OffsetLayer.new.toImage (http://localhost:58128/packages/flutter/src/rendering/layer.dart.lib.js:1386:20)
at proxy_box.RenderRepaintBoundary.new.toImage (http://localhost:58128/packages/flutter/src/rendering/proxy_box.dart.lib.js:3158:26)
at screenshot.ScreenshotController.new.<anonymous> (http://localhost:58128/packages/screenshot/screenshot.dart.lib.js:162:39)
    at Generator.next (<anonymous>)
    at runBody (http://localhost:58128/dart_sdk.js:38659:34)
    at Object._async [as async] (http://localhost:58128/dart_sdk.js:38690:7)
at http://localhost:58128/packages/screenshot/screenshot.dart.lib.js:150:68
    at http://localhost:58128/dart_sdk.js:33300:33
    at internalCallback (http://localhost:58128/dart_sdk.js:25436:11)

Run Code Online (Sandbox Code Playgroud)
  1. 最后我发现了 html5 和 javascript 是如何做到的(在这个网站上)。并尝试将其转换为 dart 代码(带有 HTML 包)。这是我的尝试,我试图使其尽可能相似:
  @override
  Widget build(BuildContext context) {
    if (widget.item.thumbnail != null) {
      return Image.network(widget.item.thumbnail!);
    }
    //this is my video's path
    String path = widget.path;
    VideoElement video;

    // ignore:undefined_prefixed_name
    ui.platformViewRegistry.registerViewFactory(path, (int viewId) {
      video = document.createElement('video') as VideoElement;
      // just making sure I don't have some weird issues with crossOrigins
      video.crossOrigin = 'anonymous';

      // Here I make sure, that the video is ready to play and I seek the first frame
      video.onLoadedMetadata.listen((event) {
        video.currentTime = 1;
      });

      // Here I make sure, that the video is actually showing a frame
      video.onSeeking.listen((event) async {
        var canva = document.createElement('canvas') as CanvasElement;
        canva
          ..height = video.videoHeight
          ..width = video.videoWidth;

        canva.context2D..drawImage(video, video.videoWidth, video.videoHeight);
        var data = Uri.parse(canva.toDataUrl()).data;
        DaysService.exportThumbnail(data?.contentAsBytes());
      });
      video.src = path;
      return video;
    });

    //I also still show the video. The endgoal would be to show the Thumbnail tho
    return HtmlElementView(
          viewType: path,
        );
  }
Run Code Online (Sandbox Code Playgroud)

结果是一张具有正确纵横比的灰色图片。我不知道为什么它是灰色的,我几乎觉得我做错了什么,而不是因为它是网络所以这是不可能的。

如果您知道我如何解决我的解决方案,或者您有其他想法,请告诉我:)

小智 1

你可以试试ffmpeg_wasm打包。它是ffmpeg_wasm.

首先,您必须初始化并加载 ffmpeg。

FFmpeg ffmpeg = createFFmpeg(true, "https://unpkg.com/@ffmpeg/core@0.11.0/dist/ffmpeg-core.js");

void loadFFmpeg() async {
  await promiseToFuture(ffmpeg.load());
}
Run Code Online (Sandbox Code Playgroud)

您可以在访问任何函数之前确保 ffmpeg 是否已加载ffmpeg.isLoaded();

当您选择一个文件时,首先需要将该文件写入内存。

 ffmpeg.writeFile(
        'writeFile', 'input.mp4', filePickerResult!.files.single.bytes);
Run Code Online (Sandbox Code Playgroud)

然后,对于这个特定的用例,您可以使用以下 ffmpeg 命令。

await promiseToFuture(ffmpeg.run7("-i", "input.mp4", '-vf',
          'select=\'eq(n,0)\'', '-vsync', '0', 'frame1.webp'));
var firstFrameData = ffmpeg.readFile('readFile', 'frame1.webp');
Run Code Online (Sandbox Code Playgroud)

生成的firstFrameData 是html blob 文件。

Ps 我是那个包的作者。我也面临同样的问题。对于我的用例,我在部署时也遇到了 cors 错误,因此我需要下载 ffmpeg-core 和 canvaskit js 包并将其存储在 Web 文件夹中。