以下包括一些常见的问题,和我们推荐的解决方案。
调整弹幕速度无需更改代码。相比之下我们更推荐使用 CommentManager.options
下调整。
相关参数是:
options.global.scale
全局生存时间加成options.scroll.scale
滚动弹幕生存时间加成
默认的弹幕生存时间是 4.000s
,加成参数叠加使用,比如如果 global.scale = a
,
scroll.scale = b
那么一条滚动弹幕(滚动,底部滚动,逆向)的总生存时间就是 4 * a * b
秒,而固定弹幕(顶部,底部)的生存时间则是 4 * a
秒。
加成数值越大, 弹幕运行速度越低。不过值得注意的是,每条弹幕的“速度”是不一样的,根据弹幕的 长度决定。这里的加成数值只改变弹幕的 “生存时间“ 。
注意:改变全局加成会改变包括高级弹幕在内的所有弹幕的生存时间。请确认你真的希望这样做才更改
global
。
如果希望实现弹幕的速度和字号同步拉伸的话,比起更改每条弹幕,我们更加推荐直接拉伸弹幕舞台/“容器”。 注意的步骤如下:
- 不要 更改弹幕速度的加成数值,弹幕大了自然速度就慢了,没必要继续降低速度。
- 首先,确定默认的容器大小,这个大小为弹幕字号 1:1 的大小。
- 注意更新 setBounds让等比放大的弹幕容器还能继续填满视频
实现的伪代码如下:
// cm.stage 的父对象或播放器容器等会变大的东西
var containerParent = document.getElementById("container-parent");
// 记住默认大小,此处亦可换成固定的默认大小
var defWidth = CM.width, defHeight = CM.height;
// 这里代表任何播放器/父对象的的resize,如果有多处可能触发resize的地方(如全屏化)都须绑定
containerParent.addEventListener('resize', function () {
var stage = CM.stage; // CommentManager 的 stage
// 计算缩放比例,只看宽度
var scale = containerParent.offsetWidth / defWidth;
var relHeight = containerParent.offsetHeight / scale;
// 把弹幕舞台设置成小版舞台
CM.setBounds(defWidth, relHeight);
stage.style.width = defWidth;
stage.style.height = relHeight;
// 用 CSS 来拉伸
stage.style.transform = "scale(" + scale + ")";
});
TBD
TBD
弹幕透明度有两个设置方法,位于
options.global.opacity
全局透明度上限options.scroll.opacity
滚动弹幕透明度上限
一般情况下,允许用户更改的透明度上限应该是滚动弹幕上限。更改全局上限的话可能引发弹幕字符画的
不利显示。注意:虽然在设置里叫 opacity
其实这个是对应弹幕的 alpha
字段。
默认情况下当两个 time()
调用提供的时间 t2 - t1 >= 2000ms
时,播放器将会判定这个操作
是因为用户在重新定位时间轴。这个操作和 t2 < t1
的调用会导致弹幕管理器清除现在显示的弹幕,
seek到距离 t2
最近的未来弹幕然后从那个位置开始继续播放。
如果你的播放器或者时钟来源通报 time
的间隔 > 2000ms
会导致管理器一直处于reset模式,
不显示任何弹幕。这种情况下可以调整 options.seekTrigger
到另一个更大的数值来避免过度积极
重置荧幕。
如果你发现播放环境用户经常需要跳跃 < 2000ms
导致大量弹幕同屏,可以考虑降低这个数值更早的触发
“用户拖动时间轴”的环境。注意当 t2 < t1
时(回到之前的播放时间),总会触发reset。
如果希望给弹幕更改显示模式(描边/字体/粗体等等),则可以采取更改 options.global.className
的方法,来直接更改未来弹幕的寄生默认 CSS 类。默认情况下,弹幕都属于 cmt
但是你可以改掉如下:
.cmt { /** 默认,描边,无粗体 **/ }
.cmt.bold { font-weight: bold; }
.cmt.shadow { /** 某些魔法的实现影子的CSS **/ }
进而通过让用户设置 options.global.className = 'cmt bold'
即可开启/关闭粗体等等。
注意:切换这个设置只会对之后生成的弹幕生效。
实现加边框很容易,不过采用了一些机智的逻辑。实现可以参考如下代码:
$('#send-button').click(function (){ // 为了简单采取类似 jQuery 的语法
var myCommentText = $('#danmaku-textarea').value;
// 检测以下是不是可以发
if (myCommentText.length === 0 || !canSend(myCommentText)) {
$('#user-notice').showNotice('弹幕空或者不符合规定'); // 提醒用户
return; // 取消发送
}
var comment = {
'text': myCommentText,
'stime': video.currentTime * 1000 - 1, // 比现在时间稍微慢一ms
'mode': 1,
'color': 0xffffff,
'border': true
};
CM.time(video.currentTime * 1000) // 强行更新以下 CM 的时间戳
CM.insert(comment); // 插入这个弹幕,因为时间 < CM内现在播放时间,所以一定不会被显示
sendCommentToServer(comment); // 把弹幕发到服务器上,注意服务器要无视 border=true 的
CM.send(comment); // 立刻显示一次自己这条弹幕,保证用户肯定会看到
//由于弹幕也被插入了 timeline, 如果用户回看,会发现弹幕依然带边框
});
实时弹幕也需要后端服务器的支持,而且比发送弹幕要复杂一些。实时弹幕可以采取 Polling(定时读取) 或者 Push Notify(监听等待)两个主动和被动模式实现。实时弹幕还有绝对实时和相对性时间轴更新 两个时间模式。
-
Polling 查询
Polling是指设计的弹幕播放器定时访问服务器,询问服务器在某个弹幕池内某段时间后新产生的弹幕, 然后把它添加到播放列表或者呈现出来的一种简单的模式。优点是:仅仅基于HTTP,可以在各种服务器 (VPS、云、共享主机)和语言(PHP,Python,Ruby,Nodejs)上实现。缺点是:需要反复联网, 效率底下,对服务器压力大。
推荐用于实时性不强的系统,比如播放非直播视频时、动态载入新弹幕。
-
Push Notify 推送
Push Notify是指在客户端连接到服务器的一个端口,在有新的弹幕时,服务器主动发送弹幕信息, 而客户端在收到信息后被动的呈现或者更新列表。优点:速度快,效率高,处理开销低。缺点: 需要用 Web Sockets 或者 Flash 作为桥,在兼容性上都有一定局限性。
推荐用于非常实时的系统,如不可回看的直播间、大型荧幕上滚动弹幕等。
时间轴模式也不一样
-
实时(无时间轴) 每当收到实时弹幕就直接显示,不保存或者非常少量缓存历史弹幕。由于没有使用时间轴,所以不能 通过 CCL 自由的回看刚刚滚过去的弹幕。优点是占用内存小,弹幕显示后就不保存在时间轴里了, 同时适合直播等没有时间轴的媒体。
-
传统(基于时间轴) 定期更新时间轴,把弹幕按顺序正确插入,保持弹幕时间轴新鲜度,可以自由更改播放时间 (快进快退)。优点是可以支持用户搓时间轴,缺点是直播环境下需要控制时间轴不能过度饱和。
// Polling example code
var hasLastCheckReturned = true; // 标记之前检测是否已经完成,避免服务器过载
var lastCheckedTime = 0; // 上次检测时间
setInterval(function() {
if (!hasLastCheckReturned) {
return; // 上次还没返回结果。放弃这次请求。
}
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.responseCode === 200) {
// 解析新增的弹幕
var danmakuList = yourFormatParser(xhr.responseText);
for (var i = 0; i < danmakuList.length; i++) {
CM.insert(danmakuList[i]); // 把增量弹幕每一个都插入
}
lastCheckedTime = Date.now(); // 更新上次检测的时间
hasLastCheckReturned = true;
} else {
// 可能出了问题
console.log(xhr.responseText);
hasLastCheckReturned = true;
}
}
};
// 告诉服务器上次检查的时间,来获取增量
xhr.open('GET', 'http://yoururl/somevideoid/?from=' + lastCheckedTime,
true);
xhr.send(); // 发送请求
hasLastCheckReturned = false;
}, 3000); // 每3s检查新的弹幕
以及:
// Push notify example code
// 基于 socket.io 和 JQuery来简化代码
var socket = io(); //开启流
socket.on('danmaku', function(data){
// 当遇到 danmaku 事件,就把推送来的弹幕推送给 CCL
var danmaku = yourFormatParser(data);
if (danmaku.hasOwnProperty('stime')) {
// 弹幕有时间轴位置,那就插入时间轴
CM.insert(danmaku);
} else {
// 弹幕没有时间轴位置就立刻显示(不记录)
CM.send(danmaku);
}
});
$('#send-danmaku-btn').click(function(){
//当按了发送弹幕的按钮
var data = {
"text": $('#send-danmaku-field').value,
"stime": myVideo.currentTime,
...
};// 通过UI获取新弹幕的信息
//包装并发射弹幕
socket.emit('danmaku', JSON.stringify(yourFormatPackager(data)));
//清除 UI 文字部分
$('#send-danmaku-field').value("");
});
实时弹幕的时间轴需求会根据实现目标不同而不同,以下是两种常见场景的实现方法。
大屏幕直播弹幕:用户使用自己的终端发送弹幕,大屏幕上实时进行显示。由于只需要实时显示(无需回到 过去的时间点),建议采用 无时间轴模式:
CM = new CommentManager(...);
CM.init();
CM.start();
// 注意我们在这种模式下无需调用time或者伪造时间轴
// 某种方式获得了新的弹幕后...(此处示例socket模式)
socket.on('danmaku', function (data) {
CM.send(commentData); // 注意这里直接用了 CM.send 全程也无需调用 time
});
网上视频直播:视频直播虽然需要随时播放弹幕,但是同时还允许用户回到过往的时间部分,看过往部分 的弹幕。这时需要有时间轴模式
CM = new CommentManager(...);
CM.init();
CM.start();
videoStream.addEventListener('timeUpdate', function () {
CM.time(videoStream.currentTime * 1000); // 时刻通知流媒体播放时间
});
socket.on('danmaku-update', function (data) {
CM.insert(data); // 注意这里是把弹幕插入时间轴。
});