download
raw
13.7 kB
import { buildAbsoluteURL } from 'url-toolkit';
import { LoadStats } from './load-stats';
import type { LevelKey } from './level-key';
import type {
FragmentLoaderContext,
KeyLoaderContext,
Loader,
PlaylistLevelType,
} from '../types/loader';
import type { AttrList } from '../utils/attr-list';
import type { KeySystemFormats } from '../utils/mediakeys-helper';
export const enum ElementaryStreamTypes {
AUDIO = 'audio',
VIDEO = 'video',
AUDIOVIDEO = 'audiovideo',
}
export interface ElementaryStreamInfo {
startPTS: number;
endPTS: number;
startDTS: number;
endDTS: number;
partial?: boolean;
}
export type ElementaryStreams = Record<
ElementaryStreamTypes,
ElementaryStreamInfo | null
>;
export type Base = {
url: string;
};
export class BaseSegment {
private _byteRange: [number, number] | null = null;
private _url: string | null = null;
private _stats: LoadStats | null = null;
private _streams: ElementaryStreams | null = null;
// baseurl is the URL to the playlist
public readonly base: Base;
// relurl is the portion of the URL that comes from inside the playlist.
public relurl?: string;
constructor(base: Base | string) {
if (typeof base === 'string') {
base = { url: base };
}
this.base = base;
makeEnumerable(this, 'stats');
}
// setByteRange converts a EXT-X-BYTERANGE attribute into a two element array
setByteRange(value: string, previous?: BaseSegment) {
const params = value.split('@', 2);
let start: number;
if (params.length === 1) {
start = previous?.byteRangeEndOffset || 0;
} else {
start = parseInt(params[1]);
}
this._byteRange = [start, parseInt(params[0]) + start];
}
get baseurl(): string {
return this.base.url;
}
get byteRange(): [number, number] | [] {
if (this._byteRange === null) {
return [];
}
return this._byteRange;
}
get byteRangeStartOffset(): number | undefined {
return this.byteRange[0];
}
get byteRangeEndOffset(): number | undefined {
return this.byteRange[1];
}
get elementaryStreams(): ElementaryStreams {
if (this._streams === null) {
this._streams = {
[ElementaryStreamTypes.AUDIO]: null,
[ElementaryStreamTypes.VIDEO]: null,
[ElementaryStreamTypes.AUDIOVIDEO]: null,
};
}
return this._streams;
}
set elementaryStreams(value: ElementaryStreams) {
this._streams = value;
}
get hasStats(): boolean {
return this._stats !== null;
}
get hasStreams(): boolean {
return this._streams !== null;
}
get stats(): LoadStats {
if (this._stats === null) {
this._stats = new LoadStats();
}
return this._stats;
}
set stats(value: LoadStats) {
this._stats = value;
}
get url(): string {
if (!this._url && this.baseurl && this.relurl) {
this._url = buildAbsoluteURL(this.baseurl, this.relurl, {
alwaysNormalize: true,
});
}
return this._url || '';
}
set url(value: string) {
this._url = value;
}
clearElementaryStreamInfo() {
const { elementaryStreams } = this;
elementaryStreams[ElementaryStreamTypes.AUDIO] = null;
elementaryStreams[ElementaryStreamTypes.VIDEO] = null;
elementaryStreams[ElementaryStreamTypes.AUDIOVIDEO] = null;
}
}
export interface MediaFragment extends Fragment {
sn: number;
ref: MediaFragmentRef;
}
export type MediaFragmentRef = {
base: Base;
start: number;
duration: number;
sn: number;
programDateTime: number | null;
};
export function isMediaFragment(frag: Fragment): frag is MediaFragment {
return frag.sn !== 'initSegment';
}
/**
* Object representing parsed data from an HLS Segment. Found in {@link hls.js#LevelDetails.fragments}.
*/
export class Fragment extends BaseSegment {
private _decryptdata: LevelKey | null = null;
private _programDateTime: number | null = null;
private _ref: MediaFragmentRef | null = null;
// Approximate bit rate of the fragment expressed in bits per second (bps) as indicated by the last EXT-X-BITRATE (kbps) tag
private _bitrate?: number;
public rawProgramDateTime: string | null = null;
public tagList: Array<string[]> = [];
// EXTINF has to be present for a m3u8 to be considered valid
public duration: number = 0;
// sn notates the sequence number for a segment, and if set to a string can be 'initSegment'
public sn: number | 'initSegment' = 0;
// levelkeys are the EXT-X-KEY tags that apply to this segment for decryption
// core difference from the private field _decryptdata is the lack of the initialized IV
// _decryptdata will set the IV for this segment based on the segment number in the fragment
public levelkeys?: { [key: string]: LevelKey | undefined };
// A string representing the fragment type
public readonly type: PlaylistLevelType;
// A reference to the loader. Set while the fragment is loading, and removed afterwards. Used to abort fragment loading
public loader: Loader<FragmentLoaderContext> | null = null;
// A reference to the key loader. Set while the key is loading, and removed afterwards. Used to abort key loading
public keyLoader: Loader<KeyLoaderContext> | null = null;
// The level/track index to which the fragment belongs
public level: number = -1;
// The continuity counter of the fragment
public cc: number = 0;
// The starting Presentation Time Stamp (PTS) of the fragment. Set after transmux complete.
public startPTS?: number;
// The ending Presentation Time Stamp (PTS) of the fragment. Set after transmux complete.
public endPTS?: number;
// The starting Decode Time Stamp (DTS) of the fragment. Set after transmux complete.
public startDTS?: number;
// The ending Decode Time Stamp (DTS) of the fragment. Set after transmux complete.
public endDTS?: number;
// The start time of the fragment, as listed in the manifest. Updated after transmux complete.
public start: number = 0;
// The offset time (seconds) of the fragment from the start of the Playlist
public playlistOffset: number = 0;
// Set by `updateFragPTSDTS` in level-helper
public deltaPTS?: number;
// The maximum starting Presentation Time Stamp (audio/video PTS) of the fragment. Set after transmux complete.
public maxStartPTS?: number;
// The minimum ending Presentation Time Stamp (audio/video PTS) of the fragment. Set after transmux complete.
public minEndPTS?: number;
// Init Segment bytes (unset for media segments)
public data?: Uint8Array;
// A flag indicating whether the segment was downloaded in order to test bitrate, and was not buffered
public bitrateTest: boolean = false;
// #EXTINF segment title
public title: string | null = null;
// The Media Initialization Section for this segment
public initSegment: Fragment | null = null;
// Fragment is the last fragment in the media playlist
public endList?: boolean;
// Fragment is marked by an EXT-X-GAP tag indicating that it does not contain media data and should not be loaded
public gap?: boolean;
// Deprecated
public urlId: number = 0;
constructor(type: PlaylistLevelType, base: Base | string) {
super(base);
this.type = type;
}
get byteLength(): number | null {
if (this.hasStats) {
const total = this.stats.total;
if (total) {
return total;
}
}
if (this.byteRange.length) {
const start = this.byteRange[0];
const end = this.byteRange[1];
if (Number.isFinite(start) && Number.isFinite(end)) {
return (end as number) - (start as number);
}
}
return null;
}
get bitrate(): number | null {
if (this.byteLength) {
return (this.byteLength * 8) / this.duration;
}
if (this._bitrate) {
return this._bitrate;
}
return null;
}
set bitrate(value: number) {
this._bitrate = value;
}
get decryptdata(): LevelKey | null {
const { levelkeys } = this;
if (!levelkeys || levelkeys.NONE) {
return null;
}
if (levelkeys.identity) {
if (!this._decryptdata) {
this._decryptdata = levelkeys.identity.getDecryptData(this.sn);
}
} else if (!this._decryptdata?.keyId) {
const keyFormats = Object.keys(levelkeys);
if (keyFormats.length === 1) {
const levelKey = (this._decryptdata = levelkeys[keyFormats[0]] || null);
if (levelKey) {
this._decryptdata = levelKey.getDecryptData(this.sn, levelkeys);
}
} else {
// Multiple keys. key-loader to call Fragment.setKeyFormat based on selected key-system.
}
}
return this._decryptdata;
}
get end(): number {
return this.start + this.duration;
}
get endProgramDateTime() {
if (this.programDateTime === null) {
return null;
}
const duration = !Number.isFinite(this.duration) ? 0 : this.duration;
return this.programDateTime + duration * 1000;
}
get encrypted() {
// At the m3u8-parser level we need to add support for manifest signalled keyformats
// when we want the fragment to start reporting that it is encrypted.
// Currently, keyFormat will only be set for identity keys
if (this._decryptdata?.encrypted) {
return true;
} else if (this.levelkeys) {
const keyFormats = Object.keys(this.levelkeys);
const len = keyFormats.length;
if (len > 1 || (len === 1 && this.levelkeys[keyFormats[0]]?.encrypted)) {
return true;
}
}
return false;
}
get programDateTime(): number | null {
if (this._programDateTime === null && this.rawProgramDateTime) {
this.programDateTime = Date.parse(this.rawProgramDateTime);
}
return this._programDateTime;
}
set programDateTime(value: number | null) {
if (!Number.isFinite(value)) {
this._programDateTime = this.rawProgramDateTime = null;
return;
}
this._programDateTime = value;
}
get ref(): MediaFragmentRef | null {
if (!isMediaFragment(this)) {
return null;
}
if (!this._ref) {
this._ref = {
base: this.base,
start: this.start,
duration: this.duration,
sn: this.sn,
programDateTime: this.programDateTime,
};
}
return this._ref;
}
addStart(value: number) {
this.setStart(this.start + value);
}
setStart(value: number) {
this.start = value;
if (this._ref) {
this._ref.start = value;
}
}
setDuration(value: number) {
this.duration = value;
if (this._ref) {
this._ref.duration = value;
}
}
setKeyFormat(keyFormat: KeySystemFormats) {
const levelkeys = this.levelkeys;
if (levelkeys) {
const key = levelkeys[keyFormat];
if (key && !this._decryptdata?.keyId) {
this._decryptdata = key.getDecryptData(this.sn, levelkeys);
}
}
}
abortRequests(): void {
this.loader?.abort();
this.keyLoader?.abort();
}
setElementaryStreamInfo(
type: ElementaryStreamTypes,
startPTS: number,
endPTS: number,
startDTS: number,
endDTS: number,
partial: boolean = false,
) {
const { elementaryStreams } = this;
const info = elementaryStreams[type];
if (!info) {
elementaryStreams[type] = {
startPTS,
endPTS,
startDTS,
endDTS,
partial,
};
return;
}
info.startPTS = Math.min(info.startPTS, startPTS);
info.endPTS = Math.max(info.endPTS, endPTS);
info.startDTS = Math.min(info.startDTS, startDTS);
info.endDTS = Math.max(info.endDTS, endDTS);
}
}
/**
* Object representing parsed data from an HLS Partial Segment. Found in {@link hls.js#LevelDetails.partList}.
*/
export class Part extends BaseSegment {
public readonly fragOffset: number = 0;
public readonly duration: number = 0;
public readonly gap: boolean = false;
public readonly independent: boolean = false;
public readonly relurl: string;
public readonly fragment: MediaFragment;
public readonly index: number;
constructor(
partAttrs: AttrList,
frag: MediaFragment,
base: Base | string,
index: number,
previous?: Part,
) {
super(base);
this.duration = partAttrs.decimalFloatingPoint('DURATION');
this.gap = partAttrs.bool('GAP');
this.independent = partAttrs.bool('INDEPENDENT');
this.relurl = partAttrs.enumeratedString('URI') as string;
this.fragment = frag;
this.index = index;
const byteRange = partAttrs.enumeratedString('BYTERANGE');
if (byteRange) {
this.setByteRange(byteRange, previous);
}
if (previous) {
this.fragOffset = previous.fragOffset + previous.duration;
}
}
get start(): number {
return this.fragment.start + this.fragOffset;
}
get end(): number {
return this.start + this.duration;
}
get loaded(): boolean {
const { elementaryStreams } = this;
return !!(
elementaryStreams.audio ||
elementaryStreams.video ||
elementaryStreams.audiovideo
);
}
}
function getOwnPropertyDescriptorFromPrototypeChain(
object: Object | undefined,
property: string,
) {
const prototype = Object.getPrototypeOf(object);
if (prototype) {
const propertyDescriptor = Object.getOwnPropertyDescriptor(
prototype,
property,
);
if (propertyDescriptor) {
return propertyDescriptor;
}
return getOwnPropertyDescriptorFromPrototypeChain(prototype, property);
}
}
function makeEnumerable(object: Object, property: string) {
const d = getOwnPropertyDescriptorFromPrototypeChain(object, property);
if (d) {
d.enumerable = true;
Object.defineProperty(object, property, d);
}
}

Xet Storage Details

Size:
13.7 kB
·
Xet hash:
37f6a75d5f89142eec3be849fe9a847a85ed566c84c4f1f1dd10412f6ada5dc3

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.