UNPKG

@oplayer/danmaku

Version:

Danmaku plugin for oplayer

1,119 lines (1,118 loc) 42.4 kB
/** * 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 };