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
- Verify the file exists and the path is correct; log inputFile.AbsolutePath from the exception to compare with the actual deployed location.
- 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.
- Request and grant runtime READ_EXTERNAL_STORAGE / READ_MEDIA_AUDIO permissions before playback when reading from shared storage.
- 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
- Mark audio files as AndroidAsset so they are packaged into the APK.
- Request runtime storage/audio permissions before playing user-selected media.
- Copy picked files into app-internal storage at selection time and stream from there.
- Match path case exactly; Android filesystems are case-sensitive.
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
- AVAssetReader (audio) create failed
- Unable to read
- unexpected result from audio decoder.DequeueOutputBuffer
- No track found in
- VideoInstance mediaCodec failed to get the AudioEngine
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)