mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-02 21:33:52 +02:00
Add missing doc blocks for everything, add PathedTarget and PathedSource, few other changes & fixes
This commit is contained in:
@@ -23,6 +23,7 @@ import { EncodedPacket } from '../packet';
|
||||
import { readAllLines } from '../reader';
|
||||
import { AttributeList, canIgnoreLine } from './hls-misc';
|
||||
import { HlsSegmentedInput } from './hls-segmented-input';
|
||||
import { PathedSource } from '../source';
|
||||
|
||||
type InternalTrack = {
|
||||
id: number;
|
||||
@@ -66,7 +67,8 @@ export class HlsDemuxer extends Demuxer {
|
||||
|
||||
readMetadata() {
|
||||
return this.metadataPromise ??= (async () => {
|
||||
assert(this.input._entryPath !== null);
|
||||
assert(this.input._source instanceof PathedSource);
|
||||
const source = this.input._source;
|
||||
|
||||
const slice = await this.input._reader.requestEntireFile();
|
||||
assert(slice);
|
||||
@@ -96,7 +98,7 @@ export class HlsDemuxer extends Demuxer {
|
||||
throw new Error('Incorrect M3U8 file; a line must follow the #EXT-X-STREAM-INF tag.');
|
||||
}
|
||||
|
||||
const fullPath = joinPaths(this.input._entryPath, playlistPath);
|
||||
const fullPath = joinPaths(source.rootPath, playlistPath);
|
||||
const attributes = new AttributeList(line.slice(18));
|
||||
|
||||
const bandwidth = attributes.getAsNumber('bandwidth');
|
||||
@@ -123,7 +125,7 @@ export class HlsDemuxer extends Demuxer {
|
||||
);
|
||||
}
|
||||
|
||||
const fullPath = joinPaths(this.input._entryPath, playlistPath);
|
||||
const fullPath = joinPaths(source.rootPath, playlistPath);
|
||||
|
||||
variantStreams.push({
|
||||
fullPath,
|
||||
@@ -151,7 +153,7 @@ export class HlsDemuxer extends Demuxer {
|
||||
let fullPath: string | null = null;
|
||||
const uri = attributes.get('uri');
|
||||
if (uri !== null) {
|
||||
fullPath = joinPaths(this.input._entryPath, uri);
|
||||
fullPath = joinPaths(source.rootPath, uri);
|
||||
}
|
||||
|
||||
mediaTags.push({ fullPath, attributes, lineNumber: i });
|
||||
@@ -159,7 +161,7 @@ export class HlsDemuxer extends Demuxer {
|
||||
// iFramesOnlyTagFound = true;
|
||||
} else if (line.startsWith('#EXTINF:')) {
|
||||
// This is a media playlist, not a master playlist
|
||||
const segmentedInput = new HlsSegmentedInput(this, this.input._entryPath, lines);
|
||||
const segmentedInput = new HlsSegmentedInput(this, source.rootPath, lines);
|
||||
this.segmentedInputs = [segmentedInput];
|
||||
this.hasMasterPlaylist = false;
|
||||
|
||||
@@ -236,7 +238,7 @@ export class HlsDemuxer extends Demuxer {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fullPath = joinPaths(this.input._entryPath!, uri);
|
||||
const fullPath = joinPaths(source.rootPath, uri);
|
||||
const segmentedInput = this.getSegmentedInputForPath(fullPath);
|
||||
const input = segmentedInput.toInput();
|
||||
const videoTrack = await input.getPrimaryVideoTrack();
|
||||
@@ -277,7 +279,7 @@ export class HlsDemuxer extends Demuxer {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fullPath = joinPaths(this.input._entryPath!, uri);
|
||||
const fullPath = joinPaths(source.rootPath, uri);
|
||||
const segmentedInput = this.getSegmentedInputForPath(fullPath);
|
||||
const input = segmentedInput.toInput();
|
||||
const audioTrack = await input.getPrimaryAudioTrack();
|
||||
|
||||
+34
-29
@@ -15,7 +15,6 @@ import {
|
||||
OutputAudioTrack,
|
||||
OutputSubtitleTrack,
|
||||
OutputTrack,
|
||||
outputTracksArePairable,
|
||||
OutputVideoTrack,
|
||||
TrackType,
|
||||
} from '../output';
|
||||
@@ -29,7 +28,7 @@ import {
|
||||
import { Writer } from '../writer';
|
||||
import { EncodedPacket } from '../packet';
|
||||
import { SubtitleCue, SubtitleMetadata } from '../subtitles';
|
||||
import { NullTarget, Target } from '../target';
|
||||
import { NullTarget, PathedTarget, Target, TargetRequest } from '../target';
|
||||
|
||||
type HlsTrackData = {
|
||||
track: OutputTrack;
|
||||
@@ -105,8 +104,8 @@ export class HlsMuxer extends Muxer {
|
||||
playlistDeclarations: PlaylistDeclaration[] = [];
|
||||
|
||||
constructor(output: Output, format: HlsOutputFormat) {
|
||||
if (!output._targetIsFunction()) {
|
||||
throw new TypeError('HLS outputs require `OutputOptions.target` to be a function.');
|
||||
if (!(output._target instanceof PathedTarget)) {
|
||||
throw new TypeError('HLS outputs require `OutputOptions.target` to be a PathedTarget.');
|
||||
}
|
||||
|
||||
super(output);
|
||||
@@ -173,7 +172,7 @@ export class HlsMuxer extends Muxer {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!outputTracksArePairable(track, otherTrack)) {
|
||||
if (!track.canBePairedWith(otherTrack)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -807,7 +806,8 @@ export class HlsMuxer extends Muxer {
|
||||
let relativeSegmentPath: string;
|
||||
let fullSegmentPath: string;
|
||||
|
||||
assert(this.output._rootPath !== null);
|
||||
assert(this.output._target instanceof PathedTarget);
|
||||
const pathedTarget = this.output._target;
|
||||
|
||||
if (this.singleFilePerPlaylist) {
|
||||
if (playlist.singleFile === null) {
|
||||
@@ -822,7 +822,7 @@ export class HlsMuxer extends Muxer {
|
||||
validateSegmentPath(relativeSegmentPath);
|
||||
|
||||
fullSegmentPath = joinPaths(
|
||||
joinPaths(this.output._rootPath, playlist.path),
|
||||
joinPaths(pathedTarget.rootPath, playlist.path),
|
||||
relativeSegmentPath,
|
||||
);
|
||||
|
||||
@@ -838,7 +838,7 @@ export class HlsMuxer extends Muxer {
|
||||
} else {
|
||||
relativeSegmentPath = playlist.singleFile.path;
|
||||
fullSegmentPath = joinPaths(
|
||||
joinPaths(this.output._rootPath, playlist.path),
|
||||
joinPaths(pathedTarget.rootPath, playlist.path),
|
||||
relativeSegmentPath,
|
||||
);
|
||||
}
|
||||
@@ -853,8 +853,7 @@ export class HlsMuxer extends Muxer {
|
||||
relativeSegmentPath = await this.getSegmentPath(segmentInfo);
|
||||
validateSegmentPath(relativeSegmentPath);
|
||||
|
||||
assert(this.output._rootPath !== null);
|
||||
fullSegmentPath = joinPaths(joinPaths(this.output._rootPath, playlist.path), relativeSegmentPath);
|
||||
fullSegmentPath = joinPaths(joinPaths(pathedTarget.rootPath, playlist.path), relativeSegmentPath);
|
||||
playlist.nextSegmentId++;
|
||||
}
|
||||
|
||||
@@ -863,25 +862,27 @@ export class HlsMuxer extends Muxer {
|
||||
|
||||
const output = new Output({
|
||||
format: playlist.segmentFormat,
|
||||
rootPath: fullSegmentPath,
|
||||
target: async (request) => {
|
||||
if (request.isRoot) {
|
||||
if (playlist.singleFile) {
|
||||
const slice = playlist.singleFile.target.slice(playlist.singleFile.nextOffset);
|
||||
slice.on('write', ({ end }) => segmentSize = Math.max(segmentSize, end));
|
||||
target: new PathedTarget(
|
||||
fullSegmentPath,
|
||||
async (request: TargetRequest) => {
|
||||
if (request.isRoot) {
|
||||
if (playlist.singleFile) {
|
||||
const slice = playlist.singleFile.target.slice(playlist.singleFile.nextOffset);
|
||||
slice.on('write', ({ end }) => segmentSize = Math.max(segmentSize, end));
|
||||
|
||||
return slice;
|
||||
} else {
|
||||
const target = await this.output._getTarget(request);
|
||||
outputTarget = target;
|
||||
target.on('write', ({ end }) => segmentSize = Math.max(segmentSize, end));
|
||||
return slice;
|
||||
} else {
|
||||
const target = await this.output._getTarget(request);
|
||||
outputTarget = target;
|
||||
target.on('write', ({ end }) => segmentSize = Math.max(segmentSize, end));
|
||||
|
||||
return target;
|
||||
return target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.output._getTarget(request);
|
||||
},
|
||||
return this.output._getTarget(request);
|
||||
},
|
||||
),
|
||||
initTarget: async () => {
|
||||
if (playlist.initSegment) {
|
||||
// We already have an init segment from a previous segment
|
||||
@@ -1113,7 +1114,8 @@ export class HlsMuxer extends Muxer {
|
||||
}
|
||||
|
||||
private async writePlaylist(playlist: Playlist) {
|
||||
assert(this.output._rootPath !== null);
|
||||
assert(this.output._target instanceof PathedTarget);
|
||||
const pathedTarget = this.output._target;
|
||||
|
||||
this.updatePlaylistBitrates(playlist);
|
||||
|
||||
@@ -1140,7 +1142,7 @@ export class HlsMuxer extends Muxer {
|
||||
// In live mode, target duration is not allowed to change, so we use the nominal value
|
||||
const targetDuration = this.isLive ? this.targetSegmentDuration : this.globalTargetDuration;
|
||||
|
||||
const playlistPath = joinPaths(this.output._rootPath, playlist.path);
|
||||
const playlistPath = joinPaths(pathedTarget.rootPath, playlist.path);
|
||||
const playlistText = '#EXTM3U\n'
|
||||
+ `#EXT-X-VERSION:${version}\n`
|
||||
+ (!this.isLive ? '#EXT-X-PLAYLIST-TYPE:VOD\n' : '')
|
||||
@@ -1184,6 +1186,9 @@ export class HlsMuxer extends Muxer {
|
||||
}
|
||||
|
||||
private async writeMasterPlaylist() {
|
||||
assert(this.output._target instanceof PathedTarget);
|
||||
const pathedTarget = this.output._target;
|
||||
|
||||
let masterPlaylistText = '#EXTM3U\n';
|
||||
let firstVariantWritten = false;
|
||||
|
||||
@@ -1366,7 +1371,7 @@ export class HlsMuxer extends Muxer {
|
||||
|
||||
this.format._options.onMaster?.(masterPlaylistText);
|
||||
|
||||
const target = await this.output._getTarget({ path: this.output._rootPath!, isRoot: true });
|
||||
const target = await this.output._getTarget({ path: pathedTarget.rootPath, isRoot: true });
|
||||
const writer = new Writer(target);
|
||||
|
||||
writer.start();
|
||||
@@ -1390,7 +1395,7 @@ export class HlsMuxer extends Muxer {
|
||||
}
|
||||
|
||||
async finalize() {
|
||||
assert(this.output._rootPath !== null);
|
||||
assert(this.output._target instanceof PathedTarget);
|
||||
const release = await this.mutex.acquire();
|
||||
|
||||
for (const trackData of this.trackDatas) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { ENCRYPTION_KEY_CACHE_GROUP, Input } from '../input';
|
||||
import { Segment, SegmentedInput, SegmentRetrievalOptions } from '../segmented-input';
|
||||
import { toDataView, joinPaths, last, assert, binarySearchLessOrEqual, arrayArgmin, wait } from '../misc';
|
||||
import { readAllLines, readBytes, Reader } from '../reader';
|
||||
import { ReadableStreamSource, SourceRef } from '../source';
|
||||
import { PathedSource, ReadableStreamSource, SourceRef } from '../source';
|
||||
import { HlsDemuxer } from './hls-demuxer';
|
||||
import { AttributeList, canIgnoreLine } from './hls-misc';
|
||||
|
||||
@@ -525,68 +525,70 @@ export class HlsSegmentedInput extends SegmentedInput {
|
||||
}
|
||||
|
||||
const input = new Input({
|
||||
entryPath: hlsSegment.location.path,
|
||||
source: async (request) => {
|
||||
if (request.path !== hlsSegment.location.path) {
|
||||
// This code technically allows for recursive .m3u8 files for example. Uncached because the added
|
||||
// input adds its own layer of caching, so here we just do a passthrough.
|
||||
return this.input._getSourceUncached(request);
|
||||
}
|
||||
|
||||
let ref: SourceRef;
|
||||
const needsSlice = hlsSegment.location.offset > 0 || hlsSegment.location.length !== null;
|
||||
|
||||
if (!hlsSegment.encryption) {
|
||||
ref = await this.input._getSourceCached(request);
|
||||
|
||||
if (needsSlice) {
|
||||
const slice = ref.source.slice(
|
||||
hlsSegment.location.offset,
|
||||
hlsSegment.location.length ?? undefined,
|
||||
);
|
||||
const sliceRef = slice.ref();
|
||||
ref.free();
|
||||
ref = sliceRef;
|
||||
}
|
||||
} else {
|
||||
assert(hlsSegment.encryption.iv);
|
||||
|
||||
let ciphertextRef = await this.input._getSourceCached(request);
|
||||
if (needsSlice) {
|
||||
// Slice before decrypting
|
||||
const slice = ciphertextRef.source.slice(
|
||||
hlsSegment.location.offset,
|
||||
hlsSegment.location.length ?? undefined,
|
||||
);
|
||||
const sliceRef = slice.ref();
|
||||
ciphertextRef.free();
|
||||
ciphertextRef = sliceRef;
|
||||
source: new PathedSource(
|
||||
hlsSegment.location.path,
|
||||
async (request) => {
|
||||
if (request.path !== hlsSegment.location.path) {
|
||||
// This code technically allows for recursive .m3u8 files for example. Uncached because the
|
||||
// added input adds its own layer of caching, so here we just do a passthrough.
|
||||
return this.input._getSourceUncached(request);
|
||||
}
|
||||
|
||||
const ciphertextReader = new Reader(ciphertextRef.source);
|
||||
let ref: SourceRef;
|
||||
const needsSlice = hlsSegment.location.offset > 0 || hlsSegment.location.length !== null;
|
||||
|
||||
const stream = createAes128CbcDecryptStream(ciphertextReader, async () => {
|
||||
using keyRef = await this.input._getSourceCached(
|
||||
{ path: hlsSegment.encryption!.keyUri, isRoot: false },
|
||||
ENCRYPTION_KEY_CACHE_GROUP,
|
||||
);
|
||||
const keyReader = new Reader(keyRef.source);
|
||||
const keySlice = await keyReader.requestSlice(0, AES_128_BLOCK_SIZE);
|
||||
if (!keySlice) {
|
||||
throw new Error('Invalid AES-128 key; expected at least 16 bytes of data.');
|
||||
if (!hlsSegment.encryption) {
|
||||
ref = await this.input._getSourceCached(request);
|
||||
|
||||
if (needsSlice) {
|
||||
const slice = ref.source.slice(
|
||||
hlsSegment.location.offset,
|
||||
hlsSegment.location.length ?? undefined,
|
||||
);
|
||||
const sliceRef = slice.ref();
|
||||
ref.free();
|
||||
ref = sliceRef;
|
||||
}
|
||||
const key = readBytes(keySlice, AES_128_BLOCK_SIZE);
|
||||
} else {
|
||||
assert(hlsSegment.encryption.iv);
|
||||
|
||||
return { key, iv: hlsSegment.encryption!.iv! };
|
||||
}, () => {
|
||||
ciphertextRef.free();
|
||||
});
|
||||
let ciphertextRef = await this.input._getSourceCached(request);
|
||||
if (needsSlice) {
|
||||
// Slice before decrypting
|
||||
const slice = ciphertextRef.source.slice(
|
||||
hlsSegment.location.offset,
|
||||
hlsSegment.location.length ?? undefined,
|
||||
);
|
||||
const sliceRef = slice.ref();
|
||||
ciphertextRef.free();
|
||||
ciphertextRef = sliceRef;
|
||||
}
|
||||
|
||||
ref = new ReadableStreamSource(stream).ref();
|
||||
}
|
||||
const ciphertextReader = new Reader(ciphertextRef.source);
|
||||
|
||||
return ref!;
|
||||
},
|
||||
const stream = createAes128CbcDecryptStream(ciphertextReader, async () => {
|
||||
using keyRef = await this.input._getSourceCached(
|
||||
{ path: hlsSegment.encryption!.keyUri, isRoot: false },
|
||||
ENCRYPTION_KEY_CACHE_GROUP,
|
||||
);
|
||||
const keyReader = new Reader(keyRef.source);
|
||||
const keySlice = await keyReader.requestSlice(0, AES_128_BLOCK_SIZE);
|
||||
if (!keySlice) {
|
||||
throw new Error('Invalid AES-128 key; expected at least 16 bytes of data.');
|
||||
}
|
||||
const key = readBytes(keySlice, AES_128_BLOCK_SIZE);
|
||||
|
||||
return { key, iv: hlsSegment.encryption!.iv! };
|
||||
}, () => {
|
||||
ciphertextRef.free();
|
||||
});
|
||||
|
||||
ref = new ReadableStreamSource(stream).ref();
|
||||
}
|
||||
|
||||
return ref!;
|
||||
},
|
||||
),
|
||||
formats: this.input._formats,
|
||||
initInput: initInput ?? undefined,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user