Quick answer: Convert your music from MP3 to OGG Vorbis. MP3 files contain encoder padding frames at the start and end that Pygame cannot strip, producing audible gaps when looping. OGG Vorbis has no encoder delay and loops seamlessly.

You call pygame.mixer.music.load("theme.mp3") followed by pygame.mixer.music.play(-1), expecting smooth background music that loops forever. Instead, every 90 seconds you hear a brief silence — sometimes a faint click — as the track restarts. It is subtle enough that testers might not mention it, but once you hear it you cannot unhear it. This bug has nothing to do with Pygame; it is baked into the MP3 format itself.

The Symptom

You are calling pygame.mixer.music.play(-1) (which should loop indefinitely) and one of the following happens:

What Causes This

1. MP3 encoder delay. Every MP3 file contains a few silent frames at the start and end. The MP3 decoder needs a "lookahead" of samples before it can produce the first block of output, and the encoder pads the file to accommodate that lookahead. The decoder is supposed to skip this padding on playback, but Pygame's underlying SDL_mixer does not always know how much to skip. Result: the silent padding becomes audible at the loop point.

2. Mixer buffer too large. The default Pygame mixer buffer is 4096 bytes, which is about 93 ms of audio at 44.1 kHz. When a track loops, the mixer has to flush its buffer before restarting, and that flush creates a gap exactly equal to the buffer duration.

3. Mixer not initialized before loading music. If pygame.init() runs before pygame.mixer.pre_init(), the mixer uses default settings that may not match your source file's sample rate, causing resampling artifacts at the loop boundary.

4. File truncation on some platforms. A handful of MP3 files with ID3v2 tags at the end (instead of the start) get partially truncated by SDL_mixer on Windows, cutting off the last fraction of a second. The loop then skips to the beginning mid-beat.

The Fix

Step 1: Convert music to OGG Vorbis.

This single change fixes about 90% of Pygame loop gaps. Use Audacity, ffmpeg, or any audio tool:

# ffmpeg one-liner for batch conversion
ffmpeg -i theme.mp3 -c:a libvorbis -q:a 5 theme.ogg
ffmpeg -i battle.mp3 -c:a libvorbis -q:a 5 battle.ogg

Quality level 5 on the libvorbis encoder is roughly equivalent to 160 kbps MP3 and is appropriate for game music. Use 6 or 7 if you want higher fidelity.

For absolutely seamless looping where even milliseconds matter (rhythm games, music-driven mechanics), use FLAC instead. FLAC is lossless and has no encoder delay, at the cost of ~3–5x the file size.

Step 2: Initialize the mixer with a small buffer.

import pygame

# CRITICAL: pre_init must run BEFORE pygame.init()
pygame.mixer.pre_init(
    frequency=44100,
    size=-16,
    channels=2,
    buffer=512
)
pygame.init()

pygame.mixer.music.load("theme.ogg")
pygame.mixer.music.set_volume(0.6)
pygame.mixer.music.play(-1)  # Loop forever

A 512-byte buffer gives about 11 ms of latency, which is imperceptible to humans and short enough that any loop gap from buffer flushing becomes inaudible. If you get audio crackling on slow machines, bump the buffer to 1024 — still better than the 4096 default.

Step 3: Verify the mixer's actual settings.

pygame.init()
print(pygame.mixer.get_init())
# (44100, -16, 2)  -- freq, size, channels

If get_init() returns different numbers than you asked for in pre_init, your platform does not support those settings. The most common fallback is (22050, -16, 2) — half your requested sample rate. Music loaded at 44.1 kHz will be resampled and will sound off-pitch.

Step 4: Use pygame.mixer.Sound for short loops.

For very short loops (under 30 seconds), loading the entire file into memory as a Sound object and playing it with loops=-1 is more reliable than using music:

loop_track = pygame.mixer.Sound("ambient.ogg")
channel = loop_track.play(loops=-1)
channel.set_volume(0.5)

The tradeoff is memory: a three-minute Sound uses ~30 MB of RAM, versus streaming music that uses almost none. Use Sound for short ambient loops and music for full-length tracks.

Step 5: Use an end event for precise loop handling.

If you need complete control over the loop timing (for example, to crossfade between loops), register an end event and restart manually:

MUSIC_END = pygame.USEREVENT + 1
pygame.mixer.music.set_endevent(MUSIC_END)

pygame.mixer.music.load("theme.ogg")
pygame.mixer.music.play()  # No -1 this time

while running:
    for event in pygame.event.get():
        if event.type == MUSIC_END:
            pygame.mixer.music.play()  # Restart immediately

This pattern is not truly gapless — there is still a few milliseconds of latency on the restart — but it gives you a hook for logic that should run on each loop boundary.

"If your game loop music has a gap, nine times out of ten the fix is 'convert to OGG'. The tenth time is 'lower the buffer size'. It is almost never a Pygame bug."

Related Issues

If your sound effects are playing late or cutting off, see Pygame performance tips for indie developers. For sprite collision problems that can also surface after mixer initialization order changes, check out Pygame sprite collision not detected.

MP3 is a format. OGG is a tool. Use the tool.