summaryrefslogtreecommitdiff
path: root/javascript/videojs/src/js/tech/middleware.js
diff options
context:
space:
mode:
Diffstat (limited to 'javascript/videojs/src/js/tech/middleware.js')
-rw-r--r--javascript/videojs/src/js/tech/middleware.js338
1 files changed, 338 insertions, 0 deletions
diff --git a/javascript/videojs/src/js/tech/middleware.js b/javascript/videojs/src/js/tech/middleware.js
new file mode 100644
index 0000000..78182c4
--- /dev/null
+++ b/javascript/videojs/src/js/tech/middleware.js
@@ -0,0 +1,338 @@
+/**
+ * @file middleware.js
+ * @module middleware
+ */
+import {toTitleCase} from '../utils/str.js';
+
+/** @import Player from '../player' */
+/** @import Tech from '../tech/tech' */
+
+const middlewares = {};
+const middlewareInstances = {};
+
+export const TERMINATOR = {};
+
+/**
+ * A middleware object is a plain JavaScript object that has methods that
+ * match the {@link Tech} methods found in the lists of allowed
+ * {@link module:middleware.allowedGetters|getters},
+ * {@link module:middleware.allowedSetters|setters}, and
+ * {@link module:middleware.allowedMediators|mediators}.
+ *
+ * @typedef {Object} MiddlewareObject
+ */
+
+/**
+ * A middleware factory function that should return a
+ * {@link module:middleware~MiddlewareObject|MiddlewareObject}.
+ *
+ * This factory will be called for each player when needed, with the player
+ * passed in as an argument.
+ *
+ * @callback MiddlewareFactory
+ * @param {Player} player
+ * A Video.js player.
+ */
+
+/**
+ * Define a middleware that the player should use by way of a factory function
+ * that returns a middleware object.
+ *
+ * @param {string} type
+ * The MIME type to match or `"*"` for all MIME types.
+ *
+ * @param {MiddlewareFactory} middleware
+ * A middleware factory function that will be executed for
+ * matching types.
+ */
+export function use(type, middleware) {
+ middlewares[type] = middlewares[type] || [];
+ middlewares[type].push(middleware);
+}
+
+/**
+ * Gets middlewares by type (or all middlewares).
+ *
+ * @param {string} type
+ * The MIME type to match or `"*"` for all MIME types.
+ *
+ * @return {Function[]|undefined}
+ * An array of middlewares or `undefined` if none exist.
+ */
+export function getMiddleware(type) {
+ if (type) {
+ return middlewares[type];
+ }
+
+ return middlewares;
+}
+
+/**
+ * Asynchronously sets a source using middleware by recursing through any
+ * matching middlewares and calling `setSource` on each, passing along the
+ * previous returned value each time.
+ *
+ * @param {Player} player
+ * A {@link Player} instance.
+ *
+ * @param {Tech~SourceObject} src
+ * A source object.
+ *
+ * @param {Function}
+ * The next middleware to run.
+ */
+export function setSource(player, src, next) {
+ player.setTimeout(() => setSourceHelper(src, middlewares[src.type], next, player), 1);
+}
+
+/**
+ * When the tech is set, passes the tech to each middleware's `setTech` method.
+ *
+ * @param {Object[]} middleware
+ * An array of middleware instances.
+ *
+ * @param {Tech} tech
+ * A Video.js tech.
+ */
+export function setTech(middleware, tech) {
+ middleware.forEach((mw) => mw.setTech && mw.setTech(tech));
+}
+
+/**
+ * Calls a getter on the tech first, through each middleware
+ * from right to left to the player.
+ *
+ * @param {Object[]} middleware
+ * An array of middleware instances.
+ *
+ * @param {Tech} tech
+ * The current tech.
+ *
+ * @param {string} method
+ * A method name.
+ *
+ * @return {*}
+ * The final value from the tech after middleware has intercepted it.
+ */
+export function get(middleware, tech, method) {
+ return middleware.reduceRight(middlewareIterator(method), tech[method]());
+}
+
+/**
+ * Takes the argument given to the player and calls the setter method on each
+ * middleware from left to right to the tech.
+ *
+ * @param {Object[]} middleware
+ * An array of middleware instances.
+ *
+ * @param {Tech} tech
+ * The current tech.
+ *
+ * @param {string} method
+ * A method name.
+ *
+ * @param {*} arg
+ * The value to set on the tech.
+ *
+ * @return {*}
+ * The return value of the `method` of the `tech`.
+ */
+export function set(middleware, tech, method, arg) {
+ return tech[method](middleware.reduce(middlewareIterator(method), arg));
+}
+
+/**
+ * Takes the argument given to the player and calls the `call` version of the
+ * method on each middleware from left to right.
+ *
+ * Then, call the passed in method on the tech and return the result unchanged
+ * back to the player, through middleware, this time from right to left.
+ *
+ * @param {Object[]} middleware
+ * An array of middleware instances.
+ *
+ * @param {Tech} tech
+ * The current tech.
+ *
+ * @param {string} method
+ * A method name.
+ *
+ * @param {*} arg
+ * The value to set on the tech.
+ *
+ * @return {*}
+ * The return value of the `method` of the `tech`, regardless of the
+ * return values of middlewares.
+ */
+export function mediate(middleware, tech, method, arg = null) {
+ const callMethod = 'call' + toTitleCase(method);
+ const middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg);
+ const terminated = middlewareValue === TERMINATOR;
+ // deprecated. The `null` return value should instead return TERMINATOR to
+ // prevent confusion if a techs method actually returns null.
+ const returnValue = terminated ? null : tech[method](middlewareValue);
+
+ executeRight(middleware, method, returnValue, terminated);
+
+ return returnValue;
+}
+
+/**
+ * Enumeration of allowed getters where the keys are method names.
+ *
+ * @type {Object}
+ */
+export const allowedGetters = {
+ buffered: 1,
+ currentTime: 1,
+ duration: 1,
+ muted: 1,
+ played: 1,
+ paused: 1,
+ seekable: 1,
+ volume: 1,
+ ended: 1
+};
+
+/**
+ * Enumeration of allowed setters where the keys are method names.
+ *
+ * @type {Object}
+ */
+export const allowedSetters = {
+ setCurrentTime: 1,
+ setMuted: 1,
+ setVolume: 1
+};
+
+/**
+ * Enumeration of allowed mediators where the keys are method names.
+ *
+ * @type {Object}
+ */
+export const allowedMediators = {
+ play: 1,
+ pause: 1
+};
+
+function middlewareIterator(method) {
+ return (value, mw) => {
+ // if the previous middleware terminated, pass along the termination
+ if (value === TERMINATOR) {
+ return TERMINATOR;
+ }
+
+ if (mw[method]) {
+ return mw[method](value);
+ }
+
+ return value;
+ };
+}
+
+function executeRight(mws, method, value, terminated) {
+ for (let i = mws.length - 1; i >= 0; i--) {
+ const mw = mws[i];
+
+ if (mw[method]) {
+ mw[method](terminated, value);
+ }
+ }
+}
+
+/**
+ * Clear the middleware cache for a player.
+ *
+ * @param {Player} player
+ * A {@link Player} instance.
+ */
+export function clearCacheForPlayer(player) {
+ if (middlewareInstances.hasOwnProperty(player.id())) {
+ delete middlewareInstances[player.id()];
+ }
+}
+
+/**
+ * {
+ * [playerId]: [[mwFactory, mwInstance], ...]
+ * }
+ *
+ * @private
+ */
+function getOrCreateFactory(player, mwFactory) {
+ const mws = middlewareInstances[player.id()];
+ let mw = null;
+
+ if (mws === undefined || mws === null) {
+ mw = mwFactory(player);
+ middlewareInstances[player.id()] = [[mwFactory, mw]];
+ return mw;
+ }
+
+ for (let i = 0; i < mws.length; i++) {
+ const [mwf, mwi] = mws[i];
+
+ if (mwf !== mwFactory) {
+ continue;
+ }
+
+ mw = mwi;
+ }
+
+ if (mw === null) {
+ mw = mwFactory(player);
+ mws.push([mwFactory, mw]);
+ }
+
+ return mw;
+}
+
+function setSourceHelper(src = {}, middleware = [], next, player, acc = [], lastRun = false) {
+ const [mwFactory, ...mwrest] = middleware;
+
+ // if mwFactory is a string, then we're at a fork in the road
+ if (typeof mwFactory === 'string') {
+ setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun);
+
+ // if we have an mwFactory, call it with the player to get the mw,
+ // then call the mw's setSource method
+ } else if (mwFactory) {
+ const mw = getOrCreateFactory(player, mwFactory);
+
+ // if setSource isn't present, implicitly select this middleware
+ if (!mw.setSource) {
+ acc.push(mw);
+ return setSourceHelper(src, mwrest, next, player, acc, lastRun);
+ }
+
+ mw.setSource(Object.assign({}, src), function(err, _src) {
+
+ // something happened, try the next middleware on the current level
+ // make sure to use the old src
+ if (err) {
+ return setSourceHelper(src, mwrest, next, player, acc, lastRun);
+ }
+
+ // we've succeeded, now we need to go deeper
+ acc.push(mw);
+
+ // if it's the same type, continue down the current chain
+ // otherwise, we want to go down the new chain
+ setSourceHelper(
+ _src,
+ src.type === _src.type ? mwrest : middlewares[_src.type],
+ next,
+ player,
+ acc,
+ lastRun
+ );
+ });
+
+ } else if (mwrest.length) {
+ setSourceHelper(src, mwrest, next, player, acc, lastRun);
+ } else if (lastRun) {
+ next(src, acc);
+ } else {
+ setSourceHelper(src, middlewares['*'], next, player, acc, true);
+ }
+}