Skip to content

Video

High-level scene-based video composition

The Video widget is the recommended entry point for creating Fluvie videos. It provides a declarative, scene-based API that handles timing, transitions, and audio automatically.

Table of Contents


Overview

Video is the root widget of the declarative API. It manages:

  • Scenes - Time-bounded sections of your video
  • Transitions - Effects between scenes
  • Background music - Audio that spans the entire video
  • Encoding settings - Output quality and format
import 'package:fluvie/declarative.dart';

Video(
  fps: 30,
  width: 1920,
  height: 1080,
  scenes: [
    Scene(durationInFrames: 90, children: [...]),
    Scene(durationInFrames: 60, children: [...]),
  ],
)

When to Use

Use Video when: - Building scene-based compositions - Using the declarative API - Creating Spotify Wrapped-style videos - Prototyping quickly

Use VideoComposition instead when: - You need low-level frame control - Building custom render pipelines - Integrating with existing state management


Properties

Property Type Default Description
fps int required Frames per second
width int required Output width in pixels
height int required Output height in pixels
scenes List<Scene> required List of scenes in order
encoding EncodingConfig? null Quality and format settings
defaultTransition SceneTransition? null Transition between all scenes
backgroundMusicAsset String? null Asset path for background music
backgroundMusicFile String? null File path for background music
backgroundMusicUrl String? null URL for background music
musicVolume double 1.0 Background music volume (0.0-1.0)
musicFadeInFrames int 0 Frames to fade in music
musicFadeOutFrames int 0 Frames to fade out music
controller RenderController? null Controller for playback control

Examples

Basic Video

Video(
  fps: 30,
  width: 1920,
  height: 1080,
  scenes: [
    Scene(
      durationInFrames: 90, // 3 seconds
      background: Background.solid(Colors.blue),
      children: [
        VCenter(
          child: Text('Hello, World!'),
        ),
      ],
    ),
  ],
)

Multiple Scenes with Transitions

Video(
  fps: 30,
  width: 1920,
  height: 1080,
  defaultTransition: const SceneTransition.crossFade(durationInFrames: 15),
  scenes: [
    Scene(
      durationInFrames: 90,
      background: Background.solid(Colors.purple),
      children: [
        VCenter(child: Text('Scene 1')),
      ],
    ),
    Scene(
      durationInFrames: 60,
      background: Background.solid(Colors.blue),
      children: [
        VCenter(child: Text('Scene 2')),
      ],
    ),
    Scene(
      durationInFrames: 90,
      background: Background.solid(Colors.teal),
      children: [
        VCenter(child: Text('Scene 3')),
      ],
    ),
  ],
)

With Background Music

Video(
  fps: 30,
  width: 1920,
  height: 1080,
  backgroundMusicAsset: 'assets/audio/music.mp3',
  musicVolume: 0.7,
  musicFadeInFrames: 60,   // 2 second fade in
  musicFadeOutFrames: 90,  // 3 second fade out
  scenes: [
    Scene(durationInFrames: 300, children: [...]),
    Scene(durationInFrames: 300, children: [...]),
  ],
)

With Encoding Settings

Video(
  fps: 30,
  width: 1920,
  height: 1080,
  encoding: const EncodingConfig(
    quality: RenderQuality.high,
    frameFormat: FrameFormat.rawRgba,
    debugFrameOutputPath: 'tmp/debug_frames',
  ),
  scenes: [...],
)

Different Aspect Ratios

// Vertical (9:16) - TikTok/Stories
Video(
  fps: 30,
  width: 1080,
  height: 1920,
  scenes: [...],
)

// Square (1:1) - Instagram
Video(
  fps: 30,
  width: 1080,
  height: 1080,
  scenes: [...],
)

// Widescreen (21:9) - Cinematic
Video(
  fps: 24,  // Film frame rate
  width: 2560,
  height: 1080,
  scenes: [...],
)

Scene Management

Total Duration

The total video duration is the sum of all scene lengths:

Video(
  fps: 30,
  scenes: [
    Scene(durationInFrames: 90),  // 3 seconds
    Scene(durationInFrames: 60),  // 2 seconds
    Scene(durationInFrames: 150), // 5 seconds
  ],
)
// Total: 300 frames = 10 seconds

Scene Transitions

Transitions overlap scenes. The transition duration comes from the end of the previous scene:

Video(
  fps: 30,
  defaultTransition: const SceneTransition.crossFade(durationInFrames: 30),
  scenes: [
    Scene(durationInFrames: 90),  // Frames 0-89
    Scene(durationInFrames: 60),  // Starts at frame 60 (30 frame transition)
    Scene(durationInFrames: 90),  // Starts at frame 90 (30 frame transition)
  ],
)

Per-Scene Transitions

Override the default for specific scenes:

Scene(
  durationInFrames: 90,
  transitionIn: const SceneTransition.slideUp(durationInFrames: 20),
  transitionOut: const SceneTransition.crossFade(durationInFrames: 15),
  children: [...],
)

Audio

Background Music Options

Three ways to provide background music:

// From Flutter assets
Video(
  backgroundMusicAsset: 'assets/audio/music.mp3',
  ...
)

// From file system
Video(
  backgroundMusicFile: '/path/to/music.mp3',
  ...
)

// From URL
Video(
  backgroundMusicUrl: 'https://example.com/music.mp3',
  ...
)

Audio Fading

Video(
  backgroundMusicAsset: 'assets/audio/music.mp3',
  musicVolume: 0.8,
  musicFadeInFrames: 60,   // Fade in over first 2 seconds
  musicFadeOutFrames: 90,  // Fade out over last 3 seconds
  ...
)

Additional Audio Tracks

Use AudioTrack within scenes for additional audio:

Scene(
  durationInFrames: 300,
  children: [
    AudioTrack(
      source: AudioSource.asset('assets/audio/effect.mp3'),
      startFrame: 60,
      durationInFrames: 30,
      volume: 0.5,
      child: const SizedBox.shrink(),
    ),
    // Other content...
  ],
)

Playback Control

Using RenderController

class MyVideoPlayer extends StatefulWidget {
  @override
  State<MyVideoPlayer> createState() => _MyVideoPlayerState();
}

class _MyVideoPlayerState extends State<MyVideoPlayer> {
  final _controller = RenderController();

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Expanded(
          child: Video(
            controller: _controller,
            fps: 30,
            width: 1920,
            height: 1080,
            scenes: [...],
          ),
        ),
        Row(
          children: [
            IconButton(
              icon: Icon(Icons.play_arrow),
              onPressed: () => _controller.play(),
            ),
            IconButton(
              icon: Icon(Icons.pause),
              onPressed: () => _controller.pause(),
            ),
            Slider(
              value: _controller.currentFrame.toDouble(),
              max: _controller.totalFrames.toDouble(),
              onChanged: (value) => _controller.seekTo(value.toInt()),
            ),
          ],
        ),
      ],
    );
  }
}