Greasy Fork

Greasy Fork is available in English.

YouTube 本地B站弹幕播放器

加载本地 B站弹幕 JSON文件,在 YouTube 视频上显示

当前为 2025-05-16 提交的版本,查看 最新版本

// ==UserScript==
// @name         YouTube 本地B站弹幕播放器
// @namespace    https://github.com/ZBpine/bilibili-danmaku-download/
// @version      1.0.1
// @description  加载本地 B站弹幕 JSON文件,在 YouTube 视频上显示
// @author       ZBpine
// @match        https://www.youtube.com/watch*
// @grant        none
// @license      MIT
// @run-at       document-end
// ==/UserScript==

(async () => {
    'use strict';

    // 创建按钮
    function insertPlayerButton() {
        function baseButtonStyle() {
            return `
                padding: 6px 10px;
                background: #555;
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                font-size: 14px;
                width: 100px;
            `;
        }

        const panel = document.createElement('div');
        Object.assign(panel.style, {
            position: 'fixed',
            left: '-110px',
            bottom: '40px',
            zIndex: '9999',
            transition: 'left 0.3s ease-in-out, opacity 0.3s ease',
            opacity: '0.2',
            display: 'flex',
            flexDirection: 'column',
            gap: '8px',
            background: '#333',
            borderRadius: '0px 20px 20px 0px',
            padding: '10px',
            width: '110px'
        });
        panel.addEventListener('mouseenter', () => {
            panel.style.left = '0px';
            panel.style.opacity = '1';
        });
        panel.addEventListener('mouseleave', () => {
            panel.style.left = '-110px';
            panel.style.opacity = '0.2';
        });

        const loadBtn = document.createElement('button');
        loadBtn.textContent = '📂 载入弹幕';
        loadBtn.style.cssText = baseButtonStyle();

        const toggleBtn = document.createElement('button');
        toggleBtn.textContent = '✅ 弹幕开';
        toggleBtn.style.cssText = baseButtonStyle();

        panel.appendChild(loadBtn);
        panel.appendChild(toggleBtn);
        document.body.appendChild(panel);

        toggleBtn.onclick = () => {
            dmPlayer.toggle();
            toggleBtn.textContent = dmPlayer.danmakuEnabled ? '✅ 弹幕开' : '❌ 弹幕关';
        };

        const fileInput = document.createElement('input');
        fileInput.type = 'file';
        fileInput.accept = '.json';
        fileInput.style.display = 'none';
        document.body.appendChild(fileInput);
        loadBtn.onclick = () => fileInput.click();
        fileInput.addEventListener('change', (e) => {
            const file = e.target.files[0];
            if (!file) return;

            const reader = new FileReader();
            reader.onload = (e) => {
                try {
                    const json = JSON.parse(e.target.result);
                    // ✅ 当用户载入 json 文件
                    dmPlayer.init();
                    dmPlayer.load(json.danmakuData);

                    const count = json.danmakuData.length;
                    const title = json.videoData?.title || '(未知标题)';
                    const readableTime = json.fetchtime ?
                        new Date(json.fetchtime * 1000).toLocaleString('zh-CN', { hour12: false }) : '(未知)';
                    dmPlayer.logTag(`🎉 已载入:\n🎬 ${title}\n💬 共 ${count} 条弹幕\n🕒 抓取时间:${readableTime}`);
                    alert(`🎉 已载入:\n🎬 ${title}\n💬 共 ${count} 条弹幕\n🕒 抓取时间:${readableTime}`);
                } catch (err) {
                    dmPlayer.logTagError('❌ 弹幕 JSON 加载失败', err);
                    alert('❌ 弹幕 JSON 加载失败:' + err.message);
                }
            };
            reader.readAsText(file);
        });
    }

    const urlOfPlayer = 'https://cdn.jsdelivr.net/gh/ZBpine/bili-danmaku-statistic/docs/BiliDanmakuPlayer.js';
    const { BiliDanmakuPlayer } = await import(urlOfPlayer);
    const dmPlayer = new BiliDanmakuPlayer();
    insertPlayerButton();
})();