Skip to content

Certain tracks return 1 byte from Akamai CDN at hi-res quality — player stalls #129

@djmacintyre

Description

@djmacintyre

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions