For performance reasons I've been trying to create a thumbnail for a list of videos I upload to the Firebase Firestore.
I thought of different ways to solve this. But I haven't found an actual working solution. The one solution that gave me the most hope was number 5, so stay tuned ;)
There is a number of thumbnail packages, like video_thumbnail but the package doesn't work on web.
I thought of maybe creating a gif from the video and then exporting a single frame but all the packages I found that do this also don't work on web (flutter_video_compress, flutter_ffmpeg
Maybe I could do a 1 second version of the video, so I looked into trimming the video, with he same result. The packages I found don't support web (video_trimmer, video_editor)
I found out there was a screenshot package. So I tried it both with video_player and with HtmlElementView. Both with the same result. Here is the code:
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,
),
);
}
But I got a big exception that as far as I understand is because I just can't take screenshots on the web? (I guess that's also why none of those thumbnail packages work on the web). Here is the exception stack:
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)
- Finally I found out how html5 and javascript do it (on this site). And gave it a try to transform it into dart code (with the HTML package). Here is my try, I tried to make it as similar as possible:
@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,
);
}
The result of this is a grey picture with the right aspect ratio. I don't know why it's grey, I almost feel like I did something wrong and not that it's not possible because it's web.
If you have any idea how I could maybe fix my solutions or if you have another idea, let me know :)