Properly handle subrequests for redirected URLs

This commit is contained in:
Vanilagy
2026-06-16 14:53:27 +02:00
parent b4a6757739
commit 2f0c040fcd
3 changed files with 32 additions and 9 deletions
+4 -1
View File
@@ -77,12 +77,15 @@ export class HlsDemuxer extends Demuxer {
readMetadata() { readMetadata() {
return this.metadataPromise ??= (async () => { return this.metadataPromise ??= (async () => {
assert(this.input._rootSource instanceof PathedSource); assert(this.input._rootSource instanceof PathedSource);
const { rootPath } = this.input._rootSource;
const slice = await this.input._reader.requestEntireFile(); const slice = await this.input._reader.requestEntireFile();
assert(slice); assert(slice);
const lines = readAllLines(slice, slice.length, { ignore: canIgnoreLine }); const lines = readAllLines(slice, slice.length, { ignore: canIgnoreLine });
// Important: get the root path AFTER reading data to get the final root path, possibly affected by
// redirects. Any follow requests should be related to the redirected path, not the original one.
const { rootPath } = this.input._rootSource;
const variantStreams: { const variantStreams: {
fullPath: string; fullPath: string;
attributes: AttributeList; attributes: AttributeList;
+12 -5
View File
@@ -20,7 +20,7 @@ import {
base64ToBytes, base64ToBytes,
} from '../misc'; } from '../misc';
import { readAllLines, readBytes, Reader } from '../reader'; import { readAllLines, readBytes, Reader } from '../reader';
import { CustomPathedSource, ReadableStreamSource, SourceRef, SourceRequest } from '../source'; import { CustomPathedSource, PathedSource, ReadableStreamSource, SourceRef, SourceRequest } from '../source';
import { HlsDemuxer } from './hls-demuxer'; import { HlsDemuxer } from './hls-demuxer';
import { import {
AttributeList, AttributeList,
@@ -68,6 +68,7 @@ export type HlsSegmentLocation = {
}; };
export class HlsSegmentedInput extends SegmentedInput { export class HlsSegmentedInput extends SegmentedInput {
rootPath: string;
demuxer: HlsDemuxer; demuxer: HlsDemuxer;
segments: HlsSegment[] = []; segments: HlsSegment[] = [];
nextLines: string[] | null = null; nextLines: string[] | null = null;
@@ -84,6 +85,7 @@ export class HlsSegmentedInput extends SegmentedInput {
) { ) {
super(demuxer.input, path, trackDeclarations); super(demuxer.input, path, trackDeclarations);
this.rootPath = path;
this.demuxer = demuxer; this.demuxer = demuxer;
this.nextLines = lines; this.nextLines = lines;
} }
@@ -126,12 +128,17 @@ export class HlsSegmentedInput extends SegmentedInput {
this.nextLines = null; this.nextLines = null;
if (!lines) { if (!lines) {
using ref = await this.demuxer.input._getSourceUncached({ path: this.path, isRoot: false }); using ref = await this.demuxer.input._getSourceUncached({ path: this.rootPath, isRoot: false });
const reader = new Reader(ref.source); const reader = new Reader(ref.source);
const slice = await reader.requestEntireFile(); const slice = await reader.requestEntireFile();
assert(slice); assert(slice);
lines = readAllLines(slice, slice.length, { ignore: canIgnoreLine }); lines = readAllLines(slice, slice.length, { ignore: canIgnoreLine });
if (ref.source instanceof PathedSource) {
// Copy back the source's path to become aware of potential redirects
this.rootPath = ref.source.rootPath;
}
} }
let headerRead = false; let headerRead = false;
@@ -219,7 +226,7 @@ export class HlsSegmentedInput extends SegmentedInput {
key = { ...key, iv }; key = { ...key, iv };
} }
const fullPath = joinPaths(this.path, line); const fullPath = joinPaths(this.rootPath, line);
const location: HlsSegmentLocation = { const location: HlsSegmentLocation = {
path: fullPath, path: fullPath,
offset: nextByteRange?.offset ?? 0, offset: nextByteRange?.offset ?? 0,
@@ -299,7 +306,7 @@ export class HlsSegmentedInput extends SegmentedInput {
} }
if (!prevLastSegment) { if (!prevLastSegment) {
const fullPath = joinPaths(this.path, uri); const fullPath = joinPaths(this.rootPath, uri);
const location: HlsSegmentLocation = { const location: HlsSegmentLocation = {
path: fullPath, path: fullPath,
offset: parsedByteRange?.offset ?? 0, offset: parsedByteRange?.offset ?? 0,
@@ -375,7 +382,7 @@ export class HlsSegmentedInput extends SegmentedInput {
currentKey = { currentKey = {
method: 'AES-128', method: 'AES-128',
keyUri: joinPaths(this.path, uri), keyUri: joinPaths(this.rootPath, uri),
iv, iv,
keyFormat, keyFormat,
}; };
+16 -3
View File
@@ -289,10 +289,15 @@ export class SourceRef<S extends Source = Source> implements Disposable {
*/ */
export abstract class PathedSource extends Source { export abstract class PathedSource extends Source {
constructor( constructor(
/** The path that points to the root file; the entry file of the media. */ /**
* The path that points to the root file; the entry file of the media.
*
* This path may be modified by the source to indicate a redirect: an updated path to perform new requests
* relative to.
*/
public rootPath: FilePath, public rootPath: FilePath,
/** The callback that is called for each requested file; must return a {@link Source} or {@link SourceRef}. */ /** The callback that is called for each requested file; must return a {@link Source} or {@link SourceRef}. */
public requestHandler: (request: SourceRequest) => MaybePromise<Source | SourceRef>, public readonly requestHandler: (request: SourceRequest) => MaybePromise<Source | SourceRef>,
) { ) {
if (typeof rootPath !== 'string') { if (typeof rootPath !== 'string') {
throw new TypeError('rootPath must be a string.'); throw new TypeError('rootPath must be a string.');
@@ -778,7 +783,10 @@ export class UrlSource extends PathedSource {
? url.href ? url.href
: url; : url;
super(urlString, request => new UrlSource(request.path, this._options)); super(
urlString,
request => new UrlSource(request.path, this._options),
);
this._url = url; this._url = url;
this._options = options; this._options = options;
@@ -905,6 +913,11 @@ export class UrlSource extends PathedSource {
throw new Error(`Error fetching ${String(this._url)}: ${response.status} ${response.statusText}`); throw new Error(`Error fetching ${String(this._url)}: ${response.status} ${response.statusText}`);
} }
if (response.redirected) {
// Modify our own root path so that future subrequests get made relative to the redirected URL
this.rootPath = response.url;
}
outer: outer:
if (this._orchestrator.fileSize === null) { if (this._orchestrator.fileSize === null) {
// See if we can deduce the file size from the response // See if we can deduce the file size from the response