-
-
Notifications
You must be signed in to change notification settings - Fork 755
Description
Which API doesn't behave as documented, and how does it misbehave?
In the documentation here for StreamAudioResponse, we have the following:
sourceLength → [int]?
When responding to a range request, this holds the byte length of the entire media, otherwise it holds null.
However when setting that value to null
as in the case of "we don't know how long the audio source will be" we get the following exception:
[ERROR:flutter/runtime/dart_vm_initializer.cc(40)] Unhandled Exception: (-11850) Operation Stopped
#0 StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:652:7)
message_codecs.dart:652
#1 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:370:18)
platform_channel.dart:370
<asynchronous suspension>
#2 MethodChannelAudioPlayer.load (package:just_audio_platform_interface/method_channel_just_audio.dart:58:34)
method_channel_just_audio.dart:58
<asynchronous suspension>
#3 _PlayerAudioHandler.customLoad (package:just_audio_background/just_audio_background.dart:447:22)
just_audio_background.dart:447
<asynchronous suspension>
#4 AudioPlayer._load.<anonymous closure> (package:just_audio/just_audio.dart:1010:17)
just_audio.dart:1010
<asynchronous suspension>
#5 AudioPlayer._load (package:just_audio/just_audio.dart:1011:24)
just_audio.dart:1011
<asynchronous suspension>
#6 AudioPlayer._setPlatformActive.setPlatform (package:just_audio/just_audio.dart:1745:26)
just_audio.dart:1745
<asynchronous suspension>
#7 AudioPlayer._setPlatformActive.<anonymous closure> (package:just_audio/just_au<…>
(tbc - I'm not 100% sure this is exception is a result of setting it to null
, however when I specify a value I do not get that exception and the audio plays as expected)
The effect of this is also that the audio does not start playing.
If I set the duration to a limited amount (ie 1 chunk size in bytes) for example, the effect is that it plays for a period of time and even though the next time it makes a request
for bytes, it only plays that first chunk and triggers a playbackEvent.complete
(if that's helpful)
Minimal reproduction project
import 'dart:async';
import 'dart:typed_data';
import 'dart:io';
import 'package:custompod/my_logger.dart';
import 'package:custompod/utils/pcm_to_wav_converter.dart';
import 'package:just_audio/just_audio.dart';
class BufferedStreamAudioSource extends StreamAudioSource {
final StreamController<List<int>> _controller =
StreamController<List<int>>.broadcast();
final Map<int, List<int>> _pcmChunkMap = {};
final String debugfile = "/tmp/live_briefing_audio.wav";
List<int> _wavBytes = [];
int _totalPcmDataSize = 0;
List<int> get bytes => _wavBytes;
Stream<List<int>> get chunkStream => _controller.stream;
int get totalPcmDataSize => _totalPcmDataSize;
BufferedStreamAudioSource({super.tag}) {
_wavBytes = PcmToWavConverter.convertPcmToWav([]).toList();
}
List<int> _combinePcmChunks() {
final sortedKeys = _pcmChunkMap.keys.toList()..sort();
final combined = <int>[];
for (final idx in sortedKeys) {
final chunk = _pcmChunkMap[idx];
if (chunk != null) {
combined.addAll(chunk);
}
}
return combined;
}
void addChunk(List<int> pcmChunk, int idx) {
_pcmChunkMap[idx] = pcmChunk;
_controller.add(pcmChunk);
_totalPcmDataSize += pcmChunk.length;
log.d(
"adding chunk $idx (${pcmChunk.length} bytes) - total PCM: $_totalPcmDataSize bytes");
}
Future<void> writeToDisk() async {
final file = File(debugfile);
if (await file.exists()) {
await file.delete();
}
final allPcmData = _combinePcmChunks();
final properWavData = PcmToWavConverter.convertPcmToWav(allPcmData);
await file.writeAsBytes(properWavData, mode: FileMode.write, flush: true);
log.d(
"WAV file written to disk: $debugfile (${properWavData.length} bytes WAV, ${allPcmData.length} PCM bytes)");
}
void reset() {
_pcmChunkMap.clear();
_totalPcmDataSize = 0;
_wavBytes = PcmToWavConverter.convertPcmToWav([]).toList();
}
void dispose() {
_controller.close();
reset();
}
@override
Future<StreamAudioResponse> request([int? start, int? end]) async {
// just_audio only supports a wav stream, so we convert it every time it's requested
final allPcmData = _combinePcmChunks();
_totalPcmDataSize = allPcmData.length;
_wavBytes = PcmToWavConverter.convertPcmToWav(allPcmData).toList();
start ??= 0;
end ??= _wavBytes.length;
end = end.clamp(start, _wavBytes.length);
final requestedBytes = _wavBytes.sublist(start, end);
return StreamAudioResponse(
contentLength: end - start,
sourceLength: _wavBytes.length, // works
// sourceLength: null, // does not
offset: start,
stream: Stream.value(Uint8List.fromList(requestedBytes)),
contentType: 'audio/wav',
);
}
}
To Reproduce (i.e. user steps, not code)
Steps to reproduce the behavior:
- Initialize a new audio stream with the BufferedStreamAudioSource above
- hit AudioPlayer.play()
Error messages
( see above )
Expected behavior
The audio to play. If we set the sourceLength to some random value it doesn't play as well, but if we specify it to be the length of the wav bytes, it works.
Screenshots
If applicable, add screenshots to help explain your problem.
Desktop (please complete the following information):
- OS: [e.g. MacOS + version] Mac OS 15.5 (24F74
- Browser [e.g. chrome, safari + version] n/a (chrome though)
Smartphone (please complete the following information):
- Device: [e.g. iPhone6]) testing on Simulator iOS device
- OS: [e.g. iOS8.1] 18.5
Flutter SDK version
> flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.29.0, on macOS 15.5 24F74 darwin-arm64, locale en-US)
[✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 16.4)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2024.3)
[✓] VS Code (version 1.101.2)
[✓] Connected device (5 available)
[✓] Network resources
• No issues found!