GPT - Player / Downloader

Enables downloads & replaces site player with native HTML5 player + controls

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(У мене вже є менеджер скриптів, дайте мені встановити його!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         GPT - Player / Downloader
// @namespace    https://github.com/BD9Max/userscripts
// @version      1.2
// @description  Enables downloads & replaces site player with native HTML5 player + controls
// @author       krbd9max
// @license      MIT
// @match        https://*.gayporntube.com/*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function () {
    'use strict';

    function restoreNativePlayer() {
        const video = document.getElementById('play-video') || document.querySelector('video');
        if (!video || video.dataset.nativeRestored) return;
        video.dataset.nativeRestored = '1';

        // 1. Try to destroy Fluid Player instance if it exists
        try {
            if (typeof fluidPlayer !== 'undefined' && window.fluidPlayerInstances) {
                // some versions store instances
                Object.values(window.fluidPlayerInstances || {}).forEach(p => {
                    try { p.destroy && p.destroy(); } catch (e) {}
                });
            }
            // also try common global
            if (video.fluidPlayer) {
                try { video.fluidPlayer.destroy(); } catch (e) {}
            }
        } catch (e) {}

        // 2. Clean source URL (remove ?br=xxxx)
        const source = video.querySelector('source');
        if (source && source.src) {
            const clean = source.src.replace(/\/\?br=\d*$/, '').replace(/\?br=\d*$/, '');
            if (clean !== source.src) {
                source.src = clean;
            }
        }

        // 3. Unwrap video from Fluid Player wrapper if present
        const wrapper = video.closest('.fluid_video_wrapper') || video.parentElement;
        if (wrapper && wrapper !== video && wrapper.classList.contains('fluid_video_wrapper')) {
            // Move video out of the wrapper
            wrapper.parentNode.insertBefore(video, wrapper);
            // Remove the entire Fluid Player DOM
            wrapper.remove();
        }

        // 4. Force native controls + download
        video.controls = true;
        video.setAttribute('controls', '');
        video.removeAttribute('controlsList');
        video.controlsList = '';
        video.setAttribute('preload', 'auto');
        video.removeAttribute('width');
        video.removeAttribute('height');

        // 5. Force visibility and proper size
        Object.assign(video.style, {
            display: 'block',
            visibility: 'visible',
            opacity: '1',
            width: '100%',
            height: 'auto',
            maxHeight: '80vh',
            position: 'relative',
            zIndex: '9999',
            background: '#000',
            margin: '0 auto'
        });

        // 6. Make sure parent containers don’t collapse
        let parent = video.parentElement;
        for (let i = 0; i < 5 && parent; i++) {
            parent.style.display = 'block';
            parent.style.visibility = 'visible';
            parent.style.height = 'auto';
            parent.style.minHeight = '300px';
            parent.style.overflow = 'visible';
            parent = parent.parentElement;
        }

        // 7. Hide any leftover Fluid Player elements that might still be around
        document.querySelectorAll(
            '.fluid_controls_container, .fluid_control_bar, .fluid_initial_play_button, ' +
            '.fluid_big_play_button, .fluid_context_menu, [class*="fluid_"]'
        ).forEach(el => {
            if (!el.contains(video)) {
                el.style.display = 'none';
                el.remove();
            }
        });

        // 8. Reload so the clean source takes effect
        video.load();

        console.log('[Native Player] Successfully restored native HTML5 player');
    }

    // Run multiple times because the player is created asynchronously
    function start() {
        restoreNativePlayer();
        setTimeout(restoreNativePlayer, 800);
        setTimeout(restoreNativePlayer, 2000);
        setTimeout(restoreNativePlayer, 4000);
    }

    start();

    // Keep watching for late injection
    const obs = new MutationObserver(() => {
        if (document.getElementById('play-video') && !document.getElementById('play-video').dataset.nativeRestored) {
            restoreNativePlayer();
        }
    });
    obs.observe(document.documentElement, { childList: true, subtree: true });
})();