Skip to content

Commit

Permalink
Merge pull request #1216 from canalplus/misc/declareInitSegment
Browse files Browse the repository at this point in the history
[Proposal] Add `declareInitSegment` method to the SegmentBuffer abstraction
  • Loading branch information
peaBerberian committed Sep 22, 2023
2 parents 2b928bb + 81ac894 commit 38e85cd
Show file tree
Hide file tree
Showing 14 changed files with 227 additions and 114 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,8 @@ import {
import config from "../../../../config";
import log from "../../../../log";
import { getLoggableSegmentId } from "../../../../manifest";
import areArraysOfNumbersEqual from "../../../../utils/are_arrays_of_numbers_equal";
import assertUnreachable from "../../../../utils/assert_unreachable";
import { toUint8Array } from "../../../../utils/byte_parsing";
import createCancellablePromise from "../../../../utils/create_cancellable_promise";
import hashBuffer from "../../../../utils/hash_buffer";
import noop from "../../../../utils/noop";
import objectAssign from "../../../../utils/object_assign";
import TaskCanceller, {
Expand Down Expand Up @@ -139,20 +136,26 @@ export default class AudioVideoSegmentBuffer extends SegmentBuffer {
private _pendingTask : IAVSBPendingTask | null;

/**
* Keep track of the of the latest init segment pushed in the linked
* SourceBuffer.
* Keep track of the unique identifier of the of the latest init segment
* pushed to the linked SourceBuffer.
*
* This allows to be sure the right initialization segment is pushed before
* any chunk is.
* Such identifiers are first declared through the `declareInitSegment`
* method and the corresponding initialization segment is then pushed through
* the `pushChunk` method.
*
* Keeping track of this allows to be sure the right initialization segment is
* pushed before any chunk is.
*
* `null` if no initialization segment have been pushed to the
* `AudioVideoSegmentBuffer` yet.
*/
private _lastInitSegment : { /** The init segment itself. */
data : Uint8Array;
/** Hash of the initSegment for fast comparison */
hash : number; } |
null;
private _lastInitSegmentUniqueId : string | null;

/**
* Link unique identifiers for initialization segments (as communicated by
* `declareInitSegment`) to the corresponding initialization data.
*/
private _initSegmentsMap : Map<string, BufferSource>;

/**
* @constructor
Expand All @@ -175,8 +178,9 @@ export default class AudioVideoSegmentBuffer extends SegmentBuffer {
this._sourceBuffer = sourceBuffer;
this._queue = [];
this._pendingTask = null;
this._lastInitSegment = null;
this._lastInitSegmentUniqueId = null;
this.codec = codec;
this._initSegmentsMap = new Map();

const onError = this._onPendingTaskError.bind(this);
const reCheck = this._flush.bind(this);
Expand All @@ -199,6 +203,20 @@ export default class AudioVideoSegmentBuffer extends SegmentBuffer {
});
}

public declareInitSegment(
uniqueId : string,
initSegmentData : unknown
) : void {
assertDataIsBufferSource(initSegmentData);
this._initSegmentsMap.set(uniqueId, initSegmentData);
}

public freeInitSegment(
uniqueId : string
) : void {
this._initSegmentsMap.delete(uniqueId);
}

/**
* Push a chunk of the media segment given to the attached SourceBuffer, in a
* FIFO queue.
Expand Down Expand Up @@ -230,12 +248,12 @@ export default class AudioVideoSegmentBuffer extends SegmentBuffer {
infos : IPushChunkInfos<unknown>,
cancellationSignal : CancellationSignal
) : Promise<void> {
assertPushedDataIsBufferSource(infos);
assertDataIsBufferSource(infos.data.chunk);
log.debug("AVSB: receiving order to push data to the SourceBuffer",
this.bufferType,
getLoggableSegmentId(infos.inventoryInfos));
return this._addToQueue({ type: SegmentBufferOperation.Push,
value: infos },
value: infos as IPushChunkInfos<BufferSource> },
cancellationSignal);
}

Expand Down Expand Up @@ -352,7 +370,7 @@ export default class AudioVideoSegmentBuffer extends SegmentBuffer {
* @param {Event} err
*/
private _onPendingTaskError(err : unknown) : void {
this._lastInitSegment = null; // initialize init segment as a security
this._lastInitSegmentUniqueId = null; // initialize init segment as a security
if (this._pendingTask !== null) {
const error = err instanceof Error ?
err :
Expand Down Expand Up @@ -449,7 +467,7 @@ export default class AudioVideoSegmentBuffer extends SegmentBuffer {
const error = e instanceof Error ?
e :
new Error("An unknown error occured when preparing a push operation");
this._lastInitSegment = null; // initialize init segment as a security
this._lastInitSegmentUniqueId = null; // initialize init segment as a security
nextItem.reject(error);
return;
}
Expand Down Expand Up @@ -565,15 +583,17 @@ export default class AudioVideoSegmentBuffer extends SegmentBuffer {
this._sourceBuffer.appendWindowEnd = appendWindow[1];
}

if (data.initSegment !== null &&
(hasUpdatedSourceBufferType || !this._isLastInitSegment(data.initSegment)))
if (data.initSegmentUniqueId !== null &&
(hasUpdatedSourceBufferType ||
!this._isLastInitSegment(data.initSegmentUniqueId)))
{
// Push initialization segment before the media segment
const segmentData = data.initSegment;
const segmentData = this._initSegmentsMap.get(data.initSegmentUniqueId);
if (segmentData === undefined) {
throw new Error("Invalid initialization segment uniqueId");
}
dataToPush.push(segmentData);
const initU8 = toUint8Array(segmentData);
this._lastInitSegment = { data: initU8,
hash: hashBuffer(initU8) };
this._lastInitSegmentUniqueId = data.initSegmentUniqueId;
}

if (data.chunk !== null) {
Expand All @@ -584,56 +604,37 @@ export default class AudioVideoSegmentBuffer extends SegmentBuffer {
}

/**
* Return `true` if the given `segmentData` is the same segment than the last
* Return `true` if the given `uniqueId` is the identifier of the last
* initialization segment pushed to the `AudioVideoSegmentBuffer`.
* @param {BufferSource} segmentData
* @param {string} uniqueId
* @returns {boolean}
*/
private _isLastInitSegment(segmentData : BufferSource) : boolean {
if (this._lastInitSegment === null) {
private _isLastInitSegment(uniqueId : string) : boolean {
if (this._lastInitSegmentUniqueId === null) {
return false;
}
if (this._lastInitSegment.data === segmentData) {
return true;
}
const oldInit = this._lastInitSegment.data;
if (oldInit.byteLength === segmentData.byteLength) {
const newInitU8 = toUint8Array(segmentData);
if (hashBuffer(newInitU8) === this._lastInitSegment.hash &&
areArraysOfNumbersEqual(oldInit, newInitU8))
{
return true;
}
}
return false;
return this._lastInitSegmentUniqueId === uniqueId;
}
}

/**
* Throw if the given input is not in the expected format.
* Allows to enforce runtime type-checking as compile-time type-checking here is
* difficult to enforce.
* @param {Object} pushedData
* @param {Object} data
*/
function assertPushedDataIsBufferSource(
pushedData : IPushChunkInfos<unknown>
) : asserts pushedData is IPushChunkInfos<BufferSource> {
function assertDataIsBufferSource(
data : unknown
) : asserts data is BufferSource {
if (__ENVIRONMENT__.CURRENT_ENV as number === __ENVIRONMENT__.PRODUCTION as number) {
return;
}
const { chunk, initSegment } = pushedData.data;
if (
typeof chunk !== "object" ||
typeof initSegment !== "object" ||
(
chunk !== null &&
!(chunk instanceof ArrayBuffer) &&
!((chunk as ArrayBufferView).buffer instanceof ArrayBuffer)
) ||
typeof data !== "object" ||
(
initSegment !== null &&
!(initSegment instanceof ArrayBuffer) &&
!((initSegment as ArrayBufferView).buffer instanceof ArrayBuffer)
data !== null &&
!(data instanceof ArrayBuffer) &&
!((data as ArrayBufferView).buffer instanceof ArrayBuffer)
)
) {
throw new Error("Invalid data given to the AudioVideoSegmentBuffer");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,22 @@ export default class ImageSegmentBuffer extends SegmentBuffer {
this._buffered = new ManualTimeRanges();
}

/**
* @param {string} uniqueId
*/
public declareInitSegment(uniqueId : string): void {
log.warn("ISB: Declaring initialization segment for image SegmentBuffer",
uniqueId);
}

/**
* @param {string} uniqueId
*/
public freeInitSegment(uniqueId : string): void {
log.warn("ISB: Freeing initialization segment for image SegmentBuffer",
uniqueId);
}

/**
* @param {Object} data
* @returns {Promise}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,22 @@ export default class HTMLTextSegmentBuffer extends SegmentBuffer {
this.autoRefreshSubtitles(this._canceller.signal);
}

/**
* @param {string} uniqueId
*/
public declareInitSegment(uniqueId : string): void {
log.warn("ISB: Declaring initialization segment for image SegmentBuffer",
uniqueId);
}

/**
* @param {string} uniqueId
*/
public freeInitSegment(uniqueId : string): void {
log.warn("ISB: Freeing initialization segment for image SegmentBuffer",
uniqueId);
}

/**
* Push text segment to the HTMLTextSegmentBuffer.
* @param {Object} infos
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,22 @@ export default class NativeTextSegmentBuffer extends SegmentBuffer {
this._trackElement = trackElement;
}

/**
* @param {string} uniqueId
*/
public declareInitSegment(uniqueId : string): void {
log.warn("ISB: Declaring initialization segment for image SegmentBuffer",
uniqueId);
}

/**
* @param {string} uniqueId
*/
public freeInitSegment(uniqueId : string): void {
log.warn("ISB: Freeing initialization segment for image SegmentBuffer",
uniqueId);
}

/**
* @param {Object} infos
* @returns {Promise}
Expand Down
20 changes: 16 additions & 4 deletions src/core/segment_buffers/implementations/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ export abstract class SegmentBuffer {
this._segmentInventory = new SegmentInventory();
}

public abstract declareInitSegment(
uniqueId : string,
initSegmentData : unknown
) : void;

public abstract freeInitSegment(uniqueId : string) : void;

/**
* Push a chunk of the media segment given to the attached buffer, in a
* FIFO queue.
Expand All @@ -96,7 +103,8 @@ export abstract class SegmentBuffer {
* pushed.
*
* Depending on the type of data appended, the pushed chunk might rely on an
* initialization segment, given through the `data.initSegment` property.
* initialization segment, which had to be previously declared through the
* `declareInitSegment` method.
*
* Such initialization segment will be first pushed to the buffer if the
* last pushed segment was associated to another initialization segment.
Expand All @@ -106,7 +114,7 @@ export abstract class SegmentBuffer {
* reference).
*
* If you don't need any initialization segment to push the wanted chunk, you
* can just set `data.initSegment` to `null`.
* can just set the corresponding property to `null`.
*
* You can also only push an initialization segment by setting the
* `data.chunk` argument to null.
Expand Down Expand Up @@ -230,12 +238,16 @@ export type IBufferType = "audio" |
*/
export interface IPushedChunkData<T> {
/**
* The whole initialization segment's data related to the chunk you want to
* The `uniqueId` of the initialization segment linked to the data you want to
* push.
*
* That identifier should previously have been declared through the
* `declareInitSegment` method and not freed.
*
* To set to `null` either if no initialization data is needed, or if you are
* confident that the last pushed one is compatible.
*/
initSegment: T | null;
initSegmentUniqueId : string | null;
/**
* Chunk you want to push.
* This can be the whole decodable segment's data or just a decodable sub-part
Expand Down
Loading

0 comments on commit 38e85cd

Please sign in to comment.