Hi Michael - thanks for all your hard work on this plugin. I noticed an issue and have been using Claude Code to try to fix it on my system. This is where I ended up:
Certain tracks return 1 byte from Akamai CDN at hi-res quality — player stalls
What I'm seeing
When preferred quality is set to 24-bit/192kHz (format_id=27), certain tracks cause the player to stall indefinitely at track transitions. squeezelite reports end of stream (1 bytes). The same tracks play fine at lower quality settings.
The failure is deterministic per track and consistent across requests — it's not a transient network issue.
Plugin version: 3.6.8
LMS version: 9.1.0
Reproduction
Play track ID 350233032 (Ethel Cain — "Willoughby's Interlude") at 192kHz quality. Player stalls and stops.
To confirm the CDN behaviour directly — grab a fresh signed URL from the LMS server log after requesting the track, then:
# format_id=27 (192kHz) — consistently returns only 1 byte:
curl -s --range 0-4095 -o /dev/null -w '%{size_download} bytes\n' '<CDN_URL_fmt27>'
# Output: 1 bytes
# format_id=7 (96kHz) — returns full data for the same track:
curl -s --range 0-4095 -o /dev/null -w '%{size_download} bytes\n' '<CDN_URL_fmt7>'
# Output: 4096 bytes
The CDN returns HTTP 206 Partial Content with a correct Content-Length header in both cases — only the body is truncated to 1 byte. I've also found tracks that fail at both format_id=27 and format_id=7, but work at format_id=6 (CD FLAC).
What's happening
readPersistentChunk detects the premature EOF and retries, but retries the same signed URL — which fails identically. The issue appears to be CDN-side: for certain masters, Akamai serves correct headers but truncates the body.
Proposed fix
I've been testing a CDN probe approach: after getFileUrl returns a signed URL but before handing it to the player, make a small Range: bytes=0-4095 request. If fewer than 1000 bytes come back, fall back to a lower format tier. I implemented a cascade: fmt=27 → fmt=7 → fmt=6 → skip track.
Latency cost per track start is ~115ms on the fast path (good track, probe passes). Worst-case cascade is ~600ms. This sits comfortably within the existing rate-switch delay.
The patch goes inside the getFileUrl callback in getNextTrack, replacing the existing CAN_FLAC_SEEK block (which I had already disabled separately — that's a related but distinct issue where onStream stalls indefinitely on large FLAC header fetches):
$api->getFileUrl(sub {
$song->streamUrl(shift);
# CDN probe cascade: fmt=27 -> fmt=7 -> fmt=6 -> skip
if ($format =~ /fla?c/i && $prefs->get('preferredFormat') >= QOBUZ_STREAMING_FLAC_HIRES2) {
my $doProbe; $doProbe = sub {
my ($url, $onOk, $onFail) = @_;
my $http = Slim::Networking::Async::HTTP->new;
my $req = HTTP::Request->new(GET => $url);
$req->header('Range' => 'bytes=0-4095');
$http->send_request({
request => $req,
onBody => sub {
my $httpObj = shift;
my $bytes = length($httpObj->response->content || '');
if ($bytes >= 1000) { $onOk->($bytes); } else { $onFail->($bytes); }
},
onError => sub {
my ($httpObj, $error) = @_;
$log->warn("CDN probe error: $error -- proceeding anyway");
$onOk->(0);
},
});
};
my $url27 = $song->streamUrl;
$doProbe->(
$url27,
sub { $successCb->(); }, # fmt=27 OK
sub { # fmt=27 fail -> try fmt=7
$api->getFileInfo(sub {
my $url7 = shift;
if (!$url7) { $errorCb->('CDN unavailable', 'Qobuz'); return; }
$song->streamUrl($url7);
$doProbe->(
$url7,
sub { $successCb->(); }, # fmt=7 OK
sub { # fmt=7 fail -> try fmt=6
$api->getFileInfo(sub {
my $url6 = shift;
if (!$url6) { $errorCb->('CDN unavailable', 'Qobuz'); return; }
$song->streamUrl($url6);
$doProbe->(
$url6,
sub { $successCb->(); }, # fmt=6 OK
sub { $errorCb->('CDN unavailable', 'Qobuz'); }, # all fail
);
}, $id, $format, 'url', 44_100);
},
);
}, $id, $format, 'url', 96_000);
},
);
} else {
$successCb->();
}
}, $id, $format, $song->master);
(getFileInfo is called with maxSupportedSamplerate to force the desired format tier without touching user preferences.)
Test results
| Track |
fmt=27 |
fmt=7 |
fmt=6 |
Result with patch |
| 350233032 |
1 byte |
4096 bytes |
— |
Falls back to 96kHz, plays |
| 204550663 |
1 byte |
1 byte |
4096 bytes |
Falls back to CD FLAC, plays |
| Normal tracks |
4096 bytes |
— |
— |
Plays at 192kHz, ~115ms added latency |
Happy to open a PR if this approach looks reasonable, or to test any alternative you have in mind.
Hi Michael - thanks for all your hard work on this plugin. I noticed an issue and have been using Claude Code to try to fix it on my system. This is where I ended up:
Certain tracks return 1 byte from Akamai CDN at hi-res quality — player stalls
What I'm seeing
When preferred quality is set to 24-bit/192kHz (format_id=27), certain tracks cause the player to stall indefinitely at track transitions. squeezelite reports
end of stream (1 bytes). The same tracks play fine at lower quality settings.The failure is deterministic per track and consistent across requests — it's not a transient network issue.
Plugin version: 3.6.8
LMS version: 9.1.0
Reproduction
Play track ID
350233032(Ethel Cain — "Willoughby's Interlude") at 192kHz quality. Player stalls and stops.To confirm the CDN behaviour directly — grab a fresh signed URL from the LMS server log after requesting the track, then:
The CDN returns
HTTP 206 Partial Contentwith a correctContent-Lengthheader in both cases — only the body is truncated to 1 byte. I've also found tracks that fail at both format_id=27 and format_id=7, but work at format_id=6 (CD FLAC).What's happening
readPersistentChunkdetects the premature EOF and retries, but retries the same signed URL — which fails identically. The issue appears to be CDN-side: for certain masters, Akamai serves correct headers but truncates the body.Proposed fix
I've been testing a CDN probe approach: after
getFileUrlreturns a signed URL but before handing it to the player, make a smallRange: bytes=0-4095request. If fewer than 1000 bytes come back, fall back to a lower format tier. I implemented a cascade: fmt=27 → fmt=7 → fmt=6 → skip track.Latency cost per track start is ~115ms on the fast path (good track, probe passes). Worst-case cascade is ~600ms. This sits comfortably within the existing rate-switch delay.
The patch goes inside the
getFileUrlcallback ingetNextTrack, replacing the existingCAN_FLAC_SEEKblock (which I had already disabled separately — that's a related but distinct issue whereonStreamstalls indefinitely on large FLAC header fetches):(
getFileInfois called withmaxSupportedSamplerateto force the desired format tier without touching user preferences.)Test results
Happy to open a PR if this approach looks reasonable, or to test any alternative you have in mind.