
中间件作为 Video.js 的一个特性,它允许 Player 和 Tech 进行交互并修改它们之间的通信方式。
本质上 Video.js 中间件是一个返回对象或类实例的函数,方法与 Tech 上的方法相匹配。目前,中间件可以理解的方法集合还比较有限。
setSource/setTech
在 Video.js 7.0.5 及更老的版本中,所有中间件都需要有 setSource()
方法:
var myMiddleware = function(player) {
return {
setSource: function(srcObj, next) {
// pass null as the first argument to indicate that the source is not rejected
next(null, srcObj);
},
};
};
var myMiddleware = function(player) {
return {
setSource: function(srcObj, next) {
// pass null as the first argument to indicate that the source is not rejected
next(null, srcObj);
},
};
};
如果您的中间件没有操作、重定向或拒绝播放源,则可以在新版本的 Video.js 中忽略此方法。这样做会隐式地选择中间件。
setTech()
也是一个方法,在中间件决定设置哪个播放源之后,一旦播放器选择了某个特定的技术,就将其与中间件关联起来。
Getters/Setters
+----------+ +----------+
| | setter middleware | |
| +----------------------> |
| Player | | Tech |
| <----------------------+ |
| | getter middleware | |
+----------+ +----------+
+----------+ +----------+
| | setter middleware | |
| +----------------------> |
| Player | | Tech |
| <----------------------+ |
| | getter middleware | |
+----------+ +----------+
Setters 首先在 Player 上被调用,并按照它们注册的顺序(在图中从左到右)运行中间件,然后在 Tech 上调用带有参数的方法。
Getters 首先在 Tech 上被调用,在将结果返回给播放器之前,按照注册的顺序(从图中的右到左)通过中间件运行。
Middleware Mediators
Mediators 是一种方法,不仅可以改变 Tech 的状态,还可以将一些值返回给 Player。目前,支持播放和暂停。
Mediators 首先在播放器上被调用,按照注册的顺序运行中间件(在下图中从左到右),然后被调用到 Tech 上。结果会原封不动地返回给播放器,以注册中间件的相反顺序调用中间件(在下图中从右到左)。
+----------+ +----------+
| | | |
| +---mediate-to-tech----> |
| Player | | Tech |
| <--mediate-to-player---+ |
| | | |
+----------+ +----------+
+----------+ +----------+
| | | |
| +---mediate-to-tech----> |
| Player | | Tech |
| <--mediate-to-player---+ |
| | | |
+----------+ +----------+
Mediators 做一次往返:从 Player 开始,向 Tech 进行调用,然后将结果再次返回给 Player。调用 {method}
方法必须由中间件提供,该中间件在向 Tech 进行中介时使用。
+----------+ +----------+
| | | |
| +----+call{method}+----> |
| Player | | Tech |
| <------+{method}+------+ |
| | | |
+----------+ +----------+
+----------+ +----------+
| | | |
| +----+call{method}+----> |
| Player | | Tech |
| <------+{method}+------+ |
| | | |
+----------+ +----------+
调用 {method}
方法时带有两个参数:terminated
,一个布尔值,指示中间件是否在向 Tech 部分进行中介期间终止;value
,是 Tech 返回的值。
Demo
你的中间件应该是一个作用域为 Player 的函数,下面是一个返回对象的中间件示例:
var myMiddleware = function(player) {
return {
currentTime: function(ct) {
return ct / 2;
},
setCurrentTime: function(time) {
return time * 2;
},
};
};
var myMiddleware = function(player) {
return {
currentTime: function(ct) {
return ct / 2;
},
setCurrentTime: function(time) {
return time * 2;
},
};
};
此中间件使视频源以双倍的速度播放,将我们从 Tech 获得的时间减半,并将我们在 Tech 上设置的时间加倍。另外,还可以使用 Mediator 方法:
var myMiddleware = function(player) {
return {
callPlay: function() {
// Terminate by returning the middleware terminator
return videojs.middleware.TERMINATOR;
},
/**
* 在返回播放器的过程中,将调用 `{method}`,并带有两个参数
* @param {boolean} terminated 指示中间件是否在向 Tech 部分进行中介期间终止
* @param {any} value,是 Tech 返回的值。
*/
play: function(terminated, value) {
// the terminated argument should be true here.
if (terminated) {
console.log('The play was middleware terminated.');
}
},
};
};
var myMiddleware = function(player) {
return {
callPlay: function() {
// Terminate by returning the middleware terminator
return videojs.middleware.TERMINATOR;
},
/**
* 在返回播放器的过程中,将调用 `{method}`,并带有两个参数
* @param {boolean} terminated 指示中间件是否在向 Tech 部分进行中介期间终止
* @param {any} value,是 Tech 返回的值。
*/
play: function(terminated, value) {
// the terminated argument should be true here.
if (terminated) {
console.log('The play was middleware terminated.');
}
},
};
};
示例中的中间件总是通过在 callPlay()
中返回终止符来终止对 play()
的调用。在 Player 中,我们可以看到对 play()
的调用被终止,并且从未在 Tech 上被调用。
基础实现
中间件以视频 MIME 类型为维度进行存储,并将针对具有该类型的任何播放源运行:
const middlewares = {};
videojs.use = middlewares.use = function use(type, middleware) {
middlewares[type] = middlewares[type] || [];
middlewares[type].push(middleware);
};
videojs.use('video/mp4', myMiddleware);
const middlewares = {};
videojs.use = middlewares.use = function use(type, middleware) {
middlewares[type] = middlewares[type] || [];
middlewares[type].push(middleware);
};
videojs.use('video/mp4', myMiddleware);
您还可以通过在 *
上注册中间件,这些中间件将在所有播放源上应用:
videojs.use('*', myMiddleware);
videojs.use('*', myMiddleware);
调用 player.src()
时,Video.js 会通过调用 setSource()
和 setTech()
方法来让中间件开始工作:
middleware.setSource(this, sources[0], (middlewareSource, mws) => {
// ...
middleware.setTech(mws, this.tech_);
});
middleware.setSource(this, sources[0], (middlewareSource, mws) => {
// ...
middleware.setTech(mws, this.tech_);
});
在 setSource()
方法中会调用 setSourceHelper()
函数,它会递归的运行中间件:
function setSourceHelper(
src = {},
middleware = [],
next,
player,
acc = [],
lastRun = false
) {
const [mwFactory, ...mwrest] = middleware;
if (typeof mwFactory === 'string') {
setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun);
} else if (mwFactory) {
// ...
} 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);
}
}
function setSourceHelper(
src = {},
middleware = [],
next,
player,
acc = [],
lastRun = false
) {
const [mwFactory, ...mwrest] = middleware;
if (typeof mwFactory === 'string') {
setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun);
} else if (mwFactory) {
// ...
} 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);
}
}
如果我们找到了一个 mwFactory,就用播放器调用它来获取 mw,然后调用 mw 的 setSource()
方法:
const mw = getOrCreateFactory(player, mwFactory);
// 如果 setSource 不存在,则隐式选择此中间件
if (!mw.setSource) {
acc.push(mw);
return setSourceHelper(src, mwrest, next, player, acc, lastRun);
}
mw.setSource(assign({}, src), function(err, _src) {
// 如果出现问题,请尝试当前级别上的下一个中间件确保使用旧的 src
if (err) {
return setSourceHelper(src, mwrest, next, player, acc, lastRun);
}
acc.push(mw);
// 如果是同一类型,则继续当前链。否则,我们就要走新的链路
setSourceHelper(
_src,
src.type === _src.type ? mwrest : middlewares[_src.type],
next,
player,
acc,
lastRun
);
});
const mw = getOrCreateFactory(player, mwFactory);
// 如果 setSource 不存在,则隐式选择此中间件
if (!mw.setSource) {
acc.push(mw);
return setSourceHelper(src, mwrest, next, player, acc, lastRun);
}
mw.setSource(assign({}, src), function(err, _src) {
// 如果出现问题,请尝试当前级别上的下一个中间件确保使用旧的 src
if (err) {
return setSourceHelper(src, mwrest, next, player, acc, lastRun);
}
acc.push(mw);
// 如果是同一类型,则继续当前链。否则,我们就要走新的链路
setSourceHelper(
_src,
src.type === _src.type ? mwrest : middlewares[_src.type],
next,
player,
acc,
lastRun
);
});
这里的 getOrCreateFactory()
函数主要是对执行过的中间件进行一个缓存,下次调用的时候就会返回之前的结果:
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 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;
}
在处理完播放源之后,就会开始陆续调用中间件的 setTech()
方法:
middleware.setTech = function setTech(middleware, tech) {
middleware.forEach((mw) => mw.setTech && mw.setTech(tech));
};
middleware.setTech = function setTech(middleware, tech) {
middleware.forEach((mw) => mw.setTech && mw.setTech(tech));
};
在播放器实例上的一些 Getters 会通过 techGet_()
方法进行处理,它会判断对应的调用是否在 Getters Map 中存在:
class Player extends Component {
techGet_(method) {
// ...
// this.middleware_ 即上面在 acc 中的中间件列表
// this.tech_ 当前加载的播放技术
if (method in middleware.allowedGetters) {
return middleware.get(this.middleware_, this.tech_, method);
} else if (method in middleware.allowedMediators) {
return middleware.mediate(this.middleware_, this.tech_, method);
}
try {
return this.tech_[method]();
} catch (e) {
// ...
}
}
}
class Player extends Component {
techGet_(method) {
// ...
// this.middleware_ 即上面在 acc 中的中间件列表
// this.tech_ 当前加载的播放技术
if (method in middleware.allowedGetters) {
return middleware.get(this.middleware_, this.tech_, method);
} else if (method in middleware.allowedMediators) {
return middleware.mediate(this.middleware_, this.tech_, method);
}
try {
return this.tech_[method]();
} catch (e) {
// ...
}
}
}
目前所支持的 Getters 包括:
const allowedGetters = {
buffered: 1,
currentTime: 1,
duration: 1,
muted: 1,
played: 1,
paused: 1,
seekable: 1,
volume: 1,
};
const allowedGetters = {
buffered: 1,
currentTime: 1,
duration: 1,
muted: 1,
played: 1,
paused: 1,
seekable: 1,
volume: 1,
};
对于这些 Getter 会优先执行 Tech 上的,然后依次从右向左依次执行,最后返回中间件处理后的结果:
function get(middleware, tech, method) {
return middleware.reduceRight(middlewareIterator(method), tech[method]());
}
function middlewareIterator(method) {
return (value, mw) => {
// 如果先前的中间件终止,后续的中间件将不会被调用
if (value === TERMINATOR) {
return TERMINATOR;
}
if (mw[method]) {
return mw[method](value);
}
return value;
};
}
function get(middleware, tech, method) {
return middleware.reduceRight(middlewareIterator(method), tech[method]());
}
function middlewareIterator(method) {
return (value, mw) => {
// 如果先前的中间件终止,后续的中间件将不会被调用
if (value === TERMINATOR) {
return TERMINATOR;
}
if (mw[method]) {
return mw[method](value);
}
return value;
};
}
类似的一些 Setters 也会调用中间件去处理,只不过调起的方式变成了 techCall_()
方法:
class Player extends Component {
techCall_(method, arg) {
// If it's not ready yet, call method when it is
this.ready(function() {
if (method in middleware.allowedSetters) {
return middleware.set(this.middleware_, this.tech_, method, arg);
} else if (method in middleware.allowedMediators) {
return middleware.mediate(this.middleware_, this.tech_, method, arg);
}
try {
if (this.tech_) {
this.tech_[method](arg);
}
} catch (e) {}
}, true);
}
}
class Player extends Component {
techCall_(method, arg) {
// If it's not ready yet, call method when it is
this.ready(function() {
if (method in middleware.allowedSetters) {
return middleware.set(this.middleware_, this.tech_, method, arg);
} else if (method in middleware.allowedMediators) {
return middleware.mediate(this.middleware_, this.tech_, method, arg);
}
try {
if (this.tech_) {
this.tech_[method](arg);
}
} catch (e) {}
}, true);
}
}
目前所支持的 Setters 包括:
export const allowedSetters = {
setCurrentTime: 1,
setMuted: 1,
setVolume: 1,
};
export const allowedSetters = {
setCurrentTime: 1,
setMuted: 1,
setVolume: 1,
};
对于这些 Setter 会先调用中间件上的方法进行处理,最后把处理后的值交给 Tech 进行处理:
function set(middleware, tech, method, arg) {
return tech[method](middleware.reduce(middlewareIterator(method), arg));
}
function set(middleware, tech, method, arg) {
return tech[method](middleware.reduce(middlewareIterator(method), arg));
}
正如上面所看到的,无论是 Getter 还是 Setter,还会判断如果它存在 Mediators 中时会调用 mediate()
方法进行处理:
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;
}
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);
}
}
}
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;
}
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);
}
}
}
内部会先遍历中间件从左到右执行 call{method}
方法,如果期间没有传递终止的信号,那么将调用 Tech 上的方法,最后再从右到左的执行中间件上的 {method}
方法。