Pixeldrain Bypass Player (Modal Overlay)

Adds a button to open a direct, hotlinked video player in a modal, bypassing Pixeldrain limits. No need for a new tab. Also works with my Pixeldrain SRT Subtitle Injector.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Pixeldrain Bypass Player (Modal Overlay)
// @namespace    pixeldrain-direct-player-modal
// @version      3.1.2
// @description  Adds a button to open a direct, hotlinked video player in a modal, bypassing Pixeldrain limits. No need for a new tab. Also works with my Pixeldrain SRT Subtitle Injector.
// @author       medy (logic inspired by Sak32009)
// @license      MIT
// @match        *://pixeldrain.com/u/*
// @match        *://pixeldrain.com/l/*
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js
// @grant        GM_addStyle
// @grant        unsafeWindow
// @run-at       document-end
// @noframes
// ==/UserScript==
/* global bootstrap, jQuery */
// Inject the entire minified Bootstrap 5.3 CSS, scoped to our container class.
(t=> {
    if(typeof GM_addStyle=="function") {
        GM_addStyle(t);
        return
    }
    const r=document.createElement("style");
    r.textContent=t,document.head.append(r)
})
(` .pd-player-scope {
    all:initial
}
.pd-player-scope * {
    all:revert
}
.pd-player-scope,.pd-player-scope[data-bs-theme=light] {
    --bs-blue:#0d6efd;
    --bs-indigo:#6610f2;
    --bs-purple:#6f42c1;
    --bs-pink:#d63384;
    --bs-red:#dc3545;
    --bs-orange:#fd7e14;
    --bs-yellow:#ffc107;
    --bs-green:#198754;
    --bs-teal:#20c997;
    --bs-cyan:#0dcaf0;
    --bs-black:#000;
    --bs-white:#fff;
    --bs-gray:#6c757d;
    --bs-gray-dark:#343a40;
    --bs-gray-100:#f8f9fa;
    --bs-gray-200:#e9ecef;
    --bs-gray-300:#dee2e6;
    --bs-gray-400:#ced4da;
    --bs-gray-500:#adb5bd;
    --bs-gray-600:#6c757d;
    --bs-gray-700:#495057;
    --bs-gray-800:#343a40;
    --bs-gray-900:#212529;
    --bs-primary:#0d6efd;
    --bs-secondary:#6c757d;
    --bs-success:#198754;
    --bs-info:#0dcaf0;
    --bs-warning:#ffc107;
    --bs-danger:#dc3545;
    --bs-light:#f8f9fa;
    --bs-dark:#212529;
    --bs-primary-rgb:13,110,253;
    --bs-secondary-rgb:108,117,125;
    --bs-success-rgb:25,135,84;
    --bs-info-rgb:13,202,240;
    --bs-warning-rgb:255,193,7;
    --bs-danger-rgb:220,53,69;
    --bs-light-rgb:248,249,250;
    --bs-dark-rgb:33,37,41;
    --bs-primary-text-emphasis:#052c65;
    --bs-secondary-text-emphasis:#2b2f32;
    --bs-success-text-emphasis:#0a3622;
    --bs-info-text-emphasis:#055160;
    --bs-warning-text-emphasis:#664d03;
    --bs-danger-text-emphasis:#58151c;
    --bs-light-text-emphasis:#495057;
    --bs-dark-text-emphasis:#495057;
    --bs-primary-bg-subtle:#cfe2ff;
    --bs-secondary-bg-subtle:#e2e3e5;
    --bs-success-bg-subtle:#d1e7dd;
    --bs-info-bg-subtle:#cff4fc;
    --bs-warning-bg-subtle:#fff3cd;
    --bs-danger-bg-subtle:#f8d7da;
    --bs-light-bg-subtle:#fcfcfd;
    --bs-dark-bg-subtle:#ced4da;
    --bs-primary-border-subtle:#9ec5fe;
    --bs-secondary-border-subtle:#c4c8cb;
    --bs-success-border-subtle:#a3cfbb;
    --bs-info-border-subtle:#9eeaf9;
    --bs-warning-border-subtle:#ffe69c;
    --bs-danger-border-subtle:#f1aeb5;
    --bs-light-border-subtle:#e9ecef;
    --bs-dark-border-subtle:#adb5bd;
    --bs-white-rgb:255,255,255;
    --bs-black-rgb:0,0,0;
    --bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans","Liberation Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
    --bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
    --bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, .15), rgba(255, 255, 255, 0));
    --bs-body-font-family:var(--bs-font-sans-serif);
    --bs-body-font-size:1rem;
    --bs-body-font-weight:400;
    --bs-body-line-height:1.5;
    --bs-body-color:#212529;
    --bs-body-color-rgb:33,37,41;
    --bs-body-bg:#fff;
    --bs-body-bg-rgb:255,255,255;
    --bs-emphasis-color:#000;
    --bs-emphasis-color-rgb:0,0,0;
    --bs-secondary-color:rgba(33,37,41,.75);
    --bs-secondary-color-rgb:33,37,41;
    --bs-secondary-bg:#e9ecef;
    --bs-secondary-bg-rgb:233,236,239;
    --bs-tertiary-color:rgba(33,37,41,.5);
    --bs-tertiary-color-rgb:33,37,41;
    --bs-tertiary-bg:#f8f9fa;
    --bs-tertiary-bg-rgb:248,249,250;
    --bs-heading-color:inherit;
    --bs-link-color:#0d6efd;
    --bs-link-color-rgb:13,110,253;
    --bs-link-decoration:underline;
    --bs-link-hover-color:#0a58ca;
    --bs-link-hover-color-rgb:10,88,202;
    --bs-code-color:#d63384;
    --bs-highlight-color:#212529;
    --bs-highlight-bg:#fff3cd;
    --bs-border-width:1px;
    --bs-border-style:solid;
    --bs-border-color:#dee2e6;
    --bs-border-color-translucent:rgba(0,0,0,.175);
    --bs-border-radius:.375rem;
    --bs-border-radius-sm:.25rem;
    --bs-border-radius-lg:.5rem;
    --bs-border-radius-xl:1rem;
    --bs-border-radius-xxl:2rem;
    --bs-border-radius-2xl:var(--bs-border-radius-xxl);
    --bs-border-radius-pill:50rem;
    --bs-box-shadow:0 .5rem 1rem rgba(0,0,0,.15);
    --bs-box-shadow-sm:0 .125rem .25rem rgba(0,0,0,.075);
    --bs-box-shadow-lg:0 1rem 3rem rgba(0,0,0,.175);
    --bs-box-shadow-inset:inset 0 1px 2px rgba(0,0,0,.075);
    --bs-focus-ring-width:.25rem;
    --bs-focus-ring-opacity:.25;
    --bs-focus-ring-color:rgba(13,110,253,.25);
    --bs-form-valid-color:#198754;
    --bs-form-valid-border-color:#198754;
    --bs-form-invalid-color:#dc3545;
    --bs-form-invalid-border-color:#dc3545
}
.pd-player-scope[data-bs-theme=dark] {
    color-scheme:dark;
    --bs-body-color:#dee2e6;
    --bs-body-color-rgb:222,226,230;
    --bs-body-bg:#212529;
    --bs-body-bg-rgb:33,37,41;
    --bs-emphasis-color:#fff;
    --bs-emphasis-color-rgb:255,255,255;
    --bs-secondary-color:rgba(222,226,230,.75);
    --bs-secondary-color-rgb:222,226,230;
    --bs-secondary-bg:#343a40;
    --bs-secondary-bg-rgb:52,58,64;
    --bs-tertiary-color:rgba(222,226,230,.5);
    --bs-tertiary-color-rgb:222,226,230;
    --bs-tertiary-bg:#2b3035;
    --bs-tertiary-bg-rgb:43,48,53;
    --bs-primary-text-emphasis:#6ea8fe;
    --bs-secondary-text-emphasis:#a7acb1;
    --bs-success-text-emphasis:#75b798;
    --bs-info-text-emphasis:#6edff6;
    --bs-warning-text-emphasis:#ffda6a;
    --bs-danger-text-emphasis:#ea868f;
    --bs-light-text-emphasis:#f8f9fa;
    --bs-dark-text-emphasis:#dee2e6;
    --bs-primary-bg-subtle:#031633;
    --bs-secondary-bg-subtle:#161719;
    --bs-success-bg-subtle:#051b11;
    --bs-info-bg-subtle:#032830;
    --bs-warning-bg-subtle:#332701;
    --bs-danger-bg-subtle:#2c0b0e;
    --bs-light-bg-subtle:#343a40;
    --bs-dark-bg-subtle:#1a1d20;
    --bs-primary-border-subtle:#084298;
    --bs-secondary-border-subtle:#41464b;
    --bs-success-border-subtle:#0f5132;
    --bs-info-border-subtle:#087990;
    --bs-warning-border-subtle:#997404;
    --bs-danger-border-subtle:#842029;
    --bs-light-border-subtle:#495057;
    --bs-dark-border-subtle:#343a40;
    --bs-heading-color:inherit;
    --bs-link-color:#6ea8fe;
    --bs-link-hover-color:#8bb9fe;
    --bs-link-color-rgb:110,168,254;
    --bs-link-hover-color-rgb:139,185,254;
    --bs-code-color:#e685b5;
    --bs-highlight-color:#dee2e6;
    --bs-highlight-bg:#664d03;
    --bs-border-color:#495057;
    --bs-border-color-translucent:rgba(255,255,255,.15);
    --bs-form-valid-color:#75b798;
    --bs-form-valid-border-color:#75b798;
    --bs-form-invalid-color:#ea868f;
    --bs-form-invalid-border-color:#ea868f
}
.pd-player-scope *,.pd-player-scope :before,.pd-player-scope :after {
    box-sizing:border-box
}
.pd-player-scope h1,.pd-player-scope h2,.pd-player-scope h3,.pd-player-scope h4,.pd-player-scope h5,.pd-player-scope h6 {
    margin-top:0;
    margin-bottom:.5rem;
    font-weight:500;
    line-height:1.2;
    color:var(--bs-heading-color)
}
.pd-player-scope .btn-close {
    --bs-btn-close-color:#000;
    --bs-btn-close-bg:url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e\");
    --bs-btn-close-opacity:.5;
    --bs-btn-close-hover-opacity:.75;
    --bs-btn-close-focus-shadow:0 0 0 .25rem rgba(13,110,253,.25);
    --bs-btn-close-focus-opacity:1;
    --bs-btn-close-disabled-opacity:.25;
    --bs-btn-close-white-filter:invert(1) grayscale(100%) brightness(200%);
    box-sizing:content-box;
    width:1em;
    height:1em;
    padding:.25em .25em;
    color:var(--bs-btn-close-color);
    background:transparent var(--bs-btn-close-bg) center/1em auto no-repeat;
    border:0;
    border-radius:.375rem;
    opacity:var(--bs-btn-close-opacity)
}
.pd-player-scope .btn-close:hover {
    color:var(--bs-btn-close-color);
    text-decoration:none;
    opacity:var(--bs-btn-close-hover-opacity)
}
.pd-player-scope .btn-close:focus {
    outline:0;
    box-shadow:var(--bs-btn-close-focus-shadow);
    opacity:var(--bs-btn-close-focus-opacity)
}
.pd-player-scope .btn-close:disabled,.pd-player-scope .btn-close.disabled {
    pointer-events:none;
    -webkit-user-select:none;
    -moz-user-select:none;
    user-select:none;
    opacity:var(--bs-btn-close-disabled-opacity)
}
.pd-player-scope .btn-close-white,.pd-player-scope[data-bs-theme=dark] .btn-close {
    filter:var(--bs-btn-close-white-filter)
}
.pd-player-scope .modal {
    --bs-modal-zindex:1055;
    --bs-modal-width:500px;
    --bs-modal-padding:1rem;
    --bs-modal-margin:.5rem;
    --bs-modal-color:;
    --bs-modal-bg:var(--bs-body-bg);
    --bs-modal-border-color:var(--bs-border-color-translucent);
    --bs-modal-border-width:var(--bs-border-width);
    --bs-modal-border-radius:var(--bs-border-radius-lg);
    --bs-modal-box-shadow:var(--bs-box-shadow-sm);
    --bs-modal-inner-border-radius:calc(var(--bs-border-radius-lg) - var(--bs-border-width));
    --bs-modal-header-padding-x:1rem;
    --bs-modal-header-padding-y:1rem;
    --bs-modal-header-padding:1rem 1rem;
    --bs-modal-header-border-color:var(--bs-border-color);
    --bs-modal-header-border-width:var(--bs-border-width);
    --bs-modal-title-line-height:1.5;
    --bs-modal-footer-gap:.5rem;
    --bs-modal-footer-bg:;
    --bs-modal-footer-border-color:var(--bs-border-color);
    --bs-modal-footer-border-width:var(--bs-border-width);
    position:fixed;
    top:0;
    left:0;
    z-index:var(--bs-modal-zindex);
    display:none;
    width:100%;
    height:100%;
    overflow-x:hidden;
    overflow-y:auto;
    outline:0
}
.pd-player-scope .modal-dialog {
    position:relative;
    width:auto;
    margin:var(--bs-modal-margin);
    pointer-events:none
}
.pd-player-scope .modal.fade .modal-dialog {
    transition:transform .3s ease-out;
    transform:translate(0,-50px)
}
@media (prefers-reduced-motion:reduce) {
    .pd-player-scope .modal.fade .modal-dialog {
        transition:none
    }
}
.pd-player-scope .modal.show .modal-dialog {
    transform:none
}
.pd-player-scope .modal.modal-static .modal-dialog {
    transform:scale(1.02)
}
.pd-player-scope .modal-dialog-scrollable {
    height:calc(100% - var(--bs-modal-margin)*2)
}
.pd-player-scope .modal-dialog-scrollable .modal-content {
    max-height:100%;
    overflow:hidden
}
.pd-player-scope .modal-dialog-scrollable .modal-body {
    overflow-y:auto
}
.pd-player-scope .modal-dialog-centered {
    display:flex;
    align-items:center;
    min-height:calc(100% - var(--bs-modal-margin)*2)
}
.pd-player-scope .modal-content {
    position:relative;
    display:flex;
    flex-direction:column;
    width:100%;
    color:var(--bs-modal-color);
    pointer-events:auto;
    background-color:var(--bs-modal-bg);
    background-clip:padding-box;
    border:var(--bs-modal-border-width) solid var(--bs-modal-border-color);
    border-radius:var(--bs-modal-border-radius);
    outline:0
}
.modal-backdrop {
    --bs-backdrop-zindex:1050;
    --bs-backdrop-bg:#000;
    --bs-backdrop-opacity:.5;
    position:fixed;
    top:0;
    left:0;
    z-index:var(--bs-backdrop-zindex);
    width:100vw;
    height:100vh;
    background-color:var(--bs-backdrop-bg)
}
.modal-backdrop.fade {
    opacity:0
}
.modal-backdrop.show {
    opacity:var(--bs-backdrop-opacity)
}
.pd-player-scope .modal-header {
    display:flex;
    flex-shrink:0;
    align-items:center;
    justify-content:space-between;
    padding:var(--bs-modal-header-padding);
    border-bottom:var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);
    border-top-left-radius:var(--bs-modal-inner-border-radius);
    border-top-right-radius:var(--bs-modal-inner-border-radius)
}
.pd-player-scope .modal-header .btn-close {
    padding:calc(var(--bs-modal-header-padding-y)*.5) calc(var(--bs-modal-header-padding-x)*.5);
    margin:calc(var(--bs-modal-header-padding-y)*-.5) calc(var(--bs-modal-header-padding-x)*-.5) calc(var(--bs-modal-header-padding-y)*-.5) auto
}
.pd-player-scope .modal-title {
    margin-bottom:0;
    line-height:var(--bs-modal-title-line-height)
}
.pd-player-scope .modal-body {
    position:relative;
    flex:1 1 auto;
    padding:var(--bs-modal-padding)
}
.pd-player-scope .modal-footer {
    display:flex;
    flex-shrink:0;
    flex-wrap:wrap;
    align-items:center;
    justify-content:flex-end;
    padding:calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap)*.5);
    background-color:var(--bs-modal-footer-bg);
    border-top:var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color);
    border-bottom-right-radius:var(--bs-modal-inner-border-radius);
    border-bottom-left-radius:var(--bs-modal-inner-border-radius)
}
.pd-player-scope .modal-footer>* {
    margin:calc(var(--bs-modal-footer-gap)*.5)
}
@media (min-width:576px) {
    .pd-player-scope .modal {
        --bs-modal-margin:1.75rem;
        --bs-modal-box-shadow:var(--bs-box-shadow)
    }
    .pd-player-scope .modal-dialog {
        max-width:var(--bs-modal-width);
        margin-right:auto;
        margin-left:auto
    }
    .pd-player-scope .modal-sm {
        --bs-modal-width:300px
    }
}
@media (min-width:992px) {
    .pd-player-scope .modal-lg,.pd-player-scope .modal-xl {
        --bs-modal-width:800px
    }
}
@media (min-width:1200px) {
    .pd-player-scope .modal-xl {
        --bs-modal-width:1140px
    }
}
.pd-player-scope .fade {
    transition:opacity .15s linear
}
@media (prefers-reduced-motion:reduce) {
    .pd-player-scope .fade {
        transition:none
    }
}
.pd-player-scope .fade:not(.show) {
    opacity:0
}
.pd-player-scope .p-0 {
    padding:0!important
}
.pd-player-scope .fw-bold {
    font-weight:700!important
}
.pd-player-scope .modal-body video {
    width:100%;
    max-height:calc(100vh - 100px);
    display:block;
    outline:none;
    background-color:#000;
}
`);
(function($, bootstrap)  {
    'use strict';
    // Use .noConflict to avoid clashes with the site's own jQuery if it has one.
    $ = $.noConflict(true);
    const SCRIPT_NAME = 'Pixeldrain Direct Player';
    const BYPASS_URL_BASE = 'https://pd.cybar.xyz';
    const CONTAINER_CLASS = 'pd-player-scope';
    const MODAL_ID = 'pd-direct-player-modal';
    let uiInitialized = false;
    // --- HTML Templates ---
    const MODAL_HTML = `
    <div class="modal fade" id="$ {
        MODAL_ID
    }
    " tabindex="-1">
    <div class="modal-dialog modal-xl modal-dialog-centered">
    <div class="modal-content">
    <div class="modal-header">
    <h5 class="modal-title fw-bold">Direct Player</h5>
    <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
    </div>
    <div class="modal-body p-0">
    <!-- Video tag will be injected here -->
    </div>
    </div>
    </div>
    </div>
    `;
    /**
    * Finds the current file ID and checks if it's a video.
    * @returns  {
        {
            id: string, name: string
        }
        |null
    }
    */
    function getCurrentFileInfo()  {
        // Primary Method: Read from the global viewer_data object. It's more comprehensive.
        if (typeof unsafeWindow.viewer_data?.api_response !== 'undefined')  {
            const data = unsafeWindow.viewer_data;
            const api = data.api_response;
            // Handle list pages (/l/...)
            if (data.type === 'list' && data.file_id && api.files)  {
                const currentFile = api.files.find(f => f.id === data.file_id);
                if (currentFile && currentFile.mime_type?.startsWith('video/'))  {
                    return  {
                        id: currentFile.id, name: currentFile.name
                    };
                }
            }
            // Handle single file pages (/u/...)
            else if (data.type === 'file' && api.id && api.mime_type?.startsWith('video/'))  {
                return  {
                    id: api.id, name: api.name
                };
            }
        }
        // Fallback Method: For initial load before viewer_data might be populated.
        const videoMetaTag = document.querySelector('meta[property="og:video"]');
        if (videoMetaTag?.content)  {
            const urlParts = videoMetaTag.content.split('/');
            const fileId = urlParts.pop();
            const fileName = document.title.split('~')[0].trim();
            if (fileId) return  {
                id: fileId, name: fileName
            };
        }
        return null;
    }
    /**
    * Creates and injects the toolbar button.
    * @param  {
        {
            id: string, name: string
        }
    }
    fileInfo
    */
    function createOrUpdateButton(fileInfo)  {
        const toolbar = document.querySelector('.toolbar');
        const templateButton = document.querySelector('.toolbar_button');
        const separatorTemplate = document.querySelector('.toolbar .separator');
        if (!toolbar || !templateButton || !separatorTemplate)  {
            console.log(`[$ {
                SCRIPT_NAME
            }
            ] Toolbar elements not found yet`);
            return false;
        }
        // Check if button already exists
        let directButton = document.querySelector('#pd-direct-player-btn');
        if (!directButton)  {
            // Create the button using Pixeldrain's template
            directButton = templateButton.cloneNode(true);
            directButton.id = 'pd-direct-player-btn';
            directButton.removeAttribute('href');
            directButton.removeAttribute('title');
            directButton.querySelector('i').textContent = 'play_arrow';
            directButton.querySelector('span').textContent = 'Play Direct';
            directButton.addEventListener('click', (e) =>  {
                e.preventDefault();
                e.stopPropagation();
                const currentId = directButton.getAttribute('data-file-id');
                const currentName = directButton.getAttribute('data-file-name');
                if (currentId)  {
                    createAndShowModal(currentId, currentName);
                }
            });
            // Insert into toolbar (after the last separator)
            const newSeparator = separatorTemplate.cloneNode(true);
            separatorTemplate.parentNode.insertBefore(newSeparator, separatorTemplate.nextSibling);
            newSeparator.parentNode.insertBefore(directButton, newSeparator.nextSibling);
            console.log(`[$ {
                SCRIPT_NAME
            }
            ] Direct player button created`);
        }
        // Update button data
        directButton.setAttribute('data-file-id', fileInfo.id);
        directButton.setAttribute('data-file-name', fileInfo.name);
        return true;
    }
    /**
    * Removes the toolbar button if it exists.
    */
    function removeButton()  {
        const directButton = document.querySelector('#pd-direct-player-btn');
        if (directButton)  {
            // Also remove the separator that was added with it
            const prevSeparator = directButton.previousElementSibling;
            if (prevSeparator && prevSeparator.classList.contains('separator'))  {
                prevSeparator.remove();
            }
            directButton.remove();
            console.log(`[$ {
                SCRIPT_NAME
            }
            ] Direct player button removed`);
        }
    }
    /**
    * Creates the Bootstrap modal, injects the video player, and shows it.
    * @param  {
        string
    }
    fileId
    * @param  {
        string
    }
    fileName
    */
    function createAndShowModal(fileId, fileName)  {
        // Ensure we have our container
        let $container = $(`.$ {
            CONTAINER_CLASS
        }
        `);
        if (!$container.length)  {
            $container = $('<div>').addClass(CONTAINER_CLASS).attr('data-bs-theme', 'dark').appendTo('body');
        }
        // Remove any old modal instance first
        $(`#$ {
            MODAL_ID
        }
        `).remove();
        const videoUrl = `$ {
            BYPASS_URL_BASE
        }
        /$ {
            fileId
        }
        `;
        const $modal = $(MODAL_HTML);
        $modal.find('.modal-title').text(fileName);
        const $video = $('<video>').attr( {
            src: videoUrl,
            controls: true,
            autoplay: true
        });
        $modal.find('.modal-body').append($video);
        $modal.appendTo($container);
        const modalInstance = new bootstrap.Modal($modal[0]);
        // Add event listener for when the modal is closed, to stop the video and clean up.
        $modal[0].addEventListener('hidden.bs.modal', event =>  {
            const videoEl = $modal.find('video')[0];
            if (videoEl)  {
                videoEl.pause();
                videoEl.src = '';
                // Detach the source
            }
            modalInstance.dispose();
            $modal.remove();
            // Remove from DOM
        });
        modalInstance.show();
        console.log(`[$ {
            SCRIPT_NAME
        }
        ] Modal opened for $ {
            fileName
        }
        `);
    }
    /**
    * Main execution logic.
    */
    function run()  {
        const fileInfo = getCurrentFileInfo();
        if (fileInfo)  {
            console.log(`[$ {
                SCRIPT_NAME
            }
            ] Video detected: $ {
                fileInfo.name
            }
            ($ {
                fileInfo.id
            })
            `);
            const buttonCreated = createOrUpdateButton(fileInfo);
            if (!buttonCreated)  {
                // If we couldn't create the button, try again later
                setTimeout(run, 1000);
            }
        }
        else  {
            console.log(`[$ {
                SCRIPT_NAME
            }
            ] No video detected on this page/view.`);
            removeButton();
        }
    }
    /**
    * Sets up UI when toolbar is available.
    */
    function setupUI()  {
        if (uiInitialized) return;
        const toolbar = document.querySelector('.toolbar');
        const templateButton = document.querySelector('.toolbar_button');
        const separatorTemplate = document.querySelector('.toolbar .separator');
        if (!toolbar || !templateButton || !separatorTemplate)  {
            console.log(`[$ {
                SCRIPT_NAME
            }
            ] Toolbar not ready yet, retrying...`);
            return false;
        }
        uiInitialized = true;
        console.log(`[$ {
            SCRIPT_NAME
        }
        ] UI Initialized`);
        // Run initial setup
        run();
        return true;
    }
    // --- Script Entry Point ---
    function initializeScript()  {
        // Wait for document body to be available
        if (!document.body)  {
            setTimeout(initializeScript, 100);
            return;
        }
        // Try to setup UI immediately
        if (setupUI())  {
            return;
            // Success
        }
        // If immediate setup failed, use observer
        const observer = new MutationObserver((mutations, obs) =>  {
            if (setupUI())  {
                obs.disconnect();
            }
        });
        observer.observe(document.body,  {
            childList: true,
            subtree: true
        });
        // Fallback timeout
        setTimeout(() =>  {
            if (!uiInitialized)  {
                console.log(`[$ {
                    SCRIPT_NAME
                }
                ] Fallback initialization`);
                setupUI();
            }
        },
        3000);
    }
    // Start initialization
    if (document.readyState === 'loading')  {
        document.addEventListener('DOMContentLoaded', initializeScript);
    }
    else  {
        initializeScript();
    }
    // Listen for hash changes on list pages to update the button for the new file.
    $(window).on('hashchange', function()  {
        // Give the site's JS time to update viewer_data
        setTimeout(run, 200);
    });
})
(jQuery, bootstrap);