compress method
Future<VideoCompressResult>
compress(
- String id,
- String path,
- VideoCompressConfig config,
- String? outputDir,
- String? outputName,
override
Compress path using config. id identifies the job (for progress
correlation and cancellation). When outputPath is non-null the encoded
file is written there; otherwise it lands in the plugin cache.
Implementation
@override
Future<VideoCompressResult> compress(
String id,
String path,
VideoCompressConfig config,
String? outputDir,
String? outputName,
) async {
// On the web there is no filesystem: the output is a blob: URL and the
// download name is chosen at saveToDownloads time, so outputDir/outputName
// don't apply here. Video is always mp4 (mp4-muxer).
await _ensureLoaded();
_busy = true;
try {
final m = await _rawInfo(path);
final srcW = (m['width'] as num).toInt();
final srcH = (m['height'] as num).toInt();
// Use the same duration `estimate` does, so the two agree. (Web doesn't
// apply the trim window yet, but the bitrate budget must still match.)
final durationMs =
_clampDuration((m['durationMs'] as num).toInt(), config);
final srcKbps = (m['bitrateKbps'] as num).toInt();
final srcBytes = (m['sizeBytes'] as num).toInt();
final (tw, th) = SizeMath.targetDimensions(srcW, srcH, config);
final videoBps = SizeMath.videoBitrateBps(
config: config,
durationMs: durationMs,
sourceBitrateKbps: srcKbps,
targetHeight: th,
// Web v1 drops the audio track, so the whole budget goes to video.
reserveAudio: false,
);
final cfg = <String, Object?>{
'id': id,
'targetWidth': tw,
'targetHeight': th,
'videoBitrateBps': videoBps,
'frameRate': config.frameRate ?? 30,
// Requested codec; the JS engine encodes HEVC when the browser supports
// it, otherwise falls back to H.264 (and reports which was used).
'codec': config.codec.name,
// Hit the target closely when a size is requested; stay efficient (VBR)
// for quality/bitrate modes.
'bitrateMode': config.targetSizeMB != null ? 'constant' : 'variable',
'keepOriginalIfLarger': config.keepOriginalIfLarger,
'originalSizeBytes': srcBytes,
}.jsify()! as JSObject;
final onProgress = (double fraction, double outBytes) {
_progressCtrl.add(CompressionProgress(
id: id,
progress: fraction,
currentOutputBytes: outBytes > 0 ? outBytes.toInt() : null,
));
}.toJS;
final resJs = await _jsCompress(path, cfg, onProgress).toDart;
final r = (resJs.dartify() as Map).cast<dynamic, dynamic>();
return VideoCompressResult(
id: id,
outputPath: r['outputUrl'] as String,
originalSizeBytes: srcBytes,
compressedSizeBytes: (r['sizeBytes'] as num).toInt(),
width: (r['width'] as num).toInt(),
height: (r['height'] as num).toInt(),
durationMs: (r['durationMs'] as num).toInt(),
codec: r['codec'] as String,
skipped: r['skipped'] == true,
frameRate: (r['frameRate'] as num?)?.toDouble(),
hasAudio: r['hasAudio'] as bool?,
);
} catch (e) {
final msg = e.toString();
if (msg.contains('cancelled')) {
throw VideoCompressCancelledException();
}
throw VideoCompressException('compress_failed', msg);
} finally {
_busy = false;
}
}