Greasy Fork is available in English.

Emby danmaku extension Reload

Emby弹幕插件

// ==UserScript==
// @name         Emby danmaku extension Reload
// @description  Emby弹幕插件
// @namespace    https://github.com/RyoLee
// @author       RyoLee,SummerTail
// @version      1.12
// @copyright    2022, RyoLee (https://github.com/RyoLee)
// @license      MIT; https://raw.githubusercontent.com/RyoLee/emby-danmaku/master/LICENSE
// @icon         https://github.githubassets.com/pinned-octocat.svg
// @grant   GM_registerMenuCommand
// @grant   GM_setValue
// @grant   GM_getValue
// @match        */web/index.html
// ==/UserScript==

(async function () {
    "use strict";
    //注册插件菜单
    GM_registerMenuCommand("Emby服务器名称", function () {
        const EmbyName = prompt("请输入服务器名称:");
        GM_setValue("EmbyName", EmbyName);
    });
    ("use strict");
    if (
        document.querySelector('meta[name="application-name"]').content ==
        GM_getValue("EmbyName", "Emby")
    ) {
        const DanmaStatu = 1;
        if (DanmaStatu == 0) {
            return null;
        }
        const check_interval = 200;
        const chConverTtitle = [
            "当前状态: 未启用",
            "当前状态: 转换为简体",
            "当前状态: 转换为繁体",
        ];
        // 0:当前状态关闭 1:当前状态打开
        const danmaku_icons = ["\uE0B9", "\uE7A2"];
        const search_icon = "\uE881";
        const translate_icon = "\uE927";
        const info_icon = "\uE0E0";
        const fontSize_icon =
            '<svg t="1707762095726" class="icon" viewBox="0 0 1064 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3633" width="24" height="24"><path d="M495.73888 195.91168a30.72 30.72 0 0 0-55.37792 2.2528L139.22304 900.79232a51.2 51.2 0 0 1-94.12608-40.30464L346.23488 157.81888c44.07296-102.8096 187.63776-108.70784 240.0256-9.8304l104.12032 196.68992a51.2 51.2 0 1 1-90.5216 47.9232l-104.12032-196.68992zM779.75552 570.28608c-10.36288-26.46016-47.9232-25.84576-57.50784 0.86016l-131.31776 367.65696a51.2 51.2 0 0 1-96.41984-34.4064l131.31776-367.65696c41.3696-115.87584 204.26752-118.3744 249.2416-3.93216l145.408 370.0736a51.2 51.2 0 1 1-95.31392 37.43744l-145.408-370.03264z" fill="#ffffff" p-id="3634" data-spm-anchor-id="a313x.search_index.0.i12.73ca3a81uN587a" class="selected"></path><path d="M163.84 593.92c0-28.2624 22.9376-51.2 51.2-51.2h266.24a51.2 51.2 0 1 1 0 102.4h-266.24c-28.2624 0-51.2-22.9376-51.2-51.2zM573.44 778.24c0-28.2624 22.9376-51.2 51.2-51.2h266.24a51.2 51.2 0 1 1 0 102.4h-266.24c-28.2624 0-51.2-22.9376-51.2-51.2z" fill="#ffffff" p-id="3635" data-spm-anchor-id="a313x.search_index.0.i13.73ca3a81uN587a" class="selected"></path></svg>';
        const danmaku_Url_icon =
            '<svg t="1707798572282" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4558" width="24" height="24"><path d="M317.226667 382.122667c17.92 17.92 17.92 47.018667 0 64.938666L252.288 512l-6.314667 6.656a183.68 183.68 0 0 0-0.213333 246.186667l6.528 6.869333 6.613333 6.314667a183.68 183.68 0 0 0 246.229334 0.213333l6.869333-6.528 64.938667-64.938667a45.909333 45.909333 0 0 1 64.938666 64.938667l-64.938666 64.938667a275.456 275.456 0 0 1-396.757334-382.122667l7.168-7.466667 64.938667-64.938666c17.92-17.92 46.976-17.92 64.938667 0z m354.816-34.730667l4.565333 4.565333a42.666667 42.666667 0 0 1 0 60.373334l-264.277333 264.277333a42.666667 42.666667 0 0 1-60.373334 0l-4.565333-4.565333a42.666667 42.666667 0 0 1 0-60.373334l264.277333-264.277333a42.666667 42.666667 0 0 1 60.373334 0z m164.608-160.042667a275.498667 275.498667 0 0 1 7.168 382.122667l-7.168 7.466667-64.938667 64.938666a45.909333 45.909333 0 0 1-64.938667-64.938666L771.712 512l6.314667-6.656a183.68 183.68 0 0 0 0.213333-246.186667l-6.528-6.869333-6.613333-6.314667a183.68 183.68 0 0 0-246.229334-0.213333L512 252.288l-64.938667 64.938667a45.909333 45.909333 0 0 1-64.938666-64.938667l64.938666-64.938667a275.456 275.456 0 0 1 389.589334 0z" fill="#ffffff" p-id="4559" data-spm-anchor-id="a313x.search_index.0.i6.29243a81qdtCEA" class="selected"></path></svg>';
        const filter_icons = ["\uE3E0", "\uE3D0", "\uE3D1", "\uE3D2"];
        const buttonOptions = {
            class: "paper-icon-button-light",
            is: "paper-icon-button-light",
        };
        const uiAnchorStr = "\uE034";
        const mediaContainerQueryStr = "body > div.view-videoosd-videoosd";
        const mediaQueryStr = "video";

        const fontSize_Setting = parseInt(
            window.localStorage.getItem("danmakuFontSize")
                ? window.localStorage.getItem("danmakuFontSize")
                : 18
        );

        const displayButtonOpts = {
            title: "弹幕开关",
            id: "displayDanmaku",
            innerText: null,
            onclick: () => {
                if (window.ede.loading) {
                    console.log("正在加载,请稍后再试");
                    return;
                }
                console.log("切换弹幕开关");
                window.ede.danmakuSwitch = (window.ede.danmakuSwitch + 1) % 2;
                window.localStorage.setItem(
                    "danmakuSwitch",
                    window.ede.danmakuSwitch
                );
                document.querySelector(
                    "#displayDanmaku"
                ).children[0].innerText =
                    danmaku_icons[window.ede.danmakuSwitch];
                if (window.ede.danmaku) {
                    window.ede.danmakuSwitch == 1
                        ? window.ede.danmaku.show()
                        : window.ede.danmaku.hide();
                }
            },
        };
        const searchButtonOpts = {
            title: "搜索弹幕",
            id: "searchDanmaku",
            innerText: search_icon,
            onclick: () => {
                if (window.ede.loading) {
                    console.log("正在加载,请稍后再试");
                    return;
                }
                console.log("手动匹配弹幕");
                reloadDanmaku("search");
            },
        };
        const getUrlDanmaku = {
            title: "下载字幕",
            id: "getUrlDanmaku",
            innerText: danmaku_Url_icon,
            onclick: () => {
                if (window.ede.loading) {
                    console.log("正在加载,请稍后再试");
                    return;
                }
                console.log("手动匹配下载弹幕");
                reloadDanmakuUrl();
            },
        };
        const translateButtonOpts = {
            title: null,
            id: "translateDanmaku",
            innerText: translate_icon,
            onclick: () => {
                if (window.ede.loading) {
                    console.log("正在加载,请稍后再试");
                    return;
                }
                console.log("切换简繁转换");
                window.ede.chConvert = (window.ede.chConvert + 1) % 3;
                window.localStorage.setItem("chConvert", window.ede.chConvert);
                document
                    .querySelector("#translateDanmaku")
                    .setAttribute(
                        "title",
                        chConverTtitle[window.ede.chConvert]
                    );
                reloadDanmaku("reload");
                console.log(
                    document
                        .querySelector("#translateDanmaku")
                        .getAttribute("title")
                );
            },
        };
        const infoButtonOpts = {
            title: "弹幕信息",
            id: "printDanmakuInfo",
            innerText: info_icon,
            onclick: () => {
                if (!window.ede.episode_info || window.ede.loading) {
                    console.log("正在加载,请稍后再试");
                    return;
                }
                console.log("显示当前信息");
                let msg = "动画名称:" + window.ede.episode_info.animeTitle;
                if (window.ede.episode_info.episodeTitle) {
                    msg += "\n分集名称:" + window.ede.episode_info.episodeTitle;
                }
                sendNotification("当前弹幕匹配", msg);
            },
        };

        const fontSizeSetting = {
            title:
                "字体大小(下次加载生效:" +
                window.localStorage.getItem("danmakuFontSize") +
                ")",
            id: "fontSizeSetting",
            innerText: fontSize_icon,
            onclick: () => {
                console.log("修改弹幕字体大小");
                const size = parseInt(prompt("请输入字体大小:"));
                if (0 < size) {
                    window.localStorage.setItem("danmakuFontSize", size);
                } else {
                    alert("请输入正确的字体大小");
                }
            },
        };

        const filterButtonOpts = {
            title: "过滤等级(下次加载生效)",
            id: "filteringDanmaku",
            innerText: null,
            onclick: () => {
                console.log("切换弹幕过滤等级");
                let level = window.localStorage.getItem("danmakuFilterLevel");
                level = ((level ? parseInt(level) : 0) + 1) % 4;
                window.localStorage.setItem("danmakuFilterLevel", level);
                document.querySelector(
                    "#filteringDanmaku"
                ).children[0].innerText = filter_icons[level];
            },
        };
        // ------ configs end------
        /* eslint-disable */
        /* https://cdn.jsdelivr.net/npm/danmaku/dist/danmaku.min.js */
        // prettier-ignore
        /*
	   /* eslint-enable */

        class EDE {
		constructor() {
			this.chConvert = 1;
			if (window.localStorage.getItem('chConvert')) {
				this.chConvert = window.localStorage.getItem('chConvert');
			}
			// 0:当前状态关闭 1:当前状态打开
			this.danmakuSwitch = 1;
			if (window.localStorage.getItem('danmakuSwitch')) {
				this.danmakuSwitch = parseInt(window.localStorage.getItem('danmakuSwitch'));
			}
			this.danmaku = null;
			this.episode_info = null;
			this.ob = null;
			this.loading = false;
		}
	}

        function createButton(opt) {
            let button = document.createElement("button", buttonOptions);
            button.setAttribute("title", opt.title);
            button.setAttribute("id", opt.id);
            let icon = document.createElement("span");
            icon.className = "md-icon";
            icon.innerHTML = opt.innerText;
            button.appendChild(icon);
            button.onclick = opt.onclick;
            return button;
        }

        function initListener() {
            let container = document.querySelector(mediaQueryStr);
            // 页面未加载
            if (!container) {
                if (window.ede.episode_info) {
                    window.ede.episode_info = null;
                }
                return;
            }
            if (!container.getAttribute("ede_listening")) {
                console.log("正在初始化Listener");
                container.setAttribute("ede_listening", true);
                container.addEventListener("play", reloadDanmaku);
                console.log("Listener初始化完成");
                reloadDanmaku("reload");
            }
        }

        function getElementsByInnerText(
            tagType,
            innerStr,
            excludeChildNode = true
        ) {
            var temp = [];
            var elements = document.getElementsByTagName(tagType);
            if (!elements || 0 == elements.length) {
                return temp;
            }
            for (let index = 0; index < elements.length; index++) {
                var e = elements[index];
                if (e.innerText.includes(innerStr)) {
                    temp.push(e);
                }
            }
            if (!excludeChildNode) {
                return temp;
            }
            var res = [];
            temp.forEach((e) => {
                var e_copy = e.cloneNode(true);
                while (e_copy.firstChild != e_copy.lastChild) {
                    e_copy.removeChild(e_copy.lastChild);
                }
                if (e_copy.innerText.includes(innerStr)) {
                    res.push(e);
                }
            });
            return res;
        }

        function initUI() {
            // 页面未加载
            let uiAnchor = getElementsByInnerText("i", uiAnchorStr);
            if (!uiAnchor || !uiAnchor[0]) {
                return;
            }
            // 已初始化
            if (document.getElementById("danmakuCtr")) {
                return;
            }
            console.log("正在初始化UI");
            // 弹幕按钮容器div
            let parent = uiAnchor[0].parentNode.parentNode.parentNode;
            let menubar = document.createElement("div");
            menubar.id = "danmakuCtr";
            if (!window.ede.episode_info) {
                menubar.style.opacity = 0.5;
            }
            parent.append(menubar);
            // 弹幕开关
            displayButtonOpts.innerText =
                danmaku_icons[window.ede.danmakuSwitch];
            menubar.appendChild(createButton(displayButtonOpts));
            //下载字幕
            menubar.appendChild(createButton(getUrlDanmaku));
            // 手动匹配
            menubar.appendChild(createButton(searchButtonOpts));
            // 简繁转换
            translateButtonOpts.title = chConverTtitle[window.ede.chConvert];
            menubar.appendChild(createButton(translateButtonOpts));
            // 屏蔽等级
            filterButtonOpts.innerText =
                filter_icons[
                    parseInt(
                        window.localStorage.getItem("danmakuFilterLevel")
                            ? window.localStorage.getItem("danmakuFilterLevel")
                            : 0
                    )
                ];
            menubar.appendChild(createButton(filterButtonOpts));
            // 弹幕信息
            menubar.appendChild(createButton(infoButtonOpts));
            //字体大小
            menubar.appendChild(createButton(fontSizeSetting));
            console.log("UI初始化完成");
        }

        function sendNotification(title, msg) {
            const Notification =
                window.Notification || window.webkitNotifications;
            console.log(msg);
            if (Notification.permission === "granted") {
                return new Notification(title, {
                    body: msg,
                });
            } else {
                Notification.requestPermission((permission) => {
                    if (permission === "granted") {
                        return new Notification(title, {
                            body: msg,
                        });
                    }
                });
            }
        }

        function getEmbyItemInfo() {
            if (location.hash != "#!/videoosd/videoosd.html") {
                return null;
            }
            return window.require(["pluginManager"]).then((items) => {
                if (items) {
                    for (let i = 0; i < items.length; i++) {
                        const item = items[i];
                        if (item.pluginsList) {
                            for (let j = 0; j < item.pluginsList.length; j++) {
                                const plugin = item.pluginsList[j];
                                if (plugin && plugin.id == "htmlvideoplayer") {
                                    return plugin._currentPlayOptions
                                        ? plugin._currentPlayOptions.item
                                        : null;
                                }
                            }
                        }
                    }
                }
                return null;
            });
        }

        async function getEpisodeInfo(is_auto = true) {
            let item = await getEmbyItemInfo();
            if (!item) {
                return null;
            }
            if (item.Type == "TvChannel") {
                return null;
            }
            let _id;
            let animeName;
            let anime_id = -1;
            let episode;
            if (item.Type == "Episode") {
                _id = item.SeasonId;
                animeName = item.SeriesName;
                episode = item.IndexNumber;
                let session = item.ParentIndexNumber;
                if (session != 1) {
                    animeName += " " + session;
                }
            } else {
                _id = item.Id;
                animeName = item.Name;
                episode = "movie";
            }
            let _id_key = "_anime_id_rel_" + _id;
            let _name_key = "_anime_name_rel_" + _id;
            let _episode_key = "_episode_id_rel_" + _id + "_" + episode;
            if (is_auto) {
                if (window.localStorage.getItem(_episode_key)) {
                    return JSON.parse(
                        window.localStorage.getItem(_episode_key)
                    );
                }
            }
            if (window.localStorage.getItem(_id_key)) {
                anime_id = window.localStorage.getItem(_id_key);
            }
            if (window.localStorage.getItem(_name_key)) {
                animeName = window.localStorage.getItem(_name_key);
            }
            if (!is_auto) {
                animeName = prompt("确认动画名:", animeName);
            }

            let searchUrl =
                "https://api.9-ch.com/cors/https://api.dandanplay.net/api/v2/search/episodes?anime=" +
                animeName +
                "&withRelated=true";
            if (is_auto) {
                searchUrl += "&episode=" + episode;
            }
            let animaInfo = await fetch(searchUrl, {
                method: "GET",
                headers: {
                    "Accept-Encoding": "gzip",
                    Accept: "application/json",
                    "User-Agent": navigator.userAgent,
                },
            })
                .then((response) => response.json())
                .catch((error) => {
                    console.log("查询失败:", error);
                    return null;
                });
            console.log("查询成功");
            console.log(animaInfo);
            let selecAnime_id = 1;
            if (anime_id != -1) {
                for (let index = 0; index < animaInfo.animes.length; index++) {
                    if (animaInfo.animes[index].animeId == anime_id) {
                        selecAnime_id = index + 1;
                    }
                }
            }
            if (!is_auto) {
                let anime_lists_str = list2string(animaInfo);
                console.log(anime_lists_str);
                selecAnime_id = prompt(
                    "选择:\n" + anime_lists_str,
                    selecAnime_id
                );
                selecAnime_id = parseInt(selecAnime_id) - 1;
                window.localStorage.setItem(
                    _id_key,
                    animaInfo.animes[selecAnime_id].animeId
                );
                window.localStorage.setItem(
                    _name_key,
                    animaInfo.animes[selecAnime_id].animeTitle
                );
                let episode_lists_str = ep2string(
                    animaInfo.animes[selecAnime_id].episodes
                );
                episode = prompt(
                    "确认集数:\n" + episode_lists_str,
                    parseInt(episode)
                );
                episode = parseInt(episode) - 1;
            } else {
                selecAnime_id = parseInt(selecAnime_id) - 1;
                episode = 0;
            }
            let episodeInfo = {
                episodeId:
                    animaInfo.animes[selecAnime_id].episodes[episode].episodeId,
                animeTitle: animaInfo.animes[selecAnime_id].animeTitle,
                episodeTitle:
                    animaInfo.animes[selecAnime_id].type == "tvseries"
                        ? animaInfo.animes[selecAnime_id].episodes[episode]
                              .episodeTitle
                        : null,
            };
            window.localStorage.setItem(
                _episode_key,
                JSON.stringify(episodeInfo)
            );
            return episodeInfo;
        }

        function getComments(episodeId) {
            let url =
                "https://api.9-ch.com/cors/https://api.dandanplay.net/api/v2/comment/" +
                episodeId +
                "?withRelated=true&chConvert=" +
                window.ede.chConvert;
            return fetch(url, {
                method: "GET",
                headers: {
                    "Accept-Encoding": "gzip",
                    Accept: "application/json",
                    "User-Agent": navigator.userAgent,
                },
            })
                .then((response) => response.json())
                .then((data) => {
                    console.log("弹幕下载成功: " + data.comments.length);
                    return data.comments;
                })
                .catch((error) => {
                    console.log("获取弹幕失败:", error);
                    return null;
                });
        }

        async function createDanmaku(comments) {
            if (!comments) {
                return;
            }
            if (window.ede.danmaku != null) {
                window.ede.danmaku.clear();
                window.ede.danmaku.destroy();
                window.ede.danmaku = null;
            }
            let _comments = danmakuFilter(danmakuParser(comments));
            console.log("弹幕加载成功: " + _comments.length);

            while (!document.querySelector(mediaContainerQueryStr)) {
                await new Promise((resolve) => setTimeout(resolve, 200));
            }

            let _container = document.querySelector(mediaContainerQueryStr);
            let _media = document.querySelector(mediaQueryStr);
            window.ede.danmaku = new Danmaku({
                container: _container,
                media: _media,
                comments: _comments,
                engine: "canvas",
            });
            window.ede.danmakuSwitch == 1
                ? window.ede.danmaku.show()
                : window.ede.danmaku.hide();
            if (window.ede.ob) {
                window.ede.ob.disconnect();
            }
            window.ede.ob = new ResizeObserver(() => {
                if (window.ede.danmaku) {
                    console.log("Resizing");
                    window.ede.danmaku.resize();
                }
            });
            window.ede.ob.observe(_container);
        }

        function reloadDanmaku(type = "check") {
            if (window.ede.loading) {
                console.log("正在重新加载");
                return;
            }
            window.ede.loading = true;
            getEpisodeInfo(type != "search")
                .then((info) => {
                    return new Promise((resolve, reject) => {
                        if (!info) {
                            if (type != "init") {
                                reject("播放器未完成加载");
                            } else {
                                reject(null);
                            }
                        }
                        if (
                            type != "search" &&
                            type != "reload" &&
                            window.ede.danmaku &&
                            window.ede.episode_info &&
                            window.ede.episode_info.episodeId == info.episodeId
                        ) {
                            reject("当前播放视频未变动");
                        } else {
                            window.ede.episode_info = info;
                            resolve(info.episodeId);
                        }
                    });
                })
                .then(
                    (episodeId) =>
                        getComments(episodeId).then((comments) =>
                            createDanmaku(comments).then(() => {
                                console.log("弹幕就位");
                            })
                        ),
                    (msg) => {
                        if (msg) {
                            console.log(msg);
                        }
                    }
                )
                .then(() => {
                    window.ede.loading = false;
                    if (document.getElementById("danmakuCtr")) {
                        if (
                            document.getElementById("danmakuCtr").style
                                .opacity != 1
                        ) {
                            document.getElementById(
                                "danmakuCtr"
                            ).style.opacity = 1;
                        }
                    }
                });
        }

        function reloadDanmakuUrl() {
            if (window.ede.loading) {
                console.log("正在重新加载");
                return;
            }
            window.ede.loading = true;
            getDownloadDanmaku()
                .then((comments) =>
                    createDanmaku(comments).then(() => {
                        console.log("弹幕就位");
                    })
                )
                .then(() => {
                    window.ede.loading = false;
                    if (document.getElementById("danmakuCtr")) {
                        if (
                            document.getElementById("danmakuCtr").style
                                .opacity != 1
                        ) {
                            document.getElementById(
                                "danmakuCtr"
                            ).style.opacity = 1;
                        }
                    }
                });
        }

        function getDownloadDanmaku() {
            const UrlPath = prompt("请输入视频站播放地址");
            let url =
                "https://api.9-ch.com/cors/https://api.dandanplay.net/api/v2/extcomment?url=" +
                UrlPath;
            return fetch(url, {
                method: "GET",
                headers: {
                    "Accept-Encoding": "gzip",
                    Accept: "application/json",
                    "User-Agent": navigator.userAgent,
                },
            })
                .then((response) => response.json())
                .then((data) => {
                    console.log("弹幕下载成功: " + data.comments.length);
                    return data.comments;
                })
                .catch((error) => {
                    console.log("获取弹幕失败:", error);
                    return null;
                });
        }

        function danmakuFilter(comments) {
            let level =
                3 -
                parseInt(
                    window.localStorage.getItem("danmakuFilterLevel")
                        ? window.localStorage.getItem("danmakuFilterLevel")
                        : 0
                );
            if (level == 0) {
                return comments;
            }
            let limit = 9 - level * 2;
            let vertical_limit = 6;
            let arr_comments = [];
            let vertical_comments = [];
            for (let index = 0; index < comments.length; index++) {
                let element = comments[index];
                let i = Math.ceil(element.time);
                let i_v = Math.ceil(element.time / 3);
                if (!arr_comments[i]) {
                    arr_comments[i] = [];
                }
                if (!vertical_comments[i_v]) {
                    vertical_comments[i_v] = [];
                }
                // TODO: 屏蔽过滤
                if (vertical_comments[i_v].length < vertical_limit) {
                    vertical_comments[i_v].push(element);
                } else {
                    element.mode = "rtl";
                }
                if (arr_comments[i].length < limit) {
                    arr_comments[i].push(element);
                }
            }
            return arr_comments.flat();
        }

        function danmakuParser($obj) {
            //const $xml = new DOMParser().parseFromString(string, 'text/xml')
            return $obj
                .map(($comment) => {
                    const p = $comment.p;
                    //if (p === null || $comment.childNodes[0] === undefined) return null
                    const values = p.split(",");
                    const mode = {
                        6: "ltr",
                        1: "rtl",
                        5: "top",
                        4: "bottom",
                    }[values[1]];
                    if (!mode) return null;
                    //const fontSize = Number(values[2]) || 25
                    const fontSize = Math.round(
                        (window.screen.height > window.screen.width
                            ? window.screen.width
                            : window.screen.height / 1080) * fontSize_Setting
                    );
                    const color = `000000${Number(values[2]).toString(
                        16
                    )}`.slice(-6);
                    return {
                        text: $comment.m,
                        mode,
                        time: values[0] * 1,
                        style: {
                            fontSize: `${fontSize}px`,
                            color: `#${color}`,
                            textShadow:
                                color === "00000"
                                    ? "-1px -1px #fff, -1px 1px #fff, 1px -1px #fff, 1px 1px #fff"
                                    : "-1px -1px #000, -1px 1px #000, 1px -1px #000, 1px 1px #000",

                            font: `${fontSize}px sans-serif`,
                            fillStyle: `#${color}`,
                            strokeStyle: color === "000000" ? "#fff" : "#000",
                            lineWidth: 2.0,
                        },
                    };
                })
                .filter((x) => x);
        }

        function list2string($obj2) {
            const $animes = $obj2.animes;
            let anime_lists = $animes.map(($single_anime) => {
                return (
                    $single_anime.animeTitle +
                    " 类型:" +
                    $single_anime.typeDescription
                );
            });
            let anime_lists_str = "1:" + anime_lists[0];
            for (let i = 1; i < anime_lists.length; i++) {
                anime_lists_str =
                    anime_lists_str +
                    "\n" +
                    (i + 1).toString() +
                    ":" +
                    anime_lists[i];
            }
            return anime_lists_str;
        }

        function ep2string($obj3) {
            const $animes = $obj3;
            let anime_lists = $animes.map(($single_ep) => {
                return $single_ep.episodeTitle;
            });
            let ep_lists_str = "1:" + anime_lists[0];
            for (let i = 1; i < anime_lists.length; i++) {
                ep_lists_str =
                    ep_lists_str +
                    "\n" +
                    (i + 1).toString() +
                    ":" +
                    anime_lists[i];
            }
            return ep_lists_str;
        }

        while (!window.require) {
            await new Promise((resolve) => setTimeout(resolve, 200));
        }
        if (!window.ede) {
            window.ede = new EDE();

            setInterval(() => {
                initUI();
            }, check_interval);
            //while (!(await getEmbyItemInfo())) {
            //    await new Promise((resolve) => setTimeout(resolve, 200));
            //}
            //if (location.hash == '#!/videoosd/videoosd.html') {
            reloadDanmaku("init");
            setInterval(() => {
                initListener();
            }, check_interval);
            //}
        }
        //}
    }
})();