// modules are defined as an array // [ module function, map of requires ] // // map of requires is short require name -> numeric require // // anything defined in a previous bundle is accessed via the // orig method which is the require for previous bundles (function ( modules, entry, mainEntry, parcelRequireName, externals, distDir, publicUrl, devServer ) { /* eslint-disable no-undef */ var globalObject = typeof globalThis !== 'undefined' ? globalThis : typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : {}; /* eslint-enable no-undef */ // Save the require from previous bundle to this closure if any var previousRequire = typeof globalObject[parcelRequireName] === 'function' && globalObject[parcelRequireName]; var importMap = previousRequire.i || {}; var cache = previousRequire.cache || {}; // Do not use `require` to prevent Webpack from trying to bundle this call var nodeRequire = typeof module !== 'undefined' && typeof module.require === 'function' && module.require.bind(module); function newRequire(name, jumped) { if (!cache[name]) { if (!modules[name]) { if (externals[name]) { return externals[name]; } // if we cannot find the module within our internal map or // cache jump to the current global require ie. the last bundle // that was added to the page. var currentRequire = typeof globalObject[parcelRequireName] === 'function' && globalObject[parcelRequireName]; if (!jumped && currentRequire) { return currentRequire(name, true); } // If there are other bundles on this page the require from the // previous one is saved to 'previousRequire'. Repeat this as // many times as there are bundles until the module is found or // we exhaust the require chain. if (previousRequire) { return previousRequire(name, true); } // Try the node require function if it exists. if (nodeRequire && typeof name === 'string') { return nodeRequire(name); } var err = new Error("Cannot find module '" + name + "'"); err.code = 'MODULE_NOT_FOUND'; throw err; } localRequire.resolve = resolve; localRequire.cache = {}; var module = (cache[name] = new newRequire.Module(name)); modules[name][0].call( module.exports, localRequire, module, module.exports, globalObject ); } return cache[name].exports; function localRequire(x) { var res = localRequire.resolve(x); return res === false ? {} : newRequire(res); } function resolve(x) { var id = modules[name][1][x]; return id != null ? id : x; } } function Module(moduleName) { this.id = moduleName; this.bundle = newRequire; this.require = nodeRequire; this.exports = {}; } newRequire.isParcelRequire = true; newRequire.Module = Module; newRequire.modules = modules; newRequire.cache = cache; newRequire.parent = previousRequire; newRequire.distDir = distDir; newRequire.publicUrl = publicUrl; newRequire.devServer = devServer; newRequire.i = importMap; newRequire.register = function (id, exports) { modules[id] = [ function (require, module) { module.exports = exports; }, {}, ]; }; // Only insert newRequire.load when it is actually used. // The code in this file is linted against ES5, so dynamic import is not allowed. // INSERT_LOAD_HERE Object.defineProperty(newRequire, 'root', { get: function () { return globalObject[parcelRequireName]; }, }); globalObject[parcelRequireName] = newRequire; for (var i = 0; i < entry.length; i++) { newRequire(entry[i]); } if (mainEntry) { // Expose entry point to Node, AMD or browser globals // Based on https://github.com/ForbesLindesay/umd/blob/master/template.js var mainExports = newRequire(mainEntry); // CommonJS if (typeof exports === 'object' && typeof module !== 'undefined') { module.exports = mainExports; // RequireJS } else if (typeof define === 'function' && define.amd) { define(function () { return mainExports; }); } } })({"hfC30":[function(require,module,exports,__globalThis) { var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); parcelHelpers.defineInteropFlag(exports); parcelHelpers.export(exports, "default", ()=>artplayerPluginDanmuku); var _danmuku = require("./danmuku"); var _danmukuDefault = parcelHelpers.interopDefault(_danmuku); var _setting = require("./setting"); var _settingDefault = parcelHelpers.interopDefault(_setting); var _heatmap = require("./heatmap"); var _heatmapDefault = parcelHelpers.interopDefault(_heatmap); function artplayerPluginDanmuku(option) { return (art)=>{ const danmuku = new (0, _danmukuDefault.default)(art, option); const setting = new (0, _settingDefault.default)(art, danmuku); if (danmuku.option.heatmap) (0, _heatmapDefault.default)(art, danmuku, danmuku.option.heatmap); return { name: 'artplayerPluginDanmuku', emit: danmuku.emit.bind(danmuku), load: danmuku.load.bind(danmuku), config: danmuku.config.bind(danmuku), hide: danmuku.hide.bind(danmuku), show: danmuku.show.bind(danmuku), reset: danmuku.reset.bind(danmuku), mount: setting.mount.bind(setting), get option () { return danmuku.option; }, get isHide () { return danmuku.isHide; }, get isStop () { return danmuku.isStop; } }; }; } artplayerPluginDanmuku.icons = (0, _settingDefault.default).icons; if (typeof window !== 'undefined') window['artplayerPluginDanmuku'] = artplayerPluginDanmuku; },{"./danmuku":"gAOAX","./setting":"3B2wj","./heatmap":"2G7iS","@parcel/transformer-js/src/esmodule-helpers.js":"8oCsH"}],"gAOAX":[function(require,module,exports,__globalThis) { var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); parcelHelpers.defineInteropFlag(exports); var _bilibili = require("./bilibili"); var _worker = require("bundle-text:./worker"); var _workerDefault = parcelHelpers.interopDefault(_worker); class Danmuku { constructor(art, option){ const { constructor, template } = art; this.utils = constructor.utils; // 工具库 this.validator = constructor.validator; // 配置校验器 this.$danmuku = template.$danmuku; // 弹幕层容器 this.$player = template.$player; // 播放器容器 this.art = art; this.queue = []; // 实际弹幕队列 this.$refs = []; // 弹幕DOM节点池 this.isStop = false; // 是否停止 this.isHide = false; // 是否隐藏 this.timer = null; // 定时器 this.index = 0; // 弹幕索引 // 格式化后的配置项 this.option = Danmuku.option; // 弹幕状态池 this.states = { wait: [], ready: [], emit: [], stop: [] }; // 初始化配置 this.config(option, true); // 创建 Web Worker, 用于计算弹幕的 top 值 const workerText2 = (0, _workerDefault.default).replace('export{e as default};', ''); const blob = new Blob([ workerText2 ], { type: 'application/javascript' }); this.worker = new Worker(URL.createObjectURL(blob)); // 绑定公用事件 this.start = this.start.bind(this); this.stop = this.stop.bind(this); this.reset = this.reset.bind(this); this.resize = this.resize.bind(this); this.destroy = this.destroy.bind(this); // 监听事件 art.on('video:play', this.start); art.on('video:playing', this.start); art.on('video:pause', this.stop); art.on('video:waiting', this.stop); art.on('destroy', this.destroy); art.on('resize', this.resize); // 开始加载弹幕 this.load(); } // 默认配置 static get option() { return { danmuku: [], speed: 5, margin: [ 10, '25%' ], opacity: 1, color: '#FFFFFF', mode: 0, modes: [ 0, 1, 2 ], fontSize: 25, antiOverlap: true, synchronousPlayback: false, mount: undefined, heatmap: false, width: 512, points: [], filter: ()=>true, beforeEmit: ()=>true, beforeVisible: ()=>true, visible: true, emitter: true, maxLength: 200, lockTime: 5, theme: 'dark', OPACITY: {}, FONT_SIZE: {}, MARGIN: {}, SPEED: {}, COLOR: [] }; } // 配置校验 static get scheme() { return { danmuku: 'array|function|string', speed: 'number', margin: 'array', opacity: 'number', color: 'string', mode: 'number', modes: 'array', fontSize: 'number|string', antiOverlap: 'boolean', synchronousPlayback: 'boolean', mount: '?htmldivelement|string', heatmap: 'object|boolean', width: 'number', points: 'array', filter: 'function', beforeEmit: 'function', beforeVisible: 'function', visible: 'boolean', emitter: 'boolean', maxLength: 'number', lockTime: 'number', theme: 'string', OPACITY: 'object', FONT_SIZE: 'object', MARGIN: 'object', SPEED: 'object', COLOR: 'array' }; } // 初始弹幕样式 static get cssText() { return ` user-select: none; position: absolute; white-space: pre; pointer-events: none; perspective: 500px; display: inline-block; will-change: transform; font-weight: normal; line-height: 1.125; visibility: hidden; font-family: SimHei, "Microsoft JhengHei", Arial, Helvetica, sans-serif; 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; `; } // 是否在移动端使用了自动旋屏,会影响弹幕的left和top值 get isRotate() { return this.art.plugins?.autoOrientation?.state; } // 计算上空白边距 get marginTop() { const { clamp } = this.utils; const value = this.option.margin[0]; const { clientHeight } = this.$player; if (typeof value === 'number') return clamp(value, 0, clientHeight); if (typeof value === 'string' && value.endsWith('%')) { const ratio = parseFloat(value) / 100; return clamp(clientHeight * ratio, 0, clientHeight); } return Danmuku.option.margin[0]; } // 计算下空白边距 get marginBottom() { const { clamp } = this.utils; const value = this.option.margin[1]; const { clientHeight } = this.$player; if (typeof value === 'number') return clamp(value, 0, clientHeight); if (typeof value === 'string' && value.endsWith('%')) { const ratio = parseFloat(value) / 100; return clamp(clientHeight * ratio, 0, clientHeight); } return Danmuku.option.margin[1]; } // 计算弹幕字体大小 get fontSize() { const { clamp } = this.utils; const { clientHeight } = this.$player; const fontSize = this.option.fontSize; if (typeof fontSize === 'number') return Math.round(clamp(fontSize, 12, clientHeight)); if (typeof fontSize === 'string' && fontSize.endsWith('%')) { const ratio = parseFloat(fontSize) / 100; return Math.round(clamp(clientHeight * ratio, 12, clientHeight)); } return Danmuku.option.fontSize; } // 获取弹幕DOM节点 get $ref() { const $ref = this.$refs.pop() || document.createElement('div'); $ref.style.cssText = Danmuku.cssText; $ref.dataset.mode = ''; $ref.className = ''; return $ref; } // 获取准备好发送的弹幕 get readys() { const { currentTime } = this.art; const result = []; // 有的是ready状态:之前因为弹幕太多而暂停发送的弹幕 this.filter('ready', (danmu)=>result.push(danmu)); // 有的是wait状态:符合时间范围的弹幕 this.filter('wait', (danmu)=>{ if (currentTime + 0.1 >= danmu.time && danmu.time >= currentTime - 0.1) result.push(danmu); }); return result; } // 可见的弹幕的数据,用于计算下一个弹幕的top值 get visibles() { const result = []; const { clientWidth } = this.$player; const clientLeft = this.getLeft(this.$player); this.filter('emit', (danmu)=>{ const top = danmu.$ref.offsetTop; const left = this.getLeft(danmu.$ref) - clientLeft; const height = danmu.$ref.clientHeight; const width = danmu.$ref.clientWidth; const distance = left + width; const right = clientWidth - distance; const speed = distance / danmu.$restTime; const emit = {}; emit.top = top; emit.left = left; emit.height = height; emit.width = width; emit.right = right; emit.speed = speed; emit.distance = distance; emit.time = danmu.$restTime; emit.mode = danmu.mode; result.push(emit); }); return result; } // 计算弹幕速度 get speed() { return this.option.synchronousPlayback && this.art.playbackRate ? this.option.speed / Number(this.art.playbackRate) : this.option.speed; } // 加载弹幕 async load(danmuku) { const { errorHandle } = this.utils; let danmus = []; const target = danmuku || this.option.danmuku; try { if (typeof target === 'function') danmus = await target(); // 异步函数获取 else if (target instanceof Promise) danmus = await target; // 从 Promise 对象获取 else if (typeof target === 'string') danmus = await (0, _bilibili.bilibiliDanmuParseFromUrl)(target); // 从B站xml链接解析 else if (Array.isArray(target)) danmus = target; // 直接传入数组 errorHandle(Array.isArray(danmus), 'Danmuku need return an array as result'); // 假如没有传入弹幕参数,则清空弹幕,否则追加弹幕 if (danmuku === undefined) { this.reset(); // 重置弹幕 this.queue = []; // 清空弹幕队列 this.states = { wait: [], ready: [], emit: [], stop: [] }; // 清空弹幕状态池 this.$refs = []; // 清空弹幕DOM节点池 this.$danmuku.innerText = ''; // 清空弹幕层 } // 逐个验证原始弹幕并转换到弹幕队列 for(let index = 0; index < danmus.length; index++){ const danmu = danmus[index]; await this.emit(danmu); } this.art.emit('artplayerPluginDanmuku:loaded', this.queue); } catch (error) { this.art.emit('artplayerPluginDanmuku:error', error); throw error; } return this; } // 把原始弹幕转换到弹幕队列 async emit(danmu) { const { clamp } = this.utils; this.validator(danmu, { text: 'string', mode: '?number', color: '?string', time: '?number', border: '?boolean', style: '?object' }); // 弹幕文本为空则直接忽略 if (!danmu.text.trim()) return this; // 设置弹幕时间,如果没有则默认为当前时间加 0.5 秒 if (danmu.time) danmu.time = clamp(danmu.time, 0, Infinity); else danmu.time = this.art.currentTime + 0.5; // 设置弹幕模式,如果没有则默认为全局配置 if (danmu.mode === undefined) danmu.mode = this.option.mode; // 设置弹幕单独样式,如果没有则默认为空对象 if (danmu.style === undefined) danmu.style = {}; // 设置弹幕颜色,如果没有则默认为全局配置 if (danmu.color === undefined) danmu.color = this.option.color; // 弹幕模式只能是 0, 1, 2 if (![ 0, 1, 2 ].includes(danmu.mode)) return this; // 自定义弹幕过滤函数 if (!this.option.filter(danmu)) return this; // 添加自定义属性 const item = { ...danmu, $state: 'wait', $id: this.index++, $ref: null, $restTime: 0, $lastStartTime: 0 }; // 转换为wait状态 this.setState(item, 'wait'); // 添加到实际弹幕队列 this.queue.push(item); // 弹幕有四个状态: // - wait: 弹幕还未开始显示,没有被添加到 DOM 中 // - ready: 弹幕准备好显示,没有被添加到 DOM 中 // - emit: 弹幕正在显示,已经被添加到 DOM 中 // - stop: 弹幕正在停止显示,已经被添加到 DOM 中 return this; } // 动态配置 config(option, isInit = false) { const { clamp } = this.utils; const { $controlsCenter } = this.art.template; // 判断配置项是否有变化 const changed = Object.keys(option).some((key)=>JSON.stringify(this.option[key]) !== JSON.stringify(option[key])); // 没有变化则直接返回 if (!changed && !isInit) return this; // 更新配置项 this.option = Object.assign({}, Danmuku.option, this.option, option); this.validator(this.option, Danmuku.scheme); this.option.mode = clamp(this.option.mode, 0, 2); this.option.speed = clamp(this.option.speed, 1, 10); this.option.opacity = clamp(this.option.opacity, 0, 1); this.option.lockTime = clamp(this.option.lockTime, 1, 60); this.option.maxLength = clamp(this.option.maxLength, 1, 1000); this.option.mount = this.option.mount || $controlsCenter; // 动态配置有字体大小,需要重新渲染 if (option.fontSize) this.reset(); // 通过配置项控制弹幕的显示和隐藏 if (this.option.visible) this.show(); else this.hide(); this.art.emit('artplayerPluginDanmuku:config', this.option); return this; } // 计算DOM的left值,受到旋屏影响 getLeft($ref) { const rect = $ref.getBoundingClientRect(); return this.isRotate ? rect.top : rect.left; } // 复杂运算交给 Web Worker 处理 postMessage(message = {}) { return new Promise((resolve)=>{ message.id = Date.now(); // 生成唯一标识 this.worker.postMessage(message); this.worker.onmessage = (event)=>{ const { data } = event; // 判断是否是当前的消息 if (data.id === message.id) resolve(data); }; }); } // 根据状态获取弹幕 filter(state, callback) { const danmus = this.states[state] || []; for(let index = 0; index < danmus.length; index++)callback(danmus[index]); return danmus; } // 设置弹幕状态 setState(danmu, state) { // 从原状态池中删除 this.states[danmu.$state] = this.states[danmu.$state].filter((item)=>item !== danmu); // 设置新状态 danmu.$state = state; // 设置DOM节点状态 if (danmu.$ref) danmu.$ref.dataset.state = state; // 添加到新状态池中 this.states[state].push(danmu); } // 重置弹幕到wait状态,回收弹幕DOM节点 makeWait(danmu) { this.setState(danmu, 'wait'); if (danmu.$ref) { danmu.$ref.style.cssText = Danmuku.cssText; danmu.$ref.style.visibility = 'hidden'; danmu.$ref.style.marginLeft = '0px'; danmu.$ref.style.transform = 'translateX(0px)'; danmu.$ref.style.transition = 'transform 0s linear 0s'; this.$refs.push(danmu.$ref); danmu.$ref = null; } } // 实时更新弹幕 update() { const { setStyles } = this.utils; this.timer = window.requestAnimationFrame(async ()=>{ if (this.art.playing && !this.isHide) { // 实时计算弹幕的剩余显示时间 this.filter('emit', (danmu)=>{ const emitTime = (Date.now() - danmu.$lastStartTime) / 1000; danmu.$restTime -= emitTime; danmu.$lastStartTime = Date.now(); // 超过时间即重置弹幕 if (danmu.$restTime <= 0) this.makeWait(danmu); }); // 获取准备好发送的弹幕,可能包含ready和wait状态的弹幕 const readys = this.readys; for(let index = 0; index < readys.length; index++){ const danmu = readys[index]; // 弹幕发送前的过滤器 const state = await this.option.beforeVisible(danmu); if (state) { const { clientWidth, clientHeight } = this.$player; danmu.$ref = this.$ref; // 获取弹幕DOM节点 danmu.$ref.innerText = danmu.text; // 设置弹幕文本 // 提前添加到弹幕层中,用于计算top值 this.$danmuku.appendChild(danmu.$ref); // 设置初始弹幕样式 danmu.$ref.style.opacity = this.option.opacity; danmu.$ref.style.fontSize = `${this.fontSize}px`; danmu.$ref.style.color = danmu.color; danmu.$ref.style.border = danmu.border ? `1px solid ${danmu.color}` : null; danmu.$ref.style.backgroundColor = danmu.border ? 'rgb(0 0 0 / 50%)' : null; // 设置单独弹幕样式 setStyles(danmu.$ref, danmu.style); // 记录弹幕时间戳 danmu.$lastStartTime = Date.now(); // 计算弹幕剩余时间 danmu.$restTime = this.speed; // 计算弹幕滚动的距离 const distance = clientWidth + danmu.$ref.clientWidth; // 计算弹幕的top值 const { result: top } = await this.postMessage({ type: 'getDanmuTop', target: { mode: danmu.mode, height: danmu.$ref.clientHeight, speed: distance / danmu.$restTime }, visibles: this.visibles, antiOverlap: this.option.antiOverlap, clientWidth: clientWidth, clientHeight: clientHeight, marginBottom: this.marginBottom, marginTop: this.marginTop }); if (danmu.$ref) { if (!this.isStop && top !== undefined) { this.setState(danmu, 'emit'); // 转换为emit状态 danmu.$ref.style.top = `${top}px`; danmu.$ref.style.visibility = 'visible'; danmu.$ref.dataset.mode = danmu.mode; // CSS控制模式的显示和隐藏 switch(danmu.mode){ // 滚动的弹幕 case 0: danmu.$ref.style.left = `${clientWidth}px`; danmu.$ref.style.marginLeft = '0px'; danmu.$ref.style.transform = `translateX(${-distance}px)`; danmu.$ref.style.transition = `transform ${danmu.$restTime}s linear 0s`; break; case 1: // falls through case 2: danmu.$ref.style.left = '50%'; danmu.$ref.style.marginLeft = `-${danmu.$ref.clientWidth / 2}px`; break; default: break; } this.art.emit('artplayerPluginDanmuku:visible', danmu); } else { // 假如弹幕已经停止或者没有 top 值,则重置弹幕为ready状态,回收弹幕DOM节点,等待下次发送 this.setState(danmu, 'ready'); this.$refs.push(danmu.$ref); danmu.$ref = null; } } } } } // 递归调用 if (!this.isStop) this.update(); }); return this; } // 重置正在显示的弹幕: stop/emit 状态的弹幕 resize() { const { clientWidth } = this.$player; this.filter('stop', (danmu)=>{ switch(danmu.mode){ // 滚动的弹幕 case 0: danmu.$ref.style.left = `${clientWidth}px`; break; default: break; } }); this.filter('emit', (danmu)=>{ danmu.$lastStartTime = Date.now(); switch(danmu.mode){ // 滚动的弹幕 case 0: { const distance = clientWidth + danmu.$ref.clientWidth; danmu.$ref.style.left = `${clientWidth}px`; danmu.$ref.style.transform = `translateX(${-distance}px)`; danmu.$ref.style.transition = `transform ${danmu.$restTime}s linear 0s`; break; } default: break; } }); } // 继续弹幕 continue() { const { clientWidth } = this.$player; this.filter('stop', (danmu)=>{ this.setState(danmu, 'emit'); // 转换为emit状态 danmu.$lastStartTime = Date.now(); switch(danmu.mode){ // 继续滚动的弹幕 case 0: { const distance = clientWidth + danmu.$ref.clientWidth; danmu.$ref.style.transform = `translateX(${-distance}px)`; danmu.$ref.style.transition = `transform ${danmu.$restTime}s linear 0s`; break; } default: break; } }); return this; } // 暂停弹幕 suspend() { const { clientWidth } = this.$player; this.filter('emit', (danmu)=>{ this.setState(danmu, 'stop'); // 转换为stop状态 switch(danmu.mode){ // 停止滚动的弹幕 case 0: { const translateX = clientWidth - (this.getLeft(danmu.$ref) - this.getLeft(this.$player)); danmu.$ref.style.transform = `translateX(${-translateX}px)`; danmu.$ref.style.transition = 'transform 0s linear 0s'; break; } default: break; } }); return this; } stop() { this.isStop = true; this.suspend(); window.cancelAnimationFrame(this.timer); this.art.emit('artplayerPluginDanmuku:stop'); return this; } start() { this.isStop = false; this.continue(); this.update(); this.art.emit('artplayerPluginDanmuku:start'); return this; } reset() { this.queue.forEach((danmu)=>this.makeWait(danmu)); this.art.emit('artplayerPluginDanmuku:reset'); return this; } show() { this.isHide = false; this.$danmuku.style.opacity = 1; this.option.visible = true; this.art.emit('artplayerPluginDanmuku:show'); return this; } hide() { this.isHide = true; this.$danmuku.style.opacity = 0; this.option.visible = false; this.art.emit('artplayerPluginDanmuku:hide'); return this; } destroy() { this.stop(); this.worker.terminate(); this.art.off('video:play', this.start); this.art.off('video:playing', this.start); this.art.off('video:pause', this.stop); this.art.off('video:waiting', this.stop); this.art.off('resize', this.reset); this.art.off('destroy', this.destroy); this.art.emit('artplayerPluginDanmuku:destroy'); } } exports.default = Danmuku; },{"./bilibili":"aIvhn","bundle-text:./worker":"2Spvp","@parcel/transformer-js/src/esmodule-helpers.js":"8oCsH"}],"aIvhn":[function(require,module,exports,__globalThis) { var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); parcelHelpers.defineInteropFlag(exports); parcelHelpers.export(exports, "bilibiliDanmuParseFromUrl", ()=>bilibiliDanmuParseFromUrl); function getMode(key) { switch(key){ case 1: case 2: case 3: return 0; case 4: return 2; case 5: return 1; default: return 0; } } function bilibiliDanmuParseFromXml(xmlString) { if (typeof xmlString !== 'string') return []; const reg = new RegExp(/(?.+?)<\/d>/gs); const matches = xmlString.matchAll(reg); return Array.from(matches).map((match)=>{ const attr = match.groups.p.split(','); if (attr.length >= 8) { const text = match.groups.text.trim().replaceAll('"', '"').replaceAll(''', "'").replaceAll('<', '<').replaceAll('>', '>').replaceAll('&', '&'); return { text, time: Number(attr[0]), mode: getMode(Number(attr[1])), fontSize: Number(attr[2]), color: `#${Number(attr[3]).toString(16)}`, timestamp: Number(attr[4]), pool: Number(attr[5]), userID: attr[6], rowID: Number(attr[7]) }; } else return null; }).filter(Boolean); } function onmessage({ data }) { const { xml, id } = data; if (!id || !xml) return; const danmus = bilibiliDanmuParseFromXml(xml); self.postMessage({ danmus, id }); } function createWorker() { const workerText = ` ${getMode.toString()} ${bilibiliDanmuParseFromXml.toString()} onmessage = ${onmessage.toString()} `; const blob = new Blob([ workerText ], { type: 'application/javascript' }); return new Worker(URL.createObjectURL(blob)); } function bilibiliDanmuParseFromUrl(url) { // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve)=>{ const res = await fetch(url); const xml = await res.text(); try { const worker = createWorker(); worker.onmessage = (event)=>{ const { danmus, id } = event.data; if (!id || !danmus) return; resolve(danmus); worker.terminate(); }; worker.postMessage({ xml, id: Date.now() }); // eslint-disable-next-line no-unused-vars } catch (error) { const danmus = bilibiliDanmuParseFromXml(xml); resolve(danmus); } }); } },{"@parcel/transformer-js/src/esmodule-helpers.js":"8oCsH"}],"8oCsH":[function(require,module,exports,__globalThis) { exports.interopDefault = function(a) { return a && a.__esModule ? a : { default: a }; }; exports.defineInteropFlag = function(a) { Object.defineProperty(a, '__esModule', { value: true }); }; exports.exportAll = function(source, dest) { Object.keys(source).forEach(function(key) { if (key === 'default' || key === '__esModule' || Object.prototype.hasOwnProperty.call(dest, key)) return; Object.defineProperty(dest, key, { enumerable: true, get: function() { return source[key]; } }); }); return dest; }; exports.export = function(dest, destName, get) { Object.defineProperty(dest, destName, { enumerable: true, get: get }); }; },{}],"2Spvp":[function(require,module,exports,__globalThis) { module.exports = "// modules are defined as an array\n// [ module function, map of requires ]\n//\n// map of requires is short require name -> numeric require\n//\n// anything defined in a previous bundle is accessed via the\n// orig method which is the require for previous bundles\n\n(function (\n modules,\n entry,\n mainEntry,\n parcelRequireName,\n externals,\n distDir,\n publicUrl,\n devServer\n) {\n /* eslint-disable no-undef */\n var globalObject =\n typeof globalThis !== 'undefined'\n ? globalThis\n : typeof self !== 'undefined'\n ? self\n : typeof window !== 'undefined'\n ? window\n : typeof global !== 'undefined'\n ? global\n : {};\n /* eslint-enable no-undef */\n\n // Save the require from previous bundle to this closure if any\n var previousRequire =\n typeof globalObject[parcelRequireName] === 'function' &&\n globalObject[parcelRequireName];\n\n var importMap = previousRequire.i || {};\n var cache = previousRequire.cache || {};\n // Do not use `require` to prevent Webpack from trying to bundle this call\n var nodeRequire =\n typeof module !== 'undefined' &&\n typeof module.require === 'function' &&\n module.require.bind(module);\n\n function newRequire(name, jumped) {\n if (!cache[name]) {\n if (!modules[name]) {\n if (externals[name]) {\n return externals[name];\n }\n // if we cannot find the module within our internal map or\n // cache jump to the current global require ie. the last bundle\n // that was added to the page.\n var currentRequire =\n typeof globalObject[parcelRequireName] === 'function' &&\n globalObject[parcelRequireName];\n if (!jumped && currentRequire) {\n return currentRequire(name, true);\n }\n\n // If there are other bundles on this page the require from the\n // previous one is saved to 'previousRequire'. Repeat this as\n // many times as there are bundles until the module is found or\n // we exhaust the require chain.\n if (previousRequire) {\n return previousRequire(name, true);\n }\n\n // Try the node require function if it exists.\n if (nodeRequire && typeof name === 'string') {\n return nodeRequire(name);\n }\n\n var err = new Error(\"Cannot find module '\" + name + \"'\");\n err.code = 'MODULE_NOT_FOUND';\n throw err;\n }\n\n localRequire.resolve = resolve;\n localRequire.cache = {};\n\n var module = (cache[name] = new newRequire.Module(name));\n\n modules[name][0].call(\n module.exports,\n localRequire,\n module,\n module.exports,\n globalObject\n );\n }\n\n return cache[name].exports;\n\n function localRequire(x) {\n var res = localRequire.resolve(x);\n return res === false ? {} : newRequire(res);\n }\n\n function resolve(x) {\n var id = modules[name][1][x];\n return id != null ? id : x;\n }\n }\n\n function Module(moduleName) {\n this.id = moduleName;\n this.bundle = newRequire;\n this.require = nodeRequire;\n this.exports = {};\n }\n\n newRequire.isParcelRequire = true;\n newRequire.Module = Module;\n newRequire.modules = modules;\n newRequire.cache = cache;\n newRequire.parent = previousRequire;\n newRequire.distDir = distDir;\n newRequire.publicUrl = publicUrl;\n newRequire.devServer = devServer;\n newRequire.i = importMap;\n newRequire.register = function (id, exports) {\n modules[id] = [\n function (require, module) {\n module.exports = exports;\n },\n {},\n ];\n };\n\n // Only insert newRequire.load when it is actually used.\n // The code in this file is linted against ES5, so dynamic import is not allowed.\n // INSERT_LOAD_HERE\n\n Object.defineProperty(newRequire, 'root', {\n get: function () {\n return globalObject[parcelRequireName];\n },\n });\n\n globalObject[parcelRequireName] = newRequire;\n\n for (var i = 0; i < entry.length; i++) {\n newRequire(entry[i]);\n }\n\n if (mainEntry) {\n // Expose entry point to Node, AMD or browser globals\n // Based on https://github.com/ForbesLindesay/umd/blob/master/template.js\n var mainExports = newRequire(mainEntry);\n\n // CommonJS\n if (typeof exports === 'object' && typeof module !== 'undefined') {\n module.exports = mainExports;\n\n // RequireJS\n } else if (typeof define === 'function' && define.amd) {\n define(function () {\n return mainExports;\n });\n }\n }\n})({\"02up6\":[function(require,module,exports,__globalThis) {\nfunction getDanmuTop({ target, visibles, clientWidth, clientHeight, marginBottom, marginTop, antiOverlap }) {\n // 弹幕最大高度\n const maxTop = clientHeight - marginBottom;\n // 过滤同模式的弹幕,即每种模式各不影响\n const danmus = visibles.filter((item)=>item.mode === target.mode && item.top <= maxTop).sort((prev, next)=>prev.top - next.top);\n // 如果没有同模式的弹幕,直接返回\n if (danmus.length === 0) {\n if (target.mode === 2) return maxTop - target.height;\n else return marginTop;\n }\n // 上下各加一个虚拟弹幕,方便计算\n danmus.unshift({\n type: 'top',\n top: 0,\n left: 0,\n right: 0,\n height: marginTop,\n width: clientWidth,\n speed: 0,\n distance: clientWidth\n });\n danmus.push({\n type: 'bottom',\n top: maxTop,\n left: 0,\n right: 0,\n height: marginBottom,\n width: clientWidth,\n speed: 0,\n distance: clientWidth\n });\n // 查找是否有多余的缝隙足以容纳当前弹幕\n if (target.mode === 2) // 倒序查找\n for(let index = danmus.length - 2; index >= 0; index -= 1){\n const item = danmus[index];\n const prev = danmus[index + 1];\n const itemBottom = item.top + item.height;\n const diff = prev.top - itemBottom;\n if (diff >= target.height) return prev.top - target.height;\n }\n else // 顺序查找\n for(let index = 1; index < danmus.length; index += 1){\n const item = danmus[index];\n const prev = danmus[index - 1];\n const prevBottom = prev.top + prev.height;\n const diff = item.top - prevBottom;\n if (diff >= target.height) return prevBottom;\n }\n const topMap = [];\n for(let index = 1; index < danmus.length - 1; index += 1){\n const item = danmus[index];\n if (topMap.length) {\n const last = topMap[topMap.length - 1];\n if (last[0].top === item.top) last.push(item);\n else topMap.push([\n item\n ]);\n } else topMap.push([\n item\n ]);\n }\n if (antiOverlap) switch(target.mode){\n case 0:\n {\n const result = topMap.find((list)=>{\n return list.every((danmu)=>{\n if (clientWidth < danmu.distance) return false;\n if (target.speed < danmu.speed) return true;\n const overlapTime = danmu.right / (target.speed - danmu.speed);\n if (overlapTime > danmu.time) return true;\n return false;\n });\n });\n return result && result[0] ? result[0].top : undefined;\n }\n // 静止弹幕没有重叠问题\n case 1:\n case 2:\n return undefined;\n default:\n break;\n }\n else {\n switch(target.mode){\n case 0:\n topMap.sort((prev, next)=>{\n const nextMinRight = Math.min(...next.map((item)=>item.right));\n const prevMinRight = Math.min(...prev.map((item)=>item.right));\n return nextMinRight * next.length - prevMinRight * prev.length;\n });\n break;\n case 1:\n case 2:\n topMap.sort((prev, next)=>{\n const nextMaxWidth = Math.max(...next.map((item)=>item.width));\n const prevMaxWidth = Math.max(...prev.map((item)=>item.width));\n return prevMaxWidth * prev.length - nextMaxWidth * next.length;\n });\n break;\n default:\n break;\n }\n return topMap[0][0].top;\n }\n}\nonmessage = (event)=>{\n const { data } = event;\n if (!data.id || !data.type) return;\n const fns = {\n getDanmuTop\n };\n const fn = fns[data.type];\n const result = fn(data);\n self.postMessage({\n result,\n id: data.id\n });\n};\n\n},{}]},[\"02up6\"], \"02up6\", \"parcelRequire4dc0\", {})\n\n"; },{}],"3B2wj":[function(require,module,exports,__globalThis) { var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); parcelHelpers.defineInteropFlag(exports); var _styleLess = require("bundle-text:./style.less"); var _styleLessDefault = parcelHelpers.interopDefault(_styleLess); var _onSvg = require("bundle-text:./img/on.svg"); var _onSvgDefault = parcelHelpers.interopDefault(_onSvg); var _offSvg = require("bundle-text:./img/off.svg"); var _offSvgDefault = parcelHelpers.interopDefault(_offSvg); var _configSvg = require("bundle-text:./img/config.svg"); var _configSvgDefault = parcelHelpers.interopDefault(_configSvg); var _styleSvg = require("bundle-text:./img/style.svg"); var _styleSvgDefault = parcelHelpers.interopDefault(_styleSvg); var _mode0OffSvg = require("bundle-text:./img/mode_0_off.svg"); var _mode0OffSvgDefault = parcelHelpers.interopDefault(_mode0OffSvg); var _mode0OnSvg = require("bundle-text:./img/mode_0_on.svg"); var _mode0OnSvgDefault = parcelHelpers.interopDefault(_mode0OnSvg); var _mode1OffSvg = require("bundle-text:./img/mode_1_off.svg"); var _mode1OffSvgDefault = parcelHelpers.interopDefault(_mode1OffSvg); var _mode1OnSvg = require("bundle-text:./img/mode_1_on.svg"); var _mode1OnSvgDefault = parcelHelpers.interopDefault(_mode1OnSvg); var _mode2OffSvg = require("bundle-text:./img/mode_2_off.svg"); var _mode2OffSvgDefault = parcelHelpers.interopDefault(_mode2OffSvg); var _mode2OnSvg = require("bundle-text:./img/mode_2_on.svg"); var _mode2OnSvgDefault = parcelHelpers.interopDefault(_mode2OnSvg); var _checkOnSvg = require("bundle-text:./img/check_on.svg"); var _checkOnSvgDefault = parcelHelpers.interopDefault(_checkOnSvg); var _checkOffSvg = require("bundle-text:./img/check_off.svg"); var _checkOffSvgDefault = parcelHelpers.interopDefault(_checkOffSvg); class Setting { constructor(art, danmuku){ this.art = art; this.danmuku = danmuku; this.utils = art.constructor.utils; const { setStyle } = this.utils; const { $controlsCenter } = art.template; setStyle($controlsCenter, 'display', 'flex'); this.template = { $controlsCenter, $mount: $controlsCenter, $danmuku: null, $toggle: null, $config: null, $configPanel: null, $configModes: null, $style: null, $stylePanel: null, $styleModes: null, $colors: null, $opacitySlider: null, $opacityValue: null, $marginSlider: null, $marginValue: null, $fontSizeSlider: null, $fontSizeValue: null, $speedSlider: null, $speedValue: null, $input: null, $send: null }; this.slider = { opacity: null, margin: null, fontSize: null, speed: null }; this.emitting = false; this.isLock = false; this.timer = null; this.createTemplate(); this.createSliders(); this.createEvents(); this.mount(this.option.mount); art.on('resize', ()=>this.resize()); art.on('fullscreen', (state)=>this.onFullscreen(state)); art.on('fullscreenWeb', (state)=>this.onFullscreen(state)); art.proxy(this.template.$config, 'mouseenter', ()=>{ this.onMouseEnter({ $control: this.template.$config, $panel: this.template.$configPanel }); }); art.proxy(this.template.$style, 'mouseenter', ()=>{ this.onMouseEnter({ $control: this.template.$style, $panel: this.template.$stylePanel }); }); } static get icons() { return { $on: (0, _onSvgDefault.default), $off: (0, _offSvgDefault.default), $config: (0, _configSvgDefault.default), $style: (0, _styleSvgDefault.default), $mode_0_off: (0, _mode0OffSvgDefault.default), $mode_0_on: (0, _mode0OnSvgDefault.default), $mode_1_off: (0, _mode1OffSvgDefault.default), $mode_1_on: (0, _mode1OnSvgDefault.default), $mode_2_off: (0, _mode2OffSvgDefault.default), $mode_2_on: (0, _mode2OnSvgDefault.default), $check_on: (0, _checkOnSvgDefault.default), $check_off: (0, _checkOffSvgDefault.default) }; } get option() { return this.danmuku.option; } get outside() { return this.template.$mount !== this.template.$controlsCenter; } get TEMPLATE() { const { option } = this; return `
${0, _onSvgDefault.default}${0, _offSvgDefault.default}
${0, _configSvgDefault.default}
\u{6309}\u{7C7B}\u{578B}\u{5C4F}\u{853D}
${0, _mode0OffSvgDefault.default}${0, _mode0OnSvgDefault.default}
\u{6EDA}\u{52A8}
${0, _mode1OffSvgDefault.default}${0, _mode1OnSvgDefault.default}
\u{9876}\u{90E8}
${0, _mode2OffSvgDefault.default}${0, _mode2OnSvgDefault.default}
\u{5E95}\u{90E8}
${0, _checkOnSvgDefault.default}${0, _checkOffSvgDefault.default} \u{9632}\u{6B62}\u{5F39}\u{5E55}\u{91CD}\u{53E0}
${0, _checkOnSvgDefault.default}${0, _checkOffSvgDefault.default} \u{540C}\u{6B65}\u{89C6}\u{9891}\u{901F}\u{5EA6}
\u{4E0D}\u{900F}\u{660E}\u{5EA6}
\u{672A}\u{77E5}
\u{663E}\u{793A}\u{533A}\u{57DF}
\u{672A}\u{77E5}
\u{5F39}\u{5E55}\u{5B57}\u{53F7}
\u{672A}\u{77E5}
\u{5F39}\u{5E55}\u{901F}\u{5EA6}
\u{672A}\u{77E5}
${0, _styleSvgDefault.default}
\u{6A21}\u{5F0F}
${0, _mode0OnSvgDefault.default}
\u{6EDA}\u{52A8}
${0, _mode1OnSvgDefault.default}
\u{9876}\u{90E8}
${0, _mode2OnSvgDefault.default}
\u{5E95}\u{90E8}
\u{989C}\u{8272}
${this.COLOR.map((color)=>`
`).join('')}
\u{53D1}\u{9001}
`; } get OPACITY() { return { min: 0, max: 100, steps: [], ...this.option.OPACITY }; } get FONT_SIZE() { return { min: 12, max: 120, steps: [], ...this.option.FONT_SIZE }; } get MARGIN() { return { min: 0, max: 3, steps: [ { name: '1/4', value: [ 10, '75%' ] }, { name: "\u534A\u5C4F", value: [ 10, '50%' ] }, { name: '3/4', value: [ 10, '25%' ] }, { name: "\u6EE1\u5C4F", value: [ 10, 10 ] } ], ...this.option.MARGIN }; } get SPEED() { return { min: 0, max: 4, steps: [ { name: "\u6781\u6162", value: 10 }, { name: "\u8F83\u6162", value: 7.5, hide: true }, { name: "\u9002\u4E2D", value: 5 }, { name: "\u8F83\u5FEB", value: 2.5, hide: true }, { name: "\u6781\u5FEB", value: 1 } ], ...this.option.SPEED }; } get COLOR() { return this.option.COLOR.length ? this.option.COLOR : [ '#FE0302', '#FF7204', '#FFAA02', '#FFD302', '#FFFF00', '#A0EE00', '#00CD00', '#019899', '#4266BE', '#89D5FF', '#CC0273', '#222222', '#9B9B9B', '#FFFFFF' ]; } query(selector) { const { query } = this.utils; const { $danmuku } = this.template; return query(selector, $danmuku); } append(el, target) { const { append } = this.utils; const children = [ ...el.children ]; if (children.some((item)=>item === target)) return; append(el, target); } setData(key, value) { const { $player } = this.art.template; const { $mount } = this.template; $player.dataset[key] = value; if (this.outside) $mount.dataset[key] = value; } createTemplate() { const { createElement, tooltip } = this.utils; const $danmuku = createElement('div'); $danmuku.className = 'artplayer-plugin-danmuku'; $danmuku.innerHTML = this.TEMPLATE; this.template.$danmuku = $danmuku; this.template.$toggle = this.query('.apd-toggle'); this.template.$config = this.query('.apd-config'); this.template.$configPanel = this.query('.apd-config-panel'); this.template.$configModes = this.query('.apd-config-mode .apd-modes'); this.template.$style = this.query('.apd-style'); this.template.$stylePanel = this.query('.apd-style-panel'); this.template.$styleModes = this.query('.apd-style-mode .apd-modes'); this.template.$colors = this.query('.apd-colors'); this.template.$antiOverlap = this.query('.apd-anti-overlap'); this.template.$syncVideo = this.query('.apd-sync-video'); this.template.$opacitySlider = this.query('.apd-config-opacity .apd-slider'); this.template.$opacityValue = this.query('.apd-config-opacity .apd-value'); this.template.$marginSlider = this.query('.apd-config-margin .apd-slider'); this.template.$marginValue = this.query('.apd-config-margin .apd-value'); this.template.$fontSizeSlider = this.query('.apd-config-fontSize .apd-slider'); this.template.$fontSizeValue = this.query('.apd-config-fontSize .apd-value'); this.template.$speedSlider = this.query('.apd-config-speed .apd-slider'); this.template.$speedValue = this.query('.apd-config-speed .apd-value'); this.template.$input = this.query('.apd-input'); this.template.$send = this.query('.apd-send'); const { $toggle } = this.template; this.art.on('artplayerPluginDanmuku:show', ()=>{ tooltip($toggle, "\u5173\u95ED\u5F39\u5E55"); }); this.art.on('artplayerPluginDanmuku:hide', ()=>{ tooltip($toggle, "\u6253\u5F00\u5F39\u5E55"); }); } createEvents() { const { $toggle, $configModes, $styleModes, $colors, $antiOverlap, $syncVideo, $send, $input } = this.template; this.art.proxy($toggle, 'click', ()=>{ this.danmuku.config({ visible: !this.option.visible }); this.reset(); }); this.art.proxy($configModes, 'click', (event)=>{ const $mode = event.target.closest('.apd-mode'); if (!$mode) return; const mode = Number($mode.dataset.mode); if (this.option.modes.includes(mode)) this.danmuku.config({ modes: this.option.modes.filter((m)=>m !== mode) }); else this.danmuku.config({ modes: [ ...this.option.modes, mode ] }); this.reset(); }); this.art.proxy($antiOverlap, 'click', ()=>{ this.danmuku.config({ antiOverlap: !this.option.antiOverlap }); this.reset(); }); this.art.proxy($syncVideo, 'click', ()=>{ this.danmuku.config({ synchronousPlayback: !this.option.synchronousPlayback }); this.reset(); }); this.art.proxy($styleModes, 'click', (event)=>{ const $mode = event.target.closest('.apd-mode'); if (!$mode) return; const mode = Number($mode.dataset.mode); this.danmuku.config({ mode: mode }); this.reset(); }); this.art.proxy($colors, 'click', (event)=>{ const $color = event.target.closest('.apd-color'); if (!$color) return; this.danmuku.config({ color: $color.dataset.color }); this.reset(); }); this.art.proxy($send, 'click', ()=>this.emit()); this.art.proxy($input, 'keypress', (event)=>{ if (event.key === 'Enter') { event.preventDefault(); this.emit(); } }); } createSliders() { this.slider.opacity = this.createSlider({ ...this.OPACITY, container: this.template.$opacitySlider, findIndex: ()=>{ return Math.round(this.option.opacity * 100); }, onChange: (index)=>{ const { $opacityValue } = this.template; $opacityValue.textContent = `${index}%`; this.danmuku.config({ opacity: index / 100 }); } }); this.slider.margin = this.createSlider({ ...this.MARGIN, container: this.template.$marginSlider, findIndex: ()=>{ return this.MARGIN.steps.findIndex((item)=>item.value[0] === this.option.margin[0] && item.value[1] === this.option.margin[1]); }, onChange: (index)=>{ const margin = this.MARGIN.steps[index]; if (!margin) return; const { $marginValue } = this.template; $marginValue.textContent = margin.name; this.danmuku.config({ margin: margin.value }); } }); this.slider.fontSize = this.createSlider({ ...this.FONT_SIZE, container: this.template.$fontSizeSlider, findIndex: ()=>{ return this.danmuku.fontSize; }, onChange: (index)=>{ const { $fontSizeValue } = this.template; $fontSizeValue.textContent = `${index}px`; if (index === this.danmuku.fontSize) return; this.danmuku.config({ fontSize: index }); } }); this.slider.speed = this.createSlider({ ...this.SPEED, container: this.template.$speedSlider, findIndex: ()=>{ return this.SPEED.steps.findIndex((item)=>item.value === this.option.speed); }, onChange: (index)=>{ const speed = this.SPEED.steps[index]; if (!speed) return; const { $speedValue } = this.template; $speedValue.textContent = speed.name; this.danmuku.config({ speed: speed.value }); } }); } createSlider({ min, max, container, findIndex, onChange, steps = [] }) { const { query, clamp, setStyle } = this.utils; setStyle(container, 'touch-action', 'none'); container.innerHTML = `
${steps.map(()=>`
`).join('')}
${steps.map((step)=>step.hide ? '' : `
${step.name}
`).join('')}
`; const $dot = query('.apd-slider-dot', container); const $progress = query('.apd-slider-progress', container); let isDroging = false; function reset(index = findIndex()) { if (index < min || index > max) return; const percentage = (index - min) / (max - min); $dot.style.left = `${percentage * 100}%`; if (steps.length === 0) $progress.style.width = $dot.style.left; onChange(index); } function updateLeft(event) { const { top, height, left, width } = container.getBoundingClientRect(); if (this.art.isRotate) { const value = clamp(event.clientY - top, 0, height); const index = Math.round(value / height * (max - min) + min); reset(index); } else { const value = clamp(event.clientX - left, 0, width); const index = Math.round(value / width * (max - min) + min); reset(index); } } this.art.proxy(container, 'click', (event)=>{ updateLeft.call(this, event); }); this.art.proxy(container, 'pointerdown', (event)=>{ isDroging = event.button === 0; }); this.art.proxy(document, 'pointermove', (event)=>{ if (isDroging) updateLeft.call(this, event); }); this.art.proxy(document, 'pointerup', (event)=>{ if (isDroging) { isDroging = false; updateLeft.call(this, event); } }); return { reset }; } onFullscreen(state) { const { $danmuku, $controlsCenter, $mount } = this.template; if (this.outside) { if (state) this.append($controlsCenter, $danmuku); else this.append($mount, $danmuku); } else this.append($controlsCenter, $danmuku); } onMouseEnter({ $control, $panel }) { const { $player } = this.art.template; const controlRect = $control.getBoundingClientRect(); const panelRect = $panel.getBoundingClientRect(); const playerRect = $player.getBoundingClientRect(); const half = panelRect.width / 2 - controlRect.width / 2; const left = playerRect.left - (controlRect.left - half); const right = controlRect.right + half - playerRect.right; if (left > 0) $panel.style.left = `${-half + left}px`; else if (right > 0) $panel.style.left = `${-half - right}px`; else $panel.style.left = `${-half}px`; } async emit() { const { $input } = this.template; const text = $input.value.trim(); if (!text.length) return; if (this.isLock) return; if (this.emitting) return; const danmu = { text: text, mode: this.option.mode, color: this.option.color, time: this.art.currentTime }; try { this.emitting = true; const state = await this.option.beforeEmit(danmu); this.emitting = false; if (state !== true) return; danmu.border = true; delete danmu.time; this.danmuku.emit(danmu); $input.value = ''; this.lock(); // eslint-disable-next-line no-unused-vars } catch (error) { this.emitting = false; } } lock() { const { addClass } = this.utils; const { $send } = this.template; this.isLock = true; let time = this.option.lockTime; $send.innerText = time; addClass($send, 'apd-lock'); const loop = ()=>{ this.timer = setTimeout(()=>{ if (time === 0) this.unlock(); else { time -= 1; $send.innerText = time; loop(); } }, 1000); }; loop(); } unlock() { const { removeClass } = this.utils; const { $send } = this.template; clearTimeout(this.timer); this.isLock = false; $send.innerText = "\u53D1\u9001"; removeClass($send, 'apd-lock'); } resize() { if (this.outside) return; if (this.art.fullscreen) return; if (this.art.fullscreenWeb) return; const { $player, $controlsCenter } = this.art.template; const { $danmuku } = this.template; if (this.art.width < this.option.width) this.append($player, $danmuku); else this.append($controlsCenter, $danmuku); } reset() { const { inverseClass, tooltip } = this.utils; const { $toggle, $colors } = this.template; this.slider.opacity.reset(); this.slider.margin.reset(); this.slider.fontSize.reset(); this.slider.speed.reset(); this.setData('danmukuVisible', this.option.visible); this.setData('danmukuMode', this.option.mode); this.setData('danmukuColor', this.option.color); this.setData('danmukuMode0', this.option.modes.includes(0)); this.setData('danmukuMode1', this.option.modes.includes(1)); this.setData('danmukuMode2', this.option.modes.includes(2)); this.setData('danmukuAntiOverlap', this.option.antiOverlap); this.setData('danmukuSyncVideo', this.option.synchronousPlayback); this.setData('danmukuTheme', this.option.theme); this.setData('danmukuEmitter', this.option.emitter); const colors = $colors.children; const $color = Array.from(colors).find((item)=>item.dataset.color === this.option.color.toUpperCase()); $color && inverseClass($color, 'apd-active'); tooltip($toggle, this.option.visible ? "\u5173\u95ED\u5F39\u5E55" : "\u6253\u5F00\u5F39\u5E55"); this.resize(); } mount(target) { const { errorHandle } = this.utils; const $el = typeof target === 'string' ? document.querySelector(target) : target; errorHandle($el, `Can not find the mount point: ${target}`); this.append($el, this.template.$danmuku); this.template.$mount = $el; this.reset(); } } exports.default = Setting; if (typeof document !== 'undefined') { const id = 'artplayer-plugin-danmuku'; let $style = document.getElementById(id); if (!$style) { $style = document.createElement('style'); $style.id = id; if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', ()=>{ document.head.appendChild($style); }); else (document.head || document.documentElement).appendChild($style); } $style.textContent = (0, _styleLessDefault.default); } },{"bundle-text:./style.less":"bh9OX","bundle-text:./img/on.svg":"8mY0A","bundle-text:./img/off.svg":"iqU8K","bundle-text:./img/config.svg":"Zd1dH","bundle-text:./img/style.svg":"jPuJr","bundle-text:./img/mode_0_off.svg":"clMb3","bundle-text:./img/mode_0_on.svg":"6jPwG","bundle-text:./img/mode_1_off.svg":"gAeHz","bundle-text:./img/mode_1_on.svg":"fK0pY","bundle-text:./img/mode_2_off.svg":"67tbB","bundle-text:./img/mode_2_on.svg":"9zaZq","bundle-text:./img/check_on.svg":"dHpvg","bundle-text:./img/check_off.svg":"iZKOG","@parcel/transformer-js/src/esmodule-helpers.js":"8oCsH"}],"bh9OX":[function(require,module,exports,__globalThis) { module.exports = ".artplayer-plugin-danmuku {\n z-index: 99;\n color: #fff;\n flex-shrink: 0;\n justify-content: center;\n align-items: center;\n gap: 10px;\n width: 100%;\n height: 32px;\n font-size: 12px;\n font-weight: 300;\n display: flex;\n position: relative;\n}\n\n.artplayer-plugin-danmuku .apd-icon {\n cursor: pointer;\n opacity: .75;\n fill: #fff;\n transition: all .2s;\n}\n\n.artplayer-plugin-danmuku .apd-icon:hover {\n opacity: 1;\n}\n\n.artplayer-plugin-danmuku .apd-config {\n display: flex;\n position: relative;\n}\n\n.artplayer-plugin-danmuku .apd-config .apd-config-panel {\n opacity: 0;\n pointer-events: none;\n width: 320px;\n padding: 10px;\n position: absolute;\n bottom: 24px;\n left: 0;\n}\n\n.artplayer-plugin-danmuku .apd-config .apd-config-panel .apd-config-panel-inner {\n background-color: #000000d9;\n border-radius: 3px;\n width: 100%;\n padding: 10px;\n}\n\n.artplayer-plugin-danmuku .apd-config:hover .apd-config-panel {\n opacity: 100;\n pointer-events: all;\n}\n\n.artplayer-plugin-danmuku .apd-config-mode, .artplayer-plugin-danmuku .apd-config-slider, .artplayer-plugin-danmuku .apd-config-other, .artplayer-plugin-danmuku .apd-style-mode {\n margin-bottom: 15px;\n}\n\n.artplayer-plugin-danmuku .apd-modes {\n align-items: center;\n gap: 20px;\n margin-top: 5px;\n display: flex;\n}\n\n.artplayer-plugin-danmuku .apd-modes .apd-mode {\n cursor: pointer;\n text-align: center;\n}\n\n.artplayer-plugin-danmuku .apd-modes .apd-mode:hover {\n color: #00a1d6;\n}\n\n.artplayer-plugin-danmuku .apd-config-slider {\n align-items: center;\n gap: 12px;\n display: flex;\n}\n\n.artplayer-plugin-danmuku .apd-config-slider .apd-value {\n text-align: right;\n width: 32px;\n}\n\n.artplayer-plugin-danmuku .apd-slider {\n cursor: pointer;\n flex: 1;\n justify-content: center;\n align-items: center;\n height: 20px;\n display: flex;\n position: relative;\n}\n\n.artplayer-plugin-danmuku .apd-slider .apd-slider-line {\n background-color: #ffffff40;\n border-radius: 3px;\n width: 100%;\n height: 2px;\n position: relative;\n overflow: hidden;\n}\n\n.artplayer-plugin-danmuku .apd-slider .apd-slider-points {\n justify-content: space-between;\n align-items: center;\n display: flex;\n position: absolute;\n inset: 0;\n}\n\n.artplayer-plugin-danmuku .apd-slider .apd-slider-points .apd-slider-point {\n background-color: #ffffff80;\n border-radius: 50%;\n width: 2px;\n height: 2px;\n}\n\n.artplayer-plugin-danmuku .apd-slider .apd-slider-progress {\n background-color: #00a1d6;\n width: 0%;\n height: 100%;\n}\n\n.artplayer-plugin-danmuku .apd-slider .apd-slider-dot {\n background-color: #00a1d6;\n border-radius: 50%;\n width: 12px;\n height: 12px;\n position: absolute;\n left: 0%;\n transform: translateX(-6px);\n}\n\n.artplayer-plugin-danmuku .apd-slider .apd-slider-steps {\n color: #777;\n justify-content: space-between;\n align-items: center;\n width: calc(100% + 32px);\n display: flex;\n position: absolute;\n bottom: -12px;\n}\n\n.artplayer-plugin-danmuku .apd-slider .apd-slider-steps .apd-slider-step {\n text-align: center;\n flex-shrink: 0;\n width: 36px;\n scale: .95;\n}\n\n.artplayer-plugin-danmuku .apd-config-other {\n align-items: center;\n gap: 20px;\n display: flex;\n}\n\n.artplayer-plugin-danmuku .apd-config-other .apd-check-off, .artplayer-plugin-danmuku .apd-config-other .apd-check-on {\n width: 16px;\n height: 16px;\n}\n\n.artplayer-plugin-danmuku .apd-config-other .apd-other {\n cursor: pointer;\n align-items: center;\n gap: 2px;\n display: flex;\n}\n\n.artplayer-plugin-danmuku .apd-config-other .apd-other:hover {\n color: #00a1d6;\n}\n\n.artplayer-plugin-danmuku .apd-emitter {\n background-color: #ffffff40;\n border-radius: 5px;\n flex: 1;\n align-items: center;\n height: 100%;\n display: flex;\n}\n\n.artplayer-plugin-danmuku .apd-style {\n justify-content: center;\n align-items: center;\n display: flex;\n position: relative;\n}\n\n.artplayer-plugin-danmuku .apd-style .apd-style-panel {\n opacity: 0;\n pointer-events: none;\n width: 200px;\n padding: 10px;\n position: absolute;\n bottom: 24px;\n left: 0;\n}\n\n.artplayer-plugin-danmuku .apd-style .apd-style-panel .apd-style-panel-inner {\n background-color: #000000d9;\n border-radius: 3px;\n width: 100%;\n padding: 10px;\n}\n\n.artplayer-plugin-danmuku .apd-style:hover .apd-style-panel {\n opacity: 100;\n pointer-events: all;\n}\n\n.artplayer-plugin-danmuku .apd-colors {\n flex-wrap: wrap;\n gap: 8px;\n margin-top: 5px;\n display: flex;\n}\n\n.artplayer-plugin-danmuku .apd-colors .apd-color {\n cursor: pointer;\n border-radius: 2px;\n width: 16px;\n height: 16px;\n}\n\n.artplayer-plugin-danmuku .apd-colors .apd-color.apd-active {\n border: 1px solid #000;\n box-shadow: 0 0 0 1px #fff;\n}\n\n.artplayer-plugin-danmuku .apd-input {\n color: #fff;\n background-color: #0000;\n border: none;\n outline: none;\n flex: 1;\n width: auto;\n min-width: 0;\n height: 100%;\n line-height: 1;\n}\n\n.artplayer-plugin-danmuku .apd-input::placeholder {\n color: #ffffff80;\n}\n\n.artplayer-plugin-danmuku .apd-send {\n cursor: pointer;\n width: 60px;\n height: 100%;\n text-shadow: none;\n background-color: #00a1d6;\n border-top-right-radius: 5px;\n border-bottom-right-radius: 5px;\n flex-shrink: 0;\n justify-content: center;\n align-items: center;\n display: flex;\n}\n\n.artplayer-plugin-danmuku .apd-send.apd-lock {\n cursor: not-allowed;\n color: #666;\n background-color: #e7e7e7;\n}\n\n.art-controls-center .apd-emitter {\n flex: none;\n width: 260px;\n}\n\n.art-fullscreen .artplayer-plugin-danmuku, .art-fullscreen-web .artplayer-plugin-danmuku {\n gap: 16px;\n height: 38px;\n}\n\n.art-fullscreen .artplayer-plugin-danmuku .apd-config-icon, .art-fullscreen-web .artplayer-plugin-danmuku .apd-config-icon, .art-fullscreen .artplayer-plugin-danmuku .apd-toggle-off, .art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-off, .art-fullscreen .artplayer-plugin-danmuku .apd-toggle-on, .art-fullscreen-web .artplayer-plugin-danmuku .apd-toggle-on {\n width: 28px;\n height: 28px;\n}\n\n.art-fullscreen .artplayer-plugin-danmuku .apd-emitter, .art-fullscreen-web .artplayer-plugin-danmuku .apd-emitter {\n flex: none;\n width: 400px;\n}\n\n.art-video-player > .artplayer-plugin-danmuku {\n padding: 0 10px;\n position: absolute;\n bottom: -40px;\n left: 0;\n right: 0;\n}\n\n.art-video-player:has( > .artplayer-plugin-danmuku) {\n margin-bottom: 40px;\n}\n\n[data-danmuku-emitter=\"false\"] .apd-emitter {\n display: none !important;\n}\n\n[data-danmuku-emitter=\"false\"] .art-controls-center .artplayer-plugin-danmuku {\n justify-content: flex-end;\n gap: 18px;\n}\n\n[data-danmuku-emitter=\"false\"].art-fullscreen .art-controls-center .artplayer-plugin-danmuku, [data-danmuku-emitter=\"false\"].art-fullscreen-web .art-controls-center .artplayer-plugin-danmuku {\n gap: 24px;\n}\n\n[data-danmuku-theme=\"light\"] > .artplayer-plugin-danmuku .apd-icon {\n fill: #333;\n}\n\n[data-danmuku-theme=\"light\"] > .artplayer-plugin-danmuku .apd-emitter {\n background-color: #f1f2f3;\n}\n\n[data-danmuku-theme=\"light\"] > .artplayer-plugin-danmuku .apd-input {\n color: #000;\n}\n\n[data-danmuku-theme=\"light\"] > .artplayer-plugin-danmuku .apd-input::placeholder {\n color: #0000004d;\n}\n\n[data-danmuku-visible=\"false\"] .apd-toggle-off {\n display: block;\n}\n\n[data-danmuku-visible=\"false\"] .apd-toggle-on, [data-danmuku-visible=\"true\"] .apd-toggle-off {\n display: none;\n}\n\n[data-danmuku-visible=\"true\"] .apd-toggle-on {\n display: block;\n}\n\n[data-danmuku-anti-overlap=\"false\"] .apd-anti-overlap .apd-check-on {\n display: none;\n}\n\n[data-danmuku-anti-overlap=\"false\"] .apd-anti-overlap .apd-check-off, [data-danmuku-anti-overlap=\"true\"] .apd-anti-overlap .apd-check-on {\n display: block;\n}\n\n[data-danmuku-anti-overlap=\"true\"] .apd-anti-overlap .apd-check-off, [data-danmuku-sync-video=\"false\"] .apd-sync-video .apd-check-on {\n display: none;\n}\n\n[data-danmuku-sync-video=\"false\"] .apd-sync-video .apd-check-off, [data-danmuku-sync-video=\"true\"] .apd-sync-video .apd-check-on {\n display: block;\n}\n\n[data-danmuku-sync-video=\"true\"] .apd-sync-video .apd-check-off {\n display: none;\n}\n\n[data-danmuku-mode0=\"false\"] .apd-config-mode .apd-mode-0-off {\n display: block;\n}\n\n[data-danmuku-mode0=\"false\"] .apd-config-mode .apd-mode-0-on {\n display: none;\n}\n\n[data-danmuku-mode0=\"false\"] .art-danmuku [data-mode=\"0\"] {\n opacity: 0 !important;\n}\n\n[data-danmuku-mode0=\"true\"] .apd-config-mode .apd-mode-0-off {\n display: none;\n}\n\n[data-danmuku-mode0=\"true\"] .apd-config-mode .apd-mode-0-on {\n display: block;\n}\n\n[data-danmuku-mode=\"0\"] .apd-style-mode [data-mode=\"0\"] {\n color: #00a1d6;\n}\n\n[data-danmuku-mode=\"0\"] .apd-style-mode [data-mode=\"0\"] path {\n fill: #00a1d6;\n}\n\n[data-danmuku-mode1=\"false\"] .apd-config-mode .apd-mode-1-off {\n display: block;\n}\n\n[data-danmuku-mode1=\"false\"] .apd-config-mode .apd-mode-1-on {\n display: none;\n}\n\n[data-danmuku-mode1=\"false\"] .art-danmuku [data-mode=\"1\"] {\n opacity: 0 !important;\n}\n\n[data-danmuku-mode1=\"true\"] .apd-config-mode .apd-mode-1-off {\n display: none;\n}\n\n[data-danmuku-mode1=\"true\"] .apd-config-mode .apd-mode-1-on {\n display: block;\n}\n\n[data-danmuku-mode=\"1\"] .apd-style-mode [data-mode=\"1\"] {\n color: #00a1d6;\n}\n\n[data-danmuku-mode=\"1\"] .apd-style-mode [data-mode=\"1\"] path {\n fill: #00a1d6;\n}\n\n[data-danmuku-mode2=\"false\"] .apd-config-mode .apd-mode-2-off {\n display: block;\n}\n\n[data-danmuku-mode2=\"false\"] .apd-config-mode .apd-mode-2-on {\n display: none;\n}\n\n[data-danmuku-mode2=\"false\"] .art-danmuku [data-mode=\"2\"] {\n opacity: 0 !important;\n}\n\n[data-danmuku-mode2=\"true\"] .apd-config-mode .apd-mode-2-off {\n display: none;\n}\n\n[data-danmuku-mode2=\"true\"] .apd-config-mode .apd-mode-2-on {\n display: block;\n}\n\n[data-danmuku-mode=\"2\"] .apd-style-mode [data-mode=\"2\"] {\n color: #00a1d6;\n}\n\n[data-danmuku-mode=\"2\"] .apd-style-mode [data-mode=\"2\"] path {\n fill: #00a1d6;\n}\n"; },{}],"8mY0A":[function(require,module,exports,__globalThis) { module.exports = ""; },{}],"iqU8K":[function(require,module,exports,__globalThis) { module.exports = ""; },{}],"Zd1dH":[function(require,module,exports,__globalThis) { module.exports = ""; },{}],"jPuJr":[function(require,module,exports,__globalThis) { module.exports = ""; },{}],"clMb3":[function(require,module,exports,__globalThis) { module.exports = ""; },{}],"6jPwG":[function(require,module,exports,__globalThis) { module.exports = ""; },{}],"gAeHz":[function(require,module,exports,__globalThis) { module.exports = ""; },{}],"fK0pY":[function(require,module,exports,__globalThis) { module.exports = ""; },{}],"67tbB":[function(require,module,exports,__globalThis) { module.exports = ""; },{}],"9zaZq":[function(require,module,exports,__globalThis) { module.exports = ""; },{}],"dHpvg":[function(require,module,exports,__globalThis) { module.exports = ""; },{}],"iZKOG":[function(require,module,exports,__globalThis) { module.exports = ""; },{}],"2G7iS":[function(require,module,exports,__globalThis) { var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); parcelHelpers.defineInteropFlag(exports); parcelHelpers.export(exports, "default", ()=>heatmap); 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) }, (v, 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) }; }; function heatmap(art, danmuku, option) { const { query } = art.constructor.utils; art.controls.add({ name: 'heatmap', position: 'top', html: '', style: { position: 'absolute', top: '-100px', left: '0px', right: '0px', height: '100px', width: '100%', pointerEvents: 'none' }, mounted ($heatmap) { let $start = null; let $stop = null; function update(arg = []) { $start = null; $stop = null; $heatmap.innerHTML = ''; if (!art.duration || art.option.isLive) return; const svg = { w: $heatmap.offsetWidth, h: $heatmap.offsetHeight }; const options = { xMin: 0, xMax: svg.w, yMin: 0, yMax: 128, scale: 0.25, opacity: 0.2, minHeight: Math.floor(svg.h * 0.05), sampling: Math.floor(svg.w / 100), smoothing: 0.2, flattening: 0.2 }; if (typeof option === 'object') Object.assign(options, option); let points = []; if (Array.isArray(arg) && arg.length) points = [ ...arg ]; else { const gap = art.duration / svg.w; for(let x = 0; x <= svg.w; x += options.sampling){ const y = danmuku.queue.filter(({ time })=>time > x * gap && time <= (x + options.sampling) * gap).length; points.push([ x, y ]); } } if (points.length === 0) return; const lastPoint = points[points.length - 1]; const lastX = lastPoint[0]; const lastY = lastPoint[1]; if (lastX !== svg.w) points.push([ svg.w, lastY ]); const yPoints = points.map((point)=>point[1]); const yMin = Math.min(...yPoints); const yMax = Math.max(...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, svg.w); const y = lib.map(e[1], options.yMin, options.yMax, svg.h, 0); return [ x, y ]; }); const pathD = pointsPositions.reduce((acc, e, i, a)=>i === 0 ? `M ${a[a.length - 1][0]},${svg.h} L ${e[0]},${svg.h} L ${e[0]},${e[1]}` : `${acc} ${bezierCommand(e, i, a)}`, ''); $heatmap.innerHTML = ` `; $start = query('#heatmap-start', $heatmap); $stop = query('#heatmap-stop', $heatmap); $start.setAttribute('offset', `${art.played * 100}%`); $stop.setAttribute('offset', `${art.played * 100}%`); } art.on('video:timeupdate', ()=>{ if ($start && $stop) { $start.setAttribute('offset', `${art.played * 100}%`); $stop.setAttribute('offset', `${art.played * 100}%`); } }); art.on('setBar', (type, percentage)=>{ if ($start && $stop && type === 'played') { $start.setAttribute('offset', `${percentage * 100}%`); $stop.setAttribute('offset', `${percentage * 100}%`); } }); art.on('ready', ()=>update()); art.on('resize', ()=>update()); art.on('artplayerPluginDanmuku:loaded', ()=>update()); art.on('artplayerPluginDanmuku:points', (points)=>update(points)); } }); } },{"@parcel/transformer-js/src/esmodule-helpers.js":"8oCsH"}]},["hfC30"], "hfC30", "parcelRequire4dc0", {}) //# sourceMappingURL=index.js.map