rx-player
Version:
Canal+ HTML5 Video Player
530 lines (484 loc) • 16.6 kB
text/typescript
import { describe, beforeEach, afterEach, it, expect, vi } from "vitest";
import type { IParsedAdaptation, IParsedRepresentation } from "../../../parsers/manifest";
import type {
IRepresentationContext,
IRepresentationFilterRepresentation,
} from "../../../public_types";
import type IAdaptation from "../adaptation";
import CodecSupportCache from "../codec_support_cache";
import type { IRepresentationIndex } from "../representation_index";
const minimalRepresentationIndex: IRepresentationIndex = {
getInitSegment() {
return null;
},
getSegments() {
return [];
},
shouldRefresh() {
return false;
},
getFirstAvailablePosition(): undefined {
return;
},
getLastAvailablePosition(): undefined {
return;
},
getEnd(): undefined {
return;
},
checkDiscontinuity() {
return null;
},
isSegmentStillAvailable(): undefined {
return;
},
canBeOutOfSyncError(): true {
return true;
},
isStillAwaitingFutureSegments() {
return false;
},
isInitialized(): true {
return true;
},
awaitSegmentBetween(): undefined {
return;
},
initialize() {
/* noop */
},
addPredictedSegments() {
/* noop */
},
getTargetSegmentDuration() {
return undefined;
},
_replace() {
/* noop */
},
_update() {
/* noop */
},
};
const mockDefaultRepresentationImpl = vi.fn((arg: IParsedRepresentation) => {
return {
bitrate: arg.bitrate,
id: arg.id,
isSupported: true,
isPlayable() {
return true;
},
index: arg.index,
};
});
describe("Manifest - Adaptation", () => {
beforeEach(() => {
vi.resetModules();
});
afterEach(() => {
mockDefaultRepresentationImpl.mockClear();
});
it("should be able to create a minimal Adaptation", async () => {
vi.doMock("../representation", () => ({
default: mockDefaultRepresentationImpl,
}));
const Adaptation = (await vi.importActual("../adaptation"))
.default as typeof IAdaptation;
const args: IParsedAdaptation = { id: "12", representations: [], type: "video" };
const codecSupportCache = new CodecSupportCache([]);
const adaptation = new Adaptation(args, codecSupportCache);
expect(adaptation.id).toBe("12");
expect(adaptation.representations).toEqual([]);
expect(adaptation.type).toBe("video");
expect(adaptation.isAudioDescription).toBe(undefined);
expect(adaptation.isClosedCaption).toBe(undefined);
expect(adaptation.language).toBe(undefined);
expect(adaptation.normalizedLanguage).toBe(undefined);
expect(adaptation.manuallyAdded).toBe(false);
expect(adaptation.getRepresentation("")).toBe(undefined);
expect(mockDefaultRepresentationImpl).not.toHaveBeenCalled();
});
it("should normalize a given language", async () => {
vi.doMock("../representation", () => ({
default: mockDefaultRepresentationImpl,
}));
const mockNormalize = vi.fn((lang: string) => lang + "foo");
vi.doMock("../../../utils/languages", () => ({
default: mockNormalize,
}));
const Adaptation = (await vi.importActual("../adaptation"))
.default as typeof IAdaptation;
const args1: IParsedAdaptation = {
id: "12",
representations: [],
language: "fr",
type: "video" as const,
};
const codecSupportCache = new CodecSupportCache([]);
const adaptation1 = new Adaptation(args1, codecSupportCache);
expect(adaptation1.language).toBe("fr");
expect(adaptation1.normalizedLanguage).toBe("frfoo");
expect(mockNormalize).toHaveBeenCalledTimes(1);
expect(mockNormalize).toHaveBeenCalledWith("fr");
mockNormalize.mockClear();
const args2: IParsedAdaptation = {
id: "12",
representations: [],
language: "toto",
type: "video" as const,
};
const adaptation2 = new Adaptation(args2, codecSupportCache);
expect(adaptation2.language).toBe("toto");
expect(adaptation2.normalizedLanguage).toBe("totofoo");
expect(mockNormalize).toHaveBeenCalledTimes(1);
expect(mockNormalize).toHaveBeenCalledWith("toto");
});
it("should not call normalize if no language is given", async () => {
vi.doMock("../representation", () => ({
default: mockDefaultRepresentationImpl,
}));
const mockNormalize = vi.fn((lang: string) => lang + "foo");
vi.doMock("../../../utils/languages", () => ({
default: mockNormalize,
}));
const Adaptation = (await vi.importActual("../adaptation"))
.default as typeof IAdaptation;
const args1: IParsedAdaptation = { id: "12", representations: [], type: "video" };
const codecSupportCache = new CodecSupportCache([]);
const adaptation1 = new Adaptation(args1, codecSupportCache);
expect(adaptation1.language).toBe(undefined);
expect(adaptation1.normalizedLanguage).toBe(undefined);
expect(mockNormalize).not.toHaveBeenCalled();
});
it("should create and sort the corresponding Representations", async () => {
vi.doMock("../representation", () => ({
default: mockDefaultRepresentationImpl,
}));
const Adaptation = (await vi.importActual("../adaptation"))
.default as typeof IAdaptation;
const rep1 = {
bitrate: 10,
id: "rep1",
cdnMetadata: [],
index: minimalRepresentationIndex,
};
const rep2 = {
bitrate: 30,
id: "rep2",
cdnMetadata: [],
index: minimalRepresentationIndex,
};
const rep3 = {
bitrate: 20,
id: "rep3",
cdnMetadata: [],
index: minimalRepresentationIndex,
};
const representations = [rep1, rep2, rep3];
const args: IParsedAdaptation = { id: "12", representations, type: "text" };
const codecSupportCache = new CodecSupportCache([]);
const adaptation = new Adaptation(args, codecSupportCache);
const parsedRepresentations = adaptation.representations;
expect(mockDefaultRepresentationImpl).toHaveBeenCalledTimes(3);
expect(mockDefaultRepresentationImpl).toHaveBeenNthCalledWith(
1,
rep1,
"text",
codecSupportCache,
);
expect(mockDefaultRepresentationImpl).toHaveBeenNthCalledWith(
2,
rep2,
"text",
codecSupportCache,
);
expect(mockDefaultRepresentationImpl).toHaveBeenNthCalledWith(
3,
rep3,
"text",
codecSupportCache,
);
expect(parsedRepresentations.length).toBe(3);
expect(parsedRepresentations[0].id).toEqual("rep1");
expect(parsedRepresentations[1].id).toEqual("rep3");
expect(parsedRepresentations[2].id).toEqual("rep2");
expect(adaptation.getRepresentation("rep2")?.bitrate).toEqual(30);
});
it("should execute the representationFilter if given", async () => {
const mockRepresentation = vi.fn((arg: IParsedRepresentation) => {
return {
bitrate: arg.bitrate,
id: arg.id,
isSupported: arg.id !== "rep4",
isPlayable() {
return true;
},
index: arg.index,
};
});
vi.doMock("../representation", () => ({
default: mockRepresentation,
}));
const Adaptation = (await vi.importActual("../adaptation"))
.default as typeof IAdaptation;
const rep1 = {
bitrate: 10,
id: "rep1",
cdnMetadata: [],
index: minimalRepresentationIndex,
};
const rep2 = {
bitrate: 20,
id: "rep2",
cdnMetadata: [],
index: minimalRepresentationIndex,
};
const rep3 = {
bitrate: 30,
id: "rep3",
cdnMetadata: [],
index: minimalRepresentationIndex,
};
const rep4 = {
bitrate: 40,
id: "rep4",
cdnMetadata: [],
index: minimalRepresentationIndex,
};
const rep5 = {
bitrate: 50,
id: "rep5",
cdnMetadata: [],
index: minimalRepresentationIndex,
};
const rep6 = {
bitrate: 60,
id: "rep6",
cdnMetadata: [],
index: minimalRepresentationIndex,
};
const representations = [rep1, rep2, rep3, rep4, rep5, rep6];
const representationFilter = vi.fn(
(
representation: IRepresentationFilterRepresentation,
adaptationInfos: IRepresentationContext,
) => {
if (
adaptationInfos.language === "fr" &&
representation.bitrate !== undefined &&
representation.bitrate < 40
) {
return false;
}
return true;
},
);
const args: IParsedAdaptation = {
id: "12",
language: "fr",
representations,
type: "text" as const,
};
const codecSupportCache = new CodecSupportCache([]);
const adaptation = new Adaptation(args, codecSupportCache, { representationFilter });
const parsedRepresentations = adaptation.representations;
expect(representationFilter).toHaveBeenCalledTimes(6);
expect(parsedRepresentations.length).toBe(3);
expect(parsedRepresentations[0].id).toEqual("rep4");
expect(parsedRepresentations[1].id).toEqual("rep5");
expect(parsedRepresentations[2].id).toEqual("rep6");
expect(adaptation.getRepresentation("rep2")).toBe(undefined);
expect(adaptation.getRepresentation("rep4")?.id).toEqual("rep4");
});
it("should set an isDub value if one", async () => {
vi.doMock("../representation", () => ({
default: mockDefaultRepresentationImpl,
}));
const mockNormalize = vi.fn((lang: string) => lang + "foo");
vi.doMock("../../../utils/languages", () => ({
default: mockNormalize,
}));
const Adaptation = (await vi.importActual("../adaptation"))
.default as typeof IAdaptation;
const args1: IParsedAdaptation = {
id: "12",
representations: [],
isDub: false,
type: "video",
};
const codecSupportCache = new CodecSupportCache([]);
const adaptation1 = new Adaptation(args1, codecSupportCache);
expect(adaptation1.language).toBe(undefined);
expect(adaptation1.normalizedLanguage).toBe(undefined);
expect(adaptation1.isDub).toEqual(false);
expect(mockNormalize).not.toHaveBeenCalled();
const args2: IParsedAdaptation = {
id: "12",
representations: [],
isDub: true,
type: "video",
};
const adaptation2 = new Adaptation(args2, codecSupportCache);
expect(adaptation2.language).toBe(undefined);
expect(adaptation2.normalizedLanguage).toBe(undefined);
expect(adaptation2.isDub).toEqual(true);
expect(mockNormalize).not.toHaveBeenCalled();
});
it("should set an isClosedCaption value if one", async () => {
vi.doMock("../representation", () => ({
default: mockDefaultRepresentationImpl,
}));
const mockNormalize = vi.fn((lang: string) => lang + "foo");
vi.doMock("../../../utils/languages", () => ({
default: mockNormalize,
}));
const Adaptation = (await vi.importActual("../adaptation"))
.default as typeof IAdaptation;
const args1: IParsedAdaptation = {
id: "12",
representations: [],
closedCaption: false,
type: "video",
};
const codecSupportCache = new CodecSupportCache([]);
const adaptation1 = new Adaptation(args1, codecSupportCache);
expect(adaptation1.language).toBe(undefined);
expect(adaptation1.normalizedLanguage).toBe(undefined);
expect(adaptation1.isClosedCaption).toEqual(false);
expect(mockNormalize).not.toHaveBeenCalled();
const args2: IParsedAdaptation = {
id: "12",
representations: [],
closedCaption: true,
type: "video",
};
const adaptation2 = new Adaptation(args2, codecSupportCache);
expect(adaptation2.language).toBe(undefined);
expect(adaptation2.normalizedLanguage).toBe(undefined);
expect(adaptation2.isClosedCaption).toEqual(true);
expect(mockNormalize).not.toHaveBeenCalled();
});
it("should set an isAudioDescription value if one", async () => {
vi.doMock("../representation", () => ({
default: mockDefaultRepresentationImpl,
}));
const mockNormalize = vi.fn((lang: string) => lang + "foo");
vi.doMock("../../../utils/languages", () => ({
default: mockNormalize,
}));
const Adaptation = (await vi.importActual("../adaptation"))
.default as typeof IAdaptation;
const args1: IParsedAdaptation = {
id: "12",
representations: [],
audioDescription: false,
type: "video",
};
const codecSupportCache = new CodecSupportCache([]);
const adaptation1 = new Adaptation(args1, codecSupportCache);
expect(adaptation1.language).toBe(undefined);
expect(adaptation1.normalizedLanguage).toBe(undefined);
expect(adaptation1.isAudioDescription).toEqual(false);
expect(mockNormalize).not.toHaveBeenCalled();
const args2: IParsedAdaptation = {
id: "12",
representations: [],
audioDescription: true,
type: "video",
};
const adaptation2 = new Adaptation(args2, codecSupportCache);
expect(adaptation2.language).toBe(undefined);
expect(adaptation2.normalizedLanguage).toBe(undefined);
expect(adaptation2.isAudioDescription).toEqual(true);
expect(mockNormalize).not.toHaveBeenCalled();
});
it("should set a manuallyAdded value if one", async () => {
vi.doMock("../representation", () => ({
default: mockDefaultRepresentationImpl,
}));
const mockNormalize = vi.fn((lang: string) => lang + "foo");
vi.doMock("../../../utils/languages", () => ({
default: mockNormalize,
}));
const Adaptation = (await vi.importActual("../adaptation"))
.default as typeof IAdaptation;
const args1: IParsedAdaptation = { id: "12", representations: [], type: "video" };
const codecSupportCache = new CodecSupportCache([]);
const adaptation1 = new Adaptation(args1, codecSupportCache, {
isManuallyAdded: false,
});
expect(adaptation1.language).toBe(undefined);
expect(adaptation1.normalizedLanguage).toBe(undefined);
expect(adaptation1.manuallyAdded).toEqual(false);
expect(mockNormalize).not.toHaveBeenCalled();
const args2: IParsedAdaptation = { id: "12", representations: [], type: "video" };
const adaptation2 = new Adaptation(args2, codecSupportCache, {
isManuallyAdded: true,
});
expect(adaptation2.language).toBe(undefined);
expect(adaptation2.normalizedLanguage).toBe(undefined);
expect(adaptation2.manuallyAdded).toEqual(true);
expect(mockNormalize).not.toHaveBeenCalled();
});
it("should return the first Representation with the given Id with `getRepresentation`", async () => {
vi.doMock("../representation", () => ({
default: mockDefaultRepresentationImpl,
}));
const Adaptation = (await vi.importActual("../adaptation"))
.default as typeof IAdaptation;
const rep1 = {
bitrate: 10,
id: "rep1",
cdnMetadata: [],
index: minimalRepresentationIndex,
};
const rep2 = {
bitrate: 20,
id: "rep2",
cdnMetadata: [],
index: minimalRepresentationIndex,
};
const rep3 = {
bitrate: 30,
id: "rep2",
cdnMetadata: [],
index: minimalRepresentationIndex,
};
const representations = [rep1, rep2, rep3];
const args: IParsedAdaptation = { id: "12", representations, type: "text" as const };
const codecSupportCache = new CodecSupportCache([]);
const adaptation = new Adaptation(args, codecSupportCache);
expect(adaptation.getRepresentation("rep1")?.bitrate).toEqual(10);
expect(adaptation.getRepresentation("rep2")?.bitrate).toEqual(20);
});
it("should return undefined in `getRepresentation` if no representation is found with this Id", async () => {
vi.doMock("../representation", () => ({
default: mockDefaultRepresentationImpl,
}));
const Adaptation = (await vi.importActual("../adaptation"))
.default as typeof IAdaptation;
const rep1 = {
bitrate: 10,
id: "rep1",
cdnMetadata: [],
index: minimalRepresentationIndex,
};
const rep2 = {
bitrate: 20,
id: "rep2",
cdnMetadata: [],
index: minimalRepresentationIndex,
};
const rep3 = {
bitrate: 30,
id: "rep2",
cdnMetadata: [],
index: minimalRepresentationIndex,
};
const representations = [rep1, rep2, rep3];
const args: IParsedAdaptation = { id: "12", representations, type: "text" as const };
const codecSupportCache = new CodecSupportCache([]);
const adaptation = new Adaptation(args, codecSupportCache);
expect(adaptation.getRepresentation("rep5")).toBe(undefined);
});
});