render method

  1. @override
Future<File> render({
  1. required Widget composition,
  2. required Aspect aspect,
  3. required Duration duration,
  4. int fps = VideoDefaults.fps,
  5. int longEdge = VideoDefaults.longEdge,
  6. bool audio = true,
  7. bool warnOnDroppedAudio = true,
  8. String compositionKey = 'render',
  9. RenderProgressCallback? onProgress,
})
override

Renders composition for aspect over duration to an MP4 file in a fresh sandbox directory, returning that file.

With audio left true, a Video's declared Audio tracks are staged into the FFmpeg mix; pass false for a silent render (warned once through the injected warning sink unless warnOnDroppedAudio is false).

Implementation

@override
Future<File> render({
  required Widget composition,
  required Aspect aspect,
  required Duration duration,
  int fps = VideoDefaults.fps,
  int longEdge = VideoDefaults.longEdge,
  bool audio = true,
  bool warnOnDroppedAudio = true,
  String compositionKey = 'render',
  RenderProgressCallback? onProgress,
}) async {
  final frameCount = frameCountFor(duration, fps);
  final sandbox = await _sandboxFactory();
  final scope = resolverScope(mediaResolver, networkAllowlist: networkAllowlist);
  try {
    if (!audio) _warnIfDroppingAudio(composition, warnOnDroppedAudio, fps, frameCount);
    onProgress?.call(RenderProgress(RenderPhase.capturing, compositionKey: compositionKey));
    final result = await runStage(
      RenderPhase.capturing,
      () => capture.render(
        // The host mounts this with no app ancestors, so the tree needs an
        // ambient Directionality for Text/RichText to lay out. Audio is
        // collected from the raw Video by the capture entry itself, so the
        // wrap is mount-only.
        composition: Directionality(textDirection: TextDirection.ltr, child: composition),
        aspect: aspect,
        frameCount: frameCount,
        outDir: sandbox,
        service: _service,
        pumpWidget: pumpWidget,
        pumpFrame: pumpFrame,
        longEdge: longEdge,
        fps: fps,
        compositionKey: compositionKey,
        stageAudio: audio ? null : _silentAudio,
        resolver: scope.resolver,
      ),
    );
    final manifest = result.manifest;
    onProgress?.call(RenderProgress(RenderPhase.encoding, compositionKey: compositionKey));
    await runStage(RenderPhase.encoding, () async {
      await _runner.encode(args: manifest.ffmpegArgs, sandbox: sandbox);
      final posterArgs = manifest.posterArgs;
      if (posterArgs != null) {
        await _runner.encode(args: posterArgs, sandbox: sandbox);
      }
    });
    onProgress?.call(RenderProgress(RenderPhase.complete, compositionKey: compositionKey));
    return File('${sandbox.path}/${manifest.outputFileName}');
  } finally {
    await runGuarded([
      scope.dispose,
    ], (error, _) => _onWarning('Cleanup after render failed: $error'));
  }
}