Videojs 全屏时显示弹幕和发送框

因为实现方式比较不规范,会深入 Video.js 内部 dom 进行操作,不确定是否适用其它版本。

全屏弹幕

我使用 vjs 的姿势是这样的:

1
2
3
<div id="danmaku-container" ref={danmakuContainerRef} divdata-vjs-player>
<video ref={videoRef} className="video-js vjs-big-play-centered" />
</div>

运行时发现它会展开成如下 dom 结构:

可以看到,video 还有其它 vjs 组件比如 vjs-control-bar(控制条)都放在了 vjs_video_3 这个 div 下边。vjs 会操作这个 vjs_video_3,给它增删几个 class, 实现 video 全屏/非全屏的切换。

我第一次尝试时,是直接将 danmaku-container 作为弹幕的容器传递给弹幕库的,即弹幕 div 是 danmaku-container 的最后一个子元素。但是全屏时就看不见弹幕了,我尝试调整弹幕的 z-index,不起作用。这个问题到现在还没搞懂。

但是我发现,同样是 div 实现,控制条就可以总是显示在视频的上层。所以我尝试将弹幕放在跟 vjs-control-bar 同层:

1
2
3
4
danmakuRef.current = new Danmaku({
// 不通过 vjs_video_3 名字获取元素,增加通用性
container: danmakuContainerRef.current.firstElementChild
});

danmaku-containerfirstElementChild 也就是 vjs_video_3 了。

Okay,全屏时也可以看到弹幕了。但是 vjs_video_3 尺寸变化时没有通知到弹幕 div,这个需要自己处理:

1
2
3
4
5
new ResizeObserver(entries => {
if (danmakuRef.current) {
danmakuRef.current.resize();
}
}).observe(videoElement);

videoElementvideo 的对象,换成 vjs_video_3 应该也是一样的。resize() 是我用的弹幕库的接口方法,按构造时传入的容器尺寸自动计算新尺寸,省了自己传递参数。

好了!不需要调整 position 乱七八糟,到这里弹幕的显示就已经完美了。

发送框显示在视频之上

vjs 支持扩展 vjs-control-barvjs-control-bar 里有一个 vjs-custom-control-spacer 组件,可以将自己的 UI 填充到里边。但问题是控制条总共就那么宽,如果过多自定义 UI 的话,势必显得很拥挤。

所以还是基于弹幕显示的原理,组合好发送 UI 之后,把最外层的 div 添加到 vjs_video_3,这种实现方式的自由度最高:可以自定义任何尺寸和位置:

1
2
3
4
5
div.style.position = "absolute";
div.style.width = `${INPUT_DIV_WIDTH}px`;
div.style.height = `${INPUT_DIV_HEIGHT}px`;
div.style.left = `${(videoRef.current.offsetWidth-INPUT_DIV_WIDTH)/2}px`;
div.style.top = `${videoRef.current.offsetHeight-INPUT_DIV_HEIGHT}px`;

我折腾的过程中发现,vjs 同样是通过操作 vjs_video_3 的 css 类实现控制条的显示/隐藏的:

1
2
3
4
5
6
7
8
9
10
11
12
// @see https://github.com/videojs/video.js/blob/main/src/js/player.js
if (this.userActive_) {
this.userActivity_ = true;
this.removeClass('vjs-user-inactive');
this.addClass('vjs-user-active');
/**
* @event Player#useractive
* @type {EventTarget~Event}
*/
this.trigger('useractive');
return;
}

vjs 没有直接引用控制条的 dom 对象,而是通过动态生成后代选择器控制控制条的显示/隐藏

1
2
3
4
5
6
7
8
9
10
11
12
.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar {
visibility: visible;
opacity: 0;
transition: visibility 1s, opacity 1s;
}

.vjs-has-started .vjs-control-bar {
display: flex;
visibility: visible;
opacity: 1;
transition: visibility 0.1s, opacity 0.1s;
}

这个信息有大用,因为我可以将我发送框 div 的类名指定为 vjs-control-bar,这样,输入框的显示/隐藏就可以跟控制条保持一致了!不需要再自己写控制逻辑了!

web 真好玩~

评论