rx-player
Version:
Canal+ HTML5 Video Player
316 lines (272 loc) • 9.86 kB
text/typescript
import { describe, it, expect, vi, beforeEach } from "vitest";
import {
formatErrorForSender,
synchronizeSegmentSinksOnObservation,
updateCodecSupportInWorkerMode,
extractExternalPlugins,
} from "../utils";
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
const { mockFormatError, mockMediaSource_, mockCreateRepresentationFilter } = vi.hoisted(
() => {
return {
mockFormatError: vi.fn(),
mockMediaSource_: { isTypeSupported: vi.fn() },
mockCreateRepresentationFilter: vi.fn(),
};
},
);
vi.mock("../../../errors", () => ({
formatError: mockFormatError,
}));
vi.mock("../../../compat/browser_compatibility_types", () => ({
default: {
MediaSource_: mockMediaSource_,
},
}));
vi.mock("../../../manifest", () => ({
createRepresentationFilterFromFnString: mockCreateRepresentationFilter,
}));
beforeEach(() => {
vi.resetAllMocks();
});
describe("formatErrorForSender", () => {
it("should call formatError with the error and default options, then serialize", () => {
const mockSerialize = vi
.fn()
.mockReturnValue({ code: "NONE", message: "An unknown error" });
mockFormatError.mockReturnValue({ serialize: mockSerialize });
const result = formatErrorForSender(new Error("test"));
expect(mockFormatError).toHaveBeenCalledWith(new Error("test"), {
defaultCode: "NONE",
defaultReason: "An unknown error stopped content playback.",
});
expect(mockSerialize).toHaveBeenCalled();
expect(result).toEqual({ code: "NONE", message: "An unknown error" });
});
});
describe("synchronizeSegmentSinksOnObservation", () => {
it("should call synchronizeInventory for initialized segment sinks", () => {
const syncInventory = vi.fn();
const segmentSinksStore = {
getStatus: vi.fn((type: string) => {
if (type === "video") {
return { type: "initialized", value: { synchronizeInventory: syncInventory } };
}
return { type: "not-initialized" };
}),
} as any;
const observation = {
buffered: {
video: [{ start: 0, end: 10 }],
audio: [],
text: [],
},
} as any;
synchronizeSegmentSinksOnObservation(observation, segmentSinksStore);
expect(syncInventory).toHaveBeenCalledTimes(1);
expect(syncInventory).toHaveBeenCalledWith([{ start: 0, end: 10 }]);
});
it("should call synchronizeInventory for all initialized sinks with their buffered ranges", () => {
const videoSync = vi.fn();
const audioSync = vi.fn();
const textSync = vi.fn();
const segmentSinksStore = {
getStatus: vi.fn((type: string) => {
if (type === "video") {
return { type: "initialized", value: { synchronizeInventory: videoSync } };
}
if (type === "audio") {
return { type: "initialized", value: { synchronizeInventory: audioSync } };
}
if (type === "text") {
return { type: "initialized", value: { synchronizeInventory: textSync } };
}
return { type: "not-initialized" };
}),
} as any;
const observation = {
buffered: {
video: [{ start: 0, end: 5 }],
audio: [{ start: 1, end: 6 }],
text: undefined,
},
} as any;
synchronizeSegmentSinksOnObservation(observation, segmentSinksStore);
expect(videoSync).toHaveBeenCalledWith([{ start: 0, end: 5 }]);
expect(audioSync).toHaveBeenCalledWith([{ start: 1, end: 6 }]);
expect(textSync).toHaveBeenCalledWith([]); // undefined falls back to []
});
});
describe("updateCodecSupportInWorkerMode", () => {
it("should do nothing if MediaSource_ is null", async () => {
// Re-import with MediaSource_ as null - we test indirectly
// by checking that isTypeSupported is never called when MediaSource_ is null
// This test verifies the null-guard works via a manifest with no periods
const manifest = { periods: [] } as any;
// Since we can't easily change the module-level import, we verify normal flow
updateCodecSupportInWorkerMode(manifest);
expect(mockMediaSource_.isTypeSupported).not.toHaveBeenCalled();
});
it("should set isCodecSupportedInWebWorker based on isTypeSupported", () => {
mockMediaSource_.isTypeSupported.mockImplementation((codec: string) => {
// eslint-disable-next-line no-restricted-properties
return codec.includes("avc1");
});
const videoRep = {
mimeType: "video/mp4",
codecs: ["avc1.42E01E"],
isCodecSupportedInWebWorker: undefined,
getMimeTypeString: () => `video/mp4;codecs="avc1.42E01E"`,
};
const audioRep = {
mimeType: "audio/mp4",
codecs: ["mp4a.40.2"],
isCodecSupportedInWebWorker: undefined,
getMimeTypeString: () => `audio/mp4;codecs="mp4a.40.2"`,
};
const manifest = {
periods: [
{
adaptations: {
video: [{ representations: [videoRep] }],
audio: [{ representations: [audioRep] }],
},
},
],
} as any;
updateCodecSupportInWorkerMode(manifest);
expect(videoRep.isCodecSupportedInWebWorker).toBe(true);
expect(audioRep.isCodecSupportedInWebWorker).toBe(false);
});
it("should cache codec support results", () => {
mockMediaSource_.isTypeSupported.mockReturnValue(true);
const rep1 = {
mimeType: "video/mp4",
codecs: ["avc1.42E01E"],
isCodecSupportedInWebWorker: undefined,
getMimeTypeString: () => `video/mp4;codecs="avc1.42E01E"`,
};
const rep2 = {
mimeType: "video/mp4",
codecs: ["avc1.42E01E"],
isCodecSupportedInWebWorker: undefined,
getMimeTypeString: () => `video/mp4;codecs="avc1.42E01E"`,
};
const manifest = {
periods: [
{
adaptations: {
video: [{ representations: [rep1, rep2] }],
audio: [],
},
},
],
} as any;
updateCodecSupportInWorkerMode(manifest);
expect(mockMediaSource_.isTypeSupported).toHaveBeenCalledTimes(1);
expect(rep1.isCodecSupportedInWebWorker).toBe(true);
expect(rep2.isCodecSupportedInWebWorker).toBe(true);
});
});
describe("extractExternalPlugins", () => {
const emptyCorePlugins = {
representationFilters: new Map(),
segmentLoaders: new Map(),
manifestLoaders: new Map(),
};
it("should return undefined for all when input has no loaders", () => {
const result = extractExternalPlugins(
{
manifestLoader: undefined,
segmentLoader: undefined,
representationFilter: undefined,
},
emptyCorePlugins,
);
expect(result).toEqual({
manifestLoader: undefined,
segmentLoader: undefined,
representationFilter: undefined,
});
});
it("should use fn directly when provided", () => {
const repFilter = vi.fn();
const manifestLoader = vi.fn();
const segmentLoader = vi.fn();
const result = extractExternalPlugins(
{
representationFilter: { fn: repFilter },
manifestLoader: { fn: manifestLoader },
segmentLoader: { fn: segmentLoader },
},
emptyCorePlugins,
);
expect(result.representationFilter).toBe(repFilter);
expect(result.manifestLoader).toBe(manifestLoader);
expect(result.segmentLoader).toBe(segmentLoader);
});
it("should use eval string for representationFilter", () => {
const createdFilter = vi.fn();
mockCreateRepresentationFilter.mockReturnValue(createdFilter);
const result = extractExternalPlugins(
{
representationFilter: { eval: "some code" },
manifestLoader: undefined,
segmentLoader: undefined,
},
emptyCorePlugins,
);
expect(mockCreateRepresentationFilter).toHaveBeenCalledWith("some code");
expect(result.representationFilter).toBe(createdFilter);
});
it("should look up workerId in corePlugins maps", () => {
const workerRepFilter = vi.fn();
const workerManifestLoader = vi.fn();
const workerSegmentLoader = vi.fn();
const corePlugins = {
representationFilters: new Map([["rf-id", workerRepFilter]]),
segmentLoaders: new Map([["sl-id", workerSegmentLoader]]),
manifestLoaders: new Map([["ml-id", workerManifestLoader]]),
};
const result = extractExternalPlugins(
{
representationFilter: { workerId: "rf-id" },
manifestLoader: { workerId: "ml-id" },
segmentLoader: { workerId: "sl-id" },
},
corePlugins,
);
expect(result.representationFilter).toBe(workerRepFilter);
expect(result.manifestLoader).toBe(workerManifestLoader);
expect(result.segmentLoader).toBe(workerSegmentLoader);
});
it("should return undefined when workerId is not found in maps", () => {
const result = extractExternalPlugins(
{
representationFilter: { workerId: "unknown" },
manifestLoader: { workerId: "unknown" },
segmentLoader: { workerId: "unknown" },
},
emptyCorePlugins,
);
expect(result.representationFilter).toBeUndefined();
expect(result.manifestLoader).toBeUndefined();
expect(result.segmentLoader).toBeUndefined();
});
it("should prefer fn over eval and workerId for representationFilter", () => {
const fnFilter = vi.fn();
mockCreateRepresentationFilter.mockReturnValue(vi.fn());
const result = extractExternalPlugins(
{
representationFilter: { fn: fnFilter, eval: "some code", workerId: "rf-id" },
manifestLoader: undefined,
segmentLoader: undefined,
},
emptyCorePlugins,
);
expect(result.representationFilter).toBe(fnFilter);
expect(mockCreateRepresentationFilter).not.toHaveBeenCalled();
});
});