UNPKG

rx-player

Version:
914 lines (848 loc) 26.8 kB
import { describe, beforeEach, it, expect, vi } from "vitest"; import type { IParsedAdaptation, IParsedPeriod } from "../../../parsers/manifest"; import type Adaptation from "../adaptation"; import CodecSupportCache from "../codec_support_cache"; import type IPeriod from "../period"; describe("Manifest - Period", () => { beforeEach(() => { vi.resetModules(); }); it("should throw if no adaptation is given", async () => { const mockAdaptation = vi.fn((arg: IParsedAdaptation) => ({ ...arg, supportStatus: { hasSupportedCodec: undefined, hasCodecWithUndefinedSupport: true, isDecipherable: undefined, }, })); vi.doMock("../adaptation", () => ({ default: mockAdaptation, SUPPORTED_ADAPTATIONS_TYPE: ["audio", "video", "text"], })); const Period = (await vi.importActual("../period")).default as typeof IPeriod; const args = { id: "12", adaptations: {}, start: 0, thumbnailTracks: [] }; let period: IPeriod | null = null; let errorReceived: unknown = null; try { const codecSupportCache = new CodecSupportCache([]); period = new Period(args, codecSupportCache); } catch (e) { errorReceived = e; } expect(period).toBe(null); expect(errorReceived).not.toBe(null); expect(errorReceived).toBeInstanceOf(Error); // Impossible check to shut-up TypeScript if (!(errorReceived instanceof Error)) { throw new Error("Impossible: already checked it was an Error instance"); } expect((errorReceived as { code?: string }).code).toBe("MANIFEST_PARSE_ERROR"); expect(errorReceived.message).toContain( "The manifest has no video nor audio tracks.", ); expect(mockAdaptation).not.toHaveBeenCalled(); }); it("should throw if no audio nor video adaptation is given", async () => { const mockAdaptation = vi.fn( (arg: IParsedAdaptation): Adaptation => ({ ...arg, supportStatus: { hasSupportedCodec: undefined, hasCodecWithUndefinedSupport: true, isDecipherable: undefined, }, }) as unknown as Adaptation, ); vi.doMock("../adaptation", () => ({ default: mockAdaptation, SUPPORTED_ADAPTATIONS_TYPE: ["audio", "video", "text", "foo"], })); const Period = (await vi.importActual("../period")).default as typeof IPeriod; const fooAda1 = { type: "foo", id: "54", representations: [{}], }; const fooAda2 = { type: "foo", id: "55", representations: [{}], }; const foo = [fooAda1, fooAda2]; const args: IParsedPeriod = { id: "12", adaptations: { foo }, start: 0, thumbnailTracks: [], } as unknown as IParsedPeriod; let period: IPeriod | null = null; let errorReceived: unknown = null; const codecSupportCache = new CodecSupportCache([]); try { period = new Period(args, codecSupportCache); } catch (e) { errorReceived = e; } expect(period).toBe(null); expect(errorReceived).not.toBe(null); expect(errorReceived).toBeInstanceOf(Error); // Impossible check to shut-up TypeScript if (!(errorReceived instanceof Error)) { throw new Error("Impossible: already checked it was an Error instance"); } expect((errorReceived as { code?: string }).code).toBe("MANIFEST_PARSE_ERROR"); expect((errorReceived as { type?: string }).type).toBe("MEDIA_ERROR"); expect(errorReceived.message).toContain( "The manifest has no video nor audio tracks.", ); expect(mockAdaptation).toHaveBeenCalledTimes(2); expect(mockAdaptation).toHaveBeenNthCalledWith(1, fooAda1, codecSupportCache, {}); expect(mockAdaptation).toHaveBeenNthCalledWith(2, fooAda2, codecSupportCache, {}); }); it("should throw if only empty audio and video adaptations is given", async () => { const mockAdaptation = vi.fn( (arg: IParsedAdaptation): Adaptation => ({ ...arg, supportStatus: { hasSupportedCodec: undefined, hasCodecWithUndefinedSupport: true, isDecipherable: undefined, }, }) as unknown as Adaptation, ); vi.doMock("../adaptation", () => ({ default: mockAdaptation, SUPPORTED_ADAPTATIONS_TYPE: ["audio", "video", "text", "foo"], })); const Period = (await vi.importActual("../period")).default as typeof IPeriod; const args = { id: "12", thumbnailTracks: [], adaptations: { video: [], audio: [] }, start: 0, }; let period: IPeriod | null = null; let errorReceived: unknown = null; try { const codecSupportCache = new CodecSupportCache([]); period = new Period(args, codecSupportCache); } catch (e) { errorReceived = e; } expect(period).toBe(null); expect(errorReceived).not.toBe(null); expect(errorReceived).toBeInstanceOf(Error); // Impossible check to shut-up TypeScript if (!(errorReceived instanceof Error)) { throw new Error("Impossible: already checked it was an Error instance"); } expect((errorReceived as { code?: string }).code).toBe("MANIFEST_PARSE_ERROR"); expect((errorReceived as { type?: string }).type).toBe("MEDIA_ERROR"); expect(errorReceived.message).toContain( "The manifest has no video nor audio tracks.", ); expect(mockAdaptation).toHaveBeenCalledTimes(0); }); it("should throw if there is no video nor audio representations in any adaptations.", async () => { const mockAdaptation = vi.fn( (arg: IParsedAdaptation): Adaptation => ({ ...arg, supportStatus: { hasSupportedCodec: undefined, hasCodecWithUndefinedSupport: true, isDecipherable: undefined, }, }) as unknown as Adaptation, ); vi.doMock("../adaptation", () => ({ default: mockAdaptation, SUPPORTED_ADAPTATIONS_TYPE: ["audio", "video", "text", "foo"], })); const Period = (await vi.importActual("../period")).default as typeof IPeriod; const videoAda1 = { type: "video", id: "54", representations: [], toVideoTrack() { return videoAda1; }, }; const videoAda2 = { type: "video", id: "56", representations: [], toVideoTrack() { return videoAda2; }, }; const videoAda3 = { type: "video", id: "57", representations: [], toVideoTrack() { return videoAda3; }, }; const video = [videoAda1, videoAda2, videoAda3]; const audioAda1 = { type: "audio", id: "58", representations: [], toAudioTrack() { return audioAda1; }, }; const audioAda2 = { type: "audio", id: "59", representations: [], toAudioTrack() { return audioAda2; }, }; const audio = [audioAda1, audioAda2]; const args: IParsedPeriod = { id: "12", adaptations: { video, audio }, thumbnailTracks: [], start: 0, } as unknown as IParsedPeriod; let period: IPeriod | null = null; let errorReceived: unknown = null; try { const codecSupportCache = new CodecSupportCache([]); period = new Period(args, codecSupportCache); } catch (e) { errorReceived = e; } expect(period).toBe(null); expect(errorReceived).not.toBe(null); expect(errorReceived).toBeInstanceOf(Error); // Impossible check to shut-up TypeScript if (!(errorReceived instanceof Error)) { throw new Error("Impossible: already checked it was an Error instance"); } expect((errorReceived as { code?: string }).code).toBe("MANIFEST_PARSE_ERROR"); expect((errorReceived as { type?: string }).type).toBe("MEDIA_ERROR"); expect(errorReceived.message).toContain( "The manifest has no video nor audio tracks.", ); }); it("should not throw if there is no video representation but it has an audio representation.", async () => { const mockAdaptation = vi.fn( (arg: IParsedAdaptation): Adaptation => ({ ...arg, supportStatus: { hasSupportedCodec: undefined, hasCodecWithUndefinedSupport: true, isDecipherable: undefined, }, }) as unknown as Adaptation, ); vi.doMock("../adaptation", () => ({ default: mockAdaptation, SUPPORTED_ADAPTATIONS_TYPE: ["audio", "video", "text"], })); const Period = (await vi.importActual("../period")).default as typeof IPeriod; const videoAdaptation = { type: "video", id: "54", representations: [], // empty representations array toVideoTrack() { return videoAdaptation; }, }; const audioAdaptation = { type: "audio", id: "58", representations: [{}], // non empty representations array toAudioTrack() { return audioAdaptation; }, }; const args: IParsedPeriod = { id: "12", adaptations: { video: [videoAdaptation], audio: [audioAdaptation] }, start: 0, thumbnailTracks: [], } as unknown as IParsedPeriod; let period: IPeriod | null = null; let errorReceived: unknown = null; try { const codecSupportCache = new CodecSupportCache([]); period = new Period(args, codecSupportCache); } catch (e) { errorReceived = e; } expect(period).not.toBe(null); expect(errorReceived).toBe(null); }); it("should report that all video adaptations are not supported", async () => { const mockAdaptation = vi.fn( (arg: IParsedAdaptation): Adaptation => ({ ...arg, supportStatus: { hasSupportedCodec: arg.type !== "video", hasCodecWithUndefinedSupport: true, isDecipherable: undefined, }, }) as unknown as Adaptation, ); vi.doMock("../adaptation", () => ({ default: mockAdaptation, SUPPORTED_ADAPTATIONS_TYPE: ["audio", "video", "text", "foo"], })); const Period = (await vi.importActual("../period")).default as typeof IPeriod; const videoAda1 = { type: "video", id: "54", representations: [{}], toVideoTrack() { return videoAda1; }, }; const videoAda2 = { type: "video", id: "55", representations: [{}], toVideoTrack() { return videoAda2; }, }; const videoAda3 = { type: "video", id: "56", representations: [{}], toVideoTrack() { return videoAda3; }, }; const video: IParsedAdaptation[] = [ videoAda1, videoAda2, videoAda3, ] as unknown as IParsedAdaptation[]; const audioAda1 = { type: "audio", id: "58", representations: [{}], toAudioTrack() { return audioAda1; }, }; const audioAda2 = { type: "audio", id: "59", representations: [{}], toAudioTrack() { return audioAda2; }, }; const audio = [audioAda1, audioAda2] as unknown as IParsedAdaptation[]; const args = { id: "12", adaptations: { video, audio }, start: 0, thumbnailTracks: [], }; const codecSupportCache = new CodecSupportCache([]); const period = new Period(args, codecSupportCache); expect(period.adaptations.video).not.toBe(undefined); expect(period.adaptations.video?.length).toBe(3); expect( period.adaptations.video?.every( (adaptation) => adaptation.supportStatus.hasSupportedCodec === false, ), ).toBe(true); }); it("should not set a parsing error if an empty unsupported adaptation is given", async () => { const mockAdaptation = vi.fn( (arg: IParsedAdaptation): Adaptation => ({ ...arg, supportStatus: { hasSupportedCodec: undefined, hasCodecWithUndefinedSupport: true, isDecipherable: undefined, }, }) as unknown as Adaptation, ); vi.doMock("../adaptation", () => ({ default: mockAdaptation, SUPPORTED_ADAPTATIONS_TYPE: ["audio", "video", "text", "foo"], })); const Period = (await vi.importActual("../period")).default as typeof IPeriod; const videoAda1 = { type: "video", id: "55", representations: [{}], toVideoTrack() { return videoAda1; }, }; const video = [videoAda1] as unknown as IParsedAdaptation[]; const bar = undefined; const args = { id: "12", thumbnailTracks: [], adaptations: { bar, video }, start: 0, }; const codecSupportCache = new CodecSupportCache([]); const period = new Period(args, codecSupportCache); expect(period.adaptations).toEqual({ video: video.map((v) => ({ ...v, supportStatus: { hasSupportedCodec: undefined, hasCodecWithUndefinedSupport: true, isDecipherable: undefined, }, })), }); expect(mockAdaptation).toHaveBeenCalledTimes(1); expect(mockAdaptation).toHaveBeenCalledWith(videoAda1, codecSupportCache, {}); expect(period.adaptations.audio).toBe(undefined); }); it("should give a representationFilter to the adaptation", async () => { const mockAdaptation = vi.fn( (arg: IParsedAdaptation): Adaptation => ({ ...arg, supportStatus: { hasSupportedCodec: undefined, hasCodecWithUndefinedSupport: true, isDecipherable: undefined, }, }) as unknown as Adaptation, ); const representationFilter = vi.fn(); vi.doMock("../adaptation", () => ({ default: mockAdaptation, SUPPORTED_ADAPTATIONS_TYPE: ["audio", "video", "text"], })); const Period = (await vi.importActual("../period")).default as typeof IPeriod; const videoAda1 = { type: "video", id: "54", representations: [{}], toVideoTrack() { return videoAda1; }, }; const videoAda2 = { type: "video", id: "55", representations: [{}], toVideoTrack() { return videoAda2; }, }; const video = [videoAda1, videoAda2] as unknown as IParsedAdaptation[]; const args = { id: "12", adaptations: { video }, start: 0, thumbnailTracks: [] }; const codecSupportCache = new CodecSupportCache([]); const period = new Period(args, codecSupportCache, representationFilter); expect(period.adaptations.video).toHaveLength(2); expect(mockAdaptation).toHaveBeenCalledTimes(2); expect(mockAdaptation).toHaveBeenNthCalledWith(1, videoAda1, codecSupportCache, { representationFilter, }); expect(mockAdaptation).toHaveBeenNthCalledWith(2, videoAda2, codecSupportCache, { representationFilter, }); expect(representationFilter).not.toHaveBeenCalled(); }); it("should not report if an Adaptation has no Representation", async () => { const mockAdaptation = vi.fn( (arg: IParsedAdaptation): Adaptation => ({ ...arg, supportStatus: { hasSupportedCodec: undefined, hasCodecWithUndefinedSupport: true, isDecipherable: undefined, }, }) as unknown as Adaptation, ); vi.doMock("../adaptation", () => ({ default: mockAdaptation, SUPPORTED_ADAPTATIONS_TYPE: ["audio", "video", "text"], })); const Period = (await vi.importActual("../period")).default as typeof IPeriod; const videoAda1 = { type: "video", id: "54", representations: [], toVideoTrack() { return videoAda1; }, }; const videoAda2 = { type: "video", id: "55", representations: [{}], toVideoTrack() { return videoAda2; }, }; const fooAda1 = { type: "audio" as const, id: "12", representations: [], }; const video = [videoAda1, videoAda2] as unknown as IParsedAdaptation[]; const audio = [fooAda1]; const args = { id: "12", thumbnailTracks: [], adaptations: { video, audio }, start: 0, }; const codecSupportCache = new CodecSupportCache([]); const period = new Period(args, codecSupportCache); // TO DO: is the test relevant? expect(period.adaptations.audio?.length).toBe(0); }); it("should set the given start", async () => { const mockAdaptation = vi.fn( (arg: IParsedAdaptation): Adaptation => ({ ...arg, supportStatus: { hasSupportedCodec: undefined, hasCodecWithUndefinedSupport: true, isDecipherable: undefined, }, }) as unknown as Adaptation, ); vi.doMock("../adaptation", () => ({ default: mockAdaptation, SUPPORTED_ADAPTATIONS_TYPE: ["audio", "video", "text"], })); const Period = (await vi.importActual("../period")).default as typeof IPeriod; const videoAda1 = { type: "video", id: "54", representations: [{}], toVideoTrack() { return videoAda1; }, }; const videoAda2 = { type: "video", id: "55", representations: [{}], toVideoTrack() { return videoAda2; }, }; const video = [videoAda1, videoAda2] as unknown as IParsedAdaptation[]; const args = { id: "12", adaptations: { video }, start: 72, thumbnailTracks: [] }; const codecSupportCache = new CodecSupportCache([]); const period = new Period(args, codecSupportCache); expect(period.start).toEqual(72); expect(period.duration).toEqual(undefined); expect(period.end).toEqual(undefined); }); it("should set a given duration", async () => { const mockAdaptation = vi.fn( (arg: IParsedAdaptation): Adaptation => ({ ...arg, supportStatus: { hasSupportedCodec: undefined, hasCodecWithUndefinedSupport: true, isDecipherable: undefined, }, }) as unknown as Adaptation, ); vi.doMock("../adaptation", () => ({ default: mockAdaptation, SUPPORTED_ADAPTATIONS_TYPE: ["audio", "video", "text"], })); const Period = (await vi.importActual("../period")).default as typeof IPeriod; const videoAda1 = { type: "video", id: "54", representations: [{}], toVideoTrack() { return videoAda1; }, }; const videoAda2 = { type: "video", id: "55", representations: [{}], toVideoTrack() { return videoAda2; }, }; const video = [videoAda1, videoAda2] as unknown as IParsedAdaptation[]; const args = { id: "12", adaptations: { video }, start: 0, duration: 12, thumbnailTracks: [], }; const codecSupportCache = new CodecSupportCache([]); const period = new Period(args, codecSupportCache); expect(period.start).toEqual(0); expect(period.duration).toEqual(12); expect(period.end).toEqual(12); }); it("should infer the end from the start and the duration", async () => { const mockAdaptation = vi.fn( (arg: IParsedAdaptation): Adaptation => ({ ...arg, supportStatus: { hasSupportedCodec: undefined, hasCodecWithUndefinedSupport: true, isDecipherable: undefined, }, }) as unknown as Adaptation, ); vi.doMock("../adaptation", () => ({ default: mockAdaptation, SUPPORTED_ADAPTATIONS_TYPE: ["audio", "video", "text"], })); const Period = (await vi.importActual("../period")).default as typeof IPeriod; const videoAda1 = { type: "video", id: "54", representations: [{}], toVideoTrack() { return videoAda1; }, }; const videoAda2 = { type: "video", id: "55", representations: [{}], toVideoTrack() { return videoAda2; }, }; const video = [videoAda1, videoAda2] as unknown as IParsedAdaptation[]; const args = { id: "12", adaptations: { video }, start: 50, duration: 12, thumbnailTracks: [], }; const codecSupportCache = new CodecSupportCache([]); const period = new Period(args, codecSupportCache); expect(period.start).toEqual(50); expect(period.duration).toEqual(12); expect(period.end).toEqual(62); }); it("should return every Adaptations combined with `getAdaptations`", async () => { const mockAdaptation = vi.fn( (arg: IParsedAdaptation): Adaptation => ({ ...arg, supportStatus: { hasSupportedCodec: undefined, hasCodecWithUndefinedSupport: true, isDecipherable: undefined, }, }) as unknown as Adaptation, ); vi.doMock("../adaptation", () => ({ default: mockAdaptation, SUPPORTED_ADAPTATIONS_TYPE: ["audio", "video", "text"], })); const Period = (await vi.importActual("../period")).default as typeof IPeriod; const videoAda1 = { type: "video", id: "54", representations: [{}], toVideoTrack() { return videoAda1; }, }; const videoAda2 = { type: "video", id: "55", representations: [{}], toVideoTrack() { return videoAda2; }, }; const video = [videoAda1, videoAda2] as unknown as IParsedAdaptation[]; const audioAda1 = { type: "audio", id: "56", representations: [{}], toAudioTrack() { return audioAda1; }, }; const audio = [audioAda1] as unknown as IParsedAdaptation[]; const args = { id: "12", thumbnailTracks: [], adaptations: { video, audio }, start: 50, duration: 12, }; const codecSupportCache = new CodecSupportCache([]); const period = new Period(args, codecSupportCache); expect(period.getAdaptations()).toHaveLength(3); expect(period.getAdaptations()).toContain(period.adaptations.video?.[0]); expect(period.getAdaptations()).toContain(period.adaptations.video?.[1]); expect(period.getAdaptations()).toContain(period.adaptations.audio?.[0]); }); it("should return every Adaptations from a given type with `getAdaptationsForType`", async () => { const mockAdaptation = vi.fn( (arg: IParsedAdaptation): Adaptation => ({ ...arg, supportStatus: { hasSupportedCodec: undefined, hasCodecWithUndefinedSupport: true, isDecipherable: undefined, }, }) as unknown as Adaptation, ); vi.doMock("../adaptation", () => ({ default: mockAdaptation, SUPPORTED_ADAPTATIONS_TYPE: ["audio", "video", "text"], })); const Period = (await vi.importActual("../period")).default as typeof IPeriod; const videoAda1 = { type: "video", id: "54", representations: [{}], toVideoTrack() { return videoAda1; }, }; const videoAda2 = { type: "video", id: "55", representations: [{}], toVideoTrack() { return videoAda2; }, }; const video = [videoAda1, videoAda2] as unknown as IParsedAdaptation[]; const audioAda1 = { type: "audio", id: "56", representations: [{}], toAudioTrack() { return audioAda1; }, }; const audio = [audioAda1] as unknown as IParsedAdaptation[]; const args = { id: "12", thumbnailTracks: [], adaptations: { video, audio }, start: 50, duration: 12, }; const codecSupportCache = new CodecSupportCache([]); const period = new Period(args, codecSupportCache); expect(period.getAdaptationsForType("video")).toHaveLength(2); expect(period.getAdaptationsForType("video")).toEqual([ period.adaptations.video?.[0], period.adaptations.video?.[1], ]); expect(period.getAdaptationsForType("audio")).toHaveLength(1); expect(period.getAdaptationsForType("audio")).toEqual([ period.adaptations.audio?.[0], ]); expect(period.getAdaptationsForType("text")).toHaveLength(0); }); it("should return the first Adaptations with a given Id when calling `getAdaptation`", async () => { const mockAdaptation = vi.fn( (arg: IParsedAdaptation): Adaptation => ({ ...arg, supportStatus: { hasSupportedCodec: undefined, hasCodecWithUndefinedSupport: true, isDecipherable: undefined, }, }) as unknown as Adaptation, ); vi.doMock("../adaptation", () => ({ default: mockAdaptation, SUPPORTED_ADAPTATIONS_TYPE: ["audio", "video", "text"], })); const Period = (await vi.importActual("../period")).default as typeof IPeriod; const videoAda1 = { type: "video", id: "54", representations: [{}], toVideoTrack() { return videoAda1; }, }; const videoAda2 = { type: "video", id: "55", representations: [{}], toVideoTrack() { return videoAda2; }, }; const videoAda3 = { type: "video", id: "55", representations: [{}], toVideoTrack() { return videoAda3; }, }; const video = [videoAda1, videoAda2, videoAda3] as unknown as IParsedAdaptation[]; const audioAda1 = { type: "audio", id: "56", representations: [{}], toAudioTrack() { return audioAda1; }, }; const audio = [audioAda1] as unknown as IParsedAdaptation[]; const args = { id: "12", thumbnailTracks: [], adaptations: { video, audio }, start: 50, duration: 12, }; const codecSupportCache = new CodecSupportCache([]); const period = new Period(args, codecSupportCache); expect(period.getAdaptation("54")).toEqual(period.adaptations.video?.[0]); expect(period.getAdaptation("55")).toEqual(period.adaptations.video?.[1]); expect(period.getAdaptation("56")).toEqual(period.adaptations.audio?.[0]); }); it("should return undefind if no adaptation has the given Id when calling `getAdaptation`", () => { const mockAdaptation = vi.fn( (arg: IParsedAdaptation): Adaptation => ({ ...arg, supportStatus: { hasSupportedCodec: undefined, hasCodecWithUndefinedSupport: true, isDecipherable: undefined, }, }) as unknown as Adaptation, ); vi.doMock("../adaptation", () => ({ default: mockAdaptation, SUPPORTED_ADAPTATIONS_TYPE: ["audio", "video", "text"], })); }); });