diff --git a/src/bigscreenplayer.js b/src/bigscreenplayer.js index 72333dc2..71f8ad18 100644 --- a/src/bigscreenplayer.js +++ b/src/bigscreenplayer.js @@ -307,6 +307,8 @@ function BigscreenPlayer() { callbacks.onError(reason) } }) + + Plugins.updateContext((context) => ({ ...context, mediaSources })) }, /** @@ -737,7 +739,7 @@ function BigscreenPlayer() { }, /** - * Register a plugin for extended events. + * Register a plugin for extended events & custom functionality. * @function * @param {*} plugin */ diff --git a/src/playercomponent.js b/src/playercomponent.js index 5abf94cb..5148f1d8 100644 --- a/src/playercomponent.js +++ b/src/playercomponent.js @@ -335,6 +335,9 @@ function PlayerComponent( }) } + // Not an ideal place for this, but I've been warned of a possible playercomponent rewrite + Plugins.updateContext((context) => ({ ...context, attemptCdnFailover })) + function clearFatalErrorTimeout() { if (fatalErrorTimeout !== null) { clearTimeout(fatalErrorTimeout) diff --git a/src/plugins.js b/src/plugins.js index 6a6bc77a..ddc1f21d 100644 --- a/src/plugins.js +++ b/src/plugins.js @@ -2,6 +2,7 @@ import PlaybackUtils from "./utils/playbackutils" import CallCallbacks from "./utils/callcallbacks" let plugins = [] +const pluginContext = {} function callOnAllPlugins(funcKey, evt) { const clonedEvent = PlaybackUtils.deepClone(evt) @@ -13,8 +14,45 @@ function callOnAllPlugins(funcKey, evt) { } export default { + /** + * @param {function (*): *} updater - a function which accepts the current context, and returns a new context + */ + updateContext: (updater) => { + const newContext = updater(PlaybackUtils.deepClone(pluginContext)) + + if (typeof newContext !== "object") { + throw new TypeError("context must be an object") + } + + // update object (preserving reference) + for (const prop of Object.keys(pluginContext)) { + delete pluginContext[prop] + } + + Object.assign(pluginContext, newContext) + + // call context update callbacks + for (const plugin of plugins) { + plugin.__onPluginContextUpdated?.(pluginContext) + } + }, + + /** + * @param {*} plugin - an object or function, functional plugins receive the context as an argument + */ registerPlugin: (plugin) => { plugins.push(plugin) + + if (typeof plugin === "function") { + plugin(pluginContext, (onPluginContextUpdated) => { + plugin.__onPluginContextUpdated = (pluginContext) => { + onPluginContextUpdated(pluginContext) + } + }) + + // provide initial update + plugin.__onPluginContextUpdated?.(pluginContext) + } }, unregisterPlugin: (plugin) => { diff --git a/src/subtitles/imscsubtitles.js b/src/subtitles/imscsubtitles.js index 21cd2291..f2487e7d 100644 --- a/src/subtitles/imscsubtitles.js +++ b/src/subtitles/imscsubtitles.js @@ -164,6 +164,8 @@ function IMSCSubtitles( } } + Plugins.updateContext((context) => ({ ...context, attemptSubtitleCdnFailover: loadErrorFailover })) + function pruneSegments() { // Before sorting, check if we've gone back in time, so we know whether to prune from front or back of array const seekedBack = segments[SEGMENTS_BUFFER_SIZE].number < segments[SEGMENTS_BUFFER_SIZE - 1].number diff --git a/src/subtitles/imscsubtitles.test.js b/src/subtitles/imscsubtitles.test.js index a72d062e..d0dc2617 100644 --- a/src/subtitles/imscsubtitles.test.js +++ b/src/subtitles/imscsubtitles.test.js @@ -7,6 +7,7 @@ import Plugins from "../plugins" jest.mock("smp-imsc") jest.mock("../utils/loadurl") jest.mock("../plugins", () => ({ + updateContext: jest.fn(), interface: { onSubtitlesTimeout: jest.fn(), onSubtitlesXMLError: jest.fn(), diff --git a/src/subtitles/legacysubtitles.test.js b/src/subtitles/legacysubtitles.test.js index f99bd65e..15d62cdc 100644 --- a/src/subtitles/legacysubtitles.test.js +++ b/src/subtitles/legacysubtitles.test.js @@ -6,6 +6,7 @@ import Renderer from "./renderer" jest.mock("../utils/loadurl") jest.mock("../plugins", () => ({ + updateContext: jest.fn(), interface: { onSubtitlesTimeout: jest.fn(), onSubtitlesXMLError: jest.fn(),