mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-03 13:53:53 +02:00
Add HlsInputFormatOptions.offsetTimestampsByDateTime option, add InputTrack.getUnixTimeForTimestamp()
This commit is contained in:
@@ -233,6 +233,35 @@ This makes cross-track synchronization trivial. To know if a track's timestamps
|
|||||||
await track.isRelativeToUnixEpoch(); // => boolean
|
await track.isRelativeToUnixEpoch(); // => boolean
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Disabling Unix offsets
|
||||||
|
|
||||||
|
If you don't want Mediabunny to offset packet timestamps to be in Unix time, you can set `offsetTimestampsByDateTime` to `false` in the input format options:
|
||||||
|
```ts
|
||||||
|
const input = new Input({
|
||||||
|
// ...
|
||||||
|
formatOptions: {
|
||||||
|
hls: {
|
||||||
|
offsetTimestampsByDateTime: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
This way, track and packet timestamps behave as if no `#EXT-X-PROGRAM-DATE-TIME` tags existed. This also means that any date time gaps are completely collapsed.
|
||||||
|
|
||||||
|
You will still be able to query the Unix time metadata via a mapping function on the `InputTrack`:
|
||||||
|
```ts
|
||||||
|
const firstTimestamp = await inputTrack.getFirstTimestamp(); // => 0
|
||||||
|
await inputTrack.getUnixTimeForTimestamp(firstTimestamp); // => 1704067200 (Unix timestamp for 2024-01-01T00:00:00Z)
|
||||||
|
```
|
||||||
|
|
||||||
|
This function performs a piecewise-continuous mapping of timestamp space into Unix time space.
|
||||||
|
|
||||||
|
If no wall-clock time information is available, `getUnixTimeForTimestamp()` will return `null`. You can check the presence of Unix time metadata via:
|
||||||
|
```ts
|
||||||
|
await inputTrack.hasUnixTimeMapping(); // boolean
|
||||||
|
```
|
||||||
|
|
||||||
## Live HLS
|
## Live HLS
|
||||||
|
|
||||||
HLS playlists may be live. You can check that a track is live via:
|
HLS playlists may be live. You can check that a track is live via:
|
||||||
|
|||||||
@@ -215,6 +215,10 @@ class AdtsAudioTrackBacking implements InputAudioTrackBacking {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUnixTimeForTimestamp() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
getPairingMask() {
|
getPairingMask() {
|
||||||
return 1n;
|
return 1n;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -595,6 +595,10 @@ class FlacAudioTrackBacking implements InputAudioTrackBacking {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUnixTimeForTimestamp() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
getPairingMask() {
|
getPairingMask() {
|
||||||
return 1n;
|
return 1n;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -759,6 +759,10 @@ abstract class HlsInputTrackBacking implements InputTrackBacking {
|
|||||||
return this.delegate(() => this.internalTrack.backingTrack!.isRelativeToUnixEpoch());
|
return this.delegate(() => this.internalTrack.backingTrack!.isRelativeToUnixEpoch());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUnixTimeForTimestamp(timestamp: number): MaybePromise<number | null> {
|
||||||
|
return this.delegate(() => this.internalTrack.backingTrack!.getUnixTimeForTimestamp(timestamp));
|
||||||
|
}
|
||||||
|
|
||||||
getBitrate(): number | null {
|
getBitrate(): number | null {
|
||||||
return this.internalTrack.peakBitrate;
|
return this.internalTrack.peakBitrate;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,8 +141,11 @@ export class HlsSegmentedInput extends SegmentedInput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const offsetTimestampsByDateTime = this.input._formatOptions.hls?.offsetTimestampsByDateTime !== false;
|
||||||
|
|
||||||
let headerRead = false;
|
let headerRead = false;
|
||||||
let accumulatedTime = 0;
|
let accumulatedTime = 0;
|
||||||
|
let accumulatedUnixTime: number | null = null;
|
||||||
let nextSegmentDuration: number | null = null;
|
let nextSegmentDuration: number | null = null;
|
||||||
let currentKey: HlsEncryptionInfo | null = null;
|
let currentKey: HlsEncryptionInfo | null = null;
|
||||||
let nextSequenceNumber = 0;
|
let nextSequenceNumber = 0;
|
||||||
@@ -189,6 +192,9 @@ export class HlsSegmentedInput extends SegmentedInput {
|
|||||||
currentFirstSegment = prevLastSegment.firstSegment;
|
currentFirstSegment = prevLastSegment.firstSegment;
|
||||||
currentInitSegment = prevLastSegment.initSegment;
|
currentInitSegment = prevLastSegment.initSegment;
|
||||||
lastProgramDateTimeSeconds = prevLastSegment.lastProgramDateTimeSeconds;
|
lastProgramDateTimeSeconds = prevLastSegment.lastProgramDateTimeSeconds;
|
||||||
|
accumulatedUnixTime = prevLastSegment.unixEpochTimestamp !== null
|
||||||
|
? prevLastSegment.unixEpochTimestamp + prevLastSegment.duration
|
||||||
|
: null;
|
||||||
prevLastSegment = null;
|
prevLastSegment = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -235,7 +241,7 @@ export class HlsSegmentedInput extends SegmentedInput {
|
|||||||
|
|
||||||
const segment: HlsSegment = {
|
const segment: HlsSegment = {
|
||||||
timestamp: accumulatedTime,
|
timestamp: accumulatedTime,
|
||||||
relativeToUnixEpoch: lastProgramDateTimeSeconds !== null,
|
unixEpochTimestamp: accumulatedUnixTime,
|
||||||
firstSegment: currentFirstSegment,
|
firstSegment: currentFirstSegment,
|
||||||
sequenceNumber: nextSequenceNumber,
|
sequenceNumber: nextSequenceNumber,
|
||||||
location,
|
location,
|
||||||
@@ -247,6 +253,9 @@ export class HlsSegmentedInput extends SegmentedInput {
|
|||||||
|
|
||||||
currentFirstSegment ??= segment;
|
currentFirstSegment ??= segment;
|
||||||
accumulatedTime += nextSegmentDuration;
|
accumulatedTime += nextSegmentDuration;
|
||||||
|
if (accumulatedUnixTime !== null) {
|
||||||
|
accumulatedUnixTime += nextSegmentDuration;
|
||||||
|
}
|
||||||
|
|
||||||
this.segments.push(segment);
|
this.segments.push(segment);
|
||||||
} else {
|
} else {
|
||||||
@@ -320,7 +329,7 @@ export class HlsSegmentedInput extends SegmentedInput {
|
|||||||
|
|
||||||
const segment: HlsSegment = {
|
const segment: HlsSegment = {
|
||||||
timestamp: accumulatedTime,
|
timestamp: accumulatedTime,
|
||||||
relativeToUnixEpoch: lastProgramDateTimeSeconds !== null,
|
unixEpochTimestamp: accumulatedUnixTime,
|
||||||
firstSegment: null,
|
firstSegment: null,
|
||||||
sequenceNumber: null,
|
sequenceNumber: null,
|
||||||
location,
|
location,
|
||||||
@@ -478,15 +487,19 @@ export class HlsSegmentedInput extends SegmentedInput {
|
|||||||
const offset = dateTimeSeconds - lastSegmentEnd;
|
const offset = dateTimeSeconds - lastSegmentEnd;
|
||||||
|
|
||||||
for (const segment of this.segments) {
|
for (const segment of this.segments) {
|
||||||
segment.timestamp += offset;
|
segment.unixEpochTimestamp = segment.timestamp + offset;
|
||||||
segment.relativeToUnixEpoch = true;
|
if (offsetTimestampsByDateTime) {
|
||||||
|
segment.timestamp = segment.unixEpochTimestamp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
accumulatedTime += offset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastProgramDateTimeSeconds = dateTimeSeconds;
|
lastProgramDateTimeSeconds = dateTimeSeconds;
|
||||||
accumulatedTime = dateTimeSeconds; // Snap the accumulated time to the datetime
|
accumulatedUnixTime = dateTimeSeconds;
|
||||||
|
|
||||||
|
if (offsetTimestampsByDateTime) {
|
||||||
|
accumulatedTime = dateTimeSeconds; // Snap the accumulated time into Unix space
|
||||||
|
}
|
||||||
} else if (line === TAG_DISCONTINUITY) {
|
} else if (line === TAG_DISCONTINUITY) {
|
||||||
currentFirstSegment = null;
|
currentFirstSegment = null;
|
||||||
// Note: the init segment is not reset; the #EXT-X-MAP statement simply lasts until the next
|
// Note: the init segment is not reset; the #EXT-X-MAP statement simply lasts until the next
|
||||||
|
|||||||
@@ -197,6 +197,7 @@ export {
|
|||||||
IsobmffInputFormat,
|
IsobmffInputFormat,
|
||||||
type IsobmffInputFormatOptions,
|
type IsobmffInputFormatOptions,
|
||||||
HlsInputFormat,
|
HlsInputFormat,
|
||||||
|
type HlsInputFormatOptions,
|
||||||
MatroskaInputFormat,
|
MatroskaInputFormat,
|
||||||
Mp3InputFormat,
|
Mp3InputFormat,
|
||||||
Mp4InputFormat,
|
Mp4InputFormat,
|
||||||
|
|||||||
@@ -727,6 +727,8 @@ export const HLS_FORMATS: InputFormat[] = [HLS, MP4, QTFF, MP3, ADTS, MPEG_TS];
|
|||||||
export type InputFormatOptions = {
|
export type InputFormatOptions = {
|
||||||
/** ISOBMFF-specific configuration. */
|
/** ISOBMFF-specific configuration. */
|
||||||
isobmff?: IsobmffInputFormatOptions;
|
isobmff?: IsobmffInputFormatOptions;
|
||||||
|
/** HLS-specific configuration. */
|
||||||
|
hls?: HlsInputFormatOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -755,6 +757,26 @@ export type IsobmffInputFormatOptions = {
|
|||||||
_suppressPsshParsing?: boolean;
|
_suppressPsshParsing?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additional HLS input configuration.
|
||||||
|
* @group Input formats
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type HlsInputFormatOptions = {
|
||||||
|
/**
|
||||||
|
* Whether, in the presence of `#EXT-X-PROGRAM-DATE-TIME` tags, to offset track and packet timestamps to be relative
|
||||||
|
* to the Unix epoch.
|
||||||
|
*
|
||||||
|
* Defaults to `true`, meaning packet timestamps map directly to wall-clock time. This guarantees AV sync across
|
||||||
|
* multiple tracks, even with gaps present.
|
||||||
|
*
|
||||||
|
* When you don't want this mapping, you can set this value to `false`. In addition to timestamps not being Unix
|
||||||
|
* timestamps anymore, any gaps in the playlist are also naturally removed. When `false`, you can still access the
|
||||||
|
* wall-clock Unix timestamps via {@link InputTrack.getUnixTimeForTimestamp}.
|
||||||
|
*/
|
||||||
|
offsetTimestampsByDateTime?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export const validateInputFormatOptions = (options: InputFormatOptions, prefix: string) => {
|
export const validateInputFormatOptions = (options: InputFormatOptions, prefix: string) => {
|
||||||
if (!options || typeof options !== 'object') {
|
if (!options || typeof options !== 'object') {
|
||||||
throw new TypeError(`${prefix}, when provided, must be an object.`);
|
throw new TypeError(`${prefix}, when provided, must be an object.`);
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export interface InputTrackBacking {
|
|||||||
getLanguageCode(): MaybePromise<string>;
|
getLanguageCode(): MaybePromise<string>;
|
||||||
getTimeResolution(): MaybePromise<number>;
|
getTimeResolution(): MaybePromise<number>;
|
||||||
isRelativeToUnixEpoch(): MaybePromise<boolean>;
|
isRelativeToUnixEpoch(): MaybePromise<boolean>;
|
||||||
|
getUnixTimeForTimestamp(timestamp: number): MaybePromise<number | null>;
|
||||||
getDisposition(): MaybePromise<TrackDisposition>;
|
getDisposition(): MaybePromise<TrackDisposition>;
|
||||||
getPairingMask(): bigint;
|
getPairingMask(): bigint;
|
||||||
getBitrate(): MaybePromise<number | null>;
|
getBitrate(): MaybePromise<number | null>;
|
||||||
@@ -203,6 +204,28 @@ export abstract class InputTrack {
|
|||||||
return this._backing.isRelativeToUnixEpoch();
|
return this._backing.isRelativeToUnixEpoch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Unix time (in seconds since January 1, 1970 00:00:00 UTC) that the given track timestamp (in seconds)
|
||||||
|
* maps to, or `null` if there is no such mapping. This provides a piecewise-continuous mapping from this track's
|
||||||
|
* timestamp space into wall-clock time. Such mapping exists, for example, for HLS playlists with
|
||||||
|
* `#EXT-X-PROGRAM-DATE-TIME` tags present.
|
||||||
|
*
|
||||||
|
* This mapping can be available even when {@link InputTrack.isRelativeToUnixEpoch} is `false`, for example for HLS
|
||||||
|
* streams with program date time information but with {@link HlsInputFormatOptions.offsetTimestampsByDateTime}
|
||||||
|
* set to `false`.
|
||||||
|
*/
|
||||||
|
async getUnixTimeForTimestamp(timestamp: number): Promise<number | null> {
|
||||||
|
return this._backing.getUnixTimeForTimestamp(timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the track's timestamps can be mapped to Unix wall clock time via
|
||||||
|
* {@link InputTrack.getUnixTimeForTimestamp}.
|
||||||
|
*/
|
||||||
|
async hasUnixTimeMapping(): Promise<boolean> {
|
||||||
|
return (await this._backing.getUnixTimeForTimestamp(await this.getFirstTimestamp())) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns the track's disposition, i.e. information about its intended usage. */
|
/** Returns the track's disposition, i.e. information about its intended usage. */
|
||||||
async getDisposition() {
|
async getDisposition() {
|
||||||
return this._backing.getDisposition();
|
return this._backing.getDisposition();
|
||||||
|
|||||||
@@ -2811,6 +2811,10 @@ abstract class IsobmffTrackBacking implements InputTrackBacking {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUnixTimeForTimestamp() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
getDisposition() {
|
getDisposition() {
|
||||||
return this.internalTrack.disposition;
|
return this.internalTrack.disposition;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1953,6 +1953,10 @@ abstract class MatroskaTrackBacking implements InputTrackBacking {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUnixTimeForTimestamp() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
getDisposition() {
|
getDisposition() {
|
||||||
return this.internalTrack.disposition;
|
return this.internalTrack.disposition;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -264,6 +264,10 @@ class Mp3AudioTrackBacking implements InputAudioTrackBacking {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUnixTimeForTimestamp() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
getPairingMask() {
|
getPairingMask() {
|
||||||
return 1n;
|
return 1n;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1103,6 +1103,10 @@ abstract class MpegTsTrackBacking implements InputTrackBacking {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUnixTimeForTimestamp() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
getPairingMask() {
|
getPairingMask() {
|
||||||
return 1n;
|
return 1n;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -454,6 +454,10 @@ class OggAudioTrackBacking implements InputAudioTrackBacking {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUnixTimeForTimestamp() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
getPairingMask() {
|
getPairingMask() {
|
||||||
return 1n;
|
return 1n;
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-2
@@ -41,7 +41,12 @@ export type AssociatedGroup = {
|
|||||||
export type Segment = {
|
export type Segment = {
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
duration: number;
|
duration: number;
|
||||||
relativeToUnixEpoch: boolean;
|
/**
|
||||||
|
* The Unix time (in seconds) corresponding to this segment's start timestamp, or null if unknown. This is computed
|
||||||
|
* whenever the source provides wall-clock information (e.g. HLS program date time), even if the segment timestamps
|
||||||
|
* themselves are not shifted into Unix time space.
|
||||||
|
*/
|
||||||
|
unixEpochTimestamp: number | null;
|
||||||
firstSegment: Segment | null;
|
firstSegment: Segment | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -97,6 +102,18 @@ export abstract class SegmentedInput {
|
|||||||
return lastSegment.timestamp + lastSegment.duration;
|
return lastSegment.timestamp + lastSegment.duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getUnixTimeForTimestamp(timestamp: number): Promise<number | null> {
|
||||||
|
let segment = await this.getSegmentAt(timestamp, {});
|
||||||
|
segment ??= await this.getFirstSegment({});
|
||||||
|
|
||||||
|
if (!segment || segment.unixEpochTimestamp === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const elapsed = timestamp - segment.timestamp;
|
||||||
|
return segment.unixEpochTimestamp + elapsed;
|
||||||
|
}
|
||||||
|
|
||||||
async getTrackBackings(): Promise<InputTrackBacking[]> {
|
async getTrackBackings(): Promise<InputTrackBacking[]> {
|
||||||
return this.trackBackingsPromise ??= (async () => {
|
return this.trackBackingsPromise ??= (async () => {
|
||||||
const backings: InputTrackBacking[] = [];
|
const backings: InputTrackBacking[] = [];
|
||||||
@@ -310,7 +327,11 @@ class SegmentedInputInputTrackBacking implements InputTrackBacking {
|
|||||||
await this.hydrate();
|
await this.hydrate();
|
||||||
|
|
||||||
assert(this.segmentedInput.firstSegment);
|
assert(this.segmentedInput.firstSegment);
|
||||||
return this.segmentedInput.firstSegment.relativeToUnixEpoch;
|
return this.segmentedInput.firstSegment.unixEpochTimestamp === this.segmentedInput.firstSegment.timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
getUnixTimeForTimestamp(timestamp: number) {
|
||||||
|
return this.segmentedInput.getUnixTimeForTimestamp(timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
getBitrate() {
|
getBitrate() {
|
||||||
|
|||||||
@@ -402,6 +402,10 @@ class WaveAudioTrackBacking implements InputAudioTrackBacking {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUnixTimeForTimestamp() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
getPairingMask() {
|
getPairingMask() {
|
||||||
return 1n;
|
return 1n;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -526,11 +526,14 @@ test.concurrent('Single-value PDT', { timeout: 15_000 }, async () => {
|
|||||||
|
|
||||||
const tracks = await input.getTracks();
|
const tracks = await input.getTracks();
|
||||||
expect((await Promise.all(tracks.map(x => x.isRelativeToUnixEpoch()))).every(x => x)).toBe(true);
|
expect((await Promise.all(tracks.map(x => x.isRelativeToUnixEpoch()))).every(x => x)).toBe(true);
|
||||||
|
expect((await Promise.all(tracks.map(x => x.hasUnixTimeMapping()))).every(x => x)).toBe(true);
|
||||||
|
|
||||||
const track = tracks[0]!;
|
const track = tracks[0]!;
|
||||||
const firstTimestamp = await track.getFirstTimestamp();
|
const firstTimestamp = await track.getFirstTimestamp();
|
||||||
expect(firstTimestamp).toBe(Date.parse('2013-05-08T17:40:50Z') / 1000);
|
expect(firstTimestamp).toBe(Date.parse('2013-05-08T17:40:50Z') / 1000);
|
||||||
|
|
||||||
|
expect(await track.getUnixTimeForTimestamp(firstTimestamp)).toBe(firstTimestamp);
|
||||||
|
|
||||||
const endTimestamp = await track.computeDuration();
|
const endTimestamp = await track.computeDuration();
|
||||||
expect(endTimestamp).toBe(firstTimestamp + 50);
|
expect(endTimestamp).toBe(firstTimestamp + 50);
|
||||||
|
|
||||||
@@ -600,6 +603,49 @@ test.concurrent('PDT with bad values', { timeout: 15_000 }, async () => {
|
|||||||
expect(await audioTrack.isRelativeToUnixEpoch()).toBe(false);
|
expect(await audioTrack.isRelativeToUnixEpoch()).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.concurrent('Single-value PDT with unix offsets disabled', { timeout: 15_000 }, async () => {
|
||||||
|
using input = new Input({
|
||||||
|
source: new UrlSource('https://playertest.longtailvideo.com/adaptive/aviion/manifest.m3u8'),
|
||||||
|
formats: ALL_FORMATS,
|
||||||
|
formatOptions: {
|
||||||
|
hls: {
|
||||||
|
offsetTimestampsByDateTime: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const tracks = await input.getTracks();
|
||||||
|
expect((await Promise.all(tracks.map(x => x.isRelativeToUnixEpoch()))).every(x => x)).toBe(false);
|
||||||
|
expect((await Promise.all(tracks.map(x => x.hasUnixTimeMapping()))).every(x => x)).toBe(true);
|
||||||
|
|
||||||
|
const track = tracks[0]!;
|
||||||
|
const firstTimestamp = await track.getFirstTimestamp();
|
||||||
|
expect(firstTimestamp).toBe(0);
|
||||||
|
|
||||||
|
const endTimestamp = await track.computeDuration();
|
||||||
|
expect(endTimestamp).toBe(firstTimestamp + 50);
|
||||||
|
|
||||||
|
const firstPacket = await new EncodedPacketSink(track).getFirstPacket();
|
||||||
|
assert(firstPacket);
|
||||||
|
expect(firstPacket.timestamp).toBe(firstTimestamp); // Kinda obvious check tbh
|
||||||
|
|
||||||
|
const unixStartTime = await track.getUnixTimeForTimestamp(firstTimestamp);
|
||||||
|
expect(unixStartTime).toBe(Date.parse('2013-05-08T17:40:50Z') / 1000);
|
||||||
|
|
||||||
|
const unixEndTime = await track.getUnixTimeForTimestamp(endTimestamp);
|
||||||
|
expect(unixEndTime).toBe(Date.parse('2013-05-08T17:41:40Z') / 1000);
|
||||||
|
|
||||||
|
const unixTimeBeforeStart = await track.getUnixTimeForTimestamp(firstTimestamp - 10);
|
||||||
|
expect(unixTimeBeforeStart).toBe(Date.parse('2013-05-08T17:40:40Z') / 1000);
|
||||||
|
|
||||||
|
const unixTimeAfterEnd = await track.getUnixTimeForTimestamp(endTimestamp + 10);
|
||||||
|
expect(unixTimeAfterEnd).toBe(Date.parse('2013-05-08T17:41:50Z') / 1000);
|
||||||
|
|
||||||
|
const timestampDt = 0.001;
|
||||||
|
const unixTimeDt = (await track.getUnixTimeForTimestamp(firstTimestamp + 0.001))! - unixStartTime!;
|
||||||
|
expect(unixTimeDt).toBeCloseTo(timestampDt);
|
||||||
|
});
|
||||||
|
|
||||||
test.concurrent('Alternative audio only', { timeout: 15_000 }, async () => {
|
test.concurrent('Alternative audio only', { timeout: 15_000 }, async () => {
|
||||||
using input = new Input({
|
using input = new Input({
|
||||||
source: new UrlSource('https://playertest.longtailvideo.com/adaptive/alt-audio-no-video/sintel/playlist.m3u8'),
|
source: new UrlSource('https://playertest.longtailvideo.com/adaptive/alt-audio-no-video/sintel/playlist.m3u8'),
|
||||||
|
|||||||
Reference in New Issue
Block a user