我在工作时制作了一个复杂的视频播放器。
我发现自己处于以下情况:我有一个视频标签(ours
),通常用于播放任何类型的视频(我的和Ads引擎的视频)。
有时,广告引擎(加载外部脚本以显示广告)会在我们使用的视频标签上设置一个新的视频标签(theirs
)(这样我可以同时拥有两个<video>
标签)。>
DOM的情况如下:
<div id="content">
<div id="ads">
<video ...></video>
<iframe ... ></iframe>
</div>
<video ...></video>
</div>
我们的视频标签已在其实例上应用了“ hack”,以重试播放音量静音或失败的内容(由于最近的浏览器自动播放音频政策)。
实际上,无法通过广告引擎来管理video tag
,我们甚至不应该选择它,因为我们不知道它是否存在或排序(可能是最后一次机会)
有时,their
个视频标签中断了ours
的整个流程,因为它由于浏览器政策而无法播放,并且我们无法得知它是否(正确地)实施了相同(或类似)的骇客行为我们用来使播放器静音,因此它会阻塞并且ours
内容无法播放。
因此,我们的想法是无条件地在HTMLMediaElement.prototype.play
上应用该骇客(或其他版本)。
由于我们的“黑客”内部有一些指令会根据本地情况运行(不应暴露于their
视频标签,如下所示),因此我们尝试创建一个“黑客” ours
(在实例上),theirs
(+ ours
,但被替换):
const originalPlay = HTMLMediaElement.prototype.play;
// -------- PROTOTYPE OVERRIDE ATTEMPT -----------
HTMLVideoElement.prototype.play = function() {
console.log("Using ours play method.", this);
const promise = originalPlay.call(this);
if (!promise) {
return Promise.reject();
}
return promise
.catch((reason: DOMException) => {
if (!(reason && (reason.name === "AbortError" || reason.name === "NotAllowedError") && this.volume > 0)) {
return Promise.reject(reason);
}
this.volume = 0;
this.muted = true;
// Original play should not fail with volume 0;
return originalPlay.call(this);
});
}
// -------- OURS VIDEO TAG INSTANCE -----------
this.videoNode.play = () => {
console.log("Calling internal play")
const promise = originalPlay.call(this.videoNode);
if (promise) {
return promise
.then(() => {
if (this.videoNode.volume !== 0) {
this.props.setAudioPlayRightAcquired();
}
})
.catch((reason: DOMException) => {
if (this.props.didAcquireAudioPlayRight() || !(reason && (reason.name === "AbortError" || reason.name === "NotAllowedError") && this.videoNode.volume > 0)) {
return Promise.reject(reason);
}
const currentVolume = this.videoNode.volume;
if (!this.props.contentStarted) {
this.props.onVolumeChange(0);
}
// Original play should not fail with volume 0;
return originalPlay.call(this.videoNode)
.catch(() => {
// The video failed again with (probably) muted volume
// So we are recovering the volume (it doesn't make sense to keep
// it muted)
if (currentVolume !== this.videoNode.volume) {
// If we changed the volume to try again
this.props.onVolumeChange(currentVolume);
}
return Promise.reject(reason);
});
});
}
return Promise.resolve();
}
ours
hack可以正常工作,但是原型的行为却很奇怪。
如果我尝试console.log
的结果,那就是它给出的结果:
console.log(originalPlay); // ƒ play() { [ native code ] }
console.log(HTMLMediaElement.prototype.play); // ƒ play() { ... my code ... }
但是,如果我尝试选择theirs
视频标签:
document.querySelectorAll("video")[0].play; // ƒ play() { [ native code ] }
如果我尝试禁用ours
骇客并选择ours
??视频标记:
document.querySelectorAll("video")[1].play // ƒ play() { ... my code ... }
如果不禁用它,我会在ours
视频标记上应用ours
骇客。
所以我不明白为什么新的<video>
标签没有使用此方法?
奇怪的是,如果我尝试停止theirs
视频并更改DOM并添加一个新元素(因此我创建了一个新的视频实例),则对其应用了hack,并且似乎可以正常工作:实际上,它可以成功打印出我插入的console.log
。
我无法解释自己的可能性,因为我在创建their
视频标签之前创建了“ hack”(我确信这一点)。
有人有任何线索吗?谢谢。