UNPKG

rx-player

Version:
515 lines (478 loc) 15.8 kB
import { describe, beforeEach, it, expect, vi } from "vitest"; import type { IParsedAdaptation, IParsedPeriod, IParsedRepresentation, } from "../../../parsers/manifest"; import getMonotonicTimeStamp from "../../../utils/monotonic_timestamp"; import CodecSupportCache from "../codec_support_cache"; import type IManifest from "../manifest"; import type IPeriod from "../period"; function generateParsedPeriod( id: string, start: number, end: number | undefined, ): IParsedPeriod { const adaptations = { audio: [generateParsedAudioAdaptation(id)] }; return { id, start, end, adaptations, duration: end === undefined ? undefined : end - start, streamEvents: [], thumbnailTracks: [], }; } function generateParsedAudioAdaptation(id: string): IParsedAdaptation { return { id, type: "audio", representations: [generateParsedRepresentation(id)], }; } function generateParsedRepresentation(id: string): IParsedRepresentation { return { id, bitrate: 100 } as IParsedRepresentation; } describe("Manifest - Manifest", () => { const fakeLogger = { warn: vi.fn(() => undefined), info: vi.fn(() => undefined), }; const fakeGenerateNewId = vi.fn(() => "fakeId"); const fakeIdGenerator = vi.fn(() => fakeGenerateNewId); beforeEach(() => { vi.resetModules(); fakeLogger.warn.mockClear(); fakeLogger.info.mockClear(); vi.doMock("../../../log", () => ({ default: fakeLogger, })); fakeGenerateNewId.mockClear(); fakeIdGenerator.mockClear(); vi.doMock("../../../utils/id_generator", () => ({ default: fakeIdGenerator, })); }); it("should create a normalized Manifest structure", async () => { const simpleFakeManifest = { id: "man", transportType: "dash", isLastPeriodKnown: true, isDynamic: false, isLive: false, duration: 5, timeBounds: { minimumSafePosition: 0, timeshiftDepth: null, maximumTimeData: { livePosition: 10, isLinear: false, maximumSafePosition: 10, time: 10, }, }, periods: [], }; const Manifest = (await vi.importActual("../manifest")).default as typeof IManifest; const manifest = new Manifest(simpleFakeManifest, {}); expect(manifest.adaptations).toEqual({}); expect(manifest.availabilityStartTime).toEqual(undefined); expect(manifest.id).toEqual("fakeId"); expect(manifest.isDynamic).toEqual(false); expect(manifest.isLive).toEqual(false); expect(manifest.lifetime).toEqual(undefined); expect(manifest.getMaximumSafePosition()).toEqual(10); expect(manifest.getMinimumSafePosition()).toEqual(0); expect(manifest.periods).toEqual([]); expect(manifest.suggestedPresentationDelay).toEqual(undefined); expect(manifest.uris).toEqual([]); expect(fakeIdGenerator).toHaveBeenCalled(); expect(fakeGenerateNewId).toHaveBeenCalledTimes(1); expect(fakeLogger.info).not.toHaveBeenCalled(); expect(fakeLogger.warn).not.toHaveBeenCalled(); }); it("should create a Period for each manifest.periods given", async () => { const period1 = generateParsedPeriod("0", 4, undefined); const period2 = generateParsedPeriod("1", 12, undefined); const simpleFakeManifest = { id: "man", transportType: "dash", isLastPeriodKnown: true, isDynamic: false, isLive: false, duration: 5, timeBounds: { minimumSafePosition: 0, timeshiftDepth: null, maximumTimeData: { livePosition: 10, isLinear: false, maximumSafePosition: 10, time: 10, }, }, periods: [period1, period2], }; const fakePeriod = vi.fn((period: IPeriod) => { return { id: `foo${period.id}`, adaptations: period.adaptations }; }); vi.doMock("../period", () => ({ default: fakePeriod, })); const Manifest = (await vi.importActual("../manifest")).default as typeof IManifest; const manifest = new Manifest(simpleFakeManifest, {}); expect(fakePeriod).toHaveBeenCalledTimes(2); expect(fakePeriod).toHaveBeenCalledWith( period1, new CodecSupportCache([]), undefined, ); expect(fakePeriod).toHaveBeenCalledWith( period2, new CodecSupportCache([]), undefined, ); expect(manifest.periods).toEqual([ { id: "foo0", adaptations: period1.adaptations }, { id: "foo1", adaptations: period2.adaptations }, ]); expect(manifest.adaptations).toEqual(period1.adaptations); expect(fakeIdGenerator).toHaveBeenCalled(); expect(fakeGenerateNewId).toHaveBeenCalledTimes(1); expect(fakeLogger.info).not.toHaveBeenCalled(); expect(fakeLogger.warn).not.toHaveBeenCalled(); }); it("should pass a `representationFilter` to the Period if given", async () => { const period1 = generateParsedPeriod("0", 4, undefined); const period2 = generateParsedPeriod("1", 12, undefined); const simpleFakeManifest = { id: "man", isDynamic: false, transportType: "dash", isLastPeriodKnown: true, isLive: false, duration: 5, timeBounds: { minimumSafePosition: 0, timeshiftDepth: null, maximumTimeData: { livePosition: 10, isLinear: false, maximumSafePosition: 10, time: 10, }, }, periods: [period1, period2], }; const representationFilter = function () { return false; }; const fakePeriod = vi.fn((period: IParsedPeriod): IPeriod => { return { id: `foo${period.id}` } as IPeriod; }); vi.doMock("../period", () => ({ default: fakePeriod, })); const Manifest = (await vi.importActual("../manifest")).default as typeof IManifest; const manifest = new Manifest(simpleFakeManifest, { representationFilter, }); expect(manifest).not.toBe(null); expect(fakePeriod).toHaveBeenCalledTimes(2); expect(fakePeriod).toHaveBeenCalledWith( period1, new CodecSupportCache([]), representationFilter, ); expect(fakePeriod).toHaveBeenCalledWith( period2, new CodecSupportCache([]), representationFilter, ); expect(fakeIdGenerator).toHaveBeenCalled(); expect(fakeGenerateNewId).toHaveBeenCalledTimes(1); expect(fakeLogger.info).not.toHaveBeenCalled(); expect(fakeLogger.warn).not.toHaveBeenCalled(); }); it("should expose the adaptations of the first period if set", async () => { const adapP1 = {}; const adapP2 = {}; const period1 = { id: "0", start: 4, adaptations: adapP1, thumbnailTracks: [] }; const period2 = { id: "1", start: 12, adaptations: adapP2, thumbnailTracks: [] }; const simpleFakeManifest = { id: "man", isDynamic: false, transportType: "dash", isLastPeriodKnown: true, isLive: false, duration: 5, timeBounds: { minimumSafePosition: 0, timeshiftDepth: null, maximumTimeData: { livePosition: 10, isLinear: false, maximumSafePosition: 10, time: 10, }, }, periods: [period1, period2], }; const fakePeriod = vi.fn((period: IParsedPeriod): IPeriod => { return { ...period, id: `foo${period.id}` } as unknown as IPeriod; }); vi.doMock("../period", () => ({ default: fakePeriod, })); const Manifest = (await vi.importActual("../manifest")).default as typeof IManifest; const manifest = new Manifest(simpleFakeManifest, {}); expect(fakePeriod).toHaveBeenCalledTimes(2); expect(fakePeriod).toHaveBeenCalledWith( period1, new CodecSupportCache([]), undefined, ); expect(fakePeriod).toHaveBeenCalledWith( period2, new CodecSupportCache([]), undefined, ); expect(manifest.periods).toEqual([ { id: "foo0", start: 4, adaptations: adapP1, thumbnailTracks: [] }, { id: "foo1", start: 12, adaptations: adapP2, thumbnailTracks: [] }, ]); expect(manifest.adaptations).toBe(adapP1); expect(fakeIdGenerator).toHaveBeenCalled(); expect(fakeGenerateNewId).toHaveBeenCalledTimes(1); expect(fakeLogger.info).not.toHaveBeenCalled(); expect(fakeLogger.warn).not.toHaveBeenCalled(); }); it("should correctly parse every manifest information given", async () => { const oldPeriod1 = generateParsedPeriod("0", 4, undefined); const oldPeriod2 = generateParsedPeriod("1", 12, undefined); const time = getMonotonicTimeStamp(); const oldManifestArgs = { availabilityStartTime: 5, duration: 12, id: "man", transportType: "dash", isLastPeriodKnown: true, isDynamic: false, isLive: false, lifetime: 13, periods: [oldPeriod1, oldPeriod2], timeBounds: { minimumSafePosition: 5, timeshiftDepth: null, maximumTimeData: { livePosition: 10, isLinear: false, maximumSafePosition: 10, time, }, }, suggestedPresentationDelay: 99, uris: ["url1", "url2"], }; const fakePeriod = vi.fn((period: IParsedPeriod): IPeriod => { return { ...period, id: `foo${period.id}` } as unknown as IPeriod; }); vi.doMock("../period", () => ({ default: fakePeriod, })); const Manifest = (await vi.importActual("../manifest")).default as typeof IManifest; const manifest = new Manifest(oldManifestArgs, {}); expect(manifest.adaptations).toEqual(oldPeriod1.adaptations); expect(manifest.availabilityStartTime).toEqual(5); expect(manifest.id).toEqual("fakeId"); expect(manifest.isDynamic).toEqual(false); expect(manifest.isLive).toEqual(false); expect(manifest.lifetime).toEqual(13); expect(manifest.getMaximumSafePosition()).toEqual(10); expect(manifest.getMinimumSafePosition()).toEqual(5); expect(manifest.periods).toEqual([ { id: "foo0", adaptations: oldPeriod1.adaptations, start: 4, streamEvents: [], thumbnailTracks: [], }, { id: "foo1", adaptations: oldPeriod2.adaptations, start: 12, streamEvents: [], thumbnailTracks: [], }, ]); expect(manifest.suggestedPresentationDelay).toEqual(99); expect(manifest.uris).toEqual(["url1", "url2"]); expect(fakeIdGenerator).toHaveBeenCalled(); expect(fakeGenerateNewId).toHaveBeenCalledTimes(1); expect(fakeLogger.info).not.toHaveBeenCalled(); expect(fakeLogger.warn).not.toHaveBeenCalled(); }); it("should return all URLs given with `getContentUrls`", async () => { const fakePeriod = vi.fn((period: IParsedPeriod): IPeriod => { return { ...period, id: `foo${period.id}` } as unknown as IPeriod; }); vi.doMock("../period", () => ({ default: fakePeriod, })); const Manifest = (await vi.importActual("../manifest")).default as typeof IManifest; const oldPeriod1 = generateParsedPeriod("0", 4, undefined); const oldPeriod2 = generateParsedPeriod("1", 12, undefined); const oldManifestArgs1 = { availabilityStartTime: 5, duration: 12, id: "man", isDynamic: false, isLive: false, transportType: "dash", isLastPeriodKnown: true, lifetime: 13, timeBounds: { minimumSafePosition: 0, timeshiftDepth: null, maximumTimeData: { livePosition: 10, isLinear: false, maximumSafePosition: 10, time: 10, }, }, periods: [oldPeriod1, oldPeriod2], suggestedPresentationDelay: 99, uris: ["url1", "url2"], }; const manifest1 = new Manifest(oldManifestArgs1, {}); expect(manifest1.getUrls()).toEqual(["url1", "url2"]); const oldManifestArgs2 = { availabilityStartTime: 5, duration: 12, id: "man", isDynamic: false, isLive: false, transportType: "dash", isLastPeriodKnown: true, lifetime: 13, periods: [ { id: "0", start: 4, adaptations: oldPeriod1.adaptations, thumbnailTracks: [] }, { id: "1", start: 12, adaptations: oldPeriod2.adaptations, thumbnailTracks: [] }, ], suggestedPresentationDelay: 99, timeBounds: { minimumSafePosition: 0, timeshiftDepth: null, maximumTimeData: { livePosition: 10, isLinear: false, maximumSafePosition: 10, time: 10, }, }, uris: [], }; const manifest2 = new Manifest(oldManifestArgs2, {}); expect(manifest2.getUrls()).toEqual([]); }); it("should replace with a new Manifest when calling `replace`", async () => { const fakePeriod = vi.fn((period: IParsedPeriod): IPeriod => { return { ...period, id: `foo${period.id}` } as unknown as IPeriod; }); const fakeReplacePeriodsRes = { updatedPeriods: [], addedPeriods: [], removedPeriods: [], }; const fakeReplacePeriods = vi.fn(() => fakeReplacePeriodsRes); vi.doMock("../period", () => ({ default: fakePeriod, })); vi.doMock("../update_periods", () => ({ replacePeriods: fakeReplacePeriods, })); const oldPeriod1 = generateParsedPeriod("0", 4, undefined); const oldPeriod2 = generateParsedPeriod("1", 12, undefined); const oldManifestArgs = { availabilityStartTime: 5, duration: 12, id: "man", transportType: "dash", isLastPeriodKnown: true, isDynamic: false, isLive: false, lifetime: 13, periods: [oldPeriod1, oldPeriod2], timeBounds: { minimumSafePosition: 7, timeshiftDepth: 10, maximumTimeData: { livePosition: 10, isLinear: true, maximumSafePosition: 30, time: 30000, }, }, suggestedPresentationDelay: 99, uris: ["url1", "url2"], }; const Manifest = (await vi.importActual("../manifest")).default as typeof IManifest; const manifest = new Manifest(oldManifestArgs, {}); const mockTrigger = vi .spyOn( manifest as IManifest & { trigger: (eventName: string, payload: unknown) => void; }, "trigger", ) .mockImplementation(vi.fn()); const newAdaptations = {}; const newPeriod1 = { id: "foo0", start: 4, adaptations: { audio: oldPeriod1.adaptations }, }; const newPeriod2 = { id: "foo1", start: 12, adaptations: { audio: oldPeriod2.adaptations }, }; const newManifest = { adaptations: newAdaptations, availabilityStartTime: 6, id: "man2", isDynamic: true, isLive: true, lifetime: 14, suggestedPresentationDelay: 100, timeShiftBufferDepth: 3, _timeBounds: { minimumSafePosition: 7, timeshiftDepth: 5, maximumTimeData: { isLinear: false, maximumSafePosition: 40, time: 30000, }, }, periods: [newPeriod1, newPeriod2], uris: ["url3", "url4"], } as unknown as IManifest; manifest.replace(newManifest); expect(fakeReplacePeriods).toHaveBeenCalledTimes(1); expect(fakeReplacePeriods).toHaveBeenCalledWith( manifest.periods, newManifest.periods, ); expect(mockTrigger).toHaveBeenCalledTimes(1); expect(mockTrigger).toHaveBeenCalledWith("manifestUpdate", fakeReplacePeriodsRes); expect(fakeIdGenerator).toHaveBeenCalled(); expect(fakeGenerateNewId).toHaveBeenCalledTimes(1); expect(fakeLogger.info).not.toHaveBeenCalled(); expect(fakeLogger.warn).not.toHaveBeenCalled(); mockTrigger.mockRestore(); }); });