invidious/assets/js/sabr_mp4_index.js
Emilien e3b3e68380 fix(sabr): correct per-segment URIs and control-bar play button size
Segment index parsers (mp4/webm) declared the per-segment URI array with
`var` inside the loop, so every SegmentReference's getUris() closure
captured the same function-scoped binding and returned the LAST segment's
URL. YouTube received the final segment's startTimeMs/sq for every
segment, replied with policy-only UMP (no media), and the player looped
forever on a black screen ("SABR throttled by YouTube"). Use `let`
(block-scoped, fresh per iteration) to match FreeTube's `const`.

Also scope the big centered play button styling to
`.shaka-play-button-container`; the bare `.shaka-play-button` selector
also matched the control-bar play button (48px siblings), oversizing it
to ~54px and pushing it out of alignment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 17:36:52 +02:00

112 lines
3.6 KiB
JavaScript

// Port of FreeTube's Mp4SegmentIndexParser.js
// Based on shaka-player's dash/mp4_segment_index_parser.js
// Reads shaka from window.shaka (loaded via <script>).
'use strict';
(function () {
var shaka = window.shaka;
var ShakaError = shaka.util.Error;
var SeverityCritical = ShakaError.Severity.CRITICAL;
var CategoryMedia = ShakaError.Category.MEDIA;
function parseSIDX(sidxOffset, initSegmentReference, timestampOffset, appendWindowStart, appendWindowEnd, uri, box) {
var references = [];
box.reader.skip(4);
var timescale = box.reader.readUint32();
if (timescale === 0) {
console.error('[parseMp4SegmentIndex] Invalid timescale.');
throw new ShakaError(SeverityCritical, CategoryMedia, ShakaError.Code.MP4_SIDX_INVALID_TIMESCALE);
}
var earliestPresentationTime;
var firstOffset;
if (box.version === 0) {
earliestPresentationTime = box.reader.readUint32();
firstOffset = box.reader.readUint32();
} else {
earliestPresentationTime = box.reader.readUint64();
firstOffset = box.reader.readUint64();
}
box.reader.skip(2);
var referenceCount = box.reader.readUint16();
var unscaledStartTime = earliestPresentationTime;
var startByte = sidxOffset + box.size + firstOffset;
for (var i = 0; i < referenceCount; i++) {
var chunk = box.reader.readUint32();
var referenceType = (chunk & 0x80000000) >>> 31;
var referenceSize = chunk & 0x7FFFFFFF;
var subsegmentDuration = box.reader.readUint32();
box.reader.skip(4);
if (referenceType === 1) {
console.error('[parseMp4SegmentIndex] Hierarchical SIDXs are not supported.');
throw new ShakaError(SeverityCritical, CategoryMedia, ShakaError.Code.MP4_SIDX_TYPE_NOT_SUPPORTED);
}
var nativeStartTime = unscaledStartTime / timescale;
var nativeEndTime = (unscaledStartTime + subsegmentDuration) / timescale;
// NOTE: must be `let` (block-scoped). With `var` the closure below
// captures the function-scoped binding, so every SegmentReference would
// return the LAST segment's URL (wrong startTimeMs/sq -> SABR serves no media).
let uris = [uri + '&startTimeMs=' + Math.round((nativeStartTime + timestampOffset) * 1000) + '&sq=' + (i + 1)];
references.push(
new shaka.media.SegmentReference(
nativeStartTime + timestampOffset,
nativeEndTime + timestampOffset,
function () { return uris; },
startByte,
startByte + referenceSize - 1,
initSegmentReference,
timestampOffset,
appendWindowStart,
appendWindowEnd
)
);
unscaledStartTime += subsegmentDuration;
startByte += referenceSize;
}
box.parser.stop();
return references;
}
function parseMp4SegmentIndex(sidxData, sidxOffset, uri, initSegmentReference, timestampOffset, appendWindowStart, appendWindowEnd) {
var references;
var parser = new shaka.util.Mp4Parser()
.fullBox('sidx', function (box) {
references = parseSIDX(
sidxOffset,
initSegmentReference,
timestampOffset,
appendWindowStart,
appendWindowEnd,
uri,
box
);
});
if (sidxData) {
parser.parse(sidxData);
}
if (references) {
return references;
} else {
console.error('[parseMp4SegmentIndex] Invalid box type, expected "sidx".');
throw new ShakaError(SeverityCritical, CategoryMedia, ShakaError.Code.MP4_SIDX_WRONG_BOX_TYPE);
}
}
window.parseMp4SegmentIndex = parseMp4SegmentIndex;
})();