stride3d/stride · error · Exception

Unable to read

Error message

Unable to read: {0} 

What it means

On Android, StreamedBufferSound.InitializeImpl opens the media file at mediaDataUrl via Java.IO.File and refuses to proceed if CanRead() is false, throwing with the file path. This guards against decoding an audio asset that is missing, on external storage without permissions, or otherwise inaccessible from the app process.

Solutions

  1. Verify the file exists and the path is correct; log inputFile.AbsolutePath from the exception to compare with the actual deployed location.
  2. Ensure the audio file is included in the APK as an AndroidAsset (or in the right storage location) so mediaDataUrl points at the deployed copy.
  3. Request and grant runtime READ_EXTERNAL_STORAGE / READ_MEDIA_AUDIO permissions before playback when reading from shared storage.
  4. Copy user-selected media into app-internal storage at pick time and stream from that private copy instead of the original URI.

Example fix

// before
sound = Sound.LoadStreamed(fileProvider, path); // throws if path not readable on device
// after
var assetPath = "Audio/track.mp3"; // ensure file is AndroidAsset
using var stream = FileProvider.OpenStream(assetPath, VirtualFileMode.Open, VirtualFileAccess.Read);
if (stream == null || stream.Length == 0)
    Log.Error($"Audio asset missing or unreadable: {assetPath}");
else
    sound = Sound.LoadStreamed(fileProvider, assetPath);
Defensive patterns

Strategy: validation

Validate before calling

// on Android, before loading a streamed sound
var path = mediaDataUrl;
if (!System.IO.File.Exists(path))
    Log.Error($"Audio file not deployed: {path}");
try
{
    using var fs = System.IO.File.OpenRead(path); // probe readability
}
catch (UnauthorizedAccessException)
{
    Log.Error($"No read permission for {path}; request storage permission.");
}

Type guard

bool IsReadableAudioFile(string path) =>
    !string.IsNullOrEmpty(path) && System.IO.File.Exists(path);

Try / catch

try
{
    sound = Sound.LoadStreamed(fileProvider, mediaDataUrl);
}
catch (Exception ex) when (ex.Message.StartsWith("Unable to read:"))
{
    Log.Error($"Media file unreadable: {ex.Message}. Check asset packaging and storage permissions.");
}

Prevention

When it happens

Trigger: Starting playback of a streamed compressed sound on Android when the file at mediaDataUrl does not exist, was not bundled/deployed with the app, lives in storage the app lacks READ_EXTERNAL_STORAGE permission for, or the path is malformed (bad case-sensitivity, wrong Content:// form).

Common situations: Asset not marked as AndroidAsset so it never got packaged into the APK, reading user-picked files from shared storage without runtime storage permission, files removed by the system between selection and playback, case-sensitive path mismatches on the device filesystem.

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/ac81e07eb92efb73. Report an issue: GitHub.

Appendix: source

Thrown at sources/engine/Stride.Audio/StreamedBufferSound.MediaCodec.cs:26

using Stride.Media;
using Stride.Core.Extensions;

namespace Stride.Audio
{
    /// <summary>
    /// Sound streamed buffer
    /// </summary>
    /// <remarks>
    /// The sound comes from an external process (such like a video decoder, ...) streaming the audio data into a buffer
    /// </remarks>
    public partial class StreamedBufferSound : SoundBase, IMediaExtractor
    {
        partial void InitializeImpl()
        {
            using (var inputFile = new Java.IO.File(mediaDataUrl))
            {
                if (!inputFile.CanRead())
                    throw new Exception(string.Format("Unable to read: {0} ", inputFile.AbsolutePath));

                using (var inputFileStream = new Java.IO.FileInputStream(inputFile.AbsolutePath))
                {
                    var audioMediaExtractor = new MediaExtractor();
                    audioMediaExtractor.SetDataSource(inputFileStream.FD, startPosition, length);
                    var trackIndexAudio = StreamedBufferSoundSource.FindAudioTrack(audioMediaExtractor);
                    if (trackIndexAudio < 0)
                        return;

                    audioMediaExtractor.SelectTrack(trackIndexAudio);
                    var audioFormat = audioMediaExtractor.GetTrackFormat(trackIndexAudio);

                    //Get the audio settings
                    //should we override the settings (channels, sampleRate, ...) from DynamicSoundSource?
                    Channels = audioFormat.GetInteger(MediaFormat.KeyChannelCount);
                    SampleRate = audioFormat.GetInteger(MediaFormat.KeySampleRate);
                    MediaDuration = TimeSpanExtensions.FromMicroSeconds(audioFormat.GetLong(MediaFormat.KeyDuration));
                }

View on GitHub (pinned to 96fad776d2)