stride3d/stride · error · InvalidOperationException

AVAssetReader (audio) create failed

Error message

AVAssetReader (audio) create failed: {error.LocalizedDescription}

What it means

On iOS/macOS, StreamedBufferSoundSource.CreateAudioReader builds an AVAssetReader for the audio track of an asset. If AVAssetReader.FromAsset reports a non-null NSError, the reader could not be created and the method throws with the OS's localized description. Without a reader the streamed audio cannot be decoded.

Solutions

  1. Read error.LocalizedDescription from the exception to identify the OS-level cause (missing file vs. undecodable format).
  2. Confirm the media file is deployed as a BundleResource and reachable via the URL passed to the sound loader.
  3. Re-encode the audio to a broadly supported format (AAC/M4A or CBR MP3) if the codec is unsupported or DRM-protected.
  4. Re-copy or re-download the file if it was removed or corrupted, then retry playback.

Example fix

// before
var sound = Sound.LoadStreamed(services, "music/track.wma"); // AVAssetReader fails on unsupported codec
// after
// ship music/track.m4a (AAC) as BundleResource instead
var sound = Sound.LoadStreamed(services, "music/track.m4a");
try { sound.CreateInstance(listener); }
catch (InvalidOperationException ex) { Log.Error($"Audio open failed: {ex.Message}"); }
Defensive patterns

Strategy: try-catch

Validate before calling

// before loading, on iOS/macOS
var url = NSBundle.MainBundle.GetUrlForResource("track", "m4a", "music");
if (url == null || !NSFileManager.DefaultManager.FileExists(url.Path))
    Log.Error("Audio asset missing from app bundle");

Type guard

bool IsPlayableMediaAsset(NSUrl assetUrl) =>
    assetUrl != null && NSFileManager.DefaultManager.FileExists(assetUrl.Path);

Try / catch

try
{
    var sound = Sound.LoadStreamed(services, mediaUrl);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("AVAssetReader"))
{
    Log.Error($"Media cannot be decoded on this device: {ex.Message}");
    // fallback: use a pre-encoded AAC copy or skip audio
}

Prevention

When it happens

Trigger: Initializing the media extractor or seeking (SeekInternalImpl) on an AVFoundation-backed streamed sound when AVAssetReader creation fails — asset URL points to a missing/unreadable file, the asset has no compatible audio track, or the file format is not decodable by AVFoundation.

Common situations: Media file not copied into the app bundle (missing BundleResource build action) so the asset URL is invalid, DRM-protected or unsupported codec/container (e.g. some WMA/FLAC variants), file deleted from the sandbox after being picked, corrupted download.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


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

Appendix: source

Thrown at sources/engine/Stride.Audio/StreamedBufferSoundSource.AVFoundation.cs:95

        audioAsset = null;
        audioTrack = null;

        if (audioTempFilePath != null && File.Exists(audioTempFilePath))
        {
            try { File.Delete(audioTempFilePath); } catch { /* best-effort */ }
        }
        audioTempFilePath = null;
    }

    private void CreateAudioReader(TimeSpan startTime)
    {
        DisposeReader();
        if (audioAsset == null || audioTrack == null)
            return;

        audioReader = AVAssetReader.FromAsset(audioAsset, out var error);
        if (error != null)
            throw new InvalidOperationException($"AVAssetReader (audio) create failed: {error.LocalizedDescription}");

        if (startTime > TimeSpan.Zero)
        {
            var startCMTime = new CMTime((long)(startTime.TotalMilliseconds), 1000);
            audioReader.TimeRange = new CMTimeRange { Start = startCMTime, Duration = CMTime.PositiveInfinity };
        }

        // 16-bit signed LE interleaved PCM — the format Stride.Audio consumes downstream.
        var settings = new NSMutableDictionary
        {
            [AVAudioSettings.AVFormatIDKey] = NSNumber.FromUInt32((uint)AudioFormatType.LinearPCM),
            [AVAudioSettings.AVLinearPCMBitDepthKey] = NSNumber.FromInt32(16),
            [AVAudioSettings.AVLinearPCMIsBigEndianKey] = NSNumber.FromBoolean(false),
            [AVAudioSettings.AVLinearPCMIsFloatKey] = NSNumber.FromBoolean(false),
            [AVAudioSettings.AVLinearPCMIsNonInterleaved] = NSNumber.FromBoolean(false),
        };

        audioOutput = new AVAssetReaderAudioMixOutput(new[] { audioTrack }, settings);

View on GitHub (pinned to 96fad776d2)