Skip to content

Commit

Permalink
添加加载本地xml弹幕功能
Browse files Browse the repository at this point in the history
  • Loading branch information
Izumiko committed Aug 31, 2024
1 parent b4de1b4 commit 6a92e84
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 4 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@
- 弹幕设置:
- 设置弹幕透明度 [0, 1]
- 设置弹幕速度 [20, 600]
- 设置弹幕字体大小 [8, 32]
- 设置弹幕字体大小 [8, 80]
- 设置弹幕区域占屏幕的高度比例 [0, 1]
- 弹幕密度: 依据水平和垂直密度过滤, 弹幕0级无限制*
- 设置弹幕用户名过滤,支持选项: 哔哩哔哩, 巴哈姆特, 弹弹Play, 其他
- 设置弹幕类型过滤,支持:底部,顶部,滚动
- 简繁转换: 在原始弹幕/简体中文/繁体中文3种模式切换
- 是否使用本地XML弹幕
- 当前集数弹幕偏移时间
- 日志开关: 开启/关闭调试日志输出
- 发送弹幕: 登录弹弹Play,并在播放界面发送弹幕
Expand All @@ -38,6 +39,8 @@

弹幕来源为 [弹弹 play](https://www.dandanplay.com/) ,已开启弹幕聚合(Acfun/Bili/Tucao/Baha/5DM/iQIYI等不知名网站弹幕融合)

**在启用了`使用本地xml弹幕`后,会尝试调用[cxfksword/jellyfin-plugin-danmu](https://github.com/cxfksword/jellyfin-plugin-danmu)的API获取其预先下载好的xml弹幕,绕过弹弹play的弹幕查询和加载**

## 数据

匹配完成后对应关系会保存在**浏览器(或客户端)本地存储**中,后续播放(包括同季的其他集)会优先按照保存的匹配记录载入弹幕
Expand Down
96 changes: 93 additions & 3 deletions ede.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// @description Jellyfin弹幕插件
// @namespace https://github.com/RyoLee
// @author RyoLee
// @version 1.43
// @version 1.44
// @copyright 2022, RyoLee (https://github.com/RyoLee)
// @license MIT; https://raw.githubusercontent.com/Izumiko/jellyfin-danmaku/jellyfin/LICENSE
// @icon https://github.githubassets.com/pinned-octocat.svg
Expand Down Expand Up @@ -154,7 +154,7 @@
</div>
<div style="display: flex;">
<span id="lbfontSize" style="flex: auto;">字体大小:</span>
<input style="width: 50%;" type="range" id="fontSize" min="8" max="40" step="1" value="${window.ede.fontSize || 18}" />
<input style="width: 50%;" type="range" id="fontSize" min="8" max="80" step="1" value="${window.ede.fontSize || 18}" />
</div>
<div style="display: flex;">
<span id="lbheightRatio" style="flex: auto;">高度比例:</span>
Expand Down Expand Up @@ -193,6 +193,13 @@
<div><input type="radio" id="chConvert2" name="chConvert" value="2" ${(window.ede.chConvert === 2) ? 'checked' : ''}>
<label for="chConvert2">繁体</label></div>
</div>
<div style="display: flex;">
<label style="flex: auto;">使用本地xml弹幕:</label>
<div><input type="radio" id="enableXmlDanmaku" name="useXmlDanmaku" value="1" ${(window.ede.useXmlDanmaku === 1) ? 'checked' : ''}>
<label for="chConvert0">是</label></div>
<div><input type="radio" id="disableXmlDanmaku" name="useXmlDanmaku" value="0" ${(window.ede.useXmlDanmaku === 0) ? 'checked' : ''}>
<label for="chConvert1">否</label></div>
</div>
<div style="display: flex;">
<label style="flex: auto;">当前弹幕偏移时间:</label>
<div><input style="flex-grow: 1;" id="danmakuOffsetTime" placeholder="秒" value="${window.ede.curEpOffset || 0}" /></div>
Expand Down Expand Up @@ -258,6 +265,9 @@
window.ede.chConvert = parseInt(document.querySelector('input[name="chConvert"]:checked').value);
window.localStorage.setItem('chConvert', window.ede.chConvert);
showDebugInfo(`设置简繁转换:${window.ede.chConvert}`);
window.ede.useXmlDanmaku = parseInt(document.querySelector('input[name="useXmlDanmaku"]:checked').value);
window.localStorage.setItem('useXmlDanmaku', window.ede.useXmlDanmaku);
showDebugInfo(`是否使用本地xml弹幕:${window.ede.useXmlDanmaku}`);
const epOffset = parseFloat(document.getElementById('danmakuOffsetTime').value);
window.ede.curEpOffsetModified = epOffset !== window.ede.curEpOffset;
if (window.ede.curEpOffsetModified) {
Expand Down Expand Up @@ -459,6 +469,9 @@
// 弹幕密度限制等级 0:不限制 1:低 2:中 3:高
const danmakuDensityLimit = window.localStorage.getItem('danmakuDensityLimit');
this.danmakuDensityLimit = danmakuDensityLimit ? parseInt(danmakuDensityLimit) : 0;
// 使用Jellyfin弹幕插件提供的xml弹幕替代本脚本在线搜索的弹幕
const useXmlDanmaku = window.localStorage.getItem('useXmlDanmaku');
this.useXmlDanmaku = useXmlDanmaku ? parseInt(useXmlDanmaku) : 0;
// 当前剧集弹幕偏移时间
this.curEpOffset = 0;
this.curEpOffsetModified = false;
Expand Down Expand Up @@ -722,6 +735,16 @@
}

async function postRelatedSource(relatedUrl) {
if (!ddplayStatus.isLogin) {
showDebugInfo('发送相关链接失败 未登录');
alert('请先登录');
return;
}
if (!window.ede.episode_info || !window.ede.episode_info.episodeId) {
showDebugInfo('发送弹幕失败 未获取到弹幕信息');
alert('请先获取弹幕信息');
return;
}
const url = apiPrefix + '/api/v2/related/' + window.ede.episode_info.episodeId;
const params = {
'episodeId': window.ede.episode_info.episodeId,
Expand Down Expand Up @@ -1018,6 +1041,44 @@
return null;
}

async function getItemId() {
let item = await getEmbyItemInfo();
if (!item) {
return null;
}
return item.Id || null;
}

async function getCommentsByPluginApi(jellyfinItemId) {
const path = window.location.pathname.replace(/\/web\//, '/api/danmu/');
const url = window.location.origin + path + jellyfinItemId + '/raw';
const response = await makeGetRequest(url);
if (!response || response.length === 0) {
return null;
}

// parse the xml data
// xml data: <d p="392.00000,1,25,16777215,0,0,[BiliBili]e6860b30,1723088443,1">弹幕内容</d>
// <d p="stime, type, fontSize, color, date, pool, sender, dbid, unknown">content</d>
// comment data: {cid: "1723088443", p: "392.00,1,16777215,[BiliBili]e6860b30", m: "弹幕内容"}
// {cid: "dbid", p: "stime, type, color, sender", m: "content"}
const parser = new DOMParser();
const data = parser.parseFromString(response, 'text/xml');
const comments = [];

for (const comment of data.getElementsByTagName('d')) {
const p = comment.getAttribute('p').split(',').map(Number);
const commentData = {
cid: p[7],
p: p[0] + ',' + p[1] + ',' + p[3] + ',' + p[6],
m: comment.textContent
};
comments.push(commentData);
}

return comments;
}

async function createDanmaku(comments) {
if (!window.obVideo) {
window.obVideo = new MutationObserver((mutationList, _observer) => {
Expand Down Expand Up @@ -1166,12 +1227,41 @@
infoContainer.innerText = `弹幕匹配信息:${info.animeTitle} - ${info.episodeTitle}`;
}

function reloadDanmaku(type = 'check') {
async function reloadDanmaku(type = 'check') {
if (window.ede.loading) {
showDebugInfo('正在重新加载');
return;
}
window.ede.loading = true;
if (window.ede.useXmlDanmaku === 1) {
const comments = await getItemId().then((itemId) => {
return new Promise((resolve, reject) => {
if (!itemId) {
if (type != 'init') {
reject('播放器未完成加载');
} else {
reject(null);
}
}
resolve(itemId);
});
}).then((itemId) => getCommentsByPluginApi(itemId));

if (comments.length > 0) {
createDanmaku(comments).then(() => {
showDebugInfo('本地弹幕就位');
}).then(() => {
window.ede.loading = false;
const danmakuCtr = document.getElementById('danmakuCtr');
if (danmakuCtr && danmakuCtr.style && danmakuCtr.style.opacity !== '1') {
danmakuCtr.style.opacity = 1;
}
});
return;
}

showDebugInfo('本地弹幕加载失败,尝试在线加载');
}
getEpisodeInfo(type != 'search')
.then((info) => {
return new Promise((resolve, reject) => {
Expand Down

0 comments on commit 6a92e84

Please sign in to comment.