Skip to content

Fix: GetPosition for buffer streams and Web hot reload/restard #261

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#### 3.2.0 (XX Xxx 2025)
- fix: GetPosition for buffer streams and Web hot reload/restard #258 and #259

#### 3.1.12 (21 Jun 2025)
- added `getStreamTimeConsumed()` to get the time consumed by a buffer stream of kind `BufferingType.released`. Since the position of this kind of stream is always 0, this method is useful to know the time already played.
- fix pause/unpause on audio stream buffering.
Expand Down
3 changes: 3 additions & 0 deletions example/lib/buffer_stream/generate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -263,18 +263,21 @@ class _GenerateState extends State<Generate> {
gap,
BufferBar(
bufferingType: BufferingType.preserved,
isBuffering: false,
sound: tone,
startingMb: 1,
label: 'tone',
),
BufferBar(
bufferingType: BufferingType.preserved,
isBuffering: false,
sound: siren,
startingMb: 1,
label: 'siren',
),
BufferBar(
bufferingType: BufferingType.preserved,
isBuffering: false,
sound: bouncing,
startingMb: 1,
label: 'bouncing',
Expand Down
9 changes: 6 additions & 3 deletions example/lib/buffer_stream/simple_noise_stream.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,17 @@ class SimpleNoise extends StatefulWidget {

class _SimpleNoiseState extends State<SimpleNoise> {
/// The size of the chunks to be sent to the buffer stream in bytes.
static const chunkSize = 1024 * 1024 / 10; // 1 MB
static const chunkSize = 1024 * 1024 / 10; // 0.1 MB

/// The type of the buffer stream.
final bufferingType = ValueNotifier<BufferingType>(BufferingType.released);
final bufferingType = ValueNotifier<BufferingType>(BufferingType.preserved);

/// The time needed to wait before unpausing the audio stream.
final bufferingTimeNeeds = 1.0;
final bufferingTimeNeeds = 2.0;

AudioSource? noise;

/// Whether the sound is awaiting for new data or not.
bool isBuffering = false;

@override
Expand Down Expand Up @@ -169,6 +171,7 @@ class _SimpleNoiseState extends State<SimpleNoise> {
),
BufferBar(
bufferingType: bufferingType.value,
isBuffering: isBuffering,
sound: noise,
startingMb: 1,
label: 'noise',
Expand Down
95 changes: 47 additions & 48 deletions example/lib/buffer_stream/ui/buffer_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:flutter_soloud/flutter_soloud.dart';
class BufferBar extends StatefulWidget {
const BufferBar({
required this.bufferingType,
required this.isBuffering,
super.key,
this.label = '',
this.sound,
Expand All @@ -18,6 +19,7 @@ class BufferBar extends StatefulWidget {
final AudioSource? sound;
final int startingMb;
final BufferingType bufferingType;
final bool isBuffering;

@override
State<BufferBar> createState() => _BufferBarState();
Expand All @@ -41,7 +43,9 @@ class _BufferBarState extends State<BufferBar> {

@override
Widget build(BuildContext context) {
if (widget.sound == null) return const SizedBox.shrink();
if (widget.sound == null) {
return const SizedBox.shrink();
}

final int bufferSize;
try {
Expand Down Expand Up @@ -83,59 +87,54 @@ class _BufferBarState extends State<BufferBar> {

final mb = (bufferSize / 1024 / 1024).toStringAsFixed(1);

return Padding(
padding: const EdgeInsets.all(8),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 150,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
return Column(
mainAxisSize: MainAxisSize.min,
spacing: 8,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (widget.label != null && widget.label!.isNotEmpty)
Text(
widget.label!,
style: const TextStyle(fontWeight: FontWeight.bold),
),
Text('using: $bufferSize b = $mb MB'),
Text('length: $humanDuration'),
Text('pos: $firstHandleHumanPos'),
],
),
ColoredBox(
color: Colors.grey,
child: Padding(
padding: const EdgeInsets.all(10),
child: Stack(
children: [
if (widget.label != null && widget.label!.isNotEmpty)
Text(
widget.label!,
style: const TextStyle(fontWeight: FontWeight.bold),
SizedBox(
height: height,
width: width,
child: LinearProgressIndicator(
value: progressValue,
backgroundColor: Colors.black,
valueColor: const AlwaysStoppedAnimation(Colors.red),
minHeight: height,
),
Text('using $mb MB'),
Text('length $humanDuration'),
Text('position: $firstHandleHumanPos'),
],
),
),
ColoredBox(
color: Colors.grey,
child: Padding(
padding: const EdgeInsets.all(10),
child: Stack(
children: [
SizedBox(
height: height,
width: width,
child: LinearProgressIndicator(
value: progressValue,
backgroundColor: Colors.black,
valueColor: const AlwaysStoppedAnimation(Colors.red),
minHeight: height,
),
for (var i = 0; i < handlesPos.length; i++)
Positioned(
left: handlesPos[i] * progressValue * width,
child: SizedBox(
height: height,
width: 3,
child: const ColoredBox(color: Colors.yellowAccent),
),
),
for (var i = 0; i < handlesPos.length; i++)
Positioned(
left: handlesPos[i] * progressValue * width,
child: SizedBox(
height: height,
width: 3,
child: const ColoredBox(color: Colors.yellowAccent),
),
),
],
),
],
),
),
],
),
),
],
);
}

Expand Down
1 change: 1 addition & 0 deletions example/lib/buffer_stream/websocket.dart
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ class _WebsocketExampleState extends State<WebsocketExample> {
const SizedBox(height: 16),
BufferBar(
bufferingType: BufferingType.preserved,
isBuffering: streamBuffering.value,
sound: currentSound,
),
const SizedBox(height: 16),
Expand Down
34 changes: 19 additions & 15 deletions src/audiobuffer/audiobuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ namespace SoLoud
{
memset(aBuffer, 0, sizeof(float) * aSamplesToRead);
// Calculate mStreamPosition based on mOffset
mStreamPosition = mOffset / (float)(sizeof(float) * mSamplerate * mChannels);
mStreamPosition = mOffset / (float)(mSamplerate * mChannels);

// This is not nice to do in the audio callback, but I didn't
// find a better way to get lenght and pause the sound and the
Expand All @@ -54,7 +54,7 @@ namespace SoLoud
{
memset(aBuffer, 0, sizeof(float) * aSamplesToRead);
// Calculate mStreamPosition based on mOffset
mStreamPosition = mOffset / (float)(sizeof(float) * mSamplerate * mChannels);
mStreamPosition = mOffset / (float)(mSamplerate * mChannels);

// This is not nice to do in the audio callback, but I didn't
// find a better way to get lenght and pause the sound and the
Expand Down Expand Up @@ -109,7 +109,8 @@ namespace SoLoud
{
mOffset += samplesToRead * mChannels;
// For PRESERVED type, streamPosition advances with the offset
mStreamPosition = mOffset / (float)(sizeof(float) * mSamplerate * mChannels);
// mStreamPosition = mOffset / (float)(sizeof(float) * mSamplerate * mChannels);
mStreamPosition = mOffset / (float)(mSamplerate * mChannels);
}

return samplesToRead;
Expand Down Expand Up @@ -162,12 +163,14 @@ namespace SoLoud
bool BufferStreamInstance::hasEnded()
{
auto b = mParent->mBuffer.bufferingType == BufferingType::PRESERVED;
// PRESERVED
if (b && mParent->dataIsEnded &&
mOffset >= mParent->mSampleCount * mParent->mPCMformat.bytesPerSample) // PRESERVED
mOffset >= mParent->mSampleCount)
{
return 1;
} else
if (!b && mParent->dataIsEnded && mParent->mSampleCount <= 0) // RELEASED
// RELEASED
if (!b && mParent->dataIsEnded && mParent->mBuffer.getFloatsBufferSize() == 0)
{
return 1;
}
Expand Down Expand Up @@ -310,7 +313,7 @@ namespace SoLoud
buffer.data(),
bufferDataToAdd);
if (newData.size() > 0)
bytesWritten = mBuffer.addData(BufferType::OPUS, newData.data(), newData.size());
bytesWritten = mBuffer.addData(BufferType::OPUS, newData.data(), newData.size()) * mPCMformat.bytesPerSample;
else
return PlayerErrors::noError;
}
Expand All @@ -324,7 +327,7 @@ namespace SoLoud
}
else
{
bytesWritten = mBuffer.addData(mPCMformat.dataType, buffer.data(), bufferDataToAdd / mPCMformat.bytesPerSample);
bytesWritten = mBuffer.addData(mPCMformat.dataType, buffer.data(), bufferDataToAdd / mPCMformat.bytesPerSample) * mPCMformat.bytesPerSample;
}

// Remove the processed data from the buffer
Expand Down Expand Up @@ -355,31 +358,33 @@ namespace SoLoud
// If a handle reaches the end and data is not ended, we have to wait for it has enough data
// to reach [TIME_FOR_BUFFERING] and restart playing it.
SoLoud::time currBufferTime = getLength();
// time addedDataTime = afterAddingBytesCount / (mBaseSamplerate * mPCMformat.bytesPerSample * mChannels);
SoLoud::time addedDataTime = (afterAddingBytesCount / mPCMformat.bytesPerSample) / (mBaseSamplerate * mChannels);

for (int i = 0; i < mParent->handle.size(); i++)
{
SoLoud::handle handle = mParent->handle[i].handle;
SoLoud::time pos = mBuffer.bufferingType == BufferingType::RELEASED ? getStreamTimeConsumed() : mThePlayer->getPosition(handle);
bool isPaused = mThePlayer->getPause(handle);

bool p = mThePlayer->getPause(handle);
// This handle needs to wait for [TIME_FOR_BUFFERING]. Pause it.
if (pos >= currBufferTime + addedDataTime && !mThePlayer->getPause(handle))
if (pos >= currBufferTime + addedDataTime && !isPaused)
{
mParent->handle[i].bufferingTime = currBufferTime;
mThePlayer->setPause(handle, true);
isPaused = true;
callOnBufferingCallback(true, handle, currBufferTime);
} else
// This handle has reached [TIME_FOR_BUFFERING]. Unpause it.
if (currBufferTime + addedDataTime - mParent->handle[i].bufferingTime >= mBufferingTimeNeeds &&
mThePlayer->getPause(handle))
isPaused)
{
mThePlayer->setPause(handle, false);
isPaused = false;
mParent->handle[i].bufferingTime = currBufferTime + addedDataTime;
callOnBufferingCallback(false, handle, currBufferTime);
} else
}
// If data is ended and the handle is paused, unpause it to listen to the rest of the data.
if (dataIsEnded && mThePlayer->getPause(handle))
if (dataIsEnded && isPaused)
{
mThePlayer->setPause(handle, false);
mParent->handle[i].bufferingTime = MAX_DOUBLE;
Expand Down Expand Up @@ -427,13 +432,12 @@ namespace SoLoud
{
if (mBaseSamplerate == 0)
return 0;
// return mSampleCount / mBaseSamplerate * mPCMformat.bytesPerSample / mPCMformat.channels;
return (mUncompressedBytesReceived / mPCMformat.bytesPerSample) / (mBaseSamplerate * mPCMformat.channels);
}

/// Get the time consumed by this stream of type RELEASED
SoLoud::time BufferStream::getStreamTimeConsumed()
{
return (mBytesConsumed / mPCMformat.bytesPerSample) / (mBaseSamplerate * mPCMformat.channels * sizeof(float));
return (mBytesConsumed / mPCMformat.bytesPerSample) / (mBaseSamplerate * mPCMformat.channels);
}
};
25 changes: 14 additions & 11 deletions src/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,22 +52,25 @@ extern "C"
printf("CPP bool createWorkerInWasm()\n");

return EM_ASM_INT({
if (!Module_soloud.wasmWorker)
if (Module_soloud.wasmWorker)
{
// Create a new Worker from the URI
var workerUri = "assets/packages/flutter_soloud/web/worker.dart.js";
console.log("EM_ASM creating Web Worker!");
// Terminate the existing worker before creating a new one
try {
Module_soloud.wasmWorker = new Worker(workerUri);
return 1;
Module_soloud.wasmWorker.terminate();
console.log("EM_ASM terminated existing Web Worker.");
} catch(e) {
console.error('Failed to create worker:', e);
return 0;
console.error('Failed to terminate existing worker:', e);
}
Module_soloud.wasmWorker = null;
}
else
{
console.log("EM_ASM web worker already created!");
// Create a new Worker from the URI
var workerUri = "assets/packages/flutter_soloud/web/worker.dart.js";
console.log("EM_ASM creating Web Worker!");
try {
Module_soloud.wasmWorker = new Worker(workerUri);
return 1;
} catch(e) {
console.error('Failed to create worker:', e);
return 0;
}
});
Expand Down
2 changes: 1 addition & 1 deletion web/libflutter_soloud_plugin.js

Large diffs are not rendered by default.

Binary file modified web/libflutter_soloud_plugin.wasm
Binary file not shown.