Skip to content

Commit

Permalink
video-dev#3312 support multiple EXT-X-MAP tags; temporarily disable s…
Browse files Browse the repository at this point in the history
…idx loading
  • Loading branch information
elv-peter committed May 5, 2021
1 parent c35441b commit a09a61f
Show file tree
Hide file tree
Showing 10 changed files with 241 additions and 193 deletions.
6 changes: 2 additions & 4 deletions api-extractor/report/hls.js.api.md
Expand Up @@ -715,6 +715,8 @@ export class Fragment extends BaseSegment {
// (undocumented)
endPTS?: number;
// (undocumented)
initSegment: Fragment | null;
// (undocumented)
level: number;
// (undocumented)
levelkey?: LevelKey;
Expand Down Expand Up @@ -1344,8 +1346,6 @@ export class LevelDetails {
// (undocumented)
holdBack: number;
// (undocumented)
initSegment: Fragment | null;
// (undocumented)
get lastPartIndex(): number;
// (undocumented)
get lastPartSn(): number;
Expand All @@ -1358,8 +1358,6 @@ export class LevelDetails {
// (undocumented)
misses: number;
// (undocumented)
needSidxRanges: boolean;
// (undocumented)
get partEnd(): number;
// (undocumented)
partHoldBack: number;
Expand Down
105 changes: 51 additions & 54 deletions src/controller/audio-stream-controller.ts
Expand Up @@ -281,67 +281,63 @@ class AudioStreamController
return;
}

let frag = trackDetails.initSegment;
// let frag = trackDetails.initSegment;
let targetBufferTime = 0;
if (!frag || frag.data) {
const mediaBuffer = this.mediaBuffer ? this.mediaBuffer : this.media;
const videoBuffer = this.videoBuffer ? this.videoBuffer : this.media;
const maxBufferHole =
pos < config.maxBufferHole
? Math.max(MAX_START_GAP_JUMP, config.maxBufferHole)
: config.maxBufferHole;
const bufferInfo = BufferHelper.bufferInfo(
mediaBuffer,
pos,
maxBufferHole
);
const mainBufferInfo = BufferHelper.bufferInfo(
videoBuffer,
pos,
maxBufferHole
);
const bufferLen = bufferInfo.len;
const maxConfigBuffer = Math.min(
config.maxBufferLength,
config.maxMaxBufferLength
);
const maxBufLen = Math.max(maxConfigBuffer, mainBufferInfo.len);
const audioSwitch = this.audioSwitch;
// if (!frag || frag.data) {
const mediaBuffer = this.mediaBuffer ? this.mediaBuffer : this.media;
const videoBuffer = this.videoBuffer ? this.videoBuffer : this.media;
const maxBufferHole =
pos < config.maxBufferHole
? Math.max(MAX_START_GAP_JUMP, config.maxBufferHole)
: config.maxBufferHole;
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer, pos, maxBufferHole);
const mainBufferInfo = BufferHelper.bufferInfo(
videoBuffer,
pos,
maxBufferHole
);
const bufferLen = bufferInfo.len;
const maxConfigBuffer = Math.min(
config.maxBufferLength,
config.maxMaxBufferLength
);
const maxBufLen = Math.max(maxConfigBuffer, mainBufferInfo.len);
const audioSwitch = this.audioSwitch;

// if buffer length is less than maxBufLen try to load a new fragment
if (bufferLen >= maxBufLen && !audioSwitch) {
return;
}
// if buffer length is less than maxBufLen try to load a new fragment
if (bufferLen >= maxBufLen && !audioSwitch) {
return;
}

if (!audioSwitch && this._streamEnded(bufferInfo, trackDetails)) {
hls.trigger(Events.BUFFER_EOS, { type: 'audio' });
this.state = State.ENDED;
return;
}
if (!audioSwitch && this._streamEnded(bufferInfo, trackDetails)) {
hls.trigger(Events.BUFFER_EOS, { type: 'audio' });
this.state = State.ENDED;
return;
}

const fragments = trackDetails.fragments;
const start = fragments[0].start;
targetBufferTime = bufferInfo.end;

if (audioSwitch) {
targetBufferTime = pos;
// if currentTime (pos) is less than alt audio playlist start time, it means that alt audio is ahead of currentTime
if (trackDetails.PTSKnown && pos < start) {
// if everything is buffered from pos to start or if audio buffer upfront, let's seek to start
if (bufferInfo.end > start || bufferInfo.nextStart) {
this.log(
'Alt audio track ahead of main track, seek to start of alt audio track'
);
media.currentTime = start + 0.05;
}
const fragments = trackDetails.fragments;
const start = fragments[0].start;
targetBufferTime = bufferInfo.end;

if (audioSwitch) {
targetBufferTime = pos;
// if currentTime (pos) is less than alt audio playlist start time, it means that alt audio is ahead of currentTime
if (trackDetails.PTSKnown && pos < start) {
// if everything is buffered from pos to start or if audio buffer upfront, let's seek to start
if (bufferInfo.end > start || bufferInfo.nextStart) {
this.log(
'Alt audio track ahead of main track, seek to start of alt audio track'
);
media.currentTime = start + 0.05;
}
}
}

frag = this.getNextFragment(targetBufferTime, trackDetails);
if (!frag) {
return;
}
const frag = this.getNextFragment(targetBufferTime, trackDetails);
if (!frag) {
return;
}
// }

if (frag.decryptdata?.keyFormat === 'identity' && !frag.decryptdata?.key) {
this.loadKey(frag, trackDetails);
Expand Down Expand Up @@ -506,7 +502,8 @@ class AudioStreamController
// Check if we have video initPTS
// If not we need to wait for it
const initPTS = this.initPTS[frag.cc];
const initSegmentData = details.initSegment?.data;
// const initSegmentData = details.initSegment?.data;
const initSegmentData = frag.initSegment?.data;
if (initPTS !== undefined) {
// this.log(`Transmuxing ${sn} of [${details.startSN} ,${details.endSN}],track ${trackId}`);
// time Offset is accurate if level PTS is known, or if playlist is not sliding (not live)
Expand Down
33 changes: 20 additions & 13 deletions src/controller/base-stream-controller.ts
Expand Up @@ -424,16 +424,17 @@ export default class BaseStreamController
details,
'Level details are defined when init segment is loaded'
);
const initSegment = details.initSegment as Fragment;
console.assert(
initSegment,
'Fragment initSegment is defined when init segment is loaded'
);
// const initSegment = details.initSegment as Fragment;
// console.assert(
// initSegment,
// 'Fragment initSegment is defined when init segment is loaded'
// );

const stats = frag.stats;
this.state = State.IDLE;
this.fragLoadError = 0;
initSegment.data = new Uint8Array(data.payload);
// initSegment.data = new Uint8Array(data.payload);
frag.data = new Uint8Array(data.payload);
stats.parsing.start = stats.buffering.start = self.performance.now();
stats.parsing.end = stats.buffering.end = self.performance.now();

Expand Down Expand Up @@ -752,13 +753,14 @@ export default class BaseStreamController
let frag;

// If an initSegment is present, it must be buffered first
if (
levelDetails.initSegment &&
!levelDetails.initSegment.data &&
!this.bitrateTest
) {
frag = levelDetails.initSegment;
} else if (levelDetails.live) {
// if (
// levelDetails.initSegment &&
// !levelDetails.initSegment.data &&
// !this.bitrateTest
// ) {
// frag = levelDetails.initSegment;
// } else if (levelDetails.live) {
if (levelDetails.live) {
const initialLiveManifestSize = config.initialLiveManifestSize;
if (fragLen < initialLiveManifestSize) {
this.warn(
Expand Down Expand Up @@ -793,6 +795,11 @@ export default class BaseStreamController
frag = this.getFragmentAtPosition(pos, end, levelDetails);
}

// If an initSegment is present, it must be buffered first
if (frag.initSegment && !frag.initSegment.data && !this.bitrateTest) {
frag = frag.initSegment;
}

return frag;
}

Expand Down
12 changes: 6 additions & 6 deletions src/controller/level-helper.ts
Expand Up @@ -166,9 +166,9 @@ export function mergeDetails(
newDetails: LevelDetails
): void {
// potentially retrieve cached initsegment
if (newDetails.initSegment && oldDetails.initSegment) {
newDetails.initSegment = oldDetails.initSegment;
}
// if (newDetails.initSegment && oldDetails.initSegment) {
// newDetails.initSegment = oldDetails.initSegment;
// }

if (oldDetails.fragmentHint) {
// prevent PTS and duration from being adjusted on the next hint
Expand Down Expand Up @@ -239,9 +239,9 @@ export function mergeDetails(
}
}
if (newDetails.skippedSegments) {
if (!newDetails.initSegment) {
newDetails.initSegment = oldDetails.initSegment;
}
// if (!newDetails.initSegment) {
// newDetails.initSegment = oldDetails.initSegment;
// }
newDetails.startCC = newDetails.fragments[0].cc;
}

Expand Down
109 changes: 57 additions & 52 deletions src/controller/stream-controller.ts
Expand Up @@ -244,63 +244,67 @@ export default class StreamController
return;
}

let frag = levelDetails.initSegment;
// let frag = levelDetails.initSegment;
let targetBufferTime = 0;
if (!frag || frag.data || this.bitrateTest) {
// compute max Buffer Length that we could get from this load level, based on level bitrate. don't buffer more than 60 MB and more than 30s
const levelBitrate = levelInfo.maxBitrate;
let maxBufLen;
if (levelBitrate) {
maxBufLen = Math.max(
(8 * config.maxBufferSize) / levelBitrate,
config.maxBufferLength
);
} else {
maxBufLen = config.maxBufferLength;
}
maxBufLen = Math.min(maxBufLen, config.maxMaxBufferLength);

// determine next candidate fragment to be loaded, based on current position and end of buffer position
// ensure up to `config.maxMaxBufferLength` of buffer upfront
const maxBufferHole =
pos < config.maxBufferHole
? Math.max(MAX_START_GAP_JUMP, config.maxBufferHole)
: config.maxBufferHole;
const bufferInfo = BufferHelper.bufferInfo(
this.mediaBuffer ? this.mediaBuffer : media,
pos,
maxBufferHole
// if (!frag || frag.data || this.bitrateTest) {
// compute max Buffer Length that we could get from this load level, based on level bitrate. don't buffer more than 60 MB and more than 30s
const levelBitrate = levelInfo.maxBitrate;
let maxBufLen;
if (levelBitrate) {
maxBufLen = Math.max(
(8 * config.maxBufferSize) / levelBitrate,
config.maxBufferLength
);
const bufferLen = bufferInfo.len;
// Stay idle if we are still with buffer margins
if (bufferLen >= maxBufLen) {
return;
} else {
maxBufLen = config.maxBufferLength;
}
maxBufLen = Math.min(maxBufLen, config.maxMaxBufferLength);

// determine next candidate fragment to be loaded, based on current position and end of buffer position
// ensure up to `config.maxMaxBufferLength` of buffer upfront
const maxBufferHole =
pos < config.maxBufferHole
? Math.max(MAX_START_GAP_JUMP, config.maxBufferHole)
: config.maxBufferHole;
const bufferInfo = BufferHelper.bufferInfo(
this.mediaBuffer ? this.mediaBuffer : media,
pos,
maxBufferHole
);
const bufferLen = bufferInfo.len;
// Stay idle if we are still with buffer margins
if (bufferLen >= maxBufLen) {
return;
}

if (this._streamEnded(bufferInfo, levelDetails)) {
const data: BufferEOSData = {};
if (this.altAudio) {
data.type = 'video';
}

if (this._streamEnded(bufferInfo, levelDetails)) {
const data: BufferEOSData = {};
if (this.altAudio) {
data.type = 'video';
}
this.hls.trigger(Events.BUFFER_EOS, data);
this.state = State.ENDED;
return;
}

this.hls.trigger(Events.BUFFER_EOS, data);
this.state = State.ENDED;
return;
}
targetBufferTime = bufferInfo.end;
let frag = this.getNextFragment(targetBufferTime, levelDetails);
// Avoid loop loading by using nextLoadPosition set for backtracking
if (
frag &&
this.fragmentTracker.getState(frag) === FragmentState.OK &&
this.nextLoadPosition > targetBufferTime
) {
frag = this.getNextFragment(this.nextLoadPosition, levelDetails);
}
if (!frag) {
return;
}
// }

targetBufferTime = bufferInfo.end;
frag = this.getNextFragment(targetBufferTime, levelDetails);
// Avoid loop loading by using nextLoadPosition set for backtracking
if (
frag &&
this.fragmentTracker.getState(frag) === FragmentState.OK &&
this.nextLoadPosition > targetBufferTime
) {
frag = this.getNextFragment(this.nextLoadPosition, levelDetails);
}
if (!frag) {
return;
}
if (frag.initSegment && !frag.initSegment.data && !this.bitrateTest) {
frag = frag.initSegment;
}

// We want to load the key if we're dealing with an identity key, because we will decrypt
Expand Down Expand Up @@ -671,7 +675,8 @@ export default class StreamController

// time Offset is accurate if level PTS is known, or if playlist is not sliding (not live)
const accurateTimeOffset = details.PTSKnown || !details.live;
const initSegmentData = details.initSegment?.data;
// const initSegmentData = details.initSegment?.data;
const initSegmentData = frag.initSegment?.data;
const audioCodec = this._getAudioCodec(currentLevel);

// transmux the MPEG-TS data to ISO-BMFF segments
Expand Down
2 changes: 2 additions & 0 deletions src/loader/fragment.ts
Expand Up @@ -139,6 +139,8 @@ export class Fragment extends BaseSegment {
public bitrateTest: boolean = false;
// #EXTINF segment title
public title: string | null = null;
// The Media Initialization Section for this segment
public initSegment: Fragment | null = null;

constructor(type: PlaylistLevelType, baseurl: string) {
super(baseurl);
Expand Down
4 changes: 2 additions & 2 deletions src/loader/level-details.ts
Expand Up @@ -13,15 +13,15 @@ export class LevelDetails {
public fragments: Fragment[];
public fragmentHint?: Fragment;
public partList: Part[] | null = null;
public initSegment: Fragment | null = null;
// public initSegment: Fragment | null = null;
public live: boolean = true;
public ageHeader: number = 0;
public advancedDateTime?: number;
public updated: boolean = true;
public advanced: boolean = true;
public availabilityDelay?: number; // Manifest reload synchronization
public misses: number = 0;
public needSidxRanges: boolean = false;
// public needSidxRanges: boolean = false;
public startCC: number = 0;
public startSN: number = 0;
public startTimeOffset: number | null = null;
Expand Down

0 comments on commit a09a61f

Please sign in to comment.