{"record":{"id":"fe3b9f88750ef3e9","repo":"stride3d/stride","slug":"mediacodec-has-already-been-initialized","errorCode":null,"errorMessage":"mediaCodec has already been initialized","messagePattern":"mediaCodec has already been initialized","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/engine/Stride.Video/Backends/MediaCodecVideoBackend.cs","lineNumber":42,"sourceCode":"    private MediaCodecVideoExtractor mediaCodecVideoExtractor;\n    private ImageReader imageReader;\n    private readonly object imageReaderLock = new();\n    private IntPtr rgbaScratch;\n    private int rgbaScratchSize;\n    private int videoWidth, videoHeight;\n\n    private StreamedBufferSound audioSound;\n    private SoundInstanceStreamedBuffer audioSoundInstance;\n    private readonly List<AudioEmitterSoundController> audioControllers = new();\n\n    public MediaCodecVideoBackend(VideoInstance instance) : base(instance) { }\n\n    public override bool UsesHardwareDecode => true;\n\n    public override bool Initialize(string url, long startPosition, long length)\n    {\n        if (mediaSynchronizer != null || mediaCodecVideoExtractor != null)\n            throw new InvalidOperationException(\"mediaCodec has already been initialized\");\n\n        // Pre-probe dimensions: ImageReader can't be resized, but MediaCodec.Configure already\n        // needs its Surface, so we have to know width/height upfront. Cheap to open a second\n        // MediaExtractor here and pull the video format.\n        (videoWidth, videoHeight) = ProbeVideoDimensions(url, startPosition, length);\n\n        // CPU YUV path. The zero-copy alternative Texture.NewFromAndroidHardwareBuffer\n        // (Stride.Graphics) needs immutable VkSamplerYcbcrConversion descriptor bindings\n        // that Stride's effect system doesn't yet expose.\n        // No OnImageAvailable listener: it doesn't fire reliably under the emulator's gfxstream\n        // BufferQueue, so Update() pulls frames with AcquireLatestImage every tick instead.\n        // maxImages is deliberately generous: on a cold/janky start the decoder needs several\n        // output buffers to dequeue into before the game thread starts draining, otherwise the\n        // gfxstream BufferQueue starves it and it produces nothing for tens of seconds.\n        imageReader = ImageReader.NewInstance(videoWidth, videoHeight, ImageFormatType.Yuv420888, maxImages: 8);\n\n        rgbaScratchSize = videoWidth * videoHeight * 4;\n        rgbaScratch = Marshal.AllocHGlobal(rgbaScratchSize);","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/engine/Stride.Video/Backends/MediaCodecVideoBackend.cs#L24-L60","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Dispose/reset the backend before re-calling Initialize","Create a fresh MediaCodecVideoBackend per video/play session","If a previous Initialize partially failed, call the backend's cleanup path before retrying","Guard callers: only Initialize once per instance lifecycle"],"exampleFix":"// before\nbackend.Initialize(url, 0, len);\nbackend.Initialize(url2, 0, len); // throws\n// after\nbackend.Dispose();\nbackend.Initialize(url2, 0, len);","handlingStrategy":"validation","validationCode":"if (backend.IsInitialized) backend.Dispose();\nbackend.Initialize(url, startPosition, length);","typeGuard":"bool CanInitialize(MediaCodecVideoBackend b) => !b.IsInitialized;","tryCatchPattern":"try { backend.Initialize(url, start, len); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"already been initialized\"))\n{ backend.Dispose(); backend.Initialize(url, start, len); }","preventionTips":["Recreate backends per play session instead of re-initializing","Clean up partially-failed initializations before retrying","Track lifecycle state explicitly in owning components"],"tags":["android","mediacodec","video","lifecycle"],"backgroundTag":"invalid-state-transition","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}