Danmuku (#759)
* 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
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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -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,
|
||||
|
||||