671 lines
26 KiB
JavaScript
671 lines
26 KiB
JavaScript
"use strict";
|
||
|
||
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
||
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
|
||
function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
|
||
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
||
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
||
function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
|
||
function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
|
||
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
||
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
||
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
||
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
|
||
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
|
||
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
|
||
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
||
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
|
||
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
||
var YbDanmu = /*#__PURE__*/function () {
|
||
function YbDanmu(container, data) {
|
||
var config = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
||
_classCallCheck(this, YbDanmu);
|
||
this.container = typeof container == 'string' ? document.querySelector(container) : container;
|
||
this.config = _objectSpread(_objectSpread({}, YbDanmu.DEFAULT_CONFIG), config);
|
||
this.data = data;
|
||
this.filter = new YbDanmuFilter(this.config.filter); //过滤规则
|
||
this.list = this._filterData(data);
|
||
this.runline = []; //正在跑动的弹幕列表
|
||
this.canvas = null; //canvas元素
|
||
this.ctx = null; //canvas实例
|
||
this.paused = true; //是否暂停
|
||
this.currentTime = 0; //当前时间轴
|
||
this._animation = null; //动画实例
|
||
}
|
||
return _createClass(YbDanmu, [{
|
||
key: "load",
|
||
value:
|
||
//加载弹幕
|
||
function load() {
|
||
if (this.container) {
|
||
var canvas = document.createElement('CANVAS');
|
||
this.container.appendChild(canvas);
|
||
this.canvas = canvas;
|
||
this.ctx = canvas.getContext('2d');
|
||
this.updateSize();
|
||
}
|
||
}
|
||
//卸载弹幕
|
||
}, {
|
||
key: "unload",
|
||
value: function unload() {
|
||
this._clearAnimationFrame();
|
||
this.list = [];
|
||
this.runline = [];
|
||
if (this.ctx) this.ctx = null;
|
||
if (this.canvas) {
|
||
this.canvas.remove();
|
||
this.canvas = null;
|
||
}
|
||
}
|
||
//更新数据
|
||
}, {
|
||
key: "setData",
|
||
value: function setData(data) {
|
||
this.data = data;
|
||
this.reset();
|
||
}
|
||
//动态配置
|
||
}, {
|
||
key: "setConfig",
|
||
value: function setConfig(key, value) {
|
||
this.config[key] = value;
|
||
}
|
||
//播放弹幕
|
||
}, {
|
||
key: "play",
|
||
value: function play() {
|
||
this.paused = false;
|
||
this._render();
|
||
}
|
||
//暂停弹幕
|
||
}, {
|
||
key: "pause",
|
||
value: function pause() {
|
||
this._clearAnimationFrame();
|
||
this.paused = true;
|
||
}
|
||
//跳转弹幕
|
||
}, {
|
||
key: "seek",
|
||
value: function seek(time) {
|
||
this.currentTime = time;
|
||
this.reset();
|
||
}
|
||
//更新时间轴(需要外部更新时间轴否则弹幕不会运行)
|
||
}, {
|
||
key: "time",
|
||
value: function time(_time) {
|
||
var _this = this;
|
||
//计算本次更新时间和上次时间的时间差
|
||
var diffrenece = _time - this.currentTime;
|
||
//记录新时间
|
||
this.currentTime = _time;
|
||
//新列表
|
||
var newList = [];
|
||
//跑动列表
|
||
var runline = [];
|
||
//循环弹幕列表
|
||
this.list.forEach(function (danmu) {
|
||
//当前时间减去弹幕显示时间
|
||
var range = _this.getCurrentTime() - danmu.time;
|
||
//获取当前时间和上次时间的时间差以内的弹幕
|
||
if (range >= 0 && range < Math.abs(diffrenece)) runline.push(danmu);
|
||
//记录不在范围内的弹幕
|
||
else newList.push(danmu);
|
||
});
|
||
//去掉已经渲染的弹幕
|
||
this.list = newList;
|
||
//计算需要渲染弹幕的布局
|
||
for (var i = 0; i < runline.length; i++) {
|
||
var bar = this._layout(runline[i]);
|
||
if (bar) this.runline.push(bar);
|
||
}
|
||
//排序弹幕
|
||
this.runline.sort(function (a, b) {
|
||
return a.time - b.time;
|
||
});
|
||
}
|
||
//清空画布
|
||
}, {
|
||
key: "clear",
|
||
value: function clear() {
|
||
this.ctx && this.ctx.clearRect(0, 0, this.canvas.offsetWidth, this.canvas.offsetHeight);
|
||
}
|
||
//显示弹幕舞台
|
||
}, {
|
||
key: "show",
|
||
value: function show() {
|
||
this.container.style.visibility = 'visible';
|
||
}
|
||
//隐藏弹幕舞台
|
||
}, {
|
||
key: "hide",
|
||
value: function hide() {
|
||
this.container.style.visibility = 'hidden';
|
||
}
|
||
//更新尺寸
|
||
}, {
|
||
key: "updateSize",
|
||
value: function updateSize() {
|
||
var width = this.container.offsetWidth;
|
||
var height = this.container.offsetHeight;
|
||
var dpr = this.config.accuracy == 'auto' ? window.devicePixelRatio : this.config.accuracy;
|
||
this.canvas.width = width * dpr;
|
||
this.canvas.height = height * dpr;
|
||
this.canvas.style.width = width;
|
||
this.canvas.style.height = height;
|
||
this.ctx.scale(dpr, dpr);
|
||
}
|
||
//重置数据和画布
|
||
}, {
|
||
key: "reset",
|
||
value: function reset() {
|
||
var _this2 = this;
|
||
this.clear();
|
||
this.list = this._filterData(this.data);
|
||
this.runline = [];
|
||
var duration = this.canvas.offsetWidth / (this.config.speed * this.config.playbackRate) / 60; //滚动周期(除以60是因为为大概每秒60帧)根据滚动周期去获取可能出现在屏幕上的弹幕
|
||
var newList = [];
|
||
var runline = [];
|
||
this.list.forEach(function (danmu) {
|
||
var range = _this2.getCurrentTime() - danmu.time;
|
||
//重置数据的时候,考虑到播放进度可能在中间,未避免弹幕丢失,将时间误差控制在滚动周期以内
|
||
if (range >= 0 && range < duration) runline.push(danmu);
|
||
//记录不在范围内的弹幕
|
||
else newList.push(danmu);
|
||
});
|
||
//去掉已经渲染的弹幕
|
||
this.list = newList;
|
||
//计算渲染弹幕的布局
|
||
for (var i = 0; i < runline.length; i++) {
|
||
var bar = this._layout(runline[i]);
|
||
if (bar) this.runline.push(bar);
|
||
}
|
||
this.runline.sort(function (a, b) {
|
||
return a.time - b.time;
|
||
}); //排序弹幕
|
||
}
|
||
}, {
|
||
key: "refresh",
|
||
value: function refresh() {
|
||
this.updateSize();
|
||
this.reset();
|
||
}
|
||
//弹幕是否正在显示
|
||
}, {
|
||
key: "getVisible",
|
||
value: function getVisible() {
|
||
var _this$container$style;
|
||
return this.container ? ((_this$container$style = this.container.style) === null || _this$container$style === void 0 ? void 0 : _this$container$style.visibility) == 'visible' : false;
|
||
}
|
||
//获取时间进度
|
||
}, {
|
||
key: "getCurrentTime",
|
||
value: function getCurrentTime() {
|
||
return this.currentTime + this.config.timeDiffrence; //加上时间差
|
||
}
|
||
/**
|
||
* 发送弹幕
|
||
* @param {Object} danmu 弹幕数据
|
||
* @param {Boolean}border 使用边框
|
||
*/
|
||
}, {
|
||
key: "send",
|
||
value: function send(danmu, border) {
|
||
this.data.push(danmu);
|
||
this.runline.push(this._layout(_objectSpread(_objectSpread({}, danmu), {}, {
|
||
time: danmu.time + this.config.timeDiffrence
|
||
}), border, true)); //强制显示弹幕
|
||
}
|
||
//插入弹幕数据,但不会渲染
|
||
}, {
|
||
key: "insert",
|
||
value: function insert(danmu) {
|
||
this.data.push(danmu);
|
||
this.list.push(danmu);
|
||
}
|
||
//渲染弹幕
|
||
}, {
|
||
key: "_render",
|
||
value: function _render() {
|
||
var _this3 = this;
|
||
this._clearAnimationFrame();
|
||
if (this.paused) return;
|
||
if (this.runline.length) {
|
||
this.clear();
|
||
for (var i = 0; i < this.runline.length; i++) {
|
||
var b = this.runline[i];
|
||
//移除渲染完毕的弹幕
|
||
if (b.left + b.width <= 0 || b.boxWidth <= 0) {
|
||
this.runline.splice(i, 1);
|
||
i--;
|
||
continue;
|
||
}
|
||
// b.speed = this._detectionBump(b);//碰撞检测,保留位置,可能不需要
|
||
var speed = (this.config.speed + b.speed) * this.config.playbackRate; //弹幕随机的速度加上默认设置速度再乘以倍速,才是真正移动的速度
|
||
if (b.mode == 1 || !b.mode) b.left -= speed; //滚动弹幕将x轴位置减去速度,实现滚动动画
|
||
else b.boxWidth -= speed; //顶端和底端弹幕,将记录的画布宽度减去速度,实现渲染时长
|
||
//非滚动弹幕,当boxWidth大于画布宽度时,则不绘制,避免弹幕提前出现
|
||
if (b.mode == 1 || !b.mode || b.boxWidth <= this.canvas.offsetWidth) this._drawText(b);
|
||
}
|
||
}
|
||
this._animation = window.requestAnimationFrame(function () {
|
||
return _this3._render();
|
||
});
|
||
}
|
||
/**
|
||
* 计算弹幕布局
|
||
* @param { Object|String } danmu 弹幕对象
|
||
* @param {Boolean} border 是否使用边框
|
||
* @param {Boolean} force 是否强制显示
|
||
*/
|
||
}, {
|
||
key: "_layout",
|
||
value: function _layout(danmu, border, force) {
|
||
var ctx = this.ctx;
|
||
var canvas = this.canvas;
|
||
var config = this.config;
|
||
var boxWidth = canvas.offsetWidth;
|
||
var fontSize = danmu.fontSize || config.fontSize;
|
||
var fontFamily = danmu.fontFamily || config.fontFamily;
|
||
var fontScale = config.fontScale;
|
||
var color = danmu.color || config.color;
|
||
var mode = danmu.mode || 1;
|
||
var text = danmu.text;
|
||
var time = danmu.time;
|
||
ctx.font = "".concat(fontSize * fontScale, "px ").concat(fontFamily); //设置字体大小和样式
|
||
var width = Math.ceil(ctx.measureText(text).width); //测算文本宽度
|
||
var speed = mode == 1 ? Math.random() * 0.1 : 0; //随机移动速度(弹幕一帧移动的距离,加上随机数,可以让弹幕滚动更加有层次感)
|
||
var offset = (this.getCurrentTime() - time) * 60 * (config.speed + speed); //根据弹幕出现时间和当前时间轴的时间差,计算弹幕的初始偏移值
|
||
var left = mode == 1 ? boxWidth - offset : boxWidth / 2 - width / 2; //获取弹幕x轴位置
|
||
var track = this._getTrack(fontSize, mode, offset, force); //获取弹幕y轴位置
|
||
var top = track.top;
|
||
if (top > -1) {
|
||
return {
|
||
mode: mode,
|
||
text: text,
|
||
time: time,
|
||
fontSize: fontSize,
|
||
color: color,
|
||
top: top,
|
||
left: left,
|
||
speed: track.isOverlap ? speed + 2 + Math.random() * 0.5 : speed,
|
||
//如果是重叠的弹幕则加快速度,避免一直重叠,这样观感不好
|
||
width: width,
|
||
boxWidth: boxWidth - offset,
|
||
//顶部和底部弹幕的显示时间根据舞台宽度和移动速度决定
|
||
border: border //自己发送的弹幕需要加上边框,作区别显示
|
||
};
|
||
}
|
||
return false;
|
||
}
|
||
/**
|
||
* 绘制文字
|
||
* @param {Object} danmu 弹幕对象
|
||
* @param {String} mode 弹幕模式 1-滚动 4-底部 5-顶部
|
||
*/
|
||
}, {
|
||
key: "_drawText",
|
||
value: function _drawText(danmu) {
|
||
var mode = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
|
||
var ctx = this.ctx;
|
||
var config = this.config;
|
||
if (ctx) {
|
||
ctx.beginPath();
|
||
var fontSize = danmu.fontSize || config.fontSize;
|
||
var fontFamily = danmu.fontFamily || config.fontFamily;
|
||
var color = danmu.color || config.color;
|
||
var opacity = config.opacity;
|
||
var fontScale = config.fontScale;
|
||
var newFontSize = fontSize * fontScale;
|
||
ctx.font = "".concat(fontSize * fontScale, "px ").concat(fontFamily);
|
||
ctx.strokeStyle = YbDanmu.colorToRgba(YbDanmu.getStrokeColor(color), opacity);
|
||
ctx.strokeText(danmu.text, danmu.left, danmu.top);
|
||
ctx.fillStyle = YbDanmu.colorToRgba(color, opacity);
|
||
ctx.fillText(danmu.text, danmu.left, danmu.top);
|
||
if (danmu.border) {
|
||
//如果带边框
|
||
ctx.strokeStyle = color; //边框颜色
|
||
ctx.lineWidth = 2; //边框宽度
|
||
ctx.strokeRect(danmu.left, danmu.top - newFontSize, danmu.width, newFontSize + config.lineGap); //绘制边框
|
||
}
|
||
ctx.closePath();
|
||
}
|
||
}
|
||
/**
|
||
* 计算弹幕应该放在哪条轨道
|
||
* @param fontSize(弹幕字体大小)
|
||
* @param mode(弹幕模式)1-滚动 4-底部 5-顶部
|
||
* @param offset(弹幕初始x轴位置偏差值)
|
||
* @param force(是否强制显示,用于弹幕发送)
|
||
*/
|
||
}, {
|
||
key: "_getTrack",
|
||
value: function _getTrack(fontSize) {
|
||
var mode = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
|
||
var offset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
|
||
var force = arguments.length > 3 ? arguments[3] : undefined;
|
||
//canvas绘制文字x,y坐标是按文字左下角计算,预留30px
|
||
var canvas = this.canvas;
|
||
var isOverlap = false; //是否重叠
|
||
var nowTop = -1; //最终返回的顶部距离
|
||
var minLen = -1; //最小弹幕数量
|
||
var minTop = -1; //最小弹幕数量轨道
|
||
var boxWidth = canvas.offsetWidth;
|
||
var boxHeight = canvas.offsetHeight;
|
||
var config = this.config;
|
||
var lineGap = config.lineGap;
|
||
var top = config.top;
|
||
var bottom = config.bottom;
|
||
var fontScale = config.fontScale;
|
||
var newFontSize = fontSize * fontScale;
|
||
var reserve = 5; //画布预留位置(给画布底部流出一段距离)不预留可能导致最后一个轨道的弹幕显示不全
|
||
var trackLen = Math.floor((boxHeight - top - bottom) / (newFontSize + lineGap)); //计算轨道数量
|
||
var tracks = [];
|
||
if (mode == 4) {
|
||
//弹幕模式为底端,从底部开始加载,所以要用画布高度减去轨道高度,才是底端弹幕渲染的轨道高度
|
||
for (var i = 0; i < trackLen; i++) tracks.push(boxHeight - (i * newFontSize + i * lineGap + top + reserve)); //排列轨道
|
||
} else {
|
||
for (var _i = 0; _i < trackLen; _i++) tracks.push((_i + 1) * newFontSize + _i * lineGap + top); //排列轨道
|
||
}
|
||
for (var _i2 = 0; _i2 < tracks.length; _i2++) {
|
||
var trackTop = tracks[_i2];
|
||
//当前轨道上有多少条弹幕
|
||
var danmus = this.runline.filter(function (danmu) {
|
||
return danmu.top < trackTop + newFontSize && danmu.fontSize * fontScale + danmu.top > trackTop && danmu.mode == mode;
|
||
});
|
||
if (danmus.length < minLen || minLen == -1) {
|
||
//获取最少弹幕数量的轨道
|
||
minLen = danmus.length;
|
||
minTop = trackTop;
|
||
}
|
||
if (danmus.length == 0) {
|
||
//当前轨道没有弹幕运行,直接插入该轨道
|
||
nowTop = trackTop;
|
||
break;
|
||
} else {
|
||
if (danmus.length > 0 && mode == 1) {
|
||
//当前轨道有弹幕运行,且弹幕模式是滚动模式时
|
||
var arr = danmus.map(function (danmu) {
|
||
return danmu.left + danmu.width;
|
||
});
|
||
var max = Math.max.apply(Math, _toConsumableArray(arr)); //获取当前轨道最右的弹幕
|
||
//如果当前轨道还有空位则将弹幕放入当前轨道
|
||
if (max < boxWidth - offset - 10) {
|
||
//预留10像素间隔
|
||
nowTop = trackTop;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (nowTop == -1 && (!config.overlap || force)) {
|
||
//没有找到可插入的轨道,但没有开启防重叠或者需要强制显示弹幕,这个弹幕弹幕会无视原来的弹幕开启新的弹幕墙
|
||
nowTop = minTop; //将弹幕强制插入最少弹幕数量的轨道
|
||
isOverlap = true; //重叠弹幕
|
||
}
|
||
return {
|
||
top: nowTop + top,
|
||
//加上上边距top
|
||
isOverlap: isOverlap
|
||
};
|
||
}
|
||
//过滤数据
|
||
}, {
|
||
key: "_filterData",
|
||
value: function _filterData(data) {
|
||
var _this4 = this;
|
||
var config = this.config;
|
||
var disableScroll = config.disableScroll;
|
||
var disableTop = config.disableTop;
|
||
var disableBottom = config.disableBottom;
|
||
return YbPlayer.deepClone(data.filter(function (item) {
|
||
return (item.mode != 1 || !disableScroll) && (item.mode != 5 || !disableTop) && (item.mode != 4 || !disableBottom) && !_this4.filter.filter(item.text);
|
||
}));
|
||
}
|
||
}, {
|
||
key: "_clearAnimationFrame",
|
||
value: function _clearAnimationFrame() {
|
||
if (this._animation) {
|
||
window.cancelAnimationFrame(this._animation);
|
||
this._animation = null;
|
||
}
|
||
}
|
||
}], [{
|
||
key: "parseBiliXml",
|
||
value:
|
||
//格式化b站XML文件弹幕
|
||
function parseBiliXml(content) {
|
||
var pattern = /<d\b[^>]*>(.*?)<\/d\s*>/g;
|
||
var danmuku = [];
|
||
var match = '';
|
||
while ((match = pattern.exec(content)) !== null) {
|
||
var attr = match[0].match(/p=\"*([\s\S]*?)\"/)[1];
|
||
var attrs = attr.split(',');
|
||
danmuku.push({
|
||
mode: attrs[1],
|
||
time: Number(attrs[0]),
|
||
color: YbDanmu.decimalToRgb(Number(attrs[3])),
|
||
fontSize: attrs[2],
|
||
text: match[1]
|
||
});
|
||
}
|
||
return danmuku;
|
||
}
|
||
//格式化b站ssa文件弹幕
|
||
}, {
|
||
key: "parseBiliSsa",
|
||
value: function parseBiliSsa(content) {
|
||
var lines = content.split('\n');
|
||
var events = [];
|
||
var currentSection;
|
||
lines.forEach(function (line) {
|
||
line = line.trim();
|
||
if (line.startsWith('[') && line.endsWith(']')) {
|
||
currentSection = line.slice(1, -1).toLowerCase();
|
||
return;
|
||
}
|
||
if (currentSection === 'events') {
|
||
if (line.toLowerCase().startsWith('format:')) {
|
||
// 解析事件格式行
|
||
var format = line.slice(7).split(',').map(function (item) {
|
||
return item.trim().toLowerCase();
|
||
});
|
||
// 存储事件格式定义
|
||
} else if (line.toLowerCase().startsWith('dialogue:')) {
|
||
// 解析对话行
|
||
var dialogueValues = line.slice(10).split(',').map(function (item) {
|
||
return item.trim();
|
||
});
|
||
// 注意:文本部分可能包含逗号,所以不能简单分割
|
||
var textIndex = 9; // 根据format,文本通常是第9个字段(从0开始计数)
|
||
var text = dialogueValues.slice(9).join(','); // 合并文本部分
|
||
var dialogueData = {
|
||
start: dialogueValues[1],
|
||
end: dialogueValues[2],
|
||
style: dialogueValues[3],
|
||
text: text
|
||
};
|
||
events.push(dialogueData);
|
||
}
|
||
}
|
||
});
|
||
return events.filter(function (e) {
|
||
return !e.text.includes('\\a5\\pos');
|
||
}).map(function (e) {
|
||
var style = e.text.match(/\{*([\s\S]*?)\}/)[1];
|
||
var arr = style.split('\\');
|
||
return {
|
||
mode: 1,
|
||
time: timeToSeconds(e.start),
|
||
color: arr[2].replace('c&H', '#'),
|
||
fontSize: arr[3].replace('fs', ''),
|
||
text: e.text.replace(/\{.*?\}/g, '')
|
||
};
|
||
});
|
||
}
|
||
|
||
//根据文字颜色自动生成反色边框
|
||
}, {
|
||
key: "getStrokeColor",
|
||
value: function getStrokeColor(color) {
|
||
var r, g, b;
|
||
if (color.startsWith('#')) {
|
||
color = color.length == 7 ? color : '#' + color.slice(1, 4) + color.slice(1, 4);
|
||
r = parseInt(color.slice(1, 3), 16);
|
||
g = parseInt(color.slice(3, 5), 16);
|
||
b = parseInt(color.slice(5, 7), 16);
|
||
}
|
||
if (color.startsWith('rgb')) {
|
||
var match = color.match(/rgb\(*([\s\S]*?)\)/);
|
||
var colors = match[1].split(',');
|
||
r = colors[0];
|
||
g = colors[1];
|
||
b = colors[2];
|
||
}
|
||
var $grayLevel = r * 0.299 + g * 0.587 + b * 0.144 / 255;
|
||
//判断是否是深色
|
||
if ($grayLevel < 0.5) return '#ffffff';else return '#000000';
|
||
}
|
||
}, {
|
||
key: "colorToRgba",
|
||
value: function colorToRgba(color, opacity) {
|
||
if (color.startsWith('#')) {
|
||
color = color.length == 7 ? color : '#' + color.slice(1, 4) + color.slice(1, 4);
|
||
var _str = "rgba(";
|
||
var _r = parseInt(color.slice(1, 3), 16).toString();
|
||
var _g = parseInt(color.slice(3, 5), 16).toString();
|
||
var _b = parseInt(color.slice(5, 7), 16).toString();
|
||
_str += _r + "," + _g + "," + _b + "," + opacity + ")";
|
||
return _str;
|
||
}
|
||
if (color.startsWith('rgb')) {
|
||
var str = "rgba(";
|
||
var match = color.match(/rgb\(*([\s\S]*?)\)/);
|
||
var colors = match[1].split(',');
|
||
var r = colors[0];
|
||
var g = colors[1];
|
||
var b = colors[2];
|
||
str += r + "," + g + "," + b + "," + opacity + ")";
|
||
return str;
|
||
}
|
||
}
|
||
//16进制转rgb
|
||
}, {
|
||
key: "decimalToRgb",
|
||
value: function decimalToRgb(decimal) {
|
||
var r = decimal >> 16 & 255;
|
||
var g = decimal >> 8 & 255;
|
||
var b = decimal & 255;
|
||
return 'rgb(' + r + ',' + g + ',' + b + ')';
|
||
}
|
||
}]);
|
||
}(); //弹幕过滤
|
||
_defineProperty(YbDanmu, "DEFAULT_CONFIG", {
|
||
accuracy: 1,
|
||
//绘制精度 auto-自动使用屏幕DPI 任意整数-自定义精度 高精度会导致性能问题,谨慎使用
|
||
speed: 1,
|
||
//弹幕运行速度
|
||
playbackRate: 1,
|
||
//弹幕倍速
|
||
color: '#FFFFFF',
|
||
//弹幕默认颜色
|
||
fontSize: 18,
|
||
//弹幕默认大小
|
||
fontScale: 1,
|
||
//弹幕规格(多少倍大小)
|
||
fontFamily: 'Microsoft Yahei',
|
||
//弹幕默认字体
|
||
opacity: 1,
|
||
//弹幕透明度
|
||
top: 0,
|
||
//舞台顶部间距
|
||
bottom: 0,
|
||
//舞台底部间距
|
||
lineGap: 5,
|
||
//弹幕行间距
|
||
overlap: false,
|
||
//开启防重叠
|
||
timeDiffrence: 0,
|
||
//时间差(用于校准弹幕和视频进度的时间差,一般不需要)
|
||
disableScroll: false,
|
||
//关闭滚动弹幕
|
||
disableTop: false,
|
||
//关闭顶端弹幕
|
||
disableBottom: false,
|
||
//关闭底端弹幕
|
||
disableFilter: false,
|
||
//关闭弹幕过滤
|
||
filter: [] //过滤列表
|
||
});
|
||
var YbDanmuFilter = /*#__PURE__*/function () {
|
||
function YbDanmuFilter(list) {
|
||
_classCallCheck(this, YbDanmuFilter);
|
||
this.rules = list || [];
|
||
}
|
||
// 添加
|
||
return _createClass(YbDanmuFilter, [{
|
||
key: "add",
|
||
value: function add(rule) {
|
||
//判断是否存在同类型的相同规则
|
||
var index = this.rules.findIndex(function (r) {
|
||
return r.pattern == rule.pattern && r.type == rule.type;
|
||
});
|
||
if (index > -1) return false; //如果已经存在返回false,表示插入失败
|
||
this.rules.push(rule);
|
||
return true; //添加成功返回true
|
||
}
|
||
|
||
// 删除
|
||
}, {
|
||
key: "remove",
|
||
value: function remove(index) {
|
||
this.rules.splice(index, 1);
|
||
}
|
||
|
||
// 过滤弹幕
|
||
}, {
|
||
key: "filter",
|
||
value: function filter(danmu) {
|
||
var _iterator = _createForOfIteratorHelper(this.rules),
|
||
_step;
|
||
try {
|
||
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
||
var rule = _step.value;
|
||
if (rule.type === 'string') {
|
||
// 字符串匹配
|
||
if (danmu.includes(rule.pattern)) {
|
||
return true;
|
||
}
|
||
} else if (rule.type === 'regex') {
|
||
// 正则表达式匹配
|
||
try {
|
||
var regex = new RegExp(rule.pattern);
|
||
if (regex.test(danmu)) {
|
||
return true;
|
||
}
|
||
} catch (e) {
|
||
console.error('无效的正则表达式:', rule.pattern, e);
|
||
}
|
||
}
|
||
}
|
||
} catch (err) {
|
||
_iterator.e(err);
|
||
} finally {
|
||
_iterator.f();
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 获取所有规则
|
||
}, {
|
||
key: "getRules",
|
||
value: function getRules() {
|
||
return this.rules;
|
||
}
|
||
}]);
|
||
}(); //兼容new Function,为了挂载到window对象上
|
||
if (typeof window != 'undefined') {
|
||
window.YbDanmu = YbDanmu;
|
||
} |