@oplayer/danmaku
Version:
Danmaku plugin for oplayer
1,119 lines (1,118 loc) • 42.4 kB
JavaScript
/**
* name: @oplayer/danmaku
* version: v1.2.26-beta.0
* description: Danmaku plugin for oplayer
* author: shiyiya
* homepage: https://github.com/shiyiya/oplayer
*/
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
import { $, isMobile } from "@oplayer/core";
var transform = function() {
if (typeof document === "undefined") return "transform";
var properties2 = [
"oTransform",
// Opera 11.5
"msTransform",
// IE 9
"mozTransform",
"webkitTransform",
"transform"
];
var style = document.createElement("div").style;
for (var i = 0; i < properties2.length; i++) {
if (properties2[i] in style) {
return properties2[i];
}
}
return "transform";
}();
function createCommentNode(cmt) {
var node = document.createElement("div");
node.style.cssText = "position:absolute;";
if (typeof cmt.render === "function") {
var $el = cmt.render();
if ($el instanceof HTMLElement) {
node.appendChild($el);
return node;
}
}
node.textContent = cmt.text;
if (cmt.style) {
for (var key in cmt.style) {
node.style[key] = cmt.style[key];
}
}
return node;
}
function init() {
var stage = document.createElement("div");
stage.style.cssText = "overflow:hidden;white-space:nowrap;transform:translateZ(0);";
return stage;
}
function clear(stage) {
var lc = stage.lastChild;
while (lc) {
stage.removeChild(lc);
lc = stage.lastChild;
}
}
function resize(stage, width, height) {
stage.style.width = width + "px";
stage.style.height = height + "px";
}
function framing() {
}
function setup(stage, comments) {
var df = document.createDocumentFragment();
var i = 0;
var cmt = null;
for (i = 0; i < comments.length; i++) {
cmt = comments[i];
cmt.node = cmt.node || createCommentNode(cmt);
df.appendChild(cmt.node);
}
if (comments.length) {
stage.appendChild(df);
}
for (i = 0; i < comments.length; i++) {
cmt = comments[i];
cmt.width = cmt.width || cmt.node.offsetWidth;
cmt.height = cmt.height || cmt.node.offsetHeight;
}
}
function render(stage, cmt) {
cmt.node.style[transform] = "translate(" + cmt.x + "px," + cmt.y + "px)";
}
function remove(stage, cmt) {
stage.removeChild(cmt.node);
if (!this.media) {
cmt.node = null;
}
}
var domEngine = {
name: "dom",
init,
clear,
resize,
framing,
setup,
render,
remove
};
var dpr = typeof window !== "undefined" && window.devicePixelRatio || 1;
var canvasHeightCache = /* @__PURE__ */ Object.create(null);
function canvasHeight(font, fontSize) {
if (canvasHeightCache[font]) {
return canvasHeightCache[font];
}
var height = 12;
var regex = /(\d+(?:\.\d+)?)(px|%|em|rem)(?:\s*\/\s*(\d+(?:\.\d+)?)(px|%|em|rem)?)?/;
var p = font.match(regex);
if (p) {
var fs = p[1] * 1 || 10;
var fsu = p[2];
var lh = p[3] * 1 || 1.2;
var lhu = p[4];
if (fsu === "%") fs *= fontSize.container / 100;
if (fsu === "em") fs *= fontSize.container;
if (fsu === "rem") fs *= fontSize.root;
if (lhu === "px") height = lh;
if (lhu === "%") height = fs * lh / 100;
if (lhu === "em") height = fs * lh;
if (lhu === "rem") height = fontSize.root * lh;
if (lhu === void 0) height = fs * lh;
}
canvasHeightCache[font] = height;
return height;
}
function createCommentCanvas(cmt, fontSize) {
if (typeof cmt.render === "function") {
var cvs = cmt.render();
if (cvs instanceof HTMLCanvasElement) {
cmt.width = cvs.width;
cmt.height = cvs.height;
return cvs;
}
}
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
var style = cmt.style || {};
style.font = style.font || "10px sans-serif";
style.textBaseline = style.textBaseline || "bottom";
var strokeWidth = style.lineWidth * 1;
strokeWidth = strokeWidth > 0 && strokeWidth !== Infinity ? Math.ceil(strokeWidth) : !!style.strokeStyle * 1;
ctx.font = style.font;
cmt.width = cmt.width || Math.max(1, Math.ceil(ctx.measureText(cmt.text).width) + strokeWidth * 2);
cmt.height = cmt.height || Math.ceil(canvasHeight(style.font, fontSize)) + strokeWidth * 2;
canvas.width = cmt.width * dpr;
canvas.height = cmt.height * dpr;
ctx.scale(dpr, dpr);
for (var key in style) {
ctx[key] = style[key];
}
var baseline = 0;
switch (style.textBaseline) {
case "top":
case "hanging":
baseline = strokeWidth;
break;
case "middle":
baseline = cmt.height >> 1;
break;
default:
baseline = cmt.height - strokeWidth;
}
if (style.strokeStyle) {
ctx.strokeText(cmt.text, strokeWidth, baseline);
}
ctx.fillText(cmt.text, strokeWidth, baseline);
return canvas;
}
function computeFontSize(el) {
return window.getComputedStyle(el, null).getPropertyValue("font-size").match(/(.+)px/)[1] * 1;
}
function init$1(container) {
var stage = document.createElement("canvas");
stage.context = stage.getContext("2d");
stage._fontSize = {
root: computeFontSize(document.getElementsByTagName("html")[0]),
container: computeFontSize(container)
};
return stage;
}
function clear$1(stage, comments) {
stage.context.clearRect(0, 0, stage.width, stage.height);
for (var i = 0; i < comments.length; i++) {
comments[i].canvas = null;
}
}
function resize$1(stage, width, height) {
stage.width = width * dpr;
stage.height = height * dpr;
stage.style.width = width + "px";
stage.style.height = height + "px";
}
function framing$1(stage) {
stage.context.clearRect(0, 0, stage.width, stage.height);
}
function setup$1(stage, comments) {
for (var i = 0; i < comments.length; i++) {
var cmt = comments[i];
cmt.canvas = createCommentCanvas(cmt, stage._fontSize);
}
}
function render$1(stage, cmt) {
stage.context.drawImage(cmt.canvas, cmt.x * dpr, cmt.y * dpr);
}
function remove$1(stage, cmt) {
cmt.canvas = null;
}
var canvasEngine = {
name: "canvas",
init: init$1,
clear: clear$1,
resize: resize$1,
framing: framing$1,
setup: setup$1,
render: render$1,
remove: remove$1
};
var raf = typeof window !== "undefined" && (window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame) || function(cb) {
return setTimeout(cb, 50 / 3);
};
var caf = typeof window !== "undefined" && (window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame) || clearTimeout;
function binsearch(arr, prop, key) {
var mid = 0;
var left = 0;
var right = arr.length;
while (left < right - 1) {
mid = left + right >> 1;
if (key >= arr[mid][prop]) {
left = mid;
} else {
right = mid;
}
}
if (arr[left] && key < arr[left][prop]) {
return left;
}
return right;
}
function formatMode(mode) {
if (!/^(ltr|top|bottom)$/i.test(mode)) {
return "rtl";
}
return mode.toLowerCase();
}
function collidableRange() {
var max = 9007199254740991;
return [{
range: 0,
time: -9007199254740991,
width: max,
height: 0
}, {
range: max,
time: max,
width: 0,
height: 0
}];
}
function resetSpace(space) {
space.ltr = collidableRange();
space.rtl = collidableRange();
space.top = collidableRange();
space.bottom = collidableRange();
}
function now() {
return typeof window.performance !== "undefined" && window.performance.now ? window.performance.now() : Date.now();
}
function allocate(cmt) {
var that = this;
var ct = this.media ? this.media.currentTime : now() / 1e3;
var pbr = this.media ? this.media.playbackRate : 1;
function willCollide(cr2, cmt2) {
if (cmt2.mode === "top" || cmt2.mode === "bottom") {
return ct - cr2.time < that._.duration;
}
var crTotalWidth = that._.width + cr2.width;
var crElapsed = crTotalWidth * (ct - cr2.time) * pbr / that._.duration;
if (cr2.width > crElapsed) {
return true;
}
var crLeftTime = that._.duration + cr2.time - ct;
var cmtTotalWidth = that._.width + cmt2.width;
var cmtTime = that.media ? cmt2.time : cmt2._utc;
var cmtElapsed = cmtTotalWidth * (ct - cmtTime) * pbr / that._.duration;
var cmtArrival = that._.width - cmtElapsed;
var cmtArrivalTime = that._.duration * cmtArrival / (that._.width + cmt2.width);
return crLeftTime > cmtArrivalTime;
}
var crs = this._.space[cmt.mode];
var last = 0;
var curr = 0;
for (var i = 1; i < crs.length; i++) {
var cr = crs[i];
var requiredRange = cmt.height;
if (cmt.mode === "top" || cmt.mode === "bottom") {
requiredRange += cr.height;
}
if (cr.range - cr.height - crs[last].range >= requiredRange) {
curr = i;
break;
}
if (willCollide(cr, cmt)) {
last = i;
}
}
var channel = crs[last].range;
var crObj = {
range: channel + cmt.height,
time: this.media ? cmt.time : cmt._utc,
width: cmt.width,
height: cmt.height
};
crs.splice(last + 1, curr - last - 1, crObj);
if (cmt.mode === "bottom") {
return this._.height - cmt.height - channel % this._.height;
}
return channel % (this._.height - cmt.height);
}
function createEngine(framing2, setup2, render2, remove2) {
return function(_timestamp) {
framing2(this._.stage);
var timestamp = _timestamp || now();
var dn = timestamp / 1e3;
var ct = this.media ? this.media.currentTime : dn;
var pbr = this.media ? this.media.playbackRate : 1;
var cmt = null;
var cmtt = 0;
var i = 0;
for (i = this._.runningList.length - 1; i >= 0; i--) {
cmt = this._.runningList[i];
cmtt = this.media ? cmt.time : cmt._utc;
if (ct - cmtt > this._.duration) {
remove2(this._.stage, cmt);
this._.runningList.splice(i, 1);
}
}
var pendingList = [];
while (this._.position < this.comments.length) {
cmt = this.comments[this._.position];
cmtt = this.media ? cmt.time : cmt._utc;
if (cmtt >= ct) {
break;
}
if (ct - cmtt > this._.duration) {
++this._.position;
continue;
}
if (this.media) {
cmt._utc = dn - (this.media.currentTime - cmt.time);
}
pendingList.push(cmt);
++this._.position;
}
setup2(this._.stage, pendingList);
for (i = 0; i < pendingList.length; i++) {
cmt = pendingList[i];
cmt.y = allocate.call(this, cmt);
this._.runningList.push(cmt);
}
for (i = 0; i < this._.runningList.length; i++) {
cmt = this._.runningList[i];
var totalWidth = this._.width + cmt.width;
var elapsed = totalWidth * (dn - cmt._utc) * pbr / this._.duration;
if (cmt.mode === "ltr") cmt.x = elapsed - cmt.width;
if (cmt.mode === "rtl") cmt.x = this._.width - elapsed;
if (cmt.mode === "top" || cmt.mode === "bottom") {
cmt.x = this._.width - cmt.width >> 1;
}
render2(this._.stage, cmt);
}
};
}
function play() {
if (!this._.visible || !this._.paused) {
return this;
}
this._.paused = false;
if (this.media) {
for (var i = 0; i < this._.runningList.length; i++) {
var cmt = this._.runningList[i];
cmt._utc = now() / 1e3 - (this.media.currentTime - cmt.time);
}
}
var that = this;
var engine = createEngine(this._.engine.framing.bind(this), this._.engine.setup.bind(this), this._.engine.render.bind(this), this._.engine.remove.bind(this));
function frame(timestamp) {
engine.call(that, timestamp);
that._.requestID = raf(frame);
}
this._.requestID = raf(frame);
return this;
}
function pause() {
if (!this._.visible || this._.paused) {
return this;
}
this._.paused = true;
caf(this._.requestID);
this._.requestID = 0;
return this;
}
function seek() {
if (!this.media) {
return this;
}
this.clear();
resetSpace(this._.space);
var position = binsearch(this.comments, "time", this.media.currentTime);
this._.position = Math.max(0, position - 1);
return this;
}
function bindEvents(_) {
_.play = play.bind(this);
_.pause = pause.bind(this);
_.seeking = seek.bind(this);
this.media.addEventListener("play", _.play);
this.media.addEventListener("pause", _.pause);
this.media.addEventListener("playing", _.play);
this.media.addEventListener("waiting", _.pause);
this.media.addEventListener("seeking", _.seeking);
}
function unbindEvents(_) {
this.media.removeEventListener("play", _.play);
this.media.removeEventListener("pause", _.pause);
this.media.removeEventListener("playing", _.play);
this.media.removeEventListener("waiting", _.pause);
this.media.removeEventListener("seeking", _.seeking);
_.play = null;
_.pause = null;
_.seeking = null;
}
function init$2(opt) {
this._ = {};
this.container = opt.container || document.createElement("div");
this.media = opt.media;
this._.visible = true;
{
this.engine = (opt.engine || "DOM").toLowerCase();
this._.engine = this.engine === "canvas" ? canvasEngine : domEngine;
}
this._.requestID = 0;
this._.speed = Math.max(0, opt.speed) || 144;
this._.duration = 4;
this.comments = opt.comments || [];
this.comments.sort(function(a, b) {
return a.time - b.time;
});
for (var i = 0; i < this.comments.length; i++) {
this.comments[i].mode = formatMode(this.comments[i].mode);
}
this._.runningList = [];
this._.position = 0;
this._.paused = true;
if (this.media) {
this._.listener = {};
bindEvents.call(this, this._.listener);
}
this._.stage = this._.engine.init(this.container);
this._.stage.style.cssText += "position:relative;pointer-events:none;";
this.resize();
this.container.appendChild(this._.stage);
this._.space = {};
resetSpace(this._.space);
if (!this.media || !this.media.paused) {
seek.call(this);
play.call(this);
}
return this;
}
function destroy() {
if (!this.container) {
return this;
}
pause.call(this);
this.clear();
this.container.removeChild(this._.stage);
if (this.media) {
unbindEvents.call(this, this._.listener);
}
for (var key in this) {
if (Object.prototype.hasOwnProperty.call(this, key)) {
this[key] = null;
}
}
return this;
}
var properties = ["mode", "time", "text", "render", "style"];
function emit(obj) {
if (!obj || Object.prototype.toString.call(obj) !== "[object Object]") {
return this;
}
var cmt = {};
for (var i = 0; i < properties.length; i++) {
if (obj[properties[i]] !== void 0) {
cmt[properties[i]] = obj[properties[i]];
}
}
cmt.text = (cmt.text || "").toString();
cmt.mode = formatMode(cmt.mode);
cmt._utc = now() / 1e3;
if (this.media) {
var position = 0;
if (cmt.time === void 0) {
cmt.time = this.media.currentTime;
position = this._.position;
} else {
position = binsearch(this.comments, "time", cmt.time);
if (position < this._.position) {
this._.position += 1;
}
}
this.comments.splice(position, 0, cmt);
} else {
this.comments.push(cmt);
}
return this;
}
function show() {
if (this._.visible) {
return this;
}
this._.visible = true;
if (this.media && this.media.paused) {
return this;
}
seek.call(this);
play.call(this);
return this;
}
function hide() {
if (!this._.visible) {
return this;
}
pause.call(this);
this.clear();
this._.visible = false;
return this;
}
function clear$2() {
this._.engine.clear(this._.stage, this._.runningList);
this._.runningList = [];
return this;
}
function resize$2() {
this._.width = this.container.offsetWidth;
this._.height = this.container.offsetHeight;
this._.engine.resize(this._.stage, this._.width, this._.height);
this._.duration = this._.width / this._.speed;
return this;
}
var speed = {
get: function() {
return this._.speed;
},
set: function(s) {
if (typeof s !== "number" || isNaN(s) || !isFinite(s) || s <= 0) {
return this._.speed;
}
this._.speed = s;
if (this._.width) {
this._.duration = this._.width / s;
}
return s;
}
};
function Danmaku$1(opt) {
opt && init$2.call(this, opt);
}
Danmaku$1.prototype.destroy = function() {
return destroy.call(this);
};
Danmaku$1.prototype.emit = function(cmt) {
return emit.call(this, cmt);
};
Danmaku$1.prototype.show = function() {
return show.call(this);
};
Danmaku$1.prototype.hide = function() {
return hide.call(this);
};
Danmaku$1.prototype.clear = function() {
return clear$2.call(this);
};
Danmaku$1.prototype.resize = function() {
return resize$2.call(this);
};
Object.defineProperty(Danmaku$1.prototype, "speed", speed);
function getMode(key) {
switch (key) {
case 1:
case 2:
case 3:
return "rtl";
case 4:
return "bottom";
case 5:
return "top";
case 6:
return "ltr";
case 7:
return "top";
default:
return "top";
}
}
function danmakuParseFromXml(xmlString) {
const matches = xmlString.matchAll(/<d (?:.*? )??p="(?<p>.+?)"(?: .*?)?>(?<text>.+?)<\/d>/gs);
const result = [];
Array.from(matches).forEach((match) => {
var _a, _b, _c, _d;
const p = (_b = (_a = match.groups) == null ? void 0 : _a["p"]) == null ? void 0 : _b.split(",");
let text = (_d = (_c = match.groups) == null ? void 0 : _c["text"]) == null ? void 0 : _d.trim();
if (p && p.length >= 8 && text) {
const modeId = Number(p[1]);
const style = {
fontSize: Number(p[2]) + "px",
color: "#" + Number(p[3]).toString(16)
};
if (modeId >= 7) {
const styledText = JSON.parse(text);
text = styledText[4];
if (styledText[12]) style.fontFamily = styledText[12];
if (modeId == 7) {
style.position = "absolute";
style.left = styledText[0] + "px";
style.right = styledText[1] + "px";
}
}
result.push({
text,
style,
mode: getMode(Number(modeId)),
time: Number(p[0])
});
}
});
return result;
}
function danmakuParseFromUrl(src) {
return fetch(src).then((res) => res.text()).then((xmlString) => danmakuParseFromXml(xmlString));
}
const danmakuSvg = '<svg viewBox="0 0 1024 1024"><path d="M800 128H224C134.4 128 64 198.4 64 288v448c0 89.6 70.4 160 160 160h576c89.6 0 160-70.4 160-160V288c0-89.6-70.4-160-160-160z m96 608c0 54.4-41.6 96-96 96H224c-54.4 0-96-41.6-96-96V288c0-54.4 41.6-96 96-96h576c54.4 0 96 41.6 96 96v448z"></path><path d="M240 384h64v64h-64zM368 384h384v64h-384zM432 576h352v64h-352zM304 576h64v64h-64z"></path></svg>';
function registerInputStyle() {
const modeSelectionWrap = $.css("\n display: none;\n box-sizing: border-box;\n width: 216px;\n height: auto;\n padding: 2px 0 0;\n position: absolute;\n bottom: 100%;\n left: 50%;\n margin-left: -108px;\n background: rgba(21,21,21,.9);\n border-radius: 2px;\n user-select: none;");
const modeSelectionRowSelection = $.css("\n display: flex;\n flex-wrap: wrap;\n margin: 8px -8px 0 0;");
const danmakuTypeWrap = $.css({
width: "30px",
height: "30px",
padding: "3px 0",
"line-height": "30px",
"text-align": "center",
color: "hsla(0,0%,100%,.8)",
fill: "#757575",
position: "relative",
"box-sizing": "border-box",
cursor: "pointer",
"&:hover": {
fill: "var(--primary-color)"
},
["&:hover ." + modeSelectionWrap]: {
display: "block"
},
["& ." + modeSelectionRowSelection + " label"]: {
flex: 1,
"margin-bottom": "8px"
},
'& input[type="radio"]': {
display: "none"
},
'& input[type="radio"]:checked + label > div': {
background: "var(--primary-color)"
}
});
const danmakuType = $.css("\n display: inline-flex;\n height: 100%;\n align-items: center;\n ");
const danmakuTypeIcon = $.css("\n display: block;\n width: 36px;\n height: 24px;\n ");
const danmakuTypeSvg = $.css("width: 100%;height: 100%;transition: fill .15s ease-in-out;");
const modeSelectionRow = $.css("\n min-height: 22px;\n margin: 10px 20px;\n width: 176px;\n line-height: 22px;\n font-size: 12px;");
const modeSelectionRowTitle = $.css("text-align: left;color: #fff; line-height: 16px;");
const modeSelectionSpan = $.css("\n position: relative;\n cursor: pointer;\n border-radius: 2px;\n color: #fff;\n text-align: center;\n margin-right: 8px;\n background: hsla(0,0%,100%,.2);\n font-size: 12px;\n ");
const colorPickerInput = $.css("\n outline: none;\n background-color: transparent;\n color: #fff;\n border: 1px solid hsla(0,0%,100%,.2);\n border-radius: 2px;\n padding: 4px 7px;\n margin: 0 auto 8px auto;\n transition: background .2s;\n color:#000;background:#fff;text-shadow: 0px 0px 6px #FFF;text-align: center;\n ");
const colorPickerWrap = $.css("display: grid;grid-template-columns: repeat(7,auto);width: 100%;justify-items: center;");
const activeColor = $.css("box-shadow: 0 0 1px 1px #fff;border-color: #000;");
const colorPicker = $.css("\n width: 16px;\n height: 16px;\n border: 1px solid rgba(0,0,0,.3);\n box-sizing: border-box;\n border-radius: 2px;\n margin-bottom: 4px;\n cursor: pointer;\n display: inline-block;\n ");
const inputBar = $.css("\n min-width: 25em;\n border-radius: 6px;\n background: #f4f4f4;\n color: #999;\n height:85%;\n");
const inputBarWrap = $.css("\n flex: 1;\n display: flex;\n align-items: center;\n");
const input = $.css("\n flex-grow: 1;\n padding: 0 8px;\n height: 28px;\n border: 0;\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n background: none;\n line-height: 28px;\n color: #212121;\n font-size: 12px;\n text-decoration: none;\n outline: none;\n touch-action: manipulation;\n");
const send = $.css("\n height: 100%;\n width: 62px;\n min-width: 62px;\n border-radius: 0 6px 6px 0;\n cursor: pointer;\n width: 60px;\n min-width: 60px;\n box-sizing: border-box;\n overflow: hidden;\n");
const sendBottom = $.css("\n height: 100%;\n background-color: var(--primary-color,#6668ab);\n color: #fff;\n min-width: 60px;\n outline: none;\n font-size: 14px;\n display: flex;\n align-items: center;\n justify-content: center;\n");
return {
inputBar,
inputBarWrap,
input,
send,
sendBottom,
danmakuTypeWrap,
danmakuType,
danmakuTypeIcon,
danmakuTypeSvg,
modeSelectionWrap,
modeSelectionRow,
modeSelectionRowTitle,
modeSelectionRowSelection,
modeSelectionSpan,
colorPicker,
colorPickerInput,
colorPickerWrap,
activeColor
};
}
function registerInput(player, danmaku, options) {
if (!player.context.ui) return;
const _registerInputStyle = registerInputStyle(), inputBar = _registerInputStyle.inputBar, inputBarWrap = _registerInputStyle.inputBarWrap, input = _registerInputStyle.input, send = _registerInputStyle.send, sendBottom = _registerInputStyle.sendBottom, danmakuTypeWrap = _registerInputStyle.danmakuTypeWrap, danmakuType = _registerInputStyle.danmakuType, danmakuTypeIcon = _registerInputStyle.danmakuTypeIcon, danmakuTypeSvg = _registerInputStyle.danmakuTypeSvg, modeSelectionWrap = _registerInputStyle.modeSelectionWrap, modeSelectionRow = _registerInputStyle.modeSelectionRow, modeSelectionRowTitle = _registerInputStyle.modeSelectionRowTitle, modeSelectionRowSelection = _registerInputStyle.modeSelectionRowSelection, modeSelectionSpan = _registerInputStyle.modeSelectionSpan, colorPickerWrap = _registerInputStyle.colorPickerWrap, colorPicker = _registerInputStyle.colorPicker, colorPickerInput = _registerInputStyle.colorPickerInput, activeColor = _registerInputStyle.activeColor;
const $tpl = $.create("div." + inputBar, {}, '<div class="' + inputBarWrap + '">\n <div class="' + danmakuTypeWrap + '">\n <span class="' + danmakuType + '">\n <span class="' + danmakuTypeIcon + '">\n <svg viewBox="0 0 22 22" class="' + danmakuTypeSvg + '">\n <path d="M17 16H5c-.55 0-1 .45-1 1s.45 1 1 1h12c.55 0 1-.45 1-1s-.45-1-1-1zM6.96 15c.39 0 .74-.24.89-.6l.65-1.6h5l.66 1.6c.15.36.5.6.89.6.69 0 1.15-.71.88-1.34l-3.88-8.97C11.87 4.27 11.46 4 11 4s-.87.27-1.05.69l-3.88 8.97c-.27.63.2 1.34.89 1.34zM11 5.98L12.87 11H9.13L11 5.98z" />\n </svg>\n </span>\n </span class="' + inputBarWrap + '">\n <div class="' + modeSelectionWrap + '">\n <div class="' + modeSelectionRow + '">\n <div class="' + modeSelectionRowTitle + '">字号</div>\n <div class="' + modeSelectionRowSelection + '">\n <input type="radio" id="font-size-s" name="font-size" value="18">\n <label for=font-size-s>\n <div class="' + modeSelectionSpan + '" data-type="fontsize">\n <span>小</span>\n </div>\n </label>\n <input type="radio" id="font-size-m" name="font-size" value="25" checked>\n <label for=font-size-m>\n <div class="' + modeSelectionSpan + '" data-type="fontsize">\n <span>标准</span>\n </div>\n </label>\n </div>\n </div>\n <div class="' + modeSelectionRow + '">\n <div class="' + modeSelectionRowTitle + '">模式</div>\n <div class="' + modeSelectionRowSelection + '">\n <input type="radio" id="mode-s" name="mode" value="1" checked>\n <label for="mode-s">\n <div class="' + modeSelectionSpan + '" data-type="mode">\n <span>滚动</span>\n </div>\n </label>\n <input type="radio" id="mode-t" name="mode" value="5">\n <label for="mode-t">\n <div class="' + modeSelectionSpan + '" data-type="mode">\n <span>顶部</span>\n </div>\n </label>\n <input type="radio" id="mode-b" name="mode" value="4">\n <label for="mode-b">\n <div class="' + modeSelectionSpan + '" data-type="mode">\n <span>底部</span>\n </div>\n </label>\n </div>\n </div>\n <div class="' + modeSelectionRow + '">\n <div class="' + modeSelectionRowTitle + '">颜色</div>\n <div class="' + modeSelectionRowSelection + '">\n <input class="' + colorPickerInput + '" value="#FFFFFF" >\n <div class="' + colorPickerWrap + '">\n ' + ["#FE0302", "#FF7204", "#FFAA02", "#FFD302", "#FFFF00", "#A0EE00", "#00CD00", "#019899", "#4266BE", "#89D5FF", "#CC0273", "#222222", "#9B9B9B", "#FFFFFF"].map((color, i) => {
return '<span class="' + colorPicker + " " + (i == 13 ? activeColor : "") + '" style="background-color: ' + color + ';" data-value="' + color + '"></span>';
}).join("") + '\n </div>\n </div>\n </div>\n </div>\n </div>\n <input class="' + input + '" placeholder="发个友善的弹幕见证当下"/>\n </div>\n <div class="' + send + '">\n <div class="' + sendBottom + '">发送</div>\n </div>');
const $colorPickerInput = $tpl.querySelector("." + colorPickerInput);
const $colorPickerWrap = $tpl.querySelector("." + colorPickerWrap);
$colorPickerInput.oninput = function(e) {
e.target.style.backgroundColor = e.target.value;
};
$colorPickerWrap.onclick = function(e) {
const target = e.target;
if (target.tagName == "SPAN") {
$colorPickerInput.style.backgroundColor = $colorPickerInput.value = target.getAttribute("data-value");
Array.from(target.parentElement.children).forEach((it) => it.classList.remove(activeColor));
target.classList.add(activeColor);
}
};
const parent = player.context.ui.$controllerBottom;
const $input = $tpl.querySelector("." + input);
parent.insertBefore($tpl, parent.children[1]);
function submit() {
var _a;
const fontSize = $tpl.querySelector('input[name="font-size"]:checked').value;
const mode = $tpl.querySelector('input[name="mode"]:checked').value;
const color = $tpl.querySelector("." + colorPickerInput).value || "#FFFFFF";
if ($input.value) {
const comment = {
mode: getMode(+mode),
text: $input.value,
time: player.currentTime,
style: {
color,
fontSize: fontSize + "px"
}
};
if (((_a = options.onEmit) == null ? void 0 : _a.call(options, comment)) || true) {
const primaryColor = window.getComputedStyle(player.context.ui.$root).getPropertyValue("--primary-color");
comment.style.border = "2px solid " + primaryColor;
comment.style.marginTop = "4px";
danmaku.emit(comment);
$input.value = "";
$input.blur();
}
}
}
$tpl.querySelector("." + sendBottom).addEventListener("click", submit);
$input.addEventListener("keypress", (e) => {
if (e.key === "Enter") submit();
});
}
const lib = {
map(value, inMin, inMax, outMin, outMax) {
return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
},
range(start, end, tick) {
const s = Math.round(start / tick) * tick;
return Array.from({
length: Math.floor((end - start) / tick)
}, (_, k) => {
return k * tick + s;
});
}
};
const line = (pointA, pointB) => {
const lengthX = pointB[0] - pointA[0];
const lengthY = pointB[1] - pointA[1];
return {
length: Math.sqrt(Math.pow(lengthX, 2) + Math.pow(lengthY, 2)),
angle: Math.atan2(lengthY, lengthX)
};
};
class Heatmap {
constructor(player, danmaku, heatmap, customHeatmap) {
__publicField(this, "$root");
__publicField(this, "$start");
__publicField(this, "$stop");
__publicField(this, "loaded", false);
__publicField(this, "_update", () => {
const player = this.player, $start = this.$start, $stop = this.$stop;
$start == null ? void 0 : $start.setAttribute("offset", player.currentTime / player.duration * 100 + "%");
$stop == null ? void 0 : $stop.setAttribute("offset", player.currentTime / player.duration * 100 + "%");
});
this.player = player;
this.danmaku = danmaku;
this.heatmap = heatmap;
this.customHeatmap = customHeatmap;
if (!player.context.ui.$progress) return;
const $progress = player.context.ui.$progress.firstElementChild;
const $root = document.createElement("div");
this.$root = $root;
$root.style.cssText = "position:absolute;bottom:0.33em;height: 8em;width:100%;pointer-events:none;";
$progress.insertBefore($root, $progress.firstChild);
if (heatmap && (danmaku.comments.length || (customHeatmap == null ? void 0 : customHeatmap.length))) {
this.renderHeatmap();
this.enable();
}
player.on("videosourcechange", () => {
this.disable();
this.loaded = false;
this.customHeatmap = [];
this.$root.innerHTML = "";
player.off("timeupdate", this._update);
player.off("seeked", this._update);
});
}
enable(customHeatmap) {
if (customHeatmap) this.customHeatmap = customHeatmap;
const player = this.player, $root = this.$root;
$root.style.display = "block";
if (!this.loaded) {
if (isNaN(player.duration)) {
player.on("metadataloaded", () => {
this.renderHeatmap();
});
} else {
this.renderHeatmap();
}
}
player.on(["timeupdate", "seeked"], this._update);
}
disable() {
const player = this.player, $root = this.$root;
$root.style.display = "none";
player.off("timeupdate", this._update);
player.off("seeked", this._update);
}
renderHeatmap() {
const player = this.player, customHeatmap = this.customHeatmap, danmaku = this.danmaku, $root = this.$root;
if (!this.heatmap || !(danmaku.comments.length || (customHeatmap == null ? void 0 : customHeatmap.length))) {
return;
}
this.loaded = true;
const h = $root.offsetHeight, w = $root.offsetWidth;
const options = {
xMin: 0,
xMax: w,
yMin: 0,
yMax: 128,
scale: 0.25,
opacity: 0.5,
minHeight: Math.floor(h * 0.05),
sampling: Math.floor(w / 100),
fill: "rgba(255, 255, 255, 0.5)",
smoothing: 0.2,
flattening: 0.2
};
const points = Array.isArray(customHeatmap) ? customHeatmap : [];
if (!points.length) {
const gap = player.duration / w;
for (let x = 0; x <= w; x += options.sampling) {
const y = danmaku.comments.filter((_ref) => {
let time = _ref.time;
return !!time && time > x * gap && time <= (x + options.sampling) * gap;
}).length;
points.push([x, y]);
}
}
const _points = points[points.length - 1], lastX = _points[0], lastY = _points[1];
if (lastX !== w) {
points.push([w, lastY]);
}
const yPoints = points.map((point) => point[1]);
const yMin = Math.min.apply(Math, yPoints);
const yMax = Math.max.apply(Math, yPoints);
const yMid = (yMin + yMax) / 2;
for (let i = 0; i < points.length; i++) {
const point = points[i];
const y = point[1];
point[1] = y * (y > yMid ? 1 + options.scale : 1 - options.scale) + options.minHeight;
}
const controlPoint = (current, previous, next, reverse) => {
const p = previous || current;
const n = next || current;
const o = line(p, n);
const flat = lib.map(Math.cos(o.angle) * options.flattening, 0, 1, 1, 0);
const angle = o.angle * flat + (reverse ? Math.PI : 0);
const length = o.length * options.smoothing;
const x = current[0] + Math.cos(angle) * length;
const y = current[1] + Math.sin(angle) * length;
return [x, y];
};
const bezierCommand = (point, i, a) => {
const cps = controlPoint(a[i - 1], a[i - 2], point);
const cpe = controlPoint(point, a[i - 1], a[i + 1], true);
const close = i === a.length - 1 ? " z" : "";
return "C " + cps[0] + "," + cps[1] + " " + cpe[0] + "," + cpe[1] + " " + point[0] + "," + point[1] + close;
};
const pointsPositions = points.map((e) => {
const x = lib.map(e[0], options.xMin, options.xMax, 0, w);
const y = lib.map(e[1], options.yMin, options.yMax, h, 0);
return [x, y];
});
const pathD = pointsPositions.reduce((acc, e, i, a) => i === 0 ? "M " + a[a.length - 1][0] + "," + h + " L " + e[0] + "," + h + " L " + e[0] + "," + e[1] : acc + " " + bezierCommand(e, i, a), "");
const pa = $.css({
position: "absolute",
bottom: 0,
["@global [data-ctrl-hidden=true] &"]: {
opacity: 0,
transition: "opacity .3s"
}
});
$root.innerHTML = '\n <svg viewBox="0 0 ' + w + " " + h + '" class="' + pa + '">\n <defs>\n <linearGradient id="heatmap-solids" x1="0%" y1="0%" x2="100%" y2="0%">\n <stop offset="0%" style="stop-color:var(--primary-color); stop-opacity:' + options.opacity + '" />\n <stop offset="0%" style="stop-color:var(--primary-color); stop-opacity:' + options.opacity + '" id="heatmap-start" />\n <stop offset="0%" style="stop-color:var(--heatmap-color, ' + options.fill + ');" id="heatmap-stop" />\n <stop offset="100%" style="stop-color:var(--heatmap-color, ' + options.fill + ');" />\n </linearGradient>\n </defs>\n <path fill="url(#heatmap-solids)" d="' + pathD + '"></path>\n </svg>\n ';
this.$start = $root.querySelector("#heatmap-start");
this.$stop = $root.querySelector("#heatmap-stop");
}
}
class Danmaku {
constructor() {
__publicField(this, "key", "danmaku");
__publicField(this, "name", "oplayer-plugin-danmaku");
__publicField(this, "version", "1.2.26-beta.0");
__publicField(this, "player");
__publicField(this, "danmaku");
__publicField(this, "heatmap");
__publicField(this, "loaded", false);
__publicField(this, "$root");
__publicField(this, "options", {
speed: 144,
opacity: 1,
engine: "dom",
area: 1,
fontSize: 1,
heatmap: true,
enable: true,
displaySender: false
});
let options = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
Object.assign(this.options, options);
}
apply(player) {
if (player.isNativeUI) return;
this.player = player;
this.render();
const danmaku = this.danmaku;
player.on("fullscreenchange", () => {
danmaku.resize();
});
player.on("videosourcechange", () => {
danmaku.clear();
danmaku.comments = [];
this.loaded = false;
this.options.source = void 0;
});
const resize2 = danmaku.resize.bind(danmaku);
window.addEventListener("resize", resize2);
player.on("destroy", () => {
danmaku.destroy();
this.options.source = void 0;
this.loaded = false;
this.danmaku = null;
this.$root = null;
window.removeEventListener("resize", resize2);
});
this.registerSetting();
this.changeSource(this.options.source);
if (this.options.displaySender && !isMobile) {
registerInput(player, danmaku, this.options);
}
return this;
}
changeSource(source, customHeatmap) {
if (!source) return;
this.loaded = false;
this.options.source = source;
this.danmaku.clear();
if (!this.options.enable) return;
this._fetchSource(source, customHeatmap);
}
async _fetchSource(source, customHeatmap) {
try {
let danmakus;
if (typeof source === "function") {
danmakus = await source();
} else if (typeof source === "string") {
danmakus = await danmakuParseFromUrl(source);
} else {
danmakus = source;
}
const danmaku = this.danmaku, options = this.options;
const fontSize = options.fontSize, enable = options.enable, heatmap = options.heatmap;
if (!danmaku._) return;
danmaku.comments = danmakus.sort((a, b) => a.time - b.time);
danmaku.comments.forEach((comment) => {
var _a;
if ((_a = comment.style) == null ? void 0 : _a.fontSize) {
comment.defaultFontSize = comment.style.fontSize.slice(0, -2);
}
});
this.loaded = true;
this.setFontSize(fontSize);
if (enable) danmaku.show();
if (heatmap) this.heatmap.enable(customHeatmap);
} catch (error) {
this.player.emit("notice", {
text: "danmaku: " + error.message
});
throw error;
}
}
setFontSize(value) {
this.danmaku.comments.forEach((comment) => {
var _a;
if ((_a = comment.style) == null ? void 0 : _a.fontSize) {
comment.style.fontSize = comment.defaultFontSize * value + "px";
}
});
}
registerSetting() {
var _a, _b;
var _player$context$ui$ic;
const danmaku = this.danmaku, player = this.player;
const _this$options = this.options, enable = _this$options.enable, heatmapEnable = _this$options.heatmap, opacity = _this$options.opacity, area = _this$options.area;
(_b = player.context.ui) == null ? void 0 : _b.setting.register({
name: player.locales.get("Danmaku"),
type: "selector",
default: true,
key: "danmaku",
icon: (_player$context$ui$ic = (_a = player.context.ui) == null ? void 0 : _a.icons.danmalu) !== null && _player$context$ui$ic !== void 0 ? _player$context$ui$ic : danmakuSvg,
children: [{
name: player.locales.get("Display"),
type: "switcher",
default: enable,
key: "danmaku-switcher",
onChange: (value) => {
this.options.enable = value;
if (value) {
if (!this.loaded) {
this.changeSource(this.options.source);
} else {
danmaku.show();
}
} else {
danmaku.hide();
}
}
}, {
name: player.locales.get("Heatmap"),
type: "switcher",
default: heatmapEnable,
key: "heatmap",
onChange: (value) => {
this.options.heatmap = value;
if (value) this.heatmap.enable();
else this.heatmap.disable();
}
}, {
type: "slider",
key: "danmaku-font",
max: 1.25,
min: 0.25,
step: 0.25,
default: 1,
name: player.locales.get("FontSize"),
onChange: (value) => {
this.options.fontSize = value;
this.setFontSize(value);
}
}, {
type: "slider",
key: "danmaku-opacity",
name: player.locales.get("Opacity"),
max: 1,
min: 0.1,
step: 0.1,
default: opacity,
onChange: (value) => {
this.options.opacity = value;
this.$root.style.opacity = value;
}
}, {
type: "slider",
key: "danmaku-area",
name: player.locales.get("Display Area"),
max: 1,
min: 0.1,
step: 0.1,
default: area,
onChange: (value) => {
this.options.area = value;
this.$root.style.height = value * 100 + "%";
danmaku.resize();
}
}]
});
}
render() {
const _this$options2 = this.options, opacity = _this$options2.opacity, area = _this$options2.area, engine = _this$options2.engine, speed2 = _this$options2.speed, heatmap = _this$options2.heatmap, customHeatmap = _this$options2.customHeatmap;
const player = this.player;
this.$root = $.render($.create("div"), player.$root);
this.$root.style.cssText = "height:" + area * 100 + "%;opacity:" + opacity + ";font-weight: normal;position: absolute;left: 0;top: 0;width: 100%;height: 100%;overflow: hidden;pointer-events: none;text-shadow: rgb(0 0 0) 1px 0px 1px, rgb(0 0 0) 0px 1px 1px, rgb(0 0 0) 0px -1px 1px, rgb(0 0 0) -1px 0px 1px;color:#fff;";
this.danmaku = new Danmaku$1({
container: this.$root,
media: player.$video,
engine,
comments: [],
speed: isMobile ? speed2 / 1.5 : speed2
});
this.heatmap = new Heatmap(player, this.danmaku, heatmap, customHeatmap);
}
}
export {
Danmaku as default
};