Quick answer: Re-encode your video as OGV with constant frame rate and embedded Vorbis audio using ffmpeg -c:v libtheora -r 30 -c:a libvorbis -q:a 6 output.ogv. Variable frame rate sources drift over time. Godot’s built-in player expects fixed-rate content.
You add an intro cutscene to your Godot game. The first ten seconds look perfect. By the thirty-second mark, the dialogue is a second behind the lip movements. By the end, the audio and video are clearly out of sync. Nothing in your code controls the timing — the VideoStreamPlayer is doing its best — but Godot’s built-in video decoder has strict requirements the file is not meeting.
The Symptom
- Audio and video start in sync but drift apart over the duration of the clip.
- The drift is consistent and grows linearly — about 1 second per minute is common.
- The same video plays perfectly in VLC or your OS video player.
- Sync is worse on web exports than native exports.
Why Godot’s Video Player Is Strict
Godot ships with a simple Theora decoder for video and Vorbis for audio, packaged in an OGV container. The implementation is intentionally minimalist — it is not a full media player. It assumes the source file is well-behaved:
- Constant frame rate. Every frame is exactly the same duration.
- Constant audio bitrate. No variable bitrate Vorbis, which packs more bits into complex passages.
- Audio and video interleaved properly in the container. Packets in chronological order.
Most consumer video files violate at least one of these. Phone recordings use VFR to save battery. YouTube downloads are VBR for efficiency. Editing software leaves interleave gaps. Each violation causes the Godot player’s audio clock to drift away from the video clock.
The Fix: Re-Encode with ffmpeg
Convert the source to a strict OGV before importing:
# Basic conversion with fixed 30 fps and CBR audio
ffmpeg -i input.mp4 \
-c:v libtheora -q:v 7 -r 30 \
-c:a libvorbis -q:a 6 -ar 44100 \
-vsync cfr \
output.ogv
Key flags:
-c:v libtheora -q:v 7— Theora video, quality 7 out of 10.-r 30— force 30 fps constant frame rate.-c:a libvorbis -q:a 6— Vorbis audio, quality 6 out of 10.-ar 44100— force 44.1 kHz sample rate.-vsync cfr— force constant frame rate by duplicating or dropping frames as needed.
For a cinematic cutscene where quality matters more than file size:
ffmpeg -i input.mp4 \
-c:v libtheora -b:v 4M -r 30 \
-c:a libvorbis -b:a 192k -ar 44100 \
-vsync cfr \
output.ogv
4 Mbps video and 192 kbps audio give near-lossless quality for most cutscene content.
Verify the File
After encoding, check the file has both streams with proper parameters:
ffprobe output.ogv
# Look for:
# Stream #0:0: Video: theora, ..., 30 fps, 30 tbr, ...
# Stream #0:1: Audio: vorbis, 44100 Hz, stereo, ...
If fps and tbr are equal, the file is CFR. If they differ, you still have VFR and Godot will desync.
Import Into Godot
Drop the OGV into your project. Godot imports it automatically. Add a VideoStreamPlayer node, set its stream to the imported file, and play:
extends VideoStreamPlayer
func _ready():
stream = load("res://cutscenes/intro.ogv")
play()
# Wait for finish
await finished
get_tree().change_scene_to_file("res://scenes/main_menu.tscn")
Web Export Caveats
Web exports run the decoder in WebAssembly, which is slower than native and has stricter timing. If sync holds on native but drifts on web, lower the video frame rate to 24 fps and bitrate to 2 Mbps. The browser decoder handles the lighter load more reliably.
When You Need MP4 or WebM
Godot’s built-in player does not support MP4, WebM, AV1, or HEVC. If your pipeline requires one of these formats, use a third-party plugin like Godot Video Decoder or embed a platform-native player through GDExtension. The tradeoff is setup complexity versus format flexibility — for most games, sticking with OGV is the path of least resistance.
Verifying the Fix
Add a debug overlay that shows the current video time and audio time simultaneously. With the bug, the two numbers diverge as the video plays. With the fix, they remain within a frame of each other for the full duration.
“Godot’s video player is intentionally simple. Give it a strictly-encoded file and it does the right thing. Give it anything else and audio drifts. Pick your battles and re-encode.”
Related Issues
For audio playback issues unrelated to video, see Godot AudioStreamPlayer not playing sound. For web export audio crackling, see Godot audio crackling in web export. For audio bus issues, see Godot audio bus effects not applying.
Always use the -vsync cfr flag with ffmpeg when encoding for Godot. It forces a constant frame rate even if the source is variable, which is the single most effective fix for desync.