Skip to content
Closed
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
33 changes: 22 additions & 11 deletions Sources/CSFBAudioEngine/Player/AudioPlayer.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1811,6 +1811,7 @@ Flags clearFlags(Flags flags, std::memory_order order = std::memory_order_acq_re
}

Decoder decoder = nil;
Decoder nextDecoder = nil;
NSError *error = nil;
AVAudioFramePosition framesRendered = 0;
{
Expand All @@ -1829,6 +1830,10 @@ Flags clearFlags(Flags flags, std::memory_order order = std::memory_order_acq_re
sequenceNumber);
return false;
}

if (const auto *decoderState = firstActiveDecoderState(); decoderState != nullptr) {
nextDecoder = decoderState->decoder_;
}
}

// Mark the decoder as canceled for any scheduled render notifications
Expand All @@ -1846,19 +1851,25 @@ Flags clearFlags(Flags flags, std::memory_order order = std::memory_order_acq_re
}
}

const auto hasNoDecoders = [&] {
std::scoped_lock lock{queuedDecodersMutex_, activeDecodersMutex_};
return queuedDecoders_.empty() && activeDecoders_.empty();
}();
if (nextDecoder != nil) {
if (bits::is_clear(loadFlags(), Flags::isPlaying)) {

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bits::is_clear(loadFlags(), Flags::isPlaying) is true for both paused and stopped states. With this change, a user-initiated stop() (or any time the engine isn’t running) can trigger setNowPlaying(nextDecoder) during decoder cancellation, causing spurious nowPlayingChanged: delegate callbacks while stopping. To match the PR intent (“when paused”), gate this on the paused state (e.g., isPaused() or Flags::engineIsRunning set while Flags::isPlaying clear) rather than only checking Flags::isPlaying.

Suggested change
if (bits::is_clear(loadFlags(), Flags::isPlaying)) {
const auto flags = loadFlags();
if (bits::is_set(flags, Flags::engineIsRunning) && bits::is_clear(flags, Flags::isPlaying)) {

Copilot uses AI. Check for mistakes.
setNowPlaying(nextDecoder);
}
} else {
const auto hasNoDecoders = [&] {
std::scoped_lock lock{queuedDecodersMutex_, activeDecodersMutex_};
return queuedDecoders_.empty() && activeDecoders_.empty();
}();

if (hasNoDecoders) {
setNowPlaying(nil);
if (hasNoDecoders) {
setNowPlaying(nil);

const auto didStopEngine = stopEngineIfRunning();
if (didStopEngine) {
if (__strong id<SFBAudioPlayerDelegate> delegate = player_.delegate;
delegate != nil && [delegate respondsToSelector:@selector(audioPlayer:playbackStateChanged:)]) {
[delegate audioPlayer:player_ playbackStateChanged:SFBAudioPlayerPlaybackStateStopped];
const auto didStopEngine = stopEngineIfRunning();
if (didStopEngine) {
if (__strong id<SFBAudioPlayerDelegate> delegate = player_.delegate;
delegate != nil && [delegate respondsToSelector:@selector(audioPlayer:playbackStateChanged:)]) {
[delegate audioPlayer:player_ playbackStateChanged:SFBAudioPlayerPlaybackStateStopped];
}
}
}
}
Expand Down