* chore: Bump artplayer-plugin-danmuku version to 5.1.0

* chore: Update danmuku plugin with new mount option

* chore: Update danmuku plugin with new mount option

* chore: Update danmuku plugin with new mount option

* chore: Update danmuku plugin with new mount option

* chore: Update danmuku plugin with new mount option

* chore: Update danmuku plugin with new mount option

* chore: Refactor getDanmuTop function to handle additional cases

* chore: Refactor getDanmuTop function to handle additional cases

* chore: Update danmuku plugin with new mount option

* chore: Update danmuku plugin with new mount option

* chore: Refactor danmuku plugin to always create a new Setting instance

* Refactor danmuku plugin to always create a new Setting instance

* Refactor danmuku plugin to always create a new Setting instance

* Update danmuku.js

* Update danmuku.js

* Refactor danmuku plugin to include a new Setting instance

* Refactor danmuku plugin to include a new Setting instance

* Refactor danmuku plugin styles for better alignment and spacing

* Refactor danmuku plugin styles for better alignment and spacing

* Refactor danmuku plugin styles for better alignment and spacing

* Refactor danmuku plugin styles for better alignment and spacing

* Refactor danmuku plugin styles for better alignment and spacing

* chore: Update package.json files

* Refactor danmuku plugin to remove unnecessary whitespace in the setting template

* chore: Remove ESLint configuration files and update npm dependencies

* chore: Update ESLint configuration to disable console warnings and fix unused variable warnings

* chore: Update ESLint configuration to fix quotes rule

* Update package.json

* Update package.json

* Update setting.js

* chore: Compress CSS code in build script

* chore: Update build script to compress JavaScript code

* chore: Update build script to compress JavaScript code

* chore: Refactor setting.js to improve code organization and remove unused variables

* chore: Add isHover flag to Control component for better control hiding behavior

* chore: Update Control component to use $bottom instead of $controls for better control hiding behavior

* Update package.json

* Update package.json

* Update package.json

* chore: Update lerna.json to use independent versioning and yarn as npm client

* refactor: Update Danmuku class to support configurable modes and visibility

* refactor: Update Danmuku styles for better visibility and configurability

* feat: Update font weight in Danmuku styles for better readability

* refactor: Update Danmuku class to support configurable modes and visibility

* refactor: Add opacity, area, font, and speed sliders to Danmuku settings

* refactor: Initialize sliders for opacity, area, font, and speed in Danmuku settings

* feat: Add slider UI for opacity, area, and speed in Danmuku settings

* feat: Update slider styles in Danmuku settings for better visibility and configurability

* refactor: Improve Danmuku settings UI for better visibility and configurability

* refactor: Improve Danmuku settings UI for better visibility and configurability

* refactor: Update slider styles in Danmuku settings for better visibility and configurability

* refactor: Update margin slider initialization in Danmuku settings

* refactor: Update slider styles and initialization in Danmuku settings

* refactor: Update Danmuku class to support configurable visibility

* refactor: Update Danmuku class to support configurable visibility

* refactor: Update Danmuku class to support configurable visibility

* refactor: Update Danmuku class to support configurable visibility

* refactor: Update Danmuku settings UI for better visibility and configurability

* refactor: Update Danmuku settings UI for better visibility and configurability

* refactor: Update Danmuku settings UI for better visibility and configurability

* refactor: Update Danmuku settings UI for better visibility and configurability

* refactor: Improve Danmuku settings UI for better visibility and configurability

* add lock

* refactor: Add lock and unlock methods to Danmuku settings

* refactor: Update Danmuku settings UI for better visibility and configurability

* refactor: Update Danmuku settings UI for better visibility and configurability

* refactor: Update Danmuku settings UI for better visibility and configurability

* refactor: Update Danmuku settings UI for better visibility and configurability

* Update artplayer-plugin-danmuku.d.ts

* refactor: Update Danmuku settings UI for better visibility and configurability

* refactor: Update Danmuku settings UI for better visibility and configurability

* refactor: Update Danmuku settings UI for better visibility and configurability

* refactor: Update Danmuku settings UI for better visibility and configurability

* refactor: Update Danmuku settings event listeners

* refactor: Update Danmuku settings UI for better visibility and configurability

* refactor: Update Danmuku settings UI for better visibility and configurability

* refactor: Update Danmuku settings UI for better visibility and configurability

* refactor: Update Danmuku settings UI for better visibility and configurability
This commit is contained in:
Harvey Zack authored and GitHub committed 2024-05-26 14:57:22 +08:00
1 parent dfd1cf968d
commit 699135afcf
402 files changed
+20804 -17005

No files matched your search

@@ -3,10 +3,11 @@ export function getMode(key) {
case 1:
case 2:
case 3:
return 0;
return 0; // 滚动
case 4:
return 2; // 底部
case 5:
return 1;
return 1; // 顶部
default:
return 0;
}
@@ -45,8 +46,8 @@ export function bilibiliDanmuParseFromXml(xmlString) {
.filter(Boolean);
}
export function bilibiliDanmuParseFromUrl(url) {
return fetch(url)
.then((res) => res.text())
.then((xmlString) => bilibiliDanmuParseFromXml(xmlString));
export async function bilibiliDanmuParseFromUrl(url) {
const res = await fetch(url);
const xmlString = await res.text();
return bilibiliDanmuParseFromXml(xmlString);
}
+381 -293
View File
@@ -1,73 +1,73 @@
import { bilibiliDanmuParseFromUrl } from './bilibili';
import getDanmuTop from './getDanmuTop';
import workerText from 'bundle-text:./worker';
export default 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.utils = constructor.utils; // 工具库
this.validator = constructor.validator; // 配置校验器
this.$danmuku = template.$danmuku; // 弹幕层容器
this.$player = template.$player; // 播放器容器
this.art = art;
this.danmus = [];
this.queue = [];
this.option = {};
this.$refs = [];
this.isStop = false;
this.isHide = false;
this.timer = null;
this.config(option);
this.danmus = []; // 原始弹幕数据
this.queue = []; // 实际弹幕队列
this.option = {}; // 格式化后的配置项
this.$refs = []; // 弹幕DOM节点池
this.isStop = false; // 是否停止
this.isHide = false; // 是否隐藏
this.timer = null; // 定时器
this.config(option); // 动态配置
if (this.option.useWorker) {
try {
this.worker = new Worker(new URL('data-url:./worker.js', import.meta.url));
} catch (error) {
//
}
}
// 创建 Web Worker, 用于计算弹幕的 top 值
this.worker = new Worker(URL.createObjectURL(new Blob([workerText])));
// 绑定公用事件
this.start = this.start.bind(this);
this.stop = this.stop.bind(this);
this.reset = this.reset.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('resize', this.reset);
art.on('destroy', this.destroy);
art.on('resize', this.reset);
// 开始加载弹幕
this.load();
}
// 默认配置
static get option() {
return {
danmuku: [],
speed: 5,
margin: ['2%', '25%'],
opacity: 1,
color: '#FFFFFF',
mode: 0,
fontSize: 25,
filter: () => true,
antiOverlap: true,
useWorker: true,
synchronousPlayback: false,
lockTime: 5,
maxLength: 100,
minWidth: 200,
maxWidth: 400,
mount: undefined,
theme: 'dark',
heatmap: false,
points: [],
beforeEmit: () => true,
danmuku: [], // 弹幕数据
speed: 5, // 弹幕持续时间,范围在[1 ~ 10]
margin: [10, '25%'], // 弹幕上下边距,支持像素数字和百分比
opacity: 1, // 弹幕透明度,范围在[0 ~ 1]
color: '#FFFFFF', // 默认弹幕颜色,可以被单独弹幕项覆盖
mode: 0, // 默认弹幕模式: 0: 滚动,1: 顶部,2: 底部
modes: [0, 1, 2], // 弹幕可见的模式
fontSize: 25, // 弹幕字体大小,支持像素数字和百分比
antiOverlap: true, // 弹幕是否防重叠
synchronousPlayback: false, // 是否同步播放速度
mount: undefined, // 弹幕发射器挂载点, 默认为播放器控制栏中部
heatmap: false, // 是否开启热力图
points: [], // 热力图数据
filter: () => true, // 弹幕载入前的过滤器,只支持返回布尔值
beforeEmit: () => true, // 弹幕发送前的过滤器,支持返回 Promise
beforeVisible: () => true, // 弹幕显示前的过滤器,支持返回 Promise
visible: true, // 弹幕层是否可见
maxLength: 200, // 弹幕输入框最大长度, 范围在[1 ~ 1000]
lockTime: 5, // 输入框锁定时间,范围在[1 ~ 60]
theme: 'dark', // 弹幕主题,支持 dark 和 light,只在自定义挂载时生效
};
}
// 配置校验
static get scheme() {
return {
danmuku: 'array|function|string',
@@ -76,27 +76,47 @@ export default class Danmuku {
opacity: 'number',
color: 'string',
mode: 'number',
modes: 'array',
fontSize: 'number|string',
filter: 'function',
antiOverlap: 'boolean',
useWorker: 'boolean',
synchronousPlayback: 'boolean',
lockTime: 'number',
maxLength: 'number',
minWidth: 'number',
maxWidth: 'number',
mount: 'undefined|htmldivelement',
theme: 'string',
mount: '?htmldivelement',
heatmap: 'object|boolean',
points: 'array',
filter: 'function',
beforeEmit: 'function',
beforeVisible: 'function',
visible: 'boolean',
maxLength: 'number',
lockTime: 'number',
theme: 'string',
};
}
get isRotate() {
return this.art.plugins.autoOrientation && this.art.plugins.autoOrientation.state;
// 初始弹幕样式
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];
@@ -114,6 +134,7 @@ export default class Danmuku {
return Danmuku.option.margin[0];
}
// 计算下空白边距
get marginBottom() {
const { clamp } = this.utils;
const value = this.option.margin[1];
@@ -131,39 +152,190 @@ export default class Danmuku {
return Danmuku.option.margin[1];
}
filter(state, callback) {
return this.queue.filter((danmu) => danmu.$state === state).map(callback);
// 加载弹幕
async load() {
const { errorHandle } = this.utils;
try {
if (typeof this.option.danmuku === 'function') {
this.danmus = await this.option.danmuku();
} else if (typeof this.option.danmuku.then === 'function') {
this.danmus = await this.option.danmuku;
} else if (typeof this.option.danmuku === 'string') {
this.danmus = await bilibiliDanmuParseFromUrl(this.option.danmuku);
} else {
this.danmus = this.option.danmuku;
}
errorHandle(Array.isArray(this.danmus), 'Danmuku need return an array as result');
this.queue = []; // 清空实际弹幕队列
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;
}
return this;
}
// 把原始弹幕转换为实际弹幕
emit(danmu) {
const { clamp } = this.utils;
this.validator(danmu, {
text: 'string', // 弹幕文本
mode: '?number', // 弹幕模式: 0: 滚动,1: 顶部,2: 底部
color: '?string', // 弹幕颜色
time: '?number', // 弹幕时间
border: '?boolean', // 弹幕是否有边框
style: '?object', // 弹幕额外样式
escape: '?boolean', // 弹幕文本是否转义
});
// 弹幕文本为空则直接忽略
if (!danmu.text.trim()) return this;
// 弹幕模式只能是 0, 1, 2
if (![0, 1, 2].includes(danmu.mode)) return this;
// 自定义弹幕过滤函数
if (!this.option.filter(danmu)) 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 = {};
}
// 设置弹幕弹幕是否转义,如果没有则默认为 true,即不会显示 HTML 标签
if (danmu.escape === undefined) {
danmu.escape = true;
}
// 设置弹幕颜色,如果没有则默认为全局配置
if (danmu.color === undefined) {
danmu.color = this.option.color;
}
// 添加到实际弹幕队列
this.queue.push({
...danmu,
$state: 'wait', // 弹幕状态
$ref: null, // 弹幕 DOM 节点
$restTime: 0, // 弹幕剩余时间
$lastStartTime: 0, // 弹幕上次开始时间
});
// 弹幕有四个状态:
// - wait: 弹幕还未开始显示,没有被添加到 DOM 中
// - ready: 弹幕准备好显示,没有被添加到 DOM 中
// - emit: 弹幕正在显示,已经被添加到 DOM 中
// - stop: 弹幕正在停止显示,已经被添加到 DOM 中
return this;
}
// 动态配置
config(option) {
const { clamp } = this.utils;
const { $controlsCenter } = this.art.template;
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.option.fontSize = this.getFontSize(this.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;
}
// 获取弹幕DOM节点
getRef() {
const $refCache = this.$refs.pop();
if ($refCache) return $refCache;
const $ref = document.createElement('div');
$ref.style.cssText = `
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;
`;
const $ref = this.$refs.pop() || document.createElement('div');
$ref.style.cssText = Danmuku.cssText;
$ref.dataset.mode = '';
return $ref;
}
// 计算弹幕字体大小
getFontSize(fontSize) {
const { clamp } = this.utils;
const { clientHeight } = this.$player;
if (typeof fontSize === 'number') {
return clamp(fontSize, 12, clientHeight);
}
if (typeof fontSize === 'string' && fontSize.endsWith('%')) {
const ratio = parseFloat(fontSize) / 100;
return clamp(clientHeight * ratio, 12, clientHeight);
}
return Danmuku.option.fontSize;
}
// 计算弹幕的top值
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) {
return this.queue.filter((danmu) => danmu.$state === state).map(callback);
}
// 获取准备好发送的弹幕:有的是ready状态(如之前因为弹幕太多而暂停发送的弹幕),有的是wait状态
getReady() {
const { currentTime } = this.art;
return this.queue.filter((danmu) => {
@@ -174,6 +346,7 @@ export default class Danmuku {
});
}
// 获取正在发送的弹幕,用于计算下一个弹幕的top值
getEmits() {
const result = [];
const { clientWidth } = this.$player;
@@ -205,107 +378,144 @@ export default class Danmuku {
return result;
}
getFontSize(fontSize) {
const { clamp } = this.utils;
const { clientHeight } = this.$player;
if (typeof fontSize === 'number') {
return clamp(fontSize, 12, clientHeight);
}
if (typeof fontSize === 'string' && fontSize.endsWith('%')) {
const ratio = parseFloat(fontSize) / 100;
return clamp(clientHeight * ratio, 12, clientHeight);
}
return Danmuku.option.fontSize;
}
postMessage(message = {}) {
return new Promise((resolve) => {
if (this.option.useWorker && this.worker && this.worker.postMessage) {
message.id = Date.now();
this.worker.postMessage(message);
this.worker.onmessage = (event) => {
const { data } = event;
if (data.id === message.id) {
resolve(data);
}
};
} else {
const top = getDanmuTop(message);
resolve({ top });
}
});
}
async load() {
try {
if (typeof this.option.danmuku === 'function') {
this.danmus = await this.option.danmuku();
} else if (typeof this.option.danmuku.then === 'function') {
this.danmus = await this.option.danmuku;
} else if (typeof this.option.danmuku === 'string') {
this.danmus = await bilibiliDanmuParseFromUrl(this.option.danmuku);
} else {
this.danmus = this.option.danmuku;
}
this.utils.errorHandle(Array.isArray(this.danmus), 'Danmuku need return an array as result');
this.art.emit('artplayerPluginDanmuku:loaded', this.danmus);
this.queue = [];
this.$danmuku.innerText = '';
this.danmus.forEach((danmu) => this.emit(danmu));
} catch (error) {
this.art.emit('artplayerPluginDanmuku:error', error);
throw error;
}
return this;
}
config(option) {
const { clamp } = this.utils;
this.option = Object.assign({}, Danmuku.option, this.option, option);
this.validator(this.option, Danmuku.scheme);
this.option.speed = clamp(this.option.speed, 1, 10);
this.option.opacity = clamp(this.option.opacity, 0, 1);
this.option.lockTime = clamp(Math.floor(this.option.lockTime), 0, 60);
this.option.maxLength = clamp(this.option.maxLength, 0, 500);
this.option.minWidth = clamp(this.option.minWidth, 0, 500);
this.option.maxWidth = clamp(this.option.maxWidth, 0, Infinity);
if (option.fontSize) {
this.option.fontSize = this.getFontSize(this.option.fontSize);
this.reset();
}
this.art.emit('artplayerPluginDanmuku:config', this.option);
return this;
}
// 重置弹幕到wait状态,回收弹幕DOM节点
makeWait(danmu) {
danmu.$state = 'wait';
if (danmu.$ref) {
danmu.$ref.style.visibility = 'hidden';
danmu.$ref.style.marginLeft = '0px';
danmu.$ref.style.transform = 'translateX(0px)';
danmu.$ref.style.transition = 'transform 0s linear 0s';
danmu.$ref.style.cssText = Danmuku.cssText;
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.getReady();
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.getRef(); // 获取弹幕DOM节点
// 设置弹幕文本
if (danmu.escape) {
danmu.$ref.innerText = danmu.text;
} else {
danmu.$ref.innerHTML = danmu.text;
}
// 提前添加到弹幕层中,用于计算top值
this.$danmuku.appendChild(danmu.$ref);
// 设置初始弹幕样式
danmu.$ref.style.left = `${clientWidth}px`;
danmu.$ref.style.opacity = this.option.opacity;
danmu.$ref.style.fontSize = `${this.option.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;
danmu.$ref.style.marginLeft = '0px';
// 设置单独弹幕样式
setStyles(danmu.$ref, danmu.style);
// 记录弹幕时间戳
danmu.$lastStartTime = Date.now();
// 计算弹幕剩余时间
danmu.$restTime =
this.option.synchronousPlayback && this.art.playbackRate
? this.option.speed / Number(this.art.playbackRate)
: this.option.speed;
// 计算弹幕的top值
const { top } = await this.postMessage({
target: {
mode: danmu.mode,
height: danmu.$ref.clientHeight,
speed: (clientWidth + danmu.$ref.clientWidth) / danmu.$restTime,
}, // 当前弹幕信息
emits: this.getEmits(), // 正在发送的其它弹幕
antiOverlap: this.option.antiOverlap,
clientWidth: clientWidth,
clientHeight: clientHeight,
marginBottom: this.marginBottom,
marginTop: this.marginTop,
});
if (danmu.$ref) {
if (!this.isStop && top !== undefined) {
danmu.$state = 'emit'; // 转换为emit状态
danmu.$ref.style.visibility = 'visible';
danmu.$ref.dataset.mode = danmu.mode; // CSS控制模式的显示和隐藏
this.art.emit('artplayerPluginDanmuku:visible', danmu);
switch (danmu.mode) {
// 滚动的弹幕
case 0: {
danmu.$ref.style.top = `${top}px`;
const translateX = clientWidth + danmu.$ref.clientWidth;
danmu.$ref.style.transform = `translateX(${-translateX}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.top = `${top}px`;
danmu.$ref.style.marginLeft = `-${danmu.$ref.clientWidth / 2}px`;
break;
default:
break;
}
} else {
// 假如弹幕已经停止或者没有 top 值,则重置弹幕为ready状态,回收弹幕DOM节点,等待下次发送
danmu.$state = 'ready';
this.$refs.push(danmu.$ref);
danmu.$ref = null;
}
}
}
}
}
// 递归调用
if (!this.isStop) {
this.update();
}
});
return this;
}
// 继续弹幕
continue() {
const { clientWidth } = this.$player;
this.filter('stop', (danmu) => {
danmu.$state = 'emit';
danmu.$state = 'emit'; // 转换为emit状态
danmu.$lastStartTime = Date.now();
switch (danmu.mode) {
// 继续滚动的弹幕
case 0: {
const translateX = clientWidth + danmu.$ref.clientWidth;
danmu.$ref.style.transform = `translateX(${-translateX}px)`;
@@ -320,11 +530,13 @@ export default class Danmuku {
return this;
}
// 暂停弹幕
suspend() {
const { clientWidth } = this.$player;
this.filter('emit', (danmu) => {
danmu.$state = 'stop';
danmu.$state = '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)`;
@@ -339,98 +551,6 @@ export default class Danmuku {
return this;
}
reset() {
this.queue.forEach((danmu) => this.makeWait(danmu));
return this;
}
update() {
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);
}
});
const readys = this.getReady();
const { clientWidth, clientHeight } = this.$player;
for (let index = 0; index < readys.length; index++) {
const danmu = readys[index];
danmu.$ref = this.getRef();
danmu.$ref.innerText = danmu.text;
this.$danmuku.appendChild(danmu.$ref);
danmu.$ref.style.left = `${clientWidth}px`;
danmu.$ref.style.opacity = this.option.opacity;
danmu.$ref.style.fontSize = `${this.option.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;
danmu.$ref.style.marginLeft = '0px';
danmu.$lastStartTime = Date.now();
danmu.$restTime =
this.option.synchronousPlayback && this.art.playbackRate
? this.option.speed / Number(this.art.playbackRate)
: this.option.speed;
const target = {
mode: danmu.mode,
height: danmu.$ref.clientHeight,
speed: (clientWidth + danmu.$ref.clientWidth) / danmu.$restTime,
};
const { top } = await this.postMessage({
target,
emits: this.getEmits(),
antiOverlap: this.option.antiOverlap,
clientWidth: clientWidth,
clientHeight: clientHeight,
marginBottom: this.marginBottom,
marginTop: this.marginTop,
});
if (danmu.$ref) {
if (!this.isStop && top !== undefined) {
danmu.$state = 'emit';
danmu.$ref.style.visibility = 'visible';
switch (danmu.mode) {
case 0: {
danmu.$ref.style.top = `${top}px`;
const translateX = clientWidth + danmu.$ref.clientWidth;
danmu.$ref.style.transform = `translateX(${-translateX}px)`;
danmu.$ref.style.transition = `transform ${danmu.$restTime}s linear 0s`;
break;
}
case 1:
danmu.$ref.style.left = '50%';
danmu.$ref.style.top = `${top}px`;
danmu.$ref.style.marginLeft = `-${danmu.$ref.clientWidth / 2}px`;
break;
default:
break;
}
} else {
danmu.$state = 'ready';
this.$refs.push(danmu.$ref);
danmu.$ref = null;
}
}
}
}
if (!this.isStop) {
this.update();
}
});
return this;
}
stop() {
this.isStop = true;
this.suspend();
@@ -447,10 +567,15 @@ export default class Danmuku {
return this;
}
reset() {
this.queue.forEach((danmu) => this.makeWait(danmu));
return this;
}
show() {
this.isHide = false;
this.start();
this.$danmuku.style.display = 'block';
this.$danmuku.style.display = '';
this.art.emit('artplayerPluginDanmuku:show');
return this;
}
@@ -464,46 +589,9 @@ export default class Danmuku {
return this;
}
emit(danmu) {
this.validator(danmu, {
text: 'string',
mode: 'number|undefined',
color: 'string|undefined',
time: 'number|undefined',
border: 'boolean|undefined',
});
if (!danmu.text.trim()) return this;
if (!this.option.filter(danmu)) return this;
if (danmu.time) {
danmu.time = this.utils.clamp(danmu.time, 0, Infinity);
} else {
danmu.time = this.art.currentTime + 0.5;
}
if (danmu.mode === undefined) {
danmu.mode = this.option.mode;
}
if (danmu.color === undefined) {
danmu.color = this.option.color;
}
this.queue.push({
...danmu,
$state: 'wait',
$ref: null,
$restTime: 0,
$lastStartTime: 0,
});
return this;
}
destroy() {
this.stop();
if (this.worker && this.worker.terminate) this.worker.terminate();
this.worker.terminate();
this.art.off('video:play', this.start);
this.art.off('video:playing', this.start);
this.art.off('video:pause', this.stop);
@@ -1,105 +0,0 @@
export default function getDanmuTop({
target,
emits,
clientWidth,
clientHeight,
marginBottom,
marginTop,
antiOverlap,
}) {
const danmus = emits
.filter((item) => item.mode === target.mode && item.top <= clientHeight - marginBottom)
.sort((prev, next) => prev.top - next.top);
if (danmus.length === 0) {
return marginTop;
}
danmus.unshift({
top: 0,
left: 0,
right: 0,
height: marginTop,
width: clientWidth,
speed: 0,
distance: clientWidth,
});
danmus.push({
top: clientHeight - marginBottom,
left: 0,
right: 0,
height: marginBottom,
width: clientWidth,
speed: 0,
distance: clientWidth,
});
for (let index = 1; index < danmus.length; index += 1) {
const item = danmus[index];
const prev = danmus[index - 1];
const prevBottom = prev.top + prev.height;
const diff = item.top - prevBottom;
if (diff >= target.height) {
return prevBottom;
}
}
const topMap = [];
for (let index = 1; index < danmus.length - 1; index += 1) {
const item = danmus[index];
if (topMap.length) {
const last = topMap[topMap.length - 1];
if (last[0].top === item.top) {
last.push(item);
} else {
topMap.push([item]);
}
} else {
topMap.push([item]);
}
}
if (antiOverlap) {
switch (target.mode) {
case 0: {
const result = topMap.find((list) => {
return list.every((danmu) => {
if (clientWidth < danmu.distance) return false;
if (target.speed < danmu.speed) return true;
const overlapTime = danmu.right / (target.speed - danmu.speed);
if (overlapTime > danmu.time) return true;
return false;
});
});
return result && result[0] ? result[0].top : undefined;
}
case 1:
return undefined;
default:
break;
}
} else {
switch (target.mode) {
case 0:
topMap.sort((prev, next) => {
const nextMinRight = Math.min(...next.map((item) => item.right));
const prevMinRight = Math.min(...prev.map((item) => item.right));
return nextMinRight * next.length - prevMinRight * prev.length;
});
break;
case 1:
topMap.sort((prev, next) => {
const nextMaxWidth = Math.max(...next.map((item) => item.width));
const prevMaxWidth = Math.max(...prev.map((item) => item.width));
return prevMaxWidth * prev.length - nextMaxWidth * next.length;
});
break;
default:
break;
}
return topMap[0][0].top;
}
}
+1
View File
@@ -0,0 +1 @@
<svg class="apd-icon apd-check-off" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" data-pointer="none" viewBox="0 0 32 32" width="32" height="32" ><path d="M8 6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2H8zm0-2h16c2.21 0 4 1.79 4 4v16c0 2.21-1.79 4-4 4H8c-2.21 0-4-1.79-4-4V8c0-2.21 1.79-4 4-4z" fill="#FFFFFF"></path></svg>

After

Width:  |  Height:  |  Size: 378 B

+1
View File
@@ -0,0 +1 @@
<svg class="apd-icon apd-check-on" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" data-pointer="none" viewBox="0 0 32 32" width="32" height="32" ><path d="m13 18.25-1.8-1.8c-.6-.6-1.65-.6-2.25 0s-.6 1.5 0 2.25l2.85 2.85c.318.318.762.468 1.2.448.438.02.882-.13 1.2-.448l8.85-8.85c.6-.6.6-1.65 0-2.25s-1.65-.6-2.25 0l-7.8 7.8zM8 4h16c2.21 0 4 1.79 4 4v16c0 2.21-1.79 4-4 4H8c-2.21 0-4-1.79-4-4V8c0-2.21 1.79-4 4-4z" fill="#00AEEC"></path></svg>

After

Width:  |  Height:  |  Size: 474 B

+1
View File
@@ -0,0 +1 @@
<svg class="apd-icon apd-config-icon" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" data-pointer="none" viewBox="0 0 24 24" width="24" height="24" ><path fill-rule="evenodd" d="m15.645 4.881 1.06-1.473a.998.998 0 1 0-1.622-1.166L13.22 4.835a110.67 110.67 0 0 0-1.1-.007h-.131c-.47 0-.975.004-1.515.012L8.783 2.3A.998.998 0 0 0 7.12 3.408l.988 1.484c-.688.019-1.418.042-2.188.069a4.013 4.013 0 0 0-3.83 3.44c-.165 1.15-.245 2.545-.245 4.185 0 1.965.115 3.67.35 5.116a4.012 4.012 0 0 0 3.763 3.363c1.903.094 3.317.141 5.513.141a.988.988 0 0 0 0-1.975 97.58 97.58 0 0 1-5.416-.139 2.037 2.037 0 0 1-1.91-1.708c-.216-1.324-.325-2.924-.325-4.798 0-1.563.076-2.864.225-3.904.14-.977.96-1.713 1.945-1.747 2.444-.087 4.465-.13 6.063-.131 1.598 0 3.62.044 6.064.13.96.034 1.71.81 1.855 1.814.075.524.113 1.962.141 3.065v.002c.005.183.01.07.014-.038.004-.096.008-.189.011-.081a.987.987 0 1 0 1.974-.069c-.004-.105-.007-.009-.011.09-.002.056-.004.112-.007.135l-.002.01a.574.574 0 0 1-.005-.091v-.027c-.03-1.118-.073-2.663-.16-3.276-.273-1.906-1.783-3.438-3.74-3.507-.905-.032-1.752-.058-2.543-.079Zm-3.113 4.703h-1.307v4.643h2.2v.04l.651-1.234c.113-.215.281-.389.482-.509v-.11h.235c.137-.049.283-.074.433-.074h1.553V9.584h-1.264a8.5 8.5 0 0 0 .741-1.405l-1.078-.381c-.24.631-.501 1.23-.806 1.786h-1.503l.686-.305c-.228-.501-.5-.959-.806-1.394l-1.034.348c.294.392.566.839.817 1.35Zm-1.7 5.502h2.16l-.564 1.068h-1.595v-1.068Zm-2.498-1.863.152-1.561h1.96V8.289H7.277v.969h2.048v1.435h-1.84l-.306 3.51h2.254c0 1.155-.043 1.906-.12 2.255-.076.348-.38.523-.925.523-.305 0-.61-.022-.893-.055l.294 1.056.061.005c.282.02.546.039.81.039.991-.065 1.547-.414 1.677-1.046.11-.631.175-1.883.175-3.757H8.334Zm5.09-.8v.85h-1.188v-.85h1.187Zm-1.188-.955h1.187v-.893h-1.187v.893Zm2.322.007v-.893h1.241v.893h-1.241Zm.528 2.757a1.26 1.26 0 0 1 1.087-.627l4.003-.009a1.26 1.26 0 0 1 1.094.63l1.721 2.982c.226.39.225.872-.001 1.263l-1.743 3a1.26 1.26 0 0 1-1.086.628l-4.003.009a1.26 1.26 0 0 1-1.094-.63l-1.722-2.982a1.26 1.26 0 0 1 .002-1.263l1.742-3Zm1.967.858a1.26 1.26 0 0 0-1.08.614l-.903 1.513a1.26 1.26 0 0 0-.002 1.289l.885 1.492c.227.384.64.62 1.086.618l2.192-.005a1.26 1.26 0 0 0 1.08-.615l.904-1.518a1.26 1.26 0 0 0 .001-1.288l-.884-1.489a1.26 1.26 0 0 0-1.086-.616l-2.193.005Zm2.517 2.76a1.4 1.4 0 1 1-2.8 0 1.4 1.4 0 0 1 2.8 0Z" clip-rule="evenodd"></path></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22" width="22" height="22">
<path d="M16.5 8c1.289 0 2.49.375 3.5 1.022V6a2 2 0 00-2-2H4a2 2 0 00-2 2v10a2 2 0 002 2h7.022A6.5 6.5 0 0116.5 8zM7 13H5a1 1 0 010-2h2a1 1 0 010 2zm2-4H5a1 1 0 010-2h4a1 1 0 010 2z"></path>
<path d="M20.587 13.696l-.787-.131a3.503 3.503 0 00-.593-1.051l.301-.804a.46.46 0 00-.21-.56l-1.005-.581a.52.52 0 00-.656.113l-.499.607a3.53 3.53 0 00-1.276 0l-.499-.607a.52.52 0 00-.656-.113l-1.005.581a.46.46 0 00-.21.56l.301.804c-.254.31-.456.665-.593 1.051l-.787.131a.48.48 0 00-.413.465v1.209a.48.48 0 00.413.465l.811.135c.144.382.353.733.614 1.038l-.292.78a.46.46 0 00.21.56l1.005.581a.52.52 0 00.656-.113l.515-.626a3.549 3.549 0 001.136 0l.515.626a.52.52 0 00.656.113l1.005-.581a.46.46 0 00.21-.56l-.292-.78c.261-.305.47-.656.614-1.038l.811-.135A.48.48 0 0021 15.37v-1.209a.48.48 0 00-.413-.465zM16.5 16.057a1.29 1.29 0 11.002-2.582 1.29 1.29 0 01-.002 2.582z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 964 B

@@ -1,6 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg viewBox="0 0 1152 1024" width="22" height="22" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path fill="#fff" d="M311.296 661.504c0 4.096-4.096 8.704-8.704 12.8 0 4.096 0 4.096-4.096 8.704h-12.8c-4.096 0-8.704-4.096-16.896-8.704-8.704-8.704-16.896-8.704-25.6-8.704s-12.8 4.096-16.896 12.8c-4.096 12.8-8.704 21.504-4.096 29.696 4.096 8.704 12.8 16.896 25.6 21.504 16.896 8.704 34.304 16.896 47.104 16.896 12.8 0 21.504-4.096 34.304-8.704 8.704-4.096 16.896-16.896 25.6-29.696 8.704-12.8 12.8-34.304 16.896-55.296 4.096-21.504 4.096-51.2 4.096-85.504 0-12.8 0-21.504-4.096-29.696 0-8.704-4.096-12.8-8.704-16.896-4.096-4.096-8.704-8.704-12.8-8.704s-12.8-4.096-21.504-4.096H272.896s-4.096-4.096 0-8.704l4.096-38.4c0-4.096 0-8.704 4.096-8.704h47.104c16.896 0 25.6-4.096 34.304-12.8 8.704-8.704 12.8-21.504 12.8-42.496V324.096c0-16.896-4.096-34.304-8.704-42.496-12.8-12.8-25.6-16.896-42.496-16.896H243.2c-8.704 0-16.896 0-21.504 4.096-4.096 8.704-4.096 12.8-4.096 25.6 0 8.704 0 16.896 4.096 21.504 4.096 4.096 12.8 8.704 21.504 8.704H307.2c4.096 0 8.704 0 8.704 4.096v34.304c0 8.704 0 12.8-4.096 12.8 0 0-4.096 4.096-8.704 4.096h-34.304c-8.704 0-12.8 0-21.504 4.096-4.096 0-8.704 4.096-8.704 4.096-4.096 4.096-8.704 12.8-8.704 16.896 0 8.704-4.096 16.896-4.096 25.6l-8.704 72.704v29.696c0 8.704 4.096 12.8 8.704 16.896s8.704 4.096 16.896 8.704h68.096c4.096 0 8.704 0 8.704 4.096s4.096 8.704 4.096 16.896c0 21.504 0 42.496-4.096 55.296 0.512 9.216-3.584 22.016-8.192 26.624zM896 486.4c-93.696 0-175.104 51.2-217.6 123.904h-106.496v-34.304H640c21.504 0 34.304-4.096 42.496-12.8 8.704-8.704 12.8-21.504 12.8-42.496V358.4c0-21.504-4.096-34.304-12.8-42.496-8.704-8.704-21.504-12.8-42.496-12.8 0-4.096 4.096-4.096 4.096-8.704-4.096 0-4.096-4.096-4.096-4.096 4.096-12.8 8.704-21.504 4.096-25.6 0-8.704-4.096-12.8-12.8-21.504-8.704-4.096-16.896-4.096-21.504-4.096-8.704 4.096-12.8 8.704-21.504 21.504-4.096 8.704-8.704 12.8-12.8 21.504-4.096 8.704-8.704 12.8-12.8 21.504h-51.2c-4.096-8.704-8.704-16.896-8.704-21.504-4.096-8.704-8.704-12.8-12.8-21.504-4.096-12.8-12.8-16.896-21.504-16.896s-16.896 0-25.6 8.704C434.176 261.12 430.08 265.216 430.08 273.92c0 8.704 0 16.896 8.704 25.6l4.096 4.096 4.096 4.096c-16.896 0-29.696 4.096-38.4 12.8-8.704 4.096-12.8 21.504-12.8 38.4v157.696c0 21.504 4.096 34.304 12.8 42.496 8.704 8.704 21.504 12.8 42.496 12.8H512v34.304H413.696c-12.8 0-21.504 0-25.6 4.096-4.096 4.096-8.704 8.704-8.704 21.504v16.896c0 4.096 4.096 8.704 4.096 8.704 4.096 0 4.096 4.096 8.704 4.096H512V716.8c0 12.8 4.096 21.504 8.704 25.6 4.096 4.096 12.8 8.704 21.504 8.704 12.8 0 21.504-4.096 25.6-8.704 4.096-4.096 4.096-12.8 4.096-25.6v-55.296h80.896c-8.704 25.6-12.8 51.2-12.8 76.8 0 140.8 115.2 256 256 256s256-115.2 256-256S1036.8 486.4 896 486.4z m-328.704-128h55.296c4.096 0 4.096 0 4.096 4.096V409.6h-59.904V358.4z m0 102.4h59.904v51.2h-55.296V460.8h-4.608zM512 516.096H456.704v-51.2H512v51.2z m0-102.4H452.096v-51.2H512v51.2z m384 499.2c-93.696 0-170.496-76.8-170.496-170.496s76.8-170.496 170.496-170.496 170.496 76.8 170.496 170.496-76.8 170.496-170.496 170.496z" />
<path fill="#fff" d="M580.096 879.104H213.504c-72.704 0-128-55.296-128-119.296V230.4c0-64 55.296-119.296 128-119.296h512c72.704 0 128 55.296 128 119.296v140.8c0 25.6 16.896 42.496 42.496 42.496s42.496-16.896 42.496-42.496V230.4c0-115.2-93.696-204.8-213.504-204.8h-512C93.696 25.6 0 119.296 0 230.4v528.896c0 115.2 93.696 204.8 213.504 204.8h367.104c25.6 0 42.496-16.896 42.496-42.496 0-25.6-21.504-42.496-43.008-42.496z m171.52 10.752c-15.36-15.36-15.36-40.96 0-56.32l237.568-237.568c15.36-15.36 40.96-15.36 56.32 0s15.36 40.96 0 56.32l-237.568 237.568c-15.36 15.36-40.448 15.36-56.32 0z" />
</svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

@@ -1,6 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg viewBox="0 0 1152 1024" width="22" height="22" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path fill="#fff" d="M311.466667 661.333333c0 4.266667-4.266667 8.533333-8.533334 12.8 0 4.266667 0 4.266667-4.266666 8.533334h-12.8c-4.266667 0-8.533333-4.266667-17.066667-8.533334-8.533333-8.533333-17.066667-8.533333-25.6-8.533333-8.533333 0-12.8 4.266667-17.066667 12.8-4.266667 12.8-8.533333 21.333333-4.266666 29.866667 4.266667 8.533333 12.8 17.066667 25.6 21.333333 17.066667 8.533333 34.133333 17.066667 46.933333 17.066667 12.8 0 21.333333-4.266667 34.133333-8.533334 8.533333-4.266667 17.066667-17.066667 25.6-29.866666 8.533333-12.8 12.8-34.133333 17.066667-55.466667 4.266667-21.333333 4.266667-51.2 4.266667-85.333333 0-12.8 0-21.333333-4.266667-29.866667 0-8.533333-4.266667-12.8-8.533333-17.066667-4.266667-4.266667-8.533333-8.533333-12.8-8.533333-4.266667 0-12.8-4.266667-21.333334-4.266667H273.066667s-4.266667-4.266667 0-8.533333l4.266666-38.4c0-4.266667 0-8.533333 4.266667-8.533333h46.933333c17.066667 0 25.6-4.266667 34.133334-12.8 8.533333-8.533333 12.8-21.333333 12.8-42.666667V324.266667c0-17.066667-4.266667-34.133333-8.533334-42.666667-12.8-12.8-25.6-17.066667-42.666666-17.066667H243.2c-8.533333 0-17.066667 0-21.333333 4.266667-4.266667 8.533333-4.266667 12.8-4.266667 25.6 0 8.533333 0 17.066667 4.266667 21.333333 4.266667 4.266667 12.8 8.533333 21.333333 8.533334h64c4.266667 0 8.533333 0 8.533333 4.266666v34.133334c0 8.533333 0 12.8-4.266666 12.8 0 0-4.266667 4.266667-8.533334 4.266666h-34.133333c-8.533333 0-12.8 0-21.333333 4.266667-4.266667 0-8.533333 4.266667-8.533334 4.266667-4.266667 4.266667-8.533333 12.8-8.533333 17.066666 0 8.533333-4.266667 17.066667-4.266667 25.6l-8.533333 72.533334v29.866666c0 8.533333 4.266667 12.8 8.533333 17.066667 4.266667 4.266667 8.533333 4.266667 17.066667 8.533333h68.266667c4.266667 0 8.533333 0 8.533333 4.266667s4.266667 8.533333 4.266667 17.066667c0 21.333333 0 42.666667-4.266667 55.466666 0 8.533333-4.266667 21.333333-8.533333 25.6zM896 486.4c-93.866667 0-174.933333 51.2-217.6 123.733333h-106.666667v-34.133333H640c21.333333 0 34.133333-4.266667 42.666667-12.8 8.533333-8.533333 12.8-21.333333 12.8-42.666667V358.4c0-21.333333-4.266667-34.133333-12.8-42.666667-8.533333-8.533333-21.333333-12.8-42.666667-12.8 0-4.266667 4.266667-4.266667 4.266667-8.533333-4.266667 0-4.266667-4.266667-4.266667-4.266667 4.266667-12.8 8.533333-21.333333 4.266667-25.6 0-8.533333-4.266667-12.8-12.8-21.333333-8.533333-4.266667-17.066667-4.266667-21.333334-4.266667-8.533333 4.266667-12.8 8.533333-21.333333 21.333334-4.266667 8.533333-8.533333 12.8-12.8 21.333333-4.266667 8.533333-8.533333 12.8-12.8 21.333333H512c-4.266667-8.533333-8.533333-17.066667-8.533333-21.333333-4.266667-8.533333-8.533333-12.8-12.8-21.333333-4.266667-12.8-12.8-17.066667-21.333334-17.066667s-17.066667 0-25.6 8.533333c-8.533333 8.533333-12.8 12.8-12.8 21.333334s0 17.066667 8.533334 25.6l4.266666 4.266666 4.266667 4.266667c-17.066667 0-29.866667 4.266667-38.4 12.8-8.533333 4.266667-12.8 21.333333-12.8 38.4v157.866667c0 21.333333 4.266667 34.133333 12.8 42.666666 8.533333 8.533333 21.333333 12.8 42.666667 12.8H512v34.133334H413.866667c-12.8 0-21.333333 0-25.6 4.266666-4.266667 4.266667-8.533333 8.533333-8.533334 21.333334v17.066666c0 4.266667 4.266667 8.533333 4.266667 8.533334 4.266667 0 4.266667 4.266667 8.533333 4.266666H512v55.466667c0 12.8 4.266667 21.333333 8.533333 25.6 4.266667 4.266667 12.8 8.533333 21.333334 8.533333 12.8 0 21.333333-4.266667 25.6-8.533333 4.266667-4.266667 4.266667-12.8 4.266666-25.6v-55.466667h81.066667c-8.533333 25.6-12.8 51.2-12.8 76.8 0 140.8 115.2 256 256 256s256-115.2 256-256-115.2-251.733333-256-251.733333z m-328.533333-128h55.466666c4.266667 0 4.266667 0 4.266667 4.266667v46.933333h-59.733333V358.4z m0 102.4h59.733333V512h-55.466667v-51.2zM512 516.266667h-55.466667V465.066667H512v51.2z m0-102.4h-59.733333V366.933333v-4.266666H512v51.2z m384 499.2c-93.866667 0-170.666667-76.8-170.666667-170.666667s76.8-170.666667 170.666667-170.666667 170.666667 76.8 170.666667 170.666667-76.8 170.666667-170.666667 170.666667z" />
<path fill="#fff" d="M951.466667 669.866667l-72.533334 72.533333-29.866666-25.6c-17.066667-17.066667-42.666667-12.8-59.733334 4.266667-17.066667 17.066667-12.8 42.666667 4.266667 59.733333l59.733333 51.2c8.533333 8.533333 17.066667 8.533333 29.866667 8.533333 12.8 0 21.333333-4.266667 29.866667-12.8l102.4-102.4c17.066667-17.066667 17.066667-42.666667 0-59.733333-21.333333-12.8-46.933333-12.8-64 4.266667zM580.266667 878.933333H213.333333c-72.533333 0-128-55.466667-128-119.466666V230.4c0-64 55.466667-119.466667 128-119.466667h512c72.533333 0 128 55.466667 128 119.466667v140.8c0 25.6 17.066667 42.666667 42.666667 42.666667s42.666667-17.066667 42.666667-42.666667V230.4c0-115.2-93.866667-204.8-213.333334-204.8H213.333333C93.866667 25.6 0 119.466667 0 230.4v529.066667c0 115.2 93.866667 204.8 213.333333 204.8h366.933334c25.6 0 42.666667-17.066667 42.666666-42.666667s-21.333333-42.666667-42.666666-42.666667z" />
</svg>

Before

Width:  |  Height:  |  Size: 5.1 KiB

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22" width="24" height="24">
<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"></path>
</svg>

Before

Width:  |  Height:  |  Size: 380 B

+1
View File
@@ -0,0 +1 @@
<svg class="apd-icon apd-mode-0-off" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" data-pointer="none" style="enable-background:new 0 0 28 28" viewBox="0 0 28 28" width="28" height="28" ><path d="M23 15c1.487 0 2.866.464 4 1.255V7a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v14a4 4 0 0 0 4 4h11.674A7 7 0 0 1 23 15zM11 9h6a1 1 0 0 1 0 2h-6a1 1 0 0 1 0-2zm-3 2H6V9h2v2zm4 4h-2v-2h2v2zm2-1a1 1 0 0 1 1-1h1a1 1 0 0 1 0 2h-1a1 1 0 0 1-1-1z" fill="#00AEEC"></path><path d="M26.536 18.464a5 5 0 0 0-7.071 0 5 5 0 0 0 0 7.071 5 5 0 1 0 7.071-7.071zm-5.657 5.657a3 3 0 0 1-.586-3.415l4.001 4.001a3 3 0 0 1-3.415-.586zm4.829-.827-4.001-4.001a3.002 3.002 0 0 1 4.001 4.001z" fill="#00AEEC"></path></svg>

After

Width:  |  Height:  |  Size: 733 B

+1
View File
@@ -0,0 +1 @@
<svg class="apd-icon apd-mode-0-on" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" data-pointer="none" style="enable-background:new 0 0 28 28" viewBox="0 0 28 28" width="28" height="28" ><path d="M23 3H5a4 4 0 0 0-4 4v14a4 4 0 0 0 4 4h18a4 4 0 0 0 4-4V7a4 4 0 0 0-4-4zM11 9h6a1 1 0 0 1 0 2h-6a1 1 0 0 1 0-2zm-3 2H6V9h2v2zm4 4h-2v-2h2v2zm9 0h-6a1 1 0 0 1 0-2h6a1 1 0 0 1 0 2z" fill="#FFFFFF"></path></svg>

After

Width:  |  Height:  |  Size: 457 B

+1
View File
@@ -0,0 +1 @@
<svg class="apd-icon apd-mode-1-off" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" data-pointer="none" style="enable-background:new 0 0 28 28" viewBox="0 0 28 28" width="28" height="28" ><path d="M23 15c1.487 0 2.866.464 4 1.255V7a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v14a4 4 0 0 0 4 4h11.674A7 7 0 0 1 23 15zm-4-8h2v2h-2V7zM9 9H7V7h2v2zm4 0h-2V7h2v2zm2-2h2v2h-2V7z" fill="#00AEEC"></path><path d="M26.536 18.464a5 5 0 0 0-7.071 0 5 5 0 0 0 0 7.071 5 5 0 1 0 7.071-7.071zm-5.657 5.657a3 3 0 0 1-.586-3.415l4.001 4.001a3 3 0 0 1-3.415-.586zm4.829-.827-4.001-4.001a3.002 3.002 0 0 1 4.001 4.001z" fill="#00AEEC"></path></svg>

After

Width:  |  Height:  |  Size: 669 B

+1
View File
@@ -0,0 +1 @@
<svg class="apd-icon apd-mode-1-on" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" data-pointer="none" style="enable-background:new 0 0 28 28" viewBox="0 0 28 28" width="28" height="28" ><path d="M23 3H5a4 4 0 0 0-4 4v14a4 4 0 0 0 4 4h18a4 4 0 0 0 4-4V7a4 4 0 0 0-4-4zM9 9H7V7h2v2zm4 0h-2V7h2v2zm4 0h-2V7h2v2zm4 0h-2V7h2v2z" fill="#FFFFFF"></path></svg>

After

Width:  |  Height:  |  Size: 406 B

+1
View File
@@ -0,0 +1 @@
<svg class="apd-icon apd-mode-2-off" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" data-pointer="none" style="enable-background:new 0 0 28 28" viewBox="0 0 28 28" width="28" height="28" ><path d="M23 15c1.487 0 2.866.464 4 1.255V7a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v14a4 4 0 0 0 4 4h11.674A7 7 0 0 1 23 15zM9 21H7v-2h2v2zm4 0h-2v-2h2v2z" fill="#00AEEC"></path><path d="M26.536 18.464a5 5 0 0 0-7.071 0 5 5 0 0 0 0 7.071 5 5 0 1 0 7.071-7.071zm-5.657 5.657a3 3 0 0 1-.586-3.415l4.001 4.001a3 3 0 0 1-3.415-.586zm4.829-.827-4.001-4.001a3.002 3.002 0 0 1 4.001 4.001z" fill="#00AEEC"></path></svg>

After

Width:  |  Height:  |  Size: 643 B

+1
View File
@@ -0,0 +1 @@
<svg class="apd-icon apd-mode-2-on" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" data-pointer="none" style="enable-background:new 0 0 28 28" viewBox="0 0 28 28" width="28" height="28" ><path d="M23 3H5a4 4 0 0 0-4 4v14a4 4 0 0 0 4 4h18a4 4 0 0 0 4-4V7a4 4 0 0 0-4-4zM9 21H7v-2h2v2zm4 0h-2v-2h2v2zm4 0h-2v-2h2v2zm4 0h-2v-2h2v2z" fill="#FFFFFF"></path></svg>

After

Width:  |  Height:  |  Size: 411 B

+1
View File
@@ -0,0 +1 @@
<svg class="apd-icon apd-toggle-off" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" data-pointer="none" viewBox="0 0 24 24" width="24" height="24" ><path fill-rule="evenodd" d="m8.085 4.891-.999-1.499a1.008 1.008 0 0 1 1.679-1.118l1.709 2.566c.54-.008 1.045-.012 1.515-.012h.13c.345 0 .707.003 1.088.007l1.862-2.59a1.008 1.008 0 0 1 1.637 1.177l-1.049 1.46c.788.02 1.631.046 2.53.078 1.958.069 3.468 1.6 3.74 3.507.088.613.13 2.158.16 3.276l.001.027c.01.333.017.63.025.856a.987.987 0 0 1-1.974.069c-.008-.23-.016-.539-.025-.881v-.002c-.028-1.103-.066-2.541-.142-3.065-.143-1.004-.895-1.78-1.854-1.813-2.444-.087-4.466-.13-6.064-.131-1.598 0-3.619.044-6.063.13a2.037 2.037 0 0 0-1.945 1.748c-.15 1.04-.225 2.341-.225 3.904 0 1.874.11 3.474.325 4.798.154.949.95 1.66 1.91 1.708a97.58 97.58 0 0 0 5.416.139.988.988 0 0 1 0 1.975c-2.196 0-3.61-.047-5.513-.141A4.012 4.012 0 0 1 2.197 17.7c-.236-1.446-.351-3.151-.351-5.116 0-1.64.08-3.035.245-4.184A4.013 4.013 0 0 1 5.92 4.96c.761-.027 1.483-.05 2.164-.069Zm4.436 4.707h-1.32v4.63h2.222v.848h-2.618v1.078h2.431a5.01 5.01 0 0 1 3.575-3.115V9.598h-1.276a8.59 8.59 0 0 0 .748-1.42l-1.089-.384a14.232 14.232 0 0 1-.814 1.804h-1.518l.693-.308a8.862 8.862 0 0 0-.814-1.408l-1.045.352c.297.396.572.847.825 1.364Zm-4.18 3.564.154-1.485h1.98V8.289h-3.2v.979h2.067v1.43H7.483l-.308 3.454h2.277c0 1.166-.044 1.925-.12 2.277-.078.352-.386.528-.936.528-.308 0-.616-.022-.902-.055l.297 1.067.062.004c.285.02.551.04.818.04 1.001-.066 1.562-.418 1.694-1.056.11-.638.176-1.903.176-3.795h-2.2Zm7.458.11v-.858h-1.254v.858H15.8Zm-2.376-.858v.858h-1.199v-.858h1.2Zm-1.199-.946h1.2v-.902h-1.2v.902Zm2.321 0v-.902H15.8v.902h-1.254Zm3.517 10.594a4 4 0 1 0 0-8 4 4 0 0 0 0 8Zm-.002-1.502a2.5 2.5 0 0 1-2.217-3.657l3.326 3.398a2.49 2.49 0 0 1-1.109.259Zm2.5-2.5c0 .42-.103.815-.286 1.162l-3.328-3.401a2.5 2.5 0 0 1 3.614 2.239Z" clip-rule="evenodd"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

+1
View File
@@ -0,0 +1 @@
<svg class="apd-icon apd-toggle-on" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" data-pointer="none" viewBox="0 0 24 24" width="24" height="24" ><path fill-rule="evenodd" d="M11.989 4.828c-.47 0-.975.004-1.515.012l-1.71-2.566a1.008 1.008 0 0 0-1.678 1.118l.999 1.5c-.681.018-1.403.04-2.164.068a4.013 4.013 0 0 0-3.83 3.44c-.165 1.15-.245 2.545-.245 4.185 0 1.965.115 3.67.35 5.116a4.012 4.012 0 0 0 3.763 3.363l.906.046c1.205.063 1.808.095 3.607.095a.988.988 0 0 0 0-1.975c-1.758 0-2.339-.03-3.501-.092l-.915-.047a2.037 2.037 0 0 1-1.91-1.708c-.216-1.324-.325-2.924-.325-4.798 0-1.563.076-2.864.225-3.904.14-.977.96-1.713 1.945-1.747 2.444-.087 4.465-.13 6.063-.131 1.598 0 3.62.044 6.064.13.96.034 1.71.81 1.855 1.814.075.524.113 1.962.141 3.065v.002c.01.342.017.65.025.88a.987.987 0 1 0 1.974-.068c-.008-.226-.016-.523-.025-.856v-.027c-.03-1.118-.073-2.663-.16-3.276-.273-1.906-1.783-3.438-3.74-3.507-.9-.032-1.743-.058-2.531-.078l1.05-1.46a1.008 1.008 0 0 0-1.638-1.177l-1.862 2.59c-.38-.004-.744-.007-1.088-.007h-.13Zm.521 4.775h-1.32v4.631h2.222v.847h-2.618v1.078h2.618l.003.678c.36.026.714.163 1.01.407h.11v-1.085h2.694v-1.078h-2.695v-.847H16.8v-4.63h-1.276a8.59 8.59 0 0 0 .748-1.42L15.183 7.8a14.232 14.232 0 0 1-.814 1.804h-1.518l.693-.308a8.862 8.862 0 0 0-.814-1.408l-1.045.352c.297.396.572.847.825 1.364Zm-4.18 3.564.154-1.485h1.98V8.294h-3.2v.98H9.33v1.43H7.472l-.308 3.453h2.277c0 1.166-.044 1.925-.12 2.277-.078.352-.386.528-.936.528-.308 0-.616-.022-.902-.055l.297 1.067.062.005c.285.02.551.04.818.04 1.001-.067 1.562-.419 1.694-1.057.11-.638.176-1.903.176-3.795h-2.2Zm7.458.11v-.858h-1.254v.858h1.254Zm-2.376-.858v.858h-1.199v-.858h1.2Zm-1.199-.946h1.2v-.902h-1.2v.902Zm2.321 0v-.902h1.254v.902h-1.254Z" clip-rule="evenodd"></path><path fill="#00AEEC" fill-rule="evenodd" d="M22.846 14.627a1 1 0 0 0-1.412.075l-5.091 5.703-2.216-2.275-.097-.086-.008-.005a1 1 0 0 0-1.322 1.493l2.963 3.041.093.083.007.005c.407.315 1 .27 1.354-.124l5.81-6.505.08-.102.005-.008a1 1 0 0 0-.166-1.295Z" clip-rule="evenodd"></path></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+1
View File
@@ -0,0 +1 @@
<svg class="apd-icon apd-style-icon" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" data-pointer="none" style="enable-background:new 0 0 22 22" viewBox="0 0 22 22" width="36" height="24" ><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.98 12.87 11H9.13L11 5.98z"></path></svg>

After

Width:  |  Height:  |  Size: 532 B

+9 -16
View File
@@ -1,26 +1,16 @@
import Danmuku from './danmuku';
import setting from './setting';
import Setting from './setting';
import heatmap from './heatmap';
function checkVersion(art) {
const {
version,
utils: { errorHandle },
} = art.constructor;
const arr = version.split('.').map(Number);
const major = arr[0];
const minor = arr[1] / 100;
errorHandle(major + minor >= 5, `Artplayer.js@${version} 不兼容该弹幕库,请更新到 Artplayer.js@5.x.x 版本以上`);
}
export default function artplayerPluginDanmuku(option) {
return (art) => {
checkVersion(art);
const danmuku = new Danmuku(art, option);
setting(art, danmuku);
if (option.heatmap) {
heatmap(art, danmuku, option.heatmap);
const setting = new Setting(art, danmuku);
if (danmuku.option.heatmap) {
heatmap(art, danmuku, danmuku.option.heatmap);
}
return {
name: 'artplayerPluginDanmuku',
emit: danmuku.emit.bind(danmuku),
@@ -29,6 +19,7 @@ export default function artplayerPluginDanmuku(option) {
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;
},
@@ -42,6 +33,8 @@ export default function artplayerPluginDanmuku(option) {
};
}
artplayerPluginDanmuku.icons = Setting.icons;
if (typeof window !== 'undefined') {
window['artplayerPluginDanmuku'] = artplayerPluginDanmuku;
}
+577 -348
View File
@@ -1,401 +1,630 @@
import style from 'bundle-text:./style.less';
import danmuOn from 'bundle-text:./img/danmu-on.svg';
import danmuOff from 'bundle-text:./img/danmu-off.svg';
import danmuConfig from 'bundle-text:./img/danmu-config.svg';
import danmuStyle from 'bundle-text:./img/danmu-style.svg';
import $on from 'bundle-text:./img/on.svg';
import $off from 'bundle-text:./img/off.svg';
import $config from 'bundle-text:./img/config.svg';
import $style from 'bundle-text:./img/style.svg';
import $mode_0_off from 'bundle-text:./img/mode_0_off.svg';
import $mode_0_on from 'bundle-text:./img/mode_0_on.svg';
import $mode_1_off from 'bundle-text:./img/mode_1_off.svg';
import $mode_1_on from 'bundle-text:./img/mode_1_on.svg';
import $mode_2_off from 'bundle-text:./img/mode_2_off.svg';
import $mode_2_on from 'bundle-text:./img/mode_2_on.svg';
import $check_on from 'bundle-text:./img/check_on.svg';
import $check_off from 'bundle-text:./img/check_off.svg';
export default function setting(art, danmuku) {
const { option } = danmuku;
const {
template: { $controlsCenter, $player },
constructor: {
SETTING_ITEM_WIDTH,
utils: { removeClass, addClass, append, setStyle, tooltip, query, inverseClass, getIcon },
},
} = art;
export default class Setting {
constructor(art, danmuku) {
this.art = art;
this.danmuku = danmuku;
this.utils = art.constructor.utils;
setStyle($controlsCenter, 'display', 'flex');
const { setStyle } = this.utils;
const { $controlsCenter } = art.template;
setStyle($controlsCenter, 'display', 'flex');
const $danmuOn = getIcon('danmu-on', danmuOn);
const $danmuOff = getIcon('danmu-off', danmuOff);
const $danmuConfig = getIcon('danmu-config', danmuConfig);
const $danmuStyle = getIcon('danmu-style', danmuStyle);
function addEmitter() {
const colors = [
'#FE0302',
'#FF7204',
'#FFAA02',
'#FFD302',
'#FFFF00',
'#A0EE00',
'#00CD00',
'#019899',
'#4266BE',
'#89D5FF',
'#CC0273',
'#222222',
'#9B9B9B',
'#FFFFFF',
].map((item) => {
const isCurrent = option.color === item ? ' art-current' : '';
return `<div class="art-danmuku-style-panel-color${isCurrent}" data-color="${item}" style="background-color:${item}"></div>`;
});
const $emitter = append(
this.template = {
$controlsCenter,
`
<div class="art-danmuku-emitter" style="max-width: ${option.maxWidth ? `${option.maxWidth}px` : '100%'}">
<div class="art-danmuku-left">
<div class="art-danmuku-style">
<div class="art-danmuku-style-panel">
<div class="art-danmuku-style-panel-inner">
<div class="art-danmuku-style-panel-title">模式</div>
<div class="art-danmuku-style-panel-modes">
<div class="art-danmuku-style-panel-mode art-current" data-mode="0">滚动</div>
<div class="art-danmuku-style-panel-mode" data-mode="1">静止</div>
$mount: $controlsCenter,
$danmuku: null,
$toggle: null,
$configModes: 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('fullscreen', (state) => this.onFullscreen(state));
art.on('fullscreenWeb', (state) => this.onFullscreen(state));
}
static icons = {
$on,
$off,
$config,
$style,
$mode_0_off,
$mode_0_on,
$mode_1_off,
$mode_1_on,
$mode_2_off,
$mode_2_on,
$check_on,
$check_off,
};
get option() {
return this.danmuku.option;
}
get outside() {
return this.template.$mount !== this.template.$controlsCenter;
}
get TEMPLATE() {
const { option } = this;
return `
<div class="apd-toggle">
${$on}${$off}
</div>
<div class="apd-config">
${$config}
<div class="apd-config-panel">
<div class="apd-config-panel-inner">
<div class="apd-config-mode">
按类型屏蔽
<div class="apd-modes">
<div data-mode="0" class="apd-mode">
${$mode_0_off}${$mode_0_on}
<div>滚动</div>
</div>
<div class="art-danmuku-style-panel-title">颜色</div>
<div class="art-danmuku-style-panel-colors">
${colors.join('')}
<div data-mode="1" class="apd-mode">
${$mode_1_off}${$mode_1_on}
<div>顶部</div>
</div>
<div data-mode="2" class="apd-mode">
${$mode_2_off}${$mode_2_on}
<div>底部</div>
</div>
</div>
</div>
<div class="apd-config-other">
<div class="apd-other apd-anti-overlap">
${$check_on}${$check_off}
防止弹幕重叠
</div>
<div class="apd-other apd-sync-video">
${$check_on}${$check_off}
同步视频速度
</div>
</div>
<div class="apd-config-slider apd-config-opacity">
不透明度
<div class="apd-slider"></div>
<div class="apd-value"></div>
</div>
<div class="apd-config-slider apd-config-margin">
显示区域
<div class="apd-slider"></div>
<div class="apd-value"></div>
</div>
<div class="apd-config-slider apd-config-fontSize">
弹幕字号
<div class="apd-slider"></div>
<div class="apd-value"></div>
</div>
<div class="apd-config-slider apd-config-speed">
弹幕速度
<div class="apd-slider"></div>
<div class="apd-value"></div>
</div>
</div>
</div>
</div>
<div class="apd-emitter">
<div class="apd-style">
${$style}
<div class="apd-style-panel">
<div class="apd-style-panel-inner">
<div class="apd-style-mode">
模式
<div class="apd-modes">
<div data-mode="0" class="apd-mode">
${$mode_0_on}
<div>滚动</div>
</div>
<div data-mode="1" class="apd-mode">
${$mode_1_on}
<div>顶部</div>
</div>
<div data-mode="2" class="apd-mode">
${$mode_2_on}
<div>底部</div>
</div>
</div>
</div>
<div class="apd-style-color">
颜色
<div class="apd-colors">
${this.COLOR.map((color) => `<div data-color="${color}" class="apd-color" style="background-color: ${color}"></div>`).join('')}
</div>
</div>
</div>
</div>
<input class="art-danmuku-input" maxlength="${option.maxLength}" placeholder="发个弹幕见证当下" />
</div>
<div class="art-danmuku-send">发送</div>
<input class="apd-input" placeholder="发个友善的弹幕见证当下" autocomplete="off" maxLength="${option.maxLength}" />
<div class="apd-send">发送</div>
</div>
`,
`;
}
get MARGIN() {
return (
this.option.MARGIN || [
{
name: '1/4',
value: [10, '75%'],
},
{
name: '半屏',
value: [10, '50%'],
},
{
name: '3/4',
value: [10, '25%'],
},
{
name: '满屏',
value: [10, 10],
},
]
);
}
const $style = query('.art-danmuku-style', $emitter);
const $input = query('.art-danmuku-input', $emitter);
const $send = query('.art-danmuku-send', $emitter);
const $panel = query('.art-danmuku-style-panel-inner', $emitter);
const $modes = query('.art-danmuku-style-panel-modes', $emitter);
const $colors = query('.art-danmuku-style-panel-colors', $emitter);
const $layer = option.mount || append($player, '<div class="art-layer-danmuku-emitter"></div>');
get SPEED() {
return (
this.option.SPEED || [
{
name: '极慢',
value: 10,
},
{
name: '较慢',
value: 7.5,
hide: true,
},
{
name: '适中',
value: 5,
},
{
name: '较快',
value: 2.5,
hide: true,
},
{
name: '极快',
value: 1,
},
]
);
}
if (art.option.backdrop) {
addClass($panel, 'art-backdrop-filter');
get COLOR() {
return (
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);
}
setData(key, value) {
const { $player } = this.art.template;
const { $mount } = this.template;
$player.dataset[key] = value;
if (this.outside) {
$mount.dataset[key] = value;
}
}
if (option.theme) {
addClass($emitter, `art-danmuku-theme-${option.theme}`);
}
createTemplate() {
const { createElement, tooltip } = this.utils;
let timer = null;
let mode = option.mode;
let color = option.color;
append($style, $danmuStyle);
const $danmuku = createElement('div');
$danmuku.className = 'artplayer-plugin-danmuku';
$danmuku.innerHTML = this.TEMPLATE;
this.template.$danmuku = $danmuku;
function countdown(time) {
if (time <= 0) {
timer = null;
$send.innerText = '发送';
removeClass($send, 'art-disabled');
this.template.$toggle = this.query('.apd-toggle');
tooltip(this.template.$toggle, '弹幕开关');
this.template.$configModes = this.query('.apd-config-mode .apd-modes');
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');
}
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 {
$send.innerText = time;
timer = setTimeout(() => countdown(time - 1), 1000);
this.danmuku.config({
modes: [...this.option.modes, mode],
});
}
}
this.reset();
});
function onSend() {
const danmu = {
mode,
color,
border: true,
text: $input.value.trim(),
};
this.art.proxy($antiOverlap, 'click', () => {
this.danmuku.config({
antiOverlap: !this.option.antiOverlap,
});
this.reset();
});
if (timer === null && option.beforeEmit(danmu)) {
$input.value = '';
danmuku.emit(danmu);
addClass($send, 'art-disabled');
countdown(option.lockTime);
art.emit('artplayerPluginDanmuku:emit', danmu);
}
}
this.art.proxy($syncVideo, 'click', () => {
this.danmuku.config({
synchronousPlayback: !this.option.synchronousPlayback,
});
this.reset();
});
function onResize() {
if ($controlsCenter.clientWidth < option.minWidth) {
append($layer, $emitter);
setStyle($layer, 'display', 'flex');
addClass($emitter, 'art-danmuku-mount');
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();
});
if (!option.mount) {
setStyle($player, 'marginBottom', '40px');
}
} else {
append($controlsCenter, $emitter);
setStyle($layer, 'display', 'none');
removeClass($emitter, 'art-danmuku-mount');
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();
});
if (!option.mount) {
setStyle($player, 'marginBottom', null);
}
}
}
this.art.proxy($send, 'click', () => this.emit());
art.proxy($send, 'click', onSend);
art.proxy($input, 'keypress', (event) => {
this.art.proxy($input, 'keypress', (event) => {
if (event.key === 'Enter') {
event.preventDefault();
onSend();
}
});
art.proxy($modes, 'click', (event) => {
const { dataset } = event.target;
if (dataset.mode) {
mode = Number(dataset.mode);
inverseClass(event.target, 'art-current');
}
});
art.proxy($colors, 'click', (event) => {
const { dataset } = event.target;
if (dataset.color) {
color = dataset.color;
inverseClass(event.target, 'art-current');
}
});
onResize();
art.on('resize', () => {
if (!art.isInput) {
onResize();
}
});
art.on('destroy', () => {
if (option.mount && $emitter.parentElement === option.mount) {
option.mount.removeChild($emitter);
this.emit();
}
});
}
function addControl() {
art.controls.add({
position: 'right',
name: 'danmuku',
click: function () {
if (danmuku.isHide) {
danmuku.show();
art.notice.show = '弹幕显示';
setStyle($danmuOn, 'display', null);
setStyle($danmuOff, 'display', 'none');
createSliders() {
this.slider.opacity = this.createSlider({
min: 0,
max: 100,
steps: [],
container: this.template.$opacitySlider,
findIndex: () => {
return Math.round(this.option.opacity * 100) || 100;
},
onChange: (index) => {
const { $opacityValue } = this.template;
$opacityValue.textContent = `${index}%`;
this.danmuku.config({
opacity: index / 100,
});
},
});
this.slider.margin = this.createSlider({
min: 0,
max: 3,
steps: this.MARGIN,
container: this.template.$marginSlider,
findIndex: () => {
return (
this.MARGIN.findIndex(
(item) => item.value[0] === this.option.margin[0] && item.value[1] === this.option.margin[1],
) || 2
);
},
onChange: (index) => {
const margin = this.MARGIN[index];
const { $marginValue } = this.template;
$marginValue.textContent = margin.name;
this.danmuku.config({
margin: margin.value,
});
},
});
this.slider.fontSize = this.createSlider({
min: 1,
max: 25,
steps: [],
container: this.template.$fontSizeSlider,
findIndex: () => {
const { clientHeight } = this.art.template.$player;
if (typeof this.option.fontSize === 'number') {
return Math.round((this.option.fontSize / clientHeight) * 100) || 5;
} else {
danmuku.hide();
art.notice.show = '弹幕隐藏';
setStyle($danmuOn, 'display', 'none');
setStyle($danmuOff, 'display', null);
return Math.round(this.option.fontSize.replace('%', '')) || 5;
}
},
mounted($ref) {
append($ref, $danmuOn);
append($ref, $danmuOff);
tooltip($ref, '弹幕开关');
setStyle($danmuOff, 'display', 'none');
art.on('artplayerPluginDanmuku:hide', () => {
setStyle($danmuOn, 'display', 'none');
setStyle($danmuOff, 'display', null);
onChange: (index) => {
const { $fontSizeValue } = this.template;
$fontSizeValue.textContent = `${index}%`;
this.danmuku.config({
fontSize: `${index}%`,
});
},
});
art.on('artplayerPluginDanmuku:show', () => {
setStyle($danmuOn, 'display', null);
setStyle($danmuOff, 'display', 'none');
this.slider.speed = this.createSlider({
min: 0,
max: 4,
steps: this.SPEED,
container: this.template.$speedSlider,
findIndex: () => {
return this.SPEED.findIndex((item) => item.value === this.option.speed) || 2;
},
onChange: (index) => {
const speed = this.SPEED[index];
const { $speedValue } = this.template;
$speedValue.textContent = speed.name;
this.danmuku.config({
speed: speed.value,
});
},
});
}
function addSetting() {
art.setting.add({
width: 260,
name: 'danmuku',
html: '弹幕设置',
tooltip: '更多',
icon: $danmuConfig,
selector: [
{
width: SETTING_ITEM_WIDTH,
html: '播放速度',
icon: '',
tooltip: '适中',
selector: [
{
html: '极慢',
time: 10,
},
{
html: '较慢',
time: 7.5,
},
{
default: true,
html: '适中',
time: 5,
},
{
html: '较快',
time: 2.5,
},
{
html: '极快',
time: 1,
},
],
onSelect: function (item) {
danmuku.config({
speed: item.time,
});
return item.html;
},
},
{
width: SETTING_ITEM_WIDTH,
html: '字体大小',
icon: '',
tooltip: '适中',
selector: [
{
html: '极小',
fontSize: '4%',
},
{
html: '较小',
fontSize: '5%',
},
{
default: true,
html: '适中',
fontSize: '6%',
},
{
html: '较大',
fontSize: '7%',
},
{
html: '极大',
fontSize: '8%',
},
],
onSelect: function (item) {
danmuku.config({
fontSize: item.fontSize,
});
return item.html;
},
},
{
width: SETTING_ITEM_WIDTH,
html: '不透明度',
icon: '',
tooltip: '100%',
selector: [
{
default: true,
opacity: 1,
html: '100%',
},
{
opacity: 0.75,
html: '75%',
},
{
opacity: 0.5,
html: '50%',
},
{
opacity: 0.25,
html: '25%',
},
{
opacity: 0,
html: '0%',
},
],
onSelect: function (item) {
danmuku.config({
opacity: item.opacity,
});
return item.html;
},
},
{
width: SETTING_ITEM_WIDTH,
html: '显示范围',
icon: '',
tooltip: '3/4',
selector: [
{
html: '1/4',
margin: [10, '75%'],
},
{
html: '半屏',
margin: [10, '50%'],
},
{
default: true,
html: '3/4',
margin: [10, '25%'],
},
{
html: '满屏',
margin: [10, 10],
},
],
onSelect: function (item) {
danmuku.config({
margin: item.margin,
});
return item.html;
},
},
{
html: '弹幕防重叠',
icon: '',
tooltip: option.antiOverlap ? '开启' : '关闭',
switch: option.antiOverlap,
onSwitch(item) {
danmuku.config({
antiOverlap: !item.switch,
});
item.tooltip = item.switch ? '关闭' : '开启';
return !item.switch;
},
},
{
html: '同步视频速度',
icon: '',
tooltip: option.synchronousPlayback ? '开启' : '关闭',
switch: option.synchronousPlayback,
onSwitch(item) {
danmuku.config({
synchronousPlayback: !item.switch,
});
item.tooltip = item.switch ? '关闭' : '开启';
return !item.switch;
},
},
],
createSlider({ min, max, container, findIndex, onChange, steps = [] }) {
const { query, clamp } = this.utils;
container.innerHTML = `
<div class="apd-slider-line">
<div class="apd-slider-points">
${steps.map(() => `<div class="apd-slider-point"></div>`).join('')}
</div>
<div class="apd-slider-progress"></div>
</div>
<div class="apd-slider-dot"></div>
<div class="apd-slider-steps">
${steps.map((step) => (step.hide ? '' : `<div class="apd-slider-step">${step.name}</div>`)).join('')}
</div>
`;
const $dot = query('.apd-slider-dot', container);
const $progress = query('.apd-slider-progress', container);
let isDroging = false;
function reset(index = findIndex()) {
const value = clamp(index, min, max);
const percentage = (value - min) / (max - min);
$dot.style.left = `${percentage * 100}%`;
if (steps.length === 0) {
$progress.style.width = $dot.style.left;
}
onChange(value);
}
function updateLeft(event) {
const { left, width } = container.getBoundingClientRect();
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(event);
});
this.art.proxy(container, 'mousedown', (event) => {
isDroging = event.button === 0;
});
this.art.on('document:mousemove', (event) => {
if (isDroging) {
updateLeft(event);
}
});
this.art.on('document:mouseup', (event) => {
if (isDroging) {
isDroging = false;
updateLeft(event);
}
});
return { reset };
}
addEmitter();
addControl();
addSetting();
onFullscreen(state) {
if (this.outside) {
const { $danmuku, $controlsCenter, $mount } = this.template;
if (state) {
$controlsCenter.appendChild($danmuku);
} else {
$mount.appendChild($danmuku);
}
}
}
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();
} 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 = '发送';
removeClass($send, 'apd-lock');
}
reset() {
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);
const { inverseClass } = this.utils;
const colors = this.template.$colors.children;
const $color = Array.from(colors).find((item) => item.dataset.color === this.option.color.toUpperCase());
$color && inverseClass($color, 'apd-active');
}
mount(target = this.template.$controlsCenter) {
target.appendChild(this.template.$danmuku);
this.template.$mount = target;
this.reset();
}
}
if (typeof document !== 'undefined') {
if (!document.getElementById('artplayer-plugin-danmuku')) {
const id = 'artplayer-plugin-danmuku';
const $style = document.getElementById(id);
if ($style) {
$style.textContent = style;
} else {
const $style = document.createElement('style');
$style.id = 'artplayer-plugin-danmuku';
$style.id = id;
$style.textContent = style;
document.head.appendChild($style);
}
+381 -156
View File
@@ -1,199 +1,424 @@
.art-danmuku-emitter {
position: relative;
@highlight: #00a1d6;
.artplayer-plugin-danmuku {
display: flex;
position: relative;
z-index: 99;
align-items: center;
justify-content: center;
font-size: 12px;
height: 32px;
width: 100%;
font-size: 12px;
line-height: 1;
border-radius: 5px;
max-width: 100%;
background-color: rgb(255 255 255 / 30%);
color: #fff;
font-weight: 300;
flex-shrink: 0;
gap: 10px;
.art-backdrop-filter {
-webkit-backdrop-filter: saturate(180%) blur(20px);
backdrop-filter: saturate(180%) blur(20px);
background-color: rgba(0, 0, 0, 0.7) !important;
.apd-icon {
cursor: pointer;
opacity: 0.75;
transition: all 0.2s ease;
fill: #fff;
&:hover {
opacity: 1;
}
}
.art-danmuku-left {
.apd-config {
display: flex;
flex: 1;
border-radius: 3px 0 0 3px;
position: relative;
.art-danmuku-style {
position: relative;
display: flex;
width: 32px;
align-items: center;
justify-content: center;
.apd-config-panel {
position: absolute;
bottom: 24px;
left: -148px;
display: none;
width: 320px;
padding: 10px;
.art-danmuku-style-panel {
display: none;
position: absolute;
z-index: 999;
left: -85px;
bottom: 30px;
width: 200px;
padding-bottom: 10px;
.art-danmuku-style-panel-inner {
display: flex;
flex-direction: column;
padding: 10px 10px 0;
border-radius: 3px;
background-color: rgba(0, 0, 0, 0.9);
.art-danmuku-style-panel-title {
font-size: 13px;
margin-bottom: 10px;
}
.art-danmuku-style-panel-modes {
display: flex;
justify-content: space-between;
margin-bottom: 15px;
.art-danmuku-style-panel-mode {
display: flex;
justify-content: center;
width: 47%;
padding: 5px 0;
cursor: pointer;
color: #fff;
border: 1px solid #fff;
&.art-current {
background-color: #00a1d6;
border: 1px solid #00a1d6;
}
}
}
.art-danmuku-style-panel-colors {
display: flex;
gap: 5px;
flex-wrap: wrap;
margin-bottom: 10px;
justify-content: space-between;
.art-danmuku-style-panel-color {
cursor: pointer;
width: 20px;
height: 20px;
border: 1px solid #fff;
&.art-current {
position: relative;
box-shadow: 0 0 2px #fff;
&::before {
content: '';
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
right: 0;
bottom: 0;
border: 2px solid #000;
}
}
}
}
}
.apd-config-panel-inner {
width: 100%;
border-radius: 3px;
background-color: rgba(0, 0, 0, 0.85);
padding: 10px;
}
}
&:hover .art-danmuku-style-panel {
&:hover {
.apd-config-panel {
display: flex;
}
.art-icon {
opacity: 0.75;
cursor: pointer;
&:hover {
opacity: 1;
}
}
}
}
.art-danmuku-input {
flex: 1;
display: flex;
padding: 0 10px 0 0;
border: none;
outline: none;
width: 100%;
color: #ffffff;
background-color: transparent;
.apd-config-mode,
.apd-config-slider,
.apd-config-other,
.apd-style-mode {
margin-bottom: 15px;
}
&::placeholder,
&::-webkit-input-placeholder {
color: rgb(255 255 255 / 50%);
.apd-modes {
display: flex;
align-items: center;
margin-top: 5px;
gap: 20px;
.apd-mode {
cursor: pointer;
text-align: center;
&:hover {
color: @highlight;
}
}
}
.art-danmuku-send {
.apd-config-slider {
display: flex;
width: 50px;
align-items: center;
gap: 12px;
.apd-value {
width: 32px;
text-align: right;
}
}
.apd-slider {
position: relative;
flex: 1;
display: flex;
height: 20px;
align-items: center;
justify-content: center;
cursor: pointer;
background-color: #00a1d6;
border-radius: 0 5px 5px 0;
.apd-slider-line {
position: relative;
height: 2px;
width: 100%;
overflow: hidden;
border-radius: 3px;
background-color: rgba(255, 255, 255, 0.25);
}
.apd-slider-points {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: space-between;
.apd-slider-point {
width: 2px;
height: 2px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.5);
}
}
.apd-slider-progress {
width: 0%;
height: 100%;
background-color: @highlight;
}
.apd-slider-dot {
position: absolute;
transform: translateX(-6px);
left: 0%;
width: 12px;
height: 12px;
border-radius: 50%;
background-color: @highlight;
}
.apd-slider-steps {
display: flex;
align-items: center;
justify-content: space-between;
position: absolute;
bottom: -12px;
width: calc(100% + 32px);
color: #777;
.apd-slider-step {
flex-shrink: 0;
width: 36px;
text-align: center;
scale: 0.95;
}
}
}
.apd-config-other {
display: flex;
align-items: center;
gap: 20px;
.apd-check-off,
.apd-check-on {
width: 16px;
height: 16px;
}
.apd-other {
display: flex;
align-items: center;
cursor: pointer;
gap: 2px;
&:hover {
color: @highlight;
}
}
}
.apd-emitter {
display: flex;
flex: 1;
align-items: center;
height: 100%;
background-color: rgba(255, 255, 255, 0.25);
border-radius: 5px;
}
.apd-style {
position: relative;
display: flex;
align-items: center;
justify-content: center;
.apd-style-panel {
position: absolute;
bottom: 24px;
left: -88px;
display: none;
width: 200px;
padding: 10px;
.apd-style-panel-inner {
width: 100%;
border-radius: 3px;
background-color: rgba(0, 0, 0, 0.85);
padding: 10px;
}
}
&:hover {
background-color: #00b5e5;
}
&.art-disabled {
opacity: 0.5;
pointer-events: none;
.apd-style-panel {
display: flex;
}
}
}
&.art-danmuku-mount {
max-width: 100% !important;
.apd-colors {
display: flex;
flex-wrap: wrap;
margin-top: 5px;
gap: 8px;
.art-danmuku-left {
.art-danmuku-style {
.art-danmuku-style-panel {
left: 0;
}
.apd-color {
width: 16px;
height: 16px;
border-radius: 2px;
cursor: pointer;
&.apd-active {
border: 1px solid black;
box-shadow: 0 0 0 1px #fff;
}
}
}
.apd-input {
outline: none;
height: 100%;
flex: 1;
min-width: 0;
width: auto;
border: none;
line-height: 1;
color: #fff;
background-color: transparent;
&::placeholder {
color: rgba(255, 255, 255, 0.5);
}
}
.apd-send {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: 60px;
flex-shrink: 0;
cursor: pointer;
text-shadow: none;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
background-color: @highlight;
&.apd-lock {
cursor: not-allowed;
color: #666;
background-color: #e7e7e7;
}
}
}
.art-fullscreen,
.art-fullscreen-web {
.artplayer-plugin-danmuku {
height: 38px;
gap: 16px;
.apd-config-icon,
.apd-toggle-off,
.apd-toggle-on {
width: 28px;
height: 28px;
}
.apd-emitter {
width: 390px;
flex: none;
}
}
}
[data-danmuku-theme='light'] > .artplayer-plugin-danmuku {
.apd-icon {
fill: #333;
}
.apd-emitter {
background-color: #f1f2f3;
}
.apd-input {
color: #000;
&::placeholder {
color: rgba(0, 0, 0, 0.3);
}
}
}
[data-danmuku-visible='false'] {
.apd-toggle-off {
display: block;
}
.apd-toggle-on {
display: none;
}
}
[data-danmuku-visible='true'] {
.apd-toggle-off {
display: none;
}
.apd-toggle-on {
display: block;
}
}
[data-danmuku-anti-overlap='false'] {
.apd-anti-overlap {
.apd-check-on {
display: none;
}
.apd-check-off {
display: block;
}
}
}
[data-danmuku-anti-overlap='true'] {
.apd-anti-overlap {
.apd-check-on {
display: block;
}
.apd-check-off {
display: none;
}
}
}
[data-danmuku-sync-video='false'] {
.apd-sync-video {
.apd-check-on {
display: none;
}
.apd-check-off {
display: block;
}
}
}
[data-danmuku-sync-video='true'] {
.apd-sync-video {
.apd-check-on {
display: block;
}
.apd-check-off {
display: none;
}
}
}
.generate-mode-styles(@index) when (@index >= 0) {
.generate-mode-styles(@index - 1);
@mode: ~'data-danmuku-mode@{index}';
[@{mode}='false'] {
.apd-config-mode {
.apd-mode-@{index}-off {
display: block;
}
.apd-mode-@{index}-on {
display: none;
}
}
.art-danmuku-send {
width: 60px;
.art-danmuku [data-mode='@{index}'] {
opacity: 0 !important;
}
}
&.art-danmuku-theme-light {
.art-danmuku-left {
border: 1px solid #dadada;
background: #f4f4f4;
[@{mode}='true'] {
.apd-config-mode {
.apd-mode-@{index}-off {
display: none;
}
.art-danmuku-style .art-icon svg {
fill: #666;
}
.apd-mode-@{index}-on {
display: block;
}
}
}
.art-danmuku-input {
color: #000000;
&::placeholder,
&::-webkit-input-placeholder {
color: rgb(0 0 0 / 50%);
}
[data-danmuku-mode='@{index}'] {
.apd-style-mode {
[data-mode='@{index}'] {
color: @highlight;
path {
fill: @highlight;
}
}
}
}
}
.art-layer-danmuku-emitter {
position: absolute;
z-index: 99;
left: 0;
right: 0;
bottom: -40px;
width: 100%;
}
.generate-mode-styles(2);
+42 -10
View File
@@ -1,13 +1,24 @@
function getDanmuTop({ target, emits, clientWidth, clientHeight, marginBottom, marginTop, antiOverlap }) {
// 弹幕最大高度
const maxTop = clientHeight - marginBottom;
// 过滤同模式的弹幕,即每种模式各不影响
const danmus = emits
.filter((item) => item.mode === target.mode && item.top <= clientHeight - marginBottom)
.filter((item) => item.mode === target.mode && item.top <= maxTop)
.sort((prev, next) => prev.top - next.top);
// 如果没有同模式的弹幕,直接返回
if (danmus.length === 0) {
return marginTop;
if (target.mode === 2) {
return maxTop - target.height;
} else {
return marginTop;
}
}
// 上下各加一个虚拟弹幕,方便计算
danmus.unshift({
type: 'top',
top: 0,
left: 0,
right: 0,
@@ -18,7 +29,8 @@ function getDanmuTop({ target, emits, clientWidth, clientHeight, marginBottom, m
});
danmus.push({
top: clientHeight - marginBottom,
type: 'bottom',
top: maxTop,
left: 0,
right: 0,
height: marginBottom,
@@ -27,13 +39,28 @@ function getDanmuTop({ target, emits, clientWidth, clientHeight, marginBottom, m
distance: clientWidth,
});
for (let index = 1; index < danmus.length; index += 1) {
const item = danmus[index];
const prev = danmus[index - 1];
const prevBottom = prev.top + prev.height;
const diff = item.top - prevBottom;
if (diff >= target.height) {
return prevBottom;
// 查找是否有多余的缝隙足以容纳当前弹幕
if (target.mode === 2) {
// 倒序查找
for (let index = danmus.length - 2; index >= 0; index -= 1) {
const item = danmus[index];
const prev = danmus[index + 1];
const itemBottom = item.top + item.height;
const diff = prev.top - itemBottom;
if (diff >= target.height) {
return prev.top - target.height;
}
}
} else {
// 顺序查找
for (let index = 1; index < danmus.length; index += 1) {
const item = danmus[index];
const prev = danmus[index - 1];
const prevBottom = prev.top + prev.height;
const diff = item.top - prevBottom;
if (diff >= target.height) {
return prevBottom;
}
}
}
@@ -67,7 +94,10 @@ function getDanmuTop({ target, emits, clientWidth, clientHeight, marginBottom, m
return result && result[0] ? result[0].top : undefined;
}
// 静止弹幕没有重叠问题
case 1:
case 2:
return undefined;
default:
break;
@@ -82,6 +112,7 @@ function getDanmuTop({ target, emits, clientWidth, clientHeight, marginBottom, m
});
break;
case 1:
case 2:
topMap.sort((prev, next) => {
const nextMaxWidth = Math.max(...next.map((item) => item.width));
const prevMaxWidth = Math.max(...prev.map((item) => item.width));
@@ -98,6 +129,7 @@ function getDanmuTop({ target, emits, clientWidth, clientHeight, marginBottom, m
onmessage = (event) => {
const { data } = event;
if (!data.id) return;
const top = getDanmuTop(data);
self.postMessage({
top,