stride3d/stride · error · InvalidOperationException

mediaCodec has already been initialized

Error message

mediaCodec has already been initialized

What it means

MediaCodecVideoBackend.Initialize was called on a backend that already has a mediaSynchronizer or mediaCodecVideoExtractor, i.e. it is already initialized (or mid-initialization). The backend forbids double initialization; it must be reset or a new instance used.

Solutions

  1. Dispose/reset the backend before re-calling Initialize
  2. Create a fresh MediaCodecVideoBackend per video/play session
  3. If a previous Initialize partially failed, call the backend's cleanup path before retrying
  4. Guard callers: only Initialize once per instance lifecycle

Example fix

// before
backend.Initialize(url, 0, len);
backend.Initialize(url2, 0, len); // throws
// after
backend.Dispose();
backend.Initialize(url2, 0, len);
Defensive patterns

Strategy: validation

Validate before calling

if (backend.IsInitialized) backend.Dispose();
backend.Initialize(url, startPosition, length);

Type guard

bool CanInitialize(MediaCodecVideoBackend b) => !b.IsInitialized;

Try / catch

try { backend.Initialize(url, start, len); }
catch (InvalidOperationException ex) when (ex.Message.Contains("already been initialized"))
{ backend.Dispose(); backend.Initialize(url, start, len); }

Prevention

When it happens

Trigger: Calling Initialize twice on the same backend without Dispose/Reset; a partially failed earlier Initialize that created one of the fields and threw after; reusing a cached backend for a new URL.

Common situations: Replaying a video through the same backend object; scene reload re-initializing shared backends; error-handling paths that retry Initialize after a partial failure without cleanup.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/fe3b9f88750ef3e9. Report an issue: GitHub.

Appendix: source

Thrown at sources/engine/Stride.Video/Backends/MediaCodecVideoBackend.cs:42

    private MediaCodecVideoExtractor mediaCodecVideoExtractor;
    private ImageReader imageReader;
    private readonly object imageReaderLock = new();
    private IntPtr rgbaScratch;
    private int rgbaScratchSize;
    private int videoWidth, videoHeight;

    private StreamedBufferSound audioSound;
    private SoundInstanceStreamedBuffer audioSoundInstance;
    private readonly List<AudioEmitterSoundController> audioControllers = new();

    public MediaCodecVideoBackend(VideoInstance instance) : base(instance) { }

    public override bool UsesHardwareDecode => true;

    public override bool Initialize(string url, long startPosition, long length)
    {
        if (mediaSynchronizer != null || mediaCodecVideoExtractor != null)
            throw new InvalidOperationException("mediaCodec has already been initialized");

        // Pre-probe dimensions: ImageReader can't be resized, but MediaCodec.Configure already
        // needs its Surface, so we have to know width/height upfront. Cheap to open a second
        // MediaExtractor here and pull the video format.
        (videoWidth, videoHeight) = ProbeVideoDimensions(url, startPosition, length);

        // CPU YUV path. The zero-copy alternative Texture.NewFromAndroidHardwareBuffer
        // (Stride.Graphics) needs immutable VkSamplerYcbcrConversion descriptor bindings
        // that Stride's effect system doesn't yet expose.
        // No OnImageAvailable listener: it doesn't fire reliably under the emulator's gfxstream
        // BufferQueue, so Update() pulls frames with AcquireLatestImage every tick instead.
        // maxImages is deliberately generous: on a cold/janky start the decoder needs several
        // output buffers to dequeue into before the game thread starts draining, otherwise the
        // gfxstream BufferQueue starves it and it produces nothing for tens of seconds.
        imageReader = ImageReader.NewInstance(videoWidth, videoHeight, ImageFormatType.Yuv420888, maxImages: 8);

        rgbaScratchSize = videoWidth * videoHeight * 4;
        rgbaScratch = Marshal.AllocHGlobal(rgbaScratchSize);

View on GitHub (pinned to 96fad776d2)