From 313b6ae54db6ec352f981167b84c6750598b8ef0 Mon Sep 17 00:00:00 2001 From: Harvey Zack Date: Tue, 28 May 2024 08:45:43 +0800 Subject: [PATCH] chore: Refactor `artplayerPluginDanmuku` plugin and add `artplayerPluginChromecast` plugin --- CHANGELOG_CN.md | 5 +- .../artplayer-plugin-danmuku/index.js | 64 ++++++++++----- .../artplayer-plugin-danmuku/index.js.map | 2 +- .../artplayer-plugin-danmuku/src/danmuku.js | 81 ++++++++++++++----- .../artplayer-plugin-danmuku/src/worker.js | 4 +- 5 files changed, 111 insertions(+), 45 deletions(-) diff --git a/CHANGELOG_CN.md b/CHANGELOG_CN.md index 8182a3807..e6fbce778 100644 --- a/CHANGELOG_CN.md +++ b/CHANGELOG_CN.md @@ -8,14 +8,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [5.1.2] - 2024-6-1 +- 重构 `artplayerPluginDanmuku` 插件 +- 新增 `artplayerPluginChromecast` 插件 - 添加 `fullscreenError` 事件 -- 添加 `supportsFlex` 工具函数 - 优化双击事件: [#728](https://github.com/zhw2590582/ArtPlayer/pull/728) - 修复 `thumbnails` 延迟显示问题 -- 新增 `artplayerPluginChromecast` 插件 - 新增 `art.plugins.lock.state = true/false`, 用于手动控制 `lock` 状态 - 当鼠标在控制栏上,或者打开了设置面板,控制栏不再自动隐藏 -- 重构 `artplayerPluginDanmuku` 插件 ## [5.1.1] - 2024-1-11 diff --git a/docs/uncompiled/artplayer-plugin-danmuku/index.js b/docs/uncompiled/artplayer-plugin-danmuku/index.js index ced775f94..f736714f6 100644 --- a/docs/uncompiled/artplayer-plugin-danmuku/index.js +++ b/docs/uncompiled/artplayer-plugin-danmuku/index.js @@ -202,7 +202,15 @@ class Danmuku { this.isStop = false; // 是否停止 this.isHide = false; // 是否隐藏 this.timer = null; // 定时器 - this.config(option); // 动态配置 + // 弹幕状态池 + this.states = { + wait: [], + ready: [], + emit: [], + stop: [] + }; + // 初始化配置 + this.config(option); // 创建 Web Worker, 用于计算弹幕的 top 值 this.worker = new Worker(URL.createObjectURL(new Blob([ (0, _workerDefault.default) @@ -211,6 +219,7 @@ class Danmuku { 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); @@ -218,7 +227,7 @@ class Danmuku { art.on("video:pause", this.stop); art.on("video:waiting", this.stop); art.on("destroy", this.destroy); - art.on("resize", this.reset); + art.on("resize", this.resize); // 开始加载弹幕 this.load(); } @@ -346,12 +355,15 @@ class Danmuku { // 获取准备好发送的弹幕:有的是ready状态(如之前因为弹幕太多而暂停发送的弹幕),有的是wait状态 get readys() { const { currentTime } = this.art; - return this.queue.filter((danmu)=>{ - return danmu.$state === "ready" || danmu.$state === "wait" && currentTime + 0.1 >= danmu.time && danmu.time >= currentTime - 0.1; + const result = []; + this.filter("ready", (danmu)=>result.push(danmu)); + this.filter("wait", (danmu)=>{ + if (currentTime + 0.1 >= danmu.time && danmu.time >= currentTime - 0.1) result.push(danmu); }); + return result; } - // 获取正在发送的弹幕,用于计算下一个弹幕的top值 - get emits() { + // 可见的弹幕的数据,用于计算下一个弹幕的top值 + get visibles() { const result = []; const { clientWidth } = this.$player; const clientLeft = this.getLeft(this.$player); @@ -390,8 +402,6 @@ class Danmuku { this.$danmuku.innerText = ""; // 清空弹幕层 this.danmus.forEach((danmu)=>this.emit(danmu)); // 逐个验证原始弹幕并转换为实际弹幕 this.art.emit("artplayerPluginDanmuku:loaded", this.queue); - // TODO: 按时间从小到大排序,用于减少弹幕的遍历次数,待优化... - // this.queue = this.queue.sort((a, b) => a.time - b.time); } catch (error) { this.art.emit("artplayerPluginDanmuku:error", error); throw error; @@ -431,10 +441,11 @@ class Danmuku { ].includes(danmu.mode)) return this; // 自定义弹幕过滤函数 if (!this.option.filter(danmu)) return this; + // 转换为wait状态 + this.setState(danmu, "wait"); // 添加到实际弹幕队列 this.queue.push({ ...danmu, - $state: "wait", $ref: null, $restTime: 0, $lastStartTime: 0 @@ -485,13 +496,24 @@ class Danmuku { }; }); } - // 根据状态,获取弹幕队列中的弹幕 + // 根据状态获取弹幕 filter(state, callback) { - return this.queue.filter((danmu)=>danmu.$state === state).map(callback); + const danmus = this.states[state] || []; + for(let index = 0; index < danmus.length; index++)callback(danmus[index]); + return danmus; + } + // 设置弹幕状态 + setState(danmu, state) { + // 从原状态池中删除 + if (danmu.$state) this.states[danmu.$state] = this.states[danmu.$state].filter((item)=>item !== danmu); + // 设置新状态 + danmu.$state = state; + // 添加到新状态池中 + this.states[state].push(danmu); } // 重置弹幕到wait状态,回收弹幕DOM节点 makeWait(danmu) { - danmu.$state = "wait"; + this.setState(danmu, "wait"); if (danmu.$ref) { danmu.$ref.style.cssText = Danmuku.cssText; this.$refs.push(danmu.$ref); @@ -546,7 +568,7 @@ class Danmuku { height: danmu.$ref.clientHeight, speed: (clientWidth + danmu.$ref.clientWidth) / danmu.$restTime }, - emits: this.emits, + visibles: this.visibles, antiOverlap: this.option.antiOverlap, clientWidth: clientWidth, clientHeight: clientHeight, @@ -555,7 +577,7 @@ class Danmuku { }); if (danmu.$ref) { if (!this.isStop && top !== undefined) { - danmu.$state = "emit"; // 转换为emit状态 + this.setState(danmu, "emit"); // 转换为emit状态 danmu.$ref.style.visibility = "visible"; danmu.$ref.dataset.mode = danmu.mode; // CSS控制模式的显示和隐藏 this.art.emit("artplayerPluginDanmuku:visible", danmu); @@ -581,7 +603,7 @@ class Danmuku { } } else { // 假如弹幕已经停止或者没有 top 值,则重置弹幕为ready状态,回收弹幕DOM节点,等待下次发送 - danmu.$state = "ready"; + this.setState(danmu, "ready"); this.$refs.push(danmu.$ref); danmu.$ref = null; } @@ -594,11 +616,17 @@ class Danmuku { }); return this; } + // 重置正在显示的弹幕 + resize() { + this.filter("emit", (danmu)=>{ + // + }); + } // 继续弹幕 continue() { const { clientWidth } = this.$player; this.filter("stop", (danmu)=>{ - danmu.$state = "emit"; // 转换为emit状态 + this.setState(danmu, "emit"); // 转换为emit状态 danmu.$lastStartTime = Date.now(); switch(danmu.mode){ // 继续滚动的弹幕 @@ -619,7 +647,7 @@ class Danmuku { suspend() { const { clientWidth } = this.$player; this.filter("emit", (danmu)=>{ - danmu.$state = "stop"; // 转换为stop状态 + this.setState(danmu, "stop"); // 转换为stop状态 switch(danmu.mode){ // 停止滚动的弹幕 case 0: @@ -765,7 +793,7 @@ exports.export = function(dest, destName, get) { }; },{}],"el0Wt":[function(require,module,exports) { -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 (modules, entry, mainEntry, parcelRequireName, globalName) {\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 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 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 this\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.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.register = function (id, exports) {\n modules[id] = [\n function (require, module) {\n module.exports = exports;\n },\n {},\n ];\n };\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 //