UNPKG

rx-player

Version:
280 lines (267 loc) 8.61 kB
/** * Copyright 2015 CANAL+ Group * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { MediaError } from "../../errors"; import log from "../../log"; import type { IPeriodMetadata } from "../../manifest"; import type { IPeriod } from "../../public_types"; import arrayFindIndex from "../../utils/array_find_index"; import objectAssign from "../../utils/object_assign"; import type Period from "./period"; import { MANIFEST_UPDATE_TYPE } from "./types"; import type { IUpdatedPeriodResult } from "./update_period_in_place"; import updatePeriodInPlace from "./update_period_in_place"; /** * Update old periods by adding new periods and removing * not available ones. * @param {Array.<Object>} oldPeriods * @param {Array.<Object>} newPeriods * @returns {Object} */ export function replacePeriods( oldPeriods: Period[], newPeriods: Period[], ): IPeriodsUpdateResult { const res: IPeriodsUpdateResult = { updatedPeriods: [], addedPeriods: [], removedPeriods: [], }; let firstUnhandledPeriodIdx = 0; for (let i = 0; i < newPeriods.length; i++) { const newPeriod = newPeriods[i]; let j = firstUnhandledPeriodIdx; let oldPeriod = oldPeriods[j]; while (oldPeriod !== undefined && oldPeriod.id !== newPeriod.id) { j++; oldPeriod = oldPeriods[j]; } if (oldPeriod !== undefined) { const result = updatePeriodInPlace(oldPeriod, newPeriod, MANIFEST_UPDATE_TYPE.Full); res.updatedPeriods.push({ period: { id: oldPeriod.id, start: oldPeriod.start, end: oldPeriod.end, duration: oldPeriod.duration, streamEvents: oldPeriod.streamEvents, }, result, }); const periodsToInclude = newPeriods.slice(firstUnhandledPeriodIdx, i); const nbrOfPeriodsToRemove = j - firstUnhandledPeriodIdx; const removed = oldPeriods.splice( firstUnhandledPeriodIdx, nbrOfPeriodsToRemove, ...periodsToInclude, ); res.removedPeriods.push( ...removed.map((p) => ({ id: p.id, start: p.start, end: p.end, })), ); res.addedPeriods.push(...periodsToInclude.map((p) => p.getMetadataSnapshot())); firstUnhandledPeriodIdx = i + 1; } } if (firstUnhandledPeriodIdx > oldPeriods.length) { log.error("manifest", "error when updating Periods"); return res; } if (firstUnhandledPeriodIdx < oldPeriods.length) { const removed = oldPeriods.splice( firstUnhandledPeriodIdx, oldPeriods.length - firstUnhandledPeriodIdx, ); res.removedPeriods.push( ...removed.map((p) => ({ id: p.id, start: p.start, end: p.end, })), ); } const remainingNewPeriods = newPeriods.slice( firstUnhandledPeriodIdx, newPeriods.length, ); if (remainingNewPeriods.length > 0) { oldPeriods.push(...remainingNewPeriods); res.addedPeriods.push(...remainingNewPeriods.map((p) => p.getMetadataSnapshot())); } return res; } /** * Update old periods by adding new periods and removing * not available ones. * @param {Array.<Object>} oldPeriods * @param {Array.<Object>} newPeriods * @returns {Object} */ export function updatePeriods( oldPeriods: Period[], newPeriods: Period[], ): IPeriodsUpdateResult { const res: IPeriodsUpdateResult = { updatedPeriods: [], addedPeriods: [], removedPeriods: [], }; if (oldPeriods.length === 0) { oldPeriods.splice(0, 0, ...newPeriods); res.addedPeriods.push(...newPeriods.map((p) => p.getMetadataSnapshot())); return res; } if (newPeriods.length === 0) { return res; } const oldLastPeriod = oldPeriods[oldPeriods.length - 1]; if (oldLastPeriod.start < newPeriods[0].start) { if (oldLastPeriod.end !== newPeriods[0].start) { throw new MediaError( "MANIFEST_UPDATE_ERROR", "Cannot perform partial update: not enough data", ); } oldPeriods.push(...newPeriods); res.addedPeriods.push(...newPeriods.map((p) => p.getMetadataSnapshot())); return res; } /** Index, in `oldPeriods` of the first element of `newPeriods` */ const indexOfNewFirstPeriod = arrayFindIndex( oldPeriods, ({ id }) => id === newPeriods[0].id, ); if (indexOfNewFirstPeriod < 0) { throw new MediaError( "MANIFEST_UPDATE_ERROR", "Cannot perform partial update: incoherent data", ); } // The first updated Period can only be a partial part const updateRes = updatePeriodInPlace( oldPeriods[indexOfNewFirstPeriod], newPeriods[0], MANIFEST_UPDATE_TYPE.Partial, ); res.updatedPeriods.push({ period: objectAssign(oldPeriods[indexOfNewFirstPeriod].getMetadataSnapshot(), { adaptations: undefined, }), result: updateRes, }); // Search each consecutive elements of `newPeriods` - after the initial one already // processed - in `oldPeriods`, removing and adding unfound Periods in the process let prevIndexOfNewPeriod = indexOfNewFirstPeriod + 1; for (let i = 1; i < newPeriods.length; i++) { const newPeriod = newPeriods[i]; let indexOfNewPeriod = -1; for (let j = prevIndexOfNewPeriod; j < oldPeriods.length; j++) { if (newPeriod.id === oldPeriods[j].id) { indexOfNewPeriod = j; break; // end the loop } } if (indexOfNewPeriod < 0) { // Next element of `newPeriods` not found: insert it let toRemoveUntil = -1; for (let j = prevIndexOfNewPeriod; j < oldPeriods.length; j++) { if (newPeriod.start < oldPeriods[j].start) { toRemoveUntil = j; break; // end the loop } } const nbElementsToRemove = toRemoveUntil - prevIndexOfNewPeriod; const removed = oldPeriods.splice( prevIndexOfNewPeriod, nbElementsToRemove, newPeriod, ); res.addedPeriods.push(newPeriod.getMetadataSnapshot()); res.removedPeriods.push( ...removed.map((p) => ({ id: p.id, start: p.start, end: p.end, })), ); } else { if (indexOfNewPeriod > prevIndexOfNewPeriod) { // Some old periods were not found: remove log.warn("manifest", "old Periods not found in new when updating, removing"); const removed = oldPeriods.splice( prevIndexOfNewPeriod, indexOfNewPeriod - prevIndexOfNewPeriod, ); res.removedPeriods.push( ...removed.map((p) => ({ id: p.id, start: p.start, end: p.end, })), ); indexOfNewPeriod = prevIndexOfNewPeriod; } // Later Periods can be fully replaced const result = updatePeriodInPlace( oldPeriods[indexOfNewPeriod], newPeriod, MANIFEST_UPDATE_TYPE.Full, ); res.updatedPeriods.push({ period: objectAssign(oldPeriods[indexOfNewPeriod].getMetadataSnapshot(), { adaptations: undefined, }), result, }); } prevIndexOfNewPeriod++; } if (prevIndexOfNewPeriod < oldPeriods.length) { log.warn("manifest", "Ending Periods not found in new when updating, removing"); const removed = oldPeriods.splice( prevIndexOfNewPeriod, oldPeriods.length - prevIndexOfNewPeriod, ); res.removedPeriods.push( ...removed.map((p) => ({ id: p.id, start: p.start, end: p.end, })), ); } return res; } export type IUpdatePeriodInformation = Pick< IPeriodMetadata, "id" | "start" | "end" | "duration" | "streamEvents" >; /** Object describing a Manifest update at the Periods level. */ export interface IPeriodsUpdateResult { /** Information on Periods that have been updated. */ updatedPeriods: Array<{ /** The concerned Period's information. */ period: IUpdatePeriodInformation; /** The updates performed. */ result: IUpdatedPeriodResult; }>; /** Periods that have been added. */ addedPeriods: IPeriodMetadata[]; /** Periods that have been removed. */ removedPeriods: IPeriod[]; }