Universal Video Share Button with M3U8 Support + MP4 Remux

Share button with M3U8 support. Downloads and converts to MP4. (hold 8s for debug console)

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Universal Video Share Button with M3U8 Support + MP4 Remux
// @namespace    http://tampermonkey.net/
// @version      6.7
// @description  Share button with M3U8 support. Downloads and converts to MP4. (hold 8s for debug console)
// @author       Minoa
// @license      MIT
// @match        *://*/*
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/m3u8-parser.min.js
// @require      https://cdn.jsdelivr.net/npm/@warren-bank/[email protected]/dist/umd/ffmpeg.js
// @resource     classWorkerURL  https://cdn.jsdelivr.net/npm/@warren-bank/[email protected]/dist/umd/258.ffmpeg.js
// @resource     coreURL         https://cdn.jsdelivr.net/npm/@ffmpeg/[email protected]/dist/umd/ffmpeg-core.js
// @resource     wasmURL         https://cdn.jsdelivr.net/npm/@ffmpeg/[email protected]/dist/umd/ffmpeg-core.wasm
// @grant        GM_addStyle
// @grant        GM_getResourceURL
// ==/UserScript==

(function() {
    'use strict';

    var floatingButton = null;
    var pressTimer = null;
    var isLongPress = false;
    var checkInterval = null;
    var detectedM3U8s = [];
    var detectedM3U8Urls = [];
    var allDetectedVideos = new Map();
    var processedVideos = new Map();
    var downloadedBlobs = new Map();
    var debugMode = false;
    var debugConsole = null;
    var debugLogs = [];
    var longPressStartTime = 0;
    var ffmpegInstance = null;
    var ffmpegLoaded = false;
    var wasmBinaryCache = null;

    // Detect iOS
    const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
    const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
    const isMobile = isIOS || /Android|webOS|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);

    // Color scheme
    const COLORS = {
        button: 'rgba(85, 66, 61, 0.7)', // Semi-transparent
        buttonHover: 'rgba(107, 86, 81, 0.85)',
        icon: '#ffc0ad',
        text: '#fff3ec'
    };

    // Add critical CSS to ensure button is always on top
    GM_addStyle(`
        #universal-video-share-container {
            position: fixed !important;
            top: 15px !important;
            left: 15px !important;
            width: 50px !important;
            height: 50px !important;
            z-index: 2147483647 !important;
            pointer-events: auto !important;
            isolation: isolate !important;
        }

        #universal-video-share-float {
            position: absolute !important;
            top: 2px !important;
            left: 2px !important;
            width: 46px !important;
            height: 46px !important;
            background: ${COLORS.button} !important;
            backdrop-filter: blur(12px) !important;
            -webkit-backdrop-filter: blur(12px) !important;
            color: ${COLORS.icon} !important;
            border: 2px solid rgba(255, 255, 255, 0.3) !important;
            border-radius: 50% !important;
            font-size: 18px !important;
            cursor: pointer !important;
            display: flex !important;
            align-items: center !important;
            justify-content: center !important;
            transition: all 0.2s ease !important;
            user-select: none !important;
            box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4) !important;
            z-index: 2147483647 !important;
            pointer-events: auto !important;
        }

        #universal-video-share-float:hover {
            background: ${COLORS.buttonHover} !important;
            transform: scale(1.1) !important;
            box-shadow: 0 6px 20px rgba(0, 0, 0, 0.5) !important;
        }

        #progress-circle {
            pointer-events: none !important;
        }

        .universal-video-notification {
            z-index: 2147483646 !important;
        }

        #video-selector-popup {
            z-index: 2147483645 !important;
        }

        #debug-console {
            z-index: 2147483644 !important;
        }
    `);

    // Common video selectors
    var VIDEO_SELECTORS = [
        'video',
        '.video-player video',
        '.player video',
        '#player video',
        '.video-container video',
        '[class*="video"] video',
        '[class*="player"] video',
        'iframe[src*="youtube.com"]',
        'iframe[src*="vimeo.com"]',
        'iframe[src*="dailymotion.com"]',
        'iframe[src*="twitch.tv"]'
    ];

    var VIDEO_EXTENSIONS = ['.mp4', '.webm', '.ogg', '.mov', '.avi', '.wmv', '.flv', '.mkv', '.m4v', '.3gp'];

    // Helper: Sanitize filename
    function sanitizeFilename(filename) {
        return filename
            .replace(/[<>:"\/\\|?*\x00-\x1F]/g, '')
            .replace(/\s+/g, '_')
            .replace(/_{2,}/g, '_')
            .replace(/^\.+/, '')
            .substring(0, 200);
    }

    // Helper: Get filename from page title
    function getFilenameFromPageTitle(extension = 'mp4') {
        const pageTitle = document.title || 'video';
        const sanitized = sanitizeFilename(pageTitle);
        return sanitized + '.' + extension;
    }

    // FFmpeg helpers
    const getWasmBinary = async () => {
        if (wasmBinaryCache) {
            return wasmBinaryCache.slice(0);
        }

        const wasmURL = GM_getResourceURL('wasmURL', false);
        debugLog('[FFMPEG] Loading WASM from: ' + wasmURL.substring(0, 50) + '...');

        let wasmBinary = null;

        if (wasmURL.startsWith('data:')) {
            const index = wasmURL.indexOf(',');
            const base64 = wasmURL.substring(index + 1);
            const binaryString = atob(base64);
            const bytes = new Uint8Array(binaryString.length);
            for (let i = 0; i < binaryString.length; i++) {
                bytes[i] = binaryString.charCodeAt(i);
            }
            wasmBinary = bytes.buffer;
        } else if (wasmURL.startsWith('blob:')) {
            wasmBinary = await fetch(wasmURL).then(res => res.arrayBuffer());
        }

        if (wasmBinary) {
            debugLog('[FFMPEG] WASM loaded: ' + (wasmBinary.byteLength / 1024 / 1024).toFixed(2) + 'MB');
            wasmBinaryCache = wasmBinary.slice(0);
        }

        return wasmBinary;
    };

    const getFFmpegLoadConfig = async () => ({
        classWorkerURL: GM_getResourceURL('classWorkerURL', false),
        coreURL: GM_getResourceURL('coreURL', false),
        wasmBinary: await getWasmBinary(),
        createTrustedTypePolicy: true
    });

    async function initFFmpeg() {
        if (ffmpegLoaded && ffmpegInstance) return ffmpegInstance;

        debugLog('[FFMPEG] Initializing FFmpeg...');
        showNotification('⚙️ Loading FFmpeg...', 'info');

        try {
            ffmpegInstance = new window.FFmpegWASM.FFmpeg();

            ffmpegInstance.on('log', ({ message }) => {
                debugLog('[FFMPEG LOG] ' + message);
            });

            ffmpegInstance.on('progress', ({ progress }) => {
                const percent = Math.round(progress * 100);
                updateProgress(percent);
                debugLog('[FFMPEG] Encoding progress: ' + percent + '%');
            });

            await ffmpegInstance.load(await getFFmpegLoadConfig());
            ffmpegLoaded = true;
            debugLog('[FFMPEG] ✅ FFmpeg ready!');
            showNotification('✅ FFmpeg loaded', 'success');

            return ffmpegInstance;
        } catch(e) {
            debugLog('[ERROR] FFmpeg init failed: ' + e.message);
            showNotification('❌ FFmpeg failed to load', 'error');
            throw e;
        }
    }

    async function convertTStoMP4(tsBlob, baseFilename) {
        debugLog('[CONVERT] Starting TS to MP4 conversion...');
        debugLog('[CONVERT] Input size: ' + (tsBlob.size / 1024 / 1024).toFixed(2) + 'MB');

        try {
            showNotification('⚙️ Loading FFmpeg encoder...', 'info');

            const ffmpeg = await initFFmpeg();

            const inputName = 'input.ts';
            const outputName = baseFilename.endsWith('.mp4') ? baseFilename : baseFilename + '.mp4';

            debugLog('[CONVERT] Writing input file to FFmpeg...');
            showNotification('📝 Preparing conversion...', 'info');

            const inputData = new Uint8Array(await tsBlob.arrayBuffer());
            await ffmpeg.writeFile(inputName, inputData);

            debugLog('[CONVERT] Starting FFmpeg conversion...');
            debugLog('[CONVERT] Command: -i input.ts -c copy -movflags faststart ' + outputName);
            showNotification('🔄 Converting to MP4...', 'info');

            await ffmpeg.exec(['-i', inputName, '-c', 'copy', '-movflags', 'faststart', outputName]);

            debugLog('[CONVERT] Reading output file...');
            const data = await ffmpeg.readFile(outputName, 'binary');

            debugLog('[CONVERT] Cleaning up FFmpeg files...');
            await ffmpeg.deleteFile(inputName);
            await ffmpeg.deleteFile(outputName);

            const mp4Blob = new Blob([data.buffer], { type: 'video/mp4' });
            debugLog('[CONVERT] ✅ Conversion complete!');
            debugLog('[CONVERT] Output size: ' + (mp4Blob.size / 1024 / 1024).toFixed(2) + 'MB');

            showNotification('✅ Converted to MP4!', 'success');

            return { blob: mp4Blob, filename: outputName };

        } catch(e) {
            debugLog('[ERROR] Conversion failed: ' + e.message);
            debugLog('[ERROR] Stack: ' + e.stack);
            showNotification('❌ Conversion failed', 'error');
            throw e;
        }
    }

    // Helper function to check if URL is M3U8
    function isM3U8Url(url) {
        if (!url) return false;
        const lowerUrl = url.toLowerCase();
        return lowerUrl.includes('.m3u8') || lowerUrl.includes('.m3u');
    }

    // Helper function to check if URL is blob
    function isBlobUrl(url) {
        if (!url) return false;
        return url.startsWith('blob:');
    }

    // Debug console functions
    function createDebugConsole() {
        if (debugConsole) return;

        debugConsole = document.createElement('div');
        debugConsole.id = 'debug-console';
        debugConsole.style.cssText = `
            position: fixed;
            top: 70px;
            left: 10px;
            right: 10px;
            bottom: 10px;
            background: rgba(0, 0, 0, 0.95);
            border: 2px solid ${COLORS.icon};
            border-radius: 12px;
            z-index: 2147483644;
            display: flex;
            flex-direction: column;
            font-family: monospace;
            font-size: 11px;
            color: ${COLORS.text};
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.8);
        `;

        var header = document.createElement('div');
        header.style.cssText = `
            padding: 12px;
            background: ${COLORS.button};
            border-bottom: 1px solid ${COLORS.icon};
            display: flex;
            justify-content: space-between;
            align-items: center;
            border-radius: 10px 10px 0 0;
        `;
        header.innerHTML = `<strong style="color: ${COLORS.icon};">🐛 DEBUG CONSOLE</strong>`;

        var buttonContainer = document.createElement('div');
        buttonContainer.style.cssText = 'display: flex; gap: 8px;';

        var copyBtn = document.createElement('button');
        copyBtn.textContent = '📋 Copy';
        copyBtn.style.cssText = `
            background: ${COLORS.icon};
            color: #000;
            border: none;
            padding: 6px 12px;
            border-radius: 6px;
            font-weight: bold;
            cursor: pointer;
            font-size: 11px;
        `;
        copyBtn.onclick = function() {
            var logText = debugLogs.join('\n');
            if (navigator.clipboard && navigator.clipboard.writeText) {
                navigator.clipboard.writeText(logText).then(function() {
                    copyBtn.textContent = '✅ Copied!';
                    setTimeout(function() {
                        copyBtn.textContent = '📋 Copy';
                    }, 2000);
                });
            } else {
                var textArea = document.createElement('textarea');
                textArea.value = logText;
                textArea.style.position = 'fixed';
                textArea.style.left = '-999999px';
                document.body.appendChild(textArea);
                textArea.select();
                document.execCommand('copy');
                document.body.removeChild(textArea);
                copyBtn.textContent = '✅ Copied!';
                setTimeout(function() {
                    copyBtn.textContent = '📋 Copy';
                }, 2000);
            }
        };

        var clearBtn = document.createElement('button');
        clearBtn.textContent = '🗑️ Clear';
        clearBtn.style.cssText = `
            background: rgba(239, 68, 68, 0.8);
            color: ${COLORS.text};
            border: none;
            padding: 6px 12px;
            border-radius: 6px;
            font-weight: bold;
            cursor: pointer;
            font-size: 11px;
        `;
        clearBtn.onclick = function() {
            debugLogs = [];
            updateDebugConsole();
        };

        var closeBtn = document.createElement('button');
        closeBtn.textContent = '✕';
        closeBtn.style.cssText = `
            background: rgba(255, 255, 255, 0.2);
            color: ${COLORS.text};
            border: none;
            padding: 6px 10px;
            border-radius: 6px;
            font-weight: bold;
            cursor: pointer;
            font-size: 11px;
        `;
        closeBtn.onclick = function() {
            debugMode = false;
            debugConsole.remove();
            debugConsole = null;
        };

        buttonContainer.appendChild(copyBtn);
        buttonContainer.appendChild(clearBtn);
        buttonContainer.appendChild(closeBtn);
        header.appendChild(buttonContainer);

        var logContainer = document.createElement('div');
        logContainer.id = 'debug-log-container';
        logContainer.style.cssText = `
            flex: 1;
            overflow-y: auto;
            padding: 12px;
            line-height: 1.5;
        `;

        debugConsole.appendChild(header);
        debugConsole.appendChild(logContainer);
        document.body.appendChild(debugConsole);

        updateDebugConsole();
    }

    function updateDebugConsole() {
        if (!debugConsole) return;
        var logContainer = document.getElementById('debug-log-container');
        if (!logContainer) return;

        logContainer.innerHTML = '';

        debugLogs.forEach(function(log) {
            var logLine = document.createElement('div');
            logLine.style.cssText = 'margin-bottom: 4px; word-wrap: break-word;';

            var color = COLORS.text;
            if (log.includes('[ERROR]') || log.includes('❌')) {
                color = '#ef4444';
            } else if (log.includes('[SUCCESS]') || log.includes('✅')) {
                color = '#4ade80';
            } else if (log.includes('[INFO]') || log.includes('📥') || log.includes('🔄')) {
                color = '#3b82f6';
            } else if (log.includes('[M3U8]') || log.includes('[FFMPEG]')) {
                color = COLORS.icon;
            }

            logLine.style.color = color;
            logLine.textContent = log;
            logContainer.appendChild(logLine);
        });

        logContainer.scrollTop = logContainer.scrollHeight;
    }

    function debugLog(message) {
        var timestamp = new Date().toLocaleTimeString();
        var logMessage = `[${timestamp}] ${message}`;
        debugLogs.push(logMessage);
        console.log(message);

        if (debugMode && debugConsole) {
            updateDebugConsole();
        }
    }

    // M3U8 Detection
    (function setupNetworkDetection() {
        const originalFetch = window.fetch;
        window.fetch = function(...args) {
            const promise = originalFetch.apply(this, args);
            const url = typeof args[0] === 'string' ? args[0] : args[0]?.url;

            promise.then(response => {
                if (url) {
                    if (isM3U8Url(url)) {
                        detectM3U8(url);
                    } else if (!url.match(/seg-\d+-.*\.ts/i) && !url.endsWith('.ts') && !isBlobUrl(url)) {
                        checkUrlForVideo(url);
                    }
                }
                return response;
            }).catch(e => {
                throw e;
            });
            return promise;
        };

        const originalOpen = XMLHttpRequest.prototype.open;
        XMLHttpRequest.prototype.open = function(...args) {
            this.addEventListener("load", function() {
                try {
                    const url = args[1];
                    if (url) {
                        if (isM3U8Url(url)) {
                            detectM3U8(url);
                        } else if (!url.match(/seg-\d+-.*\.ts/i) && !url.endsWith('.ts') && !isBlobUrl(url)) {
                            checkUrlForVideo(url);
                        }
                        if (this.responseText && this.responseText.trim().startsWith("#EXTM3U")) {
                            detectM3U8(url);
                        }
                    }
                } catch(e) {}
            });
            return originalOpen.apply(this, args);
        };

        const originalText = Response.prototype.text;
        Response.prototype.text = function() {
            return originalText.call(this).then(text => {
                if (text.trim().startsWith("#EXTM3U")) {
                    detectM3U8(this.url);
                }
                return text;
            });
        };

        const originalSetAttribute = Element.prototype.setAttribute;
        Element.prototype.setAttribute = function(name, value) {
            if (this.tagName === 'VIDEO' || this.tagName === 'SOURCE') {
                if (name === 'src' && value) {
                    if (isM3U8Url(value)) {
                        detectM3U8(value);
                    } else if (!isBlobUrl(value)) {
                        checkUrlForVideo(value);
                    }
                }
            }
            return originalSetAttribute.call(this, name, value);
        };
    })();

    function checkUrlForVideo(url) {
        try {
            if (isBlobUrl(url)) {
                debugLog('[VIDEO CHECK] Ignoring blob URL: ' + url);
                return;
            }
            if (url.match(/seg-\d+-.*\.ts/i)) return;
            if (url.endsWith('.ts')) return;
            if (isM3U8Url(url)) {
                debugLog('[VIDEO CHECK] URL contains m3u8, skipping: ' + url);
                return;
            }

            const lowerUrl = url.toLowerCase();
            const isVideo = VIDEO_EXTENSIONS.some(ext => lowerUrl.includes(ext));

            if (isVideo) {
                const fullUrl = new URL(url, location.href).href;
                if (!allDetectedVideos.has(fullUrl)) {
                    debugLog('[VIDEO] Found video file: ' + fullUrl);
                    allDetectedVideos.set(fullUrl, {
                        url: fullUrl,
                        type: 'video',
                        timestamp: Date.now(),
                        title: 'Video - ' + getFilenameFromUrl(fullUrl)
                    });
                    checkForVideos();
                }
            }
        } catch(e) {
            debugLog('[ERROR] checkUrlForVideo: ' + e.message);
        }
    }

    function getFilenameFromUrl(url) {
        try {
            const pathname = new URL(url).pathname;
            const filename = pathname.split('/').pop();
            return filename || 'Unknown';
        } catch(e) {
            return 'Unknown';
        }
    }

    function getTimeAgo(timestamp) {
        const seconds = Math.floor((Date.now() - timestamp) / 1000);
        if (seconds < 60) return 'just now';
        const minutes = Math.floor(seconds / 60);
        if (minutes < 60) return minutes + 'm ago';
        const hours = Math.floor(minutes / 60);
        if (hours < 24) return hours + 'h ago';
        const days = Math.floor(hours / 24);
        return days + 'd ago';
    }

    function getBaseUrlFromSegment(segmentUrl) {
        try {
            const url = new URL(segmentUrl);
            const path = url.pathname.substring(0, url.pathname.lastIndexOf('/') + 1);
            return url.origin + path;
        } catch(e) {
            return null;
        }
    }

    function isSegmentOfPlaylist(videoUrl) {
        if (!videoUrl.endsWith('.ts')) return false;
        if (!videoUrl.match(/seg-\d+-/i)) return false;

        const baseUrl = getBaseUrlFromSegment(videoUrl);
        if (!baseUrl) return false;

        for (const [m3u8Url, data] of allDetectedVideos.entries()) {
            if (data.type === 'm3u8') {
                const m3u8Base = getBaseUrlFromSegment(m3u8Url);
                if (m3u8Base && baseUrl.startsWith(m3u8Base)) {
                    return true;
                }
            }
        }

        return false;
    }

    function hasM3U8Playlist() {
        return Array.from(allDetectedVideos.values()).some(v => v.type === 'm3u8');
    }

    function isMasterPlaylist(url, manifest) {
        if (url.includes('master.m3u8')) return true;
        if (manifest.playlists && manifest.playlists.length > 0 && (!manifest.segments || manifest.segments.length === 0)) {
            return true;
        }
        return false;
    }

    function shouldFilterM3U8(url, manifest) {
        if (!isMasterPlaylist(url, manifest)) return false;

        const baseUrl = getBaseUrlFromSegment(url);
        if (!baseUrl) return false;

        for (const [otherUrl, data] of allDetectedVideos.entries()) {
            if (data.type === 'm3u8' && otherUrl !== url) {
                const otherBase = getBaseUrlFromSegment(otherUrl);
                if (otherBase === baseUrl && otherUrl.includes('index-')) {
                    debugLog('[M3U8] Filtering master playlist: ' + url);
                    return true;
                }
            }
        }

        return false;
    }

    async function detectM3U8(url) {
        try {
            if (isBlobUrl(url)) {
                debugLog('[M3U8] Ignoring blob URL: ' + url);
                return;
            }

            url = new URL(url, location.href).href;

            const urlWithoutQuery = url.split('?')[0];

            let alreadyHasBetter = false;
            for (const [existingUrl, existingData] of allDetectedVideos.entries()) {
                if (existingData.type === 'm3u8') {
                    const existingWithoutQuery = existingUrl.split('?')[0];
                    if (existingWithoutQuery === urlWithoutQuery) {
                        if (existingData.m3u8Data && existingData.m3u8Data.manifest &&
                            existingData.m3u8Data.manifest.segments &&
                            existingData.m3u8Data.manifest.segments.length > 0) {
                            debugLog('[M3U8] Already have better version with segments: ' + existingUrl);
                            alreadyHasBetter = true;
                            break;
                        }
                    }
                }
            }

            if (alreadyHasBetter) return;

            if (detectedM3U8Urls.includes(url)) {
                debugLog('[M3U8] Already detected: ' + url);
                return;
            }
            detectedM3U8Urls.push(url);

            debugLog('[M3U8] *** DETECTED M3U8 URL ***: ' + url);
            debugLog('[M3U8] Fetching manifest...');

            const response = await fetch(url);
            const content = await response.text();

            const parser = new m3u8Parser.Parser();
            parser.push(content);
            parser.end();
            const manifest = parser.manifest;

            let duration = 0;

            if (manifest.playlists && manifest.playlists.length > 0 && (!manifest.segments || manifest.segments.length === 0)) {
                debugLog('[M3U8] This is a MASTER playlist with ' + manifest.playlists.length + ' variants');

                const bestVariant = manifest.playlists[manifest.playlists.length - 1];
                debugLog('[M3U8] Selected variant: ' + JSON.stringify(bestVariant));

                const baseUrl = url.substring(0, url.lastIndexOf('/') + 1);
                const variantUrl = bestVariant.uri.startsWith('http')
                    ? bestVariant.uri
                    : baseUrl + bestVariant.uri;

                debugLog('[M3U8] Fetching variant playlist: ' + variantUrl);

                const masterIndex = detectedM3U8Urls.indexOf(url);
                if (masterIndex > -1) {
                    detectedM3U8Urls.splice(masterIndex, 1);
                }

                for (const [existingUrl, existingData] of allDetectedVideos.entries()) {
                    if (existingData.type === 'm3u8') {
                        const existingWithoutQuery = existingUrl.split('?')[0];
                        if (existingWithoutQuery === urlWithoutQuery) {
                            debugLog('[M3U8] Removing existing master playlist: ' + existingUrl);
                            allDetectedVideos.delete(existingUrl);
                        }
                    }
                }

                detectM3U8(variantUrl);
                return;
            }

            if (manifest.segments) {
                for (var s = 0; s < manifest.segments.length; s++) {
                    duration += manifest.segments[s].duration;
                }
                debugLog('[M3U8] Found ' + manifest.segments.length + ' segments, duration: ' + duration + 's');
            } else {
                debugLog('[M3U8] No segments found!');
            }

            const m3u8Data = {
                url: url,
                manifest: manifest,
                content: content,
                duration: duration,
                title: 'M3U8 - ' + (duration ? Math.ceil(duration / 60) + 'min' : 'Unknown'),
                timestamp: Date.now()
            };

            detectedM3U8s.push(m3u8Data);

            for (const [existingUrl, existingData] of allDetectedVideos.entries()) {
                if (existingData.type === 'm3u8') {
                    const existingWithoutQuery = existingUrl.split('?')[0];
                    if (existingWithoutQuery === urlWithoutQuery) {
                        if ((!existingData.m3u8Data.manifest.segments || existingData.m3u8Data.manifest.segments.length === 0) &&
                            manifest.segments && manifest.segments.length > 0) {
                            debugLog('[M3U8] Replacing master with media playlist: ' + existingUrl + ' -> ' + url);
                            allDetectedVideos.delete(existingUrl);
                        }
                    }
                }
            }

            debugLog('[M3U8] *** ADDING TO MAP AS TYPE m3u8 ***');
            allDetectedVideos.set(url, {
                url: url,
                type: 'm3u8',
                timestamp: Date.now(),
                title: m3u8Data.title,
                m3u8Data: m3u8Data
            });

            checkForVideos();
        } catch(e) {
            debugLog('[ERROR] M3U8 parse failed: ' + e.message);
        }
    }

    function getVideoUrl(videoElement) {
        if (videoElement.tagName === 'VIDEO') {
            if (videoElement.currentSrc && !isBlobUrl(videoElement.currentSrc))
                return videoElement.currentSrc;
            if (videoElement.src && !isBlobUrl(videoElement.src))
                return videoElement.src;

            var sources = videoElement.querySelectorAll('source');
            for (var i = 0; i < sources.length; i++) {
                if (sources[i].src && !isBlobUrl(sources[i].src))
                    return sources[i].src;
            }
        }

        if (videoElement.tagName === 'IFRAME') {
            return videoElement.src;
        }

        return null;
    }

    function getUniqueVideos() {
        var videos = [];
        var seenUrls = new Set();

        debugLog('[GET VIDEOS] Checking allDetectedVideos map...');
        debugLog('[GET VIDEOS] Map size: ' + allDetectedVideos.size);

        var m3u8Videos = [];
        var regularVideos = [];

        allDetectedVideos.forEach(function(videoData, url) {
            debugLog('[GET VIDEOS] Map entry: ' + videoData.type + ' - ' + url);

            if (isBlobUrl(videoData.url)) {
                debugLog('[GET VIDEOS] Skipping blob URL: ' + url);
                return;
            }

            if (hasM3U8Playlist() && isSegmentOfPlaylist(videoData.url)) {
                debugLog('[GET VIDEOS] Skipping segment: ' + url);
                return;
            }

            if (videoData.type === 'm3u8' && shouldFilterM3U8(videoData.url, videoData.m3u8Data.manifest)) {
                debugLog('[GET VIDEOS] Filtering master playlist: ' + url);
                return;
            }

            if (!seenUrls.has(videoData.url)) {
                seenUrls.add(videoData.url);
                
                if (videoData.type === 'm3u8') {
                    m3u8Videos.push(videoData);
                    debugLog('[GET VIDEOS] Added M3U8: ' + url);
                } else {
                    regularVideos.push(videoData);
                    debugLog('[GET VIDEOS] Added regular video: ' + url);
                }
            }
        });

        videos = m3u8Videos.concat(regularVideos);

        debugLog('[GET VIDEOS] Checking video elements on page...');

        for (var s = 0; s < VIDEO_SELECTORS.length; s++) {
            var elements = document.querySelectorAll(VIDEO_SELECTORS[s]);

            for (var i = 0; i < elements.length; i++) {
                var element = elements[i];
                var rect = element.getBoundingClientRect();

                if (rect.width > 100 && rect.height > 100) {
                    var url = getVideoUrl(element);

                    if (url && isBlobUrl(url)) {
                        debugLog('[GET VIDEOS] Skipping blob from element: ' + url);
                        continue;
                    }

                    if (url && isM3U8Url(url)) {
                        debugLog('[GET VIDEOS] Found M3U8 in video element, triggering detection: ' + url);
                        detectM3U8(url);
                        continue;
                    }

                    if (url && !seenUrls.has(url) && !allDetectedVideos.has(url)) {
                        seenUrls.add(url);
                        const videoData = {
                            type: 'video',
                            element: element,
                            url: url,
                            title: element.title || element.alt || ('Video ' + (videos.length + 1)),
                            timestamp: Date.now()
                        };
                        videos.push(videoData);
                        allDetectedVideos.set(url, videoData);
                        debugLog('[GET VIDEOS] Added from element: video - ' + url);
                    }
                }
            }
        }

        var iframes = document.querySelectorAll('iframe');
        for (var i = 0; i < iframes.length; i++) {
            try {
                var iframeDoc = iframes[i].contentDocument || iframes[i].contentWindow.document;
                if (iframeDoc) {
                    var iframeVideos = iframeDoc.querySelectorAll('video');
                    for (var v = 0; v < iframeVideos.length; v++) {
                        var url = getVideoUrl(iframeVideos[v]);

                        if (url && isBlobUrl(url)) continue;

                        if (url && isM3U8Url(url)) {
                            debugLog('[GET VIDEOS] Found M3U8 in iframe, triggering detection: ' + url);
                            detectM3U8(url);
                            continue;
                        }

                        if (url && !seenUrls.has(url) && !allDetectedVideos.has(url)) {
                            seenUrls.add(url);
                            const videoData = {
                                type: 'video',
                                element: iframeVideos[v],
                                url: url,
                                title: 'Iframe Video ' + (videos.length + 1),
                                timestamp: Date.now()
                            };
                            videos.push(videoData);
                            allDetectedVideos.set(url, videoData);
                        }
                    }
                }
            } catch(e) {}
        }

        debugLog('[GET VIDEOS] Final video count: ' + videos.length);
        debugLog('[GET VIDEOS] M3U8s: ' + m3u8Videos.length + ', Regular: ' + (videos.length - m3u8Videos.length));

        return videos;
    }

    function getButtonIcon(videos) {
        if (videos.length > 1) return '⇓';
        if (videos.length === 1) {
            return videos[0].type === 'm3u8' ? '⇣' : '↯';
        }
        return '▶';
    }

    function createFloatingButton() {
        if (floatingButton) return floatingButton;

        var container = document.createElement('div');
        container.id = 'universal-video-share-container';

        var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        svg.setAttribute('width', '50');
        svg.setAttribute('height', '50');
        svg.style.cssText = 'position: absolute; top: 0; left: 0; transform: rotate(-90deg); pointer-events: none;';

        var circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
        circle.setAttribute('cx', '25');
        circle.setAttribute('cy', '25');
        circle.setAttribute('r', '22');
        circle.setAttribute('fill', 'none');
        circle.setAttribute('stroke', '#4ade80');
        circle.setAttribute('stroke-width', '3');
        circle.setAttribute('stroke-dasharray', '138');
        circle.setAttribute('stroke-dashoffset', '138');
        circle.setAttribute('stroke-linecap', 'round');
        circle.style.cssText = 'transition: stroke-dashoffset 0.3s ease;';
        circle.id = 'progress-circle';

        svg.appendChild(circle);
        container.appendChild(svg);

        floatingButton = document.createElement('div');
        floatingButton.innerHTML = '▶';
        floatingButton.id = 'universal-video-share-float';

        container.appendChild(floatingButton);
        floatingButton.progressCircle = circle;

        floatingButton.addEventListener('mousedown', function(e) {
            e.preventDefault();
            isLongPress = false;
            longPressStartTime = Date.now();
            
            pressTimer = setTimeout(function() {
                isLongPress = true;
                var duration = Date.now() - longPressStartTime;
                
                if (duration >= 8000) {
                    debugMode = true;
                    floatingButton.style.background = 'rgba(239, 68, 68, 0.8)';
                    floatingButton.innerHTML = '🐛';
                    debugLog('='.repeat(50));
                    debugLog('[INIT] DEBUG MODE ACTIVATED');
                    debugLog('='.repeat(50));
                } else {
                    floatingButton.style.background = 'rgba(74, 222, 128, 0.85)';
                    floatingButton.innerHTML = '⎘';
                }
            }, 500);
        });

        floatingButton.addEventListener('mouseup', function(e) {
            e.preventDefault();
            clearTimeout(pressTimer);

            var pressDuration = Date.now() - longPressStartTime;

            var videos = getUniqueVideos();
            floatingButton.style.background = COLORS.button;
            floatingButton.innerHTML = getButtonIcon(videos);

            if (pressDuration >= 8000) {
                debugMode = true;
                createDebugConsole();
            } else if (isLongPress) {
                handleCopy();
            } else {
                handleShare();
            }
        });

        floatingButton.addEventListener('mouseleave', function() {
            clearTimeout(pressTimer);
            var videos = getUniqueVideos();
            floatingButton.style.background = COLORS.button;
            floatingButton.innerHTML = getButtonIcon(videos);
        });

        floatingButton.addEventListener('touchstart', function(e) {
            e.preventDefault();
            isLongPress = false;
            longPressStartTime = Date.now();
            
            pressTimer = setTimeout(function() {
                isLongPress = true;
                var duration = Date.now() - longPressStartTime;
                
                if (duration >= 8000) {
                    debugMode = true;
                    floatingButton.style.background = 'rgba(239, 68, 68, 0.8)';
                    floatingButton.innerHTML = '🐛';
                    navigator.vibrate && navigator.vibrate(200);
                    debugLog('='.repeat(50));
                    debugLog('[INIT] DEBUG MODE ACTIVATED');
                    debugLog('='.repeat(50));
                } else {
                    floatingButton.style.background = 'rgba(74, 222, 128, 0.85)';
                    floatingButton.innerHTML = '⎘';
                    navigator.vibrate && navigator.vibrate(100);
                }
            }, 500);
        });

        floatingButton.addEventListener('touchend', function(e) {
            e.preventDefault();
            clearTimeout(pressTimer);

            var pressDuration = Date.now() - longPressStartTime;

            var videos = getUniqueVideos();
            floatingButton.style.background = COLORS.button;
            floatingButton.innerHTML = getButtonIcon(videos);

            if (pressDuration >= 8000) {
                debugMode = true;
                createDebugConsole();
            } else if (isLongPress) {
                handleCopy();
            } else {
                handleShare();
            }
        });

        document.body.appendChild(container);
        return floatingButton;
    }

    function updateProgress(percent) {
        if (!floatingButton || !floatingButton.progressCircle) return;
        var offset = 138 - (138 * percent / 100);
        floatingButton.progressCircle.setAttribute('stroke-dashoffset', offset);
    }

    function resetProgress() {
        if (!floatingButton || !floatingButton.progressCircle) return;
        floatingButton.progressCircle.setAttribute('stroke-dashoffset', '138');
    }

    function handleShare() {
        debugLog('[SHARE] Button clicked');
        showNotification('🔍 Checking videos...', 'info');

        var videos = getUniqueVideos();

        debugLog('[SHARE] Videos found: ' + videos.length);
        videos.forEach(v => debugLog('[SHARE] - ' + v.type + ': ' + v.url));

        if (videos.length === 0) {
            showNotification('❌ No videos found', 'error');
            debugLog('[ERROR] No videos found');
            return;
        }

        if (videos.length > 1) {
            debugLog('[SHARE] Multiple videos/playlists, showing selector');
            showVideoSelector(videos, 'share');
        } else {
            debugLog('[SHARE] Single video, calling shareVideo');
            shareVideo(videos[0]);
        }
    }

    function handleCopy() {
        debugLog('[COPY] Long press detected');
        showNotification('📋 Long press detected...', 'info');

        var videos = getUniqueVideos();

        debugLog('[COPY] Videos found: ' + videos.length);

        if (videos.length === 0) {
            showNotification('❌ No videos found', 'error');
            debugLog('[ERROR] No videos found');
            return;
        }

        if (videos.length > 1) {
            showVideoSelector(videos, 'copy');
        } else {
            if (videos[0].type === 'm3u8') {
                debugLog('[COPY] M3U8 detected, forcing download');
                downloadM3U8(videos[0], true);
            } else {
                copyVideoUrl(videos[0]);
            }
        }
    }

    function showVideoSelector(videos, action) {
        var existingSelector = document.querySelector('#video-selector-popup');
        if (existingSelector) {
            existingSelector.remove();
        }

        var popup = document.createElement('div');
        popup.id = 'video-selector-popup';
        popup.style.cssText = 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.7); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); z-index: 2147483645; display: flex; align-items: center; justify-content: center; padding: 20px; box-sizing: border-box;';

        var container = document.createElement('div');
        container.style.cssText = 'background: rgba(20, 20, 20, 0.95); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 12px; padding: 24px; max-width: 600px; max-height: 70%; overflow-y: auto; position: relative; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);';

        var closeButton = document.createElement('button');
        closeButton.innerHTML = '✕';
        closeButton.style.cssText = `position: absolute; top: 12px; right: 12px; background: rgba(255, 255, 255, 0.1); border: none; font-size: 16px; cursor: pointer; color: ${COLORS.text}; width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; transition: background 0.2s;`;

        closeButton.addEventListener('click', function() {
            popup.remove();
        });

        closeButton.addEventListener('mouseenter', function() {
            this.style.background = 'rgba(255, 255, 255, 0.2)';
        });

        closeButton.addEventListener('mouseleave', function() {
            this.style.background = 'rgba(255, 255, 255, 0.1)';
        });

        var title = document.createElement('h3');
        title.textContent = 'Select Video to ' + (action.charAt(0).toUpperCase() + action.slice(1));
        title.style.cssText = `margin: 0 0 16px 0; color: ${COLORS.text}; font-size: 16px; font-weight: 600; text-align: center;`;

        container.appendChild(closeButton);
        container.appendChild(title);

        videos.sort((a, b) => {
            if (a.type === 'm3u8' && b.type !== 'm3u8') return -1;
            if (a.type !== 'm3u8' && b.type === 'm3u8') return 1;
            return (b.timestamp || 0) - (a.timestamp || 0);
        });

        for (var i = 0; i < videos.length; i++) {
            var videoData = videos[i];
            var videoItem = document.createElement('div');
            videoItem.style.cssText = 'margin-bottom: 12px; padding: 12px; border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 8px; cursor: pointer; transition: all 0.2s ease; background: rgba(255, 255, 255, 0.05);';

            (function(currentVideoData) {
                videoItem.addEventListener('mouseenter', function() {
                    this.style.borderColor = 'rgba(255, 255, 255, 0.3)';
                    this.style.background = 'rgba(255, 255, 255, 0.1)';
                });

                videoItem.addEventListener('mouseleave', function() {
                    this.style.borderColor = 'rgba(255, 255, 255, 0.1)';
                    this.style.background = 'rgba(255, 255, 255, 0.05)';
                });

                var headerDiv = document.createElement('div');
                headerDiv.style.cssText = 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;';

                var typeBadge = document.createElement('span');
                typeBadge.textContent = currentVideoData.type === 'm3u8' ? 'M3U8 PLAYLIST' : 'VIDEO FILE';
                typeBadge.style.cssText = 'display: inline-block; background: ' + (currentVideoData.type === 'm3u8' ? 'rgba(239, 68, 68, 0.8)' : 'rgba(59, 130, 246, 0.8)') + `; color: ${COLORS.text}; padding: 4px 10px; border-radius: 4px; font-size: 11px; font-weight: 700;`;

                var timeAgo = document.createElement('span');
                timeAgo.textContent = getTimeAgo(currentVideoData.timestamp || Date.now());
                timeAgo.style.cssText = `color: ${COLORS.text}; opacity: 0.6; font-size: 10px;`;

                headerDiv.appendChild(typeBadge);
                headerDiv.appendChild(timeAgo);
                videoItem.appendChild(headerDiv);

                var videoInfo = document.createElement('div');
                videoInfo.innerHTML = `<div style="color: ${COLORS.text}; font-size: 13px; font-weight: 500; margin-bottom: 4px;">` + currentVideoData.title + `</div><div style="color: ${COLORS.text}; opacity: 0.5; font-size: 11px; word-break: break-all; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">` + currentVideoData.url + '</div>';
                videoItem.appendChild(videoInfo);

                videoItem.addEventListener('click', function() {
                    popup.remove();
                    if (action === 'share') {
                        shareVideo(currentVideoData);
                    } else {
                        if (currentVideoData.type === 'm3u8') {
                            downloadM3U8(currentVideoData, true);
                        } else {
                            copyVideoUrl(currentVideoData);
                        }
                    }
                });
            })(videoData);

            container.appendChild(videoItem);
        }

        popup.appendChild(container);

        popup.addEventListener('click', function(e) {
            if (e.target === popup) {
                popup.remove();
            }
        });

        document.body.appendChild(popup);
    }

    async function downloadM3U8(videoData, forceDownload = false) {
        debugLog('='.repeat(50));
        debugLog('[M3U8] STARTING M3U8 DOWNLOAD & CONVERSION');
        debugLog('[M3U8] URL: ' + videoData.url);

        const cachedBlob = downloadedBlobs.get(videoData.url);
        if (cachedBlob) {
            debugLog('[M3U8] Using cached MP4 blob');
            showNotification('♻️ Using cached video...', 'info');
            await shareOrDownloadBlob(cachedBlob.blob, cachedBlob.filename, videoData);
            return;
        }

        showNotification('📥 Starting M3U8 download...', 'info');
        resetProgress();

        try {
            const manifest = videoData.m3u8Data.manifest;
            const baseUrl = videoData.url.substring(0, videoData.url.lastIndexOf('/') + 1);
            const segments = manifest.segments;

            if (!segments || segments.length === 0) {
                throw new Error("No segments in playlist!");
            }

            debugLog('[M3U8] Found ' + segments.length + ' segments');
            showNotification(`📥 Downloading ${segments.length} segments...`, 'info');

            var segmentData = [];
            var totalSize = 0;

            for (var i = 0; i < segments.length; i++) {
                var segUrl = segments[i].uri.startsWith('http')
                    ? segments[i].uri
                    : baseUrl + segments[i].uri;

                var response = await fetch(segUrl);
                if (!response.ok) {
                    throw new Error('Segment ' + (i+1) + ' failed: ' + response.status);
                }

                var data = await response.arrayBuffer();
                totalSize += data.byteLength;
                segmentData.push(data);

                var percent = Math.floor((i + 1) / segments.length * 100);
                updateProgress(percent);

                if (percent % 10 === 0 || i === segments.length - 1) {
                    showNotification(`📥 ${percent}% (${(totalSize/1024/1024).toFixed(1)}MB)`, 'info');
                }
            }

            debugLog('[M3U8] All segments downloaded, merging...');
            showNotification('🔄 Merging segments...', 'info');

            var merged = new Blob(segmentData, { type: 'video/mp2t' });
            debugLog('[M3U8] Merged TS blob size: ' + (merged.size/1024/1024).toFixed(2) + 'MB');

            const filename = getFilenameFromPageTitle('mp4');

            debugLog('[M3U8] Converting TS to MP4 with filename: ' + filename);
            const converted = await convertTStoMP4(merged, filename);

            debugLog('[M3U8] MP4 conversion complete!');
            
            downloadedBlobs.set(videoData.url, { blob: converted.blob, filename: converted.filename });
            debugLog('[M3U8] Cached MP4 blob for future use');

            await shareOrDownloadBlob(converted.blob, converted.filename, videoData);

        } catch(e) {
            debugLog('[ERROR] M3U8 download/conversion failed: ' + e.message);
            showNotification('❌ Failed: ' + e.message, 'error');
            resetProgress();
        }
    }

    async function shareOrDownloadBlob(blob, filename, videoData) {
        debugLog('='.repeat(50));
        debugLog('[SHARE/DL] SHARE OR DOWNLOAD BLOB');
        debugLog('[SHARE/DL] Blob size: ' + (blob.size/1024/1024).toFixed(2) + 'MB');
        debugLog('[SHARE/DL] Filename: ' + filename);

        if (navigator.share) {
            try {
                debugLog('[SHARE/DL] Creating File object with video/mp4 MIME type...');
                var file = new File([blob], filename, { type: 'video/mp4' });
                
                debugLog('[SHARE/DL] File created: ' + file.name + ', size: ' + file.size + ', type: ' + file.type);
                
                var canShareFiles = true;
                if (navigator.canShare) {
                    canShareFiles = navigator.canShare({ files: [file] });
                    debugLog('[SHARE/DL] canShare result: ' + canShareFiles);
                }

                if (canShareFiles) {
                    showNotification('📤 Opening share...', 'info');
                    debugLog('[SHARE/DL] Calling navigator.share...');
                    
                    var shareStartTime = Date.now();

                    try {
                        await navigator.share({
                            files: [file],
                            title: filename,
                            text: 'Video file'
                        });

                        var elapsed = Date.now() - shareStartTime;
                        debugLog('[SHARE/DL] ✅ Share completed successfully (took ' + elapsed + 'ms)');
                        showNotification('✅ Shared!', 'success');
                        resetProgress();
                        return;

                    } catch(shareError) {
                        var elapsed = Date.now() - shareStartTime;
                        debugLog('[SHARE/DL] Share error after ' + elapsed + 'ms: ' + shareError.name + ' - ' + shareError.message);

                        if (elapsed > 2000) {
                            debugLog('[SHARE/DL] Assuming success (dialog was open >2s)');
                            showNotification('✅ Share completed', 'success');
                            resetProgress();
                            return;
                        }

                        debugLog('[SHARE/DL] Share cancelled/failed quickly, falling back to download');
                    }
                } else {
                    debugLog('[SHARE/DL] Cannot share files on this device/browser');
                }
            } catch(e) {
                debugLog('[SHARE/DL] Share setup error: ' + e.message);
            }
        } else {
            debugLog('[SHARE/DL] navigator.share not available');
        }

        debugLog('[SHARE/DL] Initiating download...');
        showNotification('💾 Downloading...', 'info');

        var blobUrl = URL.createObjectURL(blob);
        debugLog('[SHARE/DL] Blob URL created: ' + blobUrl.substring(0, 50) + '...');

        var a = document.createElement('a');
        a.href = blobUrl;
        a.download = filename;
        a.style.display = 'none';
        document.body.appendChild(a);

        debugLog('[SHARE/DL] Triggering download click...');
        a.click();

        setTimeout(function() {
            document.body.removeChild(a);
            URL.revokeObjectURL(blobUrl);
            debugLog('[SHARE/DL] Download cleanup complete');
        }, 1000);

        showNotification('✅ Downloaded: ' + filename, 'success');
        resetProgress();
        debugLog('[SUCCESS] Download complete!');
    }

    function shareVideo(videoData) {
        debugLog('='.repeat(50));
        debugLog('[SHARE] shareVideo() called');
        debugLog('[SHARE] Type: ' + videoData.type);
        debugLog('[SHARE] URL: ' + videoData.url);

        if (videoData.type === 'm3u8') {
            debugLog('[SHARE] *** M3U8 DETECTED - DOWNLOADING AND CONVERTING ***');
            debugLog('[SHARE] Will NOT share playlist URL, will download/convert to MP4');
            downloadM3U8(videoData, false);
            return;
        }

        debugLog('[SHARE] Regular video file detected');
        
        if (navigator.share) {
            debugLog('[SHARE] Attempting to share video URL...');
            var shareStartTime = Date.now();
            
            navigator.share({
                title: document.title,
                url: videoData.url
            }).then(function() {
                debugLog('[SHARE] ✅ URL shared successfully');
                showNotification('✅ Shared!', 'success');
            }).catch(function(error) {
                var elapsed = Date.now() - shareStartTime;
                
                if (elapsed > 2000) {
                    debugLog('[SHARE] Assuming success (elapsed: ' + elapsed + 'ms)');
                    showNotification('✅ Share completed', 'success');
                } else {
                    debugLog('[SHARE] Share cancelled, trying copy: ' + error.message);
                    copyVideoUrl(videoData);
                }
            });
        } else {
            debugLog('[SHARE] navigator.share not available, trying copy');
            copyVideoUrl(videoData);
        }
    }

    function downloadVideo(videoData) {
        showNotification('📂 Opening video...', 'info');
        debugLog('[INFO] Opening video in new tab: ' + videoData.url);
        window.open(videoData.url, '_blank');
    }

    async function copyVideoUrl(videoData) {
        debugLog('[COPY] Attempting to copy URL: ' + videoData.url);
        
        try {
            if (navigator.clipboard && navigator.clipboard.writeText) {
                await navigator.clipboard.writeText(videoData.url);
                debugLog('[COPY] ✅ URL copied via Clipboard API');
                showNotification('✅ URL copied', 'success');
            } else {
                var textArea = document.createElement('textarea');
                textArea.value = videoData.url;
                textArea.style.position = 'fixed';
                textArea.style.left = '-999999px';
                document.body.appendChild(textArea);
                textArea.select();
                var success = document.execCommand('copy');
                document.body.removeChild(textArea);
                
                if (success) {
                    debugLog('[COPY] ✅ URL copied via execCommand');
                    showNotification('✅ URL copied', 'success');
                } else {
                    throw new Error('execCommand failed');
                }
            }
        } catch(e) {
            debugLog('[COPY] ❌ Failed to copy URL: ' + e.message);
            showNotification('❌ Copy failed', 'error');
        }
    }

    function showNotification(message, type) {
        if (type === undefined) type = 'success';

        debugLog('[NOTIF] ' + message);

        var existingNotifications = document.querySelectorAll('.universal-video-notification');
        for (var i = 0; i < existingNotifications.length; i++) {
            existingNotifications[i].remove();
        }

        var notification = document.createElement('div');
        notification.textContent = message;
        notification.className = 'universal-video-notification';

        var bgColor = type === 'success' ? 'rgba(74, 222, 128, 0.9)' :
                      type === 'error' ? 'rgba(239, 68, 68, 0.9)' :
                      'rgba(59, 130, 246, 0.9)';

        notification.style.cssText = `position: fixed; top: 75px; left: 15px; background: ${bgColor}; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); color: ${COLORS.text}; padding: 10px 14px; border-radius: 8px; z-index: 2147483646; font-weight: 600; font-size: 13px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); max-width: 280px; word-wrap: break-word; transform: translateX(-350px); transition: transform 0.3s ease;`;

        document.body.appendChild(notification);

        setTimeout(function() {
            notification.style.transform = 'translateX(0)';
        }, 100);

        setTimeout(function() {
            if (notification.parentNode) {
                notification.style.transform = 'translateX(-350px)';
                setTimeout(function() {
                    notification.remove();
                }, 300);
            }
        }, 4000);
    }

    function checkForVideos() {
        var videos = getUniqueVideos();

        if (videos.length > 0) {
            if (!floatingButton) {
                createFloatingButton();
            }
            if (floatingButton) {
                floatingButton.innerHTML = getButtonIcon(videos);
            }
        } else {
            if (floatingButton && floatingButton.parentNode) {
                floatingButton.parentNode.remove();
                floatingButton = null;
            }
        }
    }

    function init() {
        debugLog('='.repeat(50));
        debugLog('[INIT] Universal Video Share v6.7 - OVERLAY BUTTON');
        debugLog('[INIT] Button always on top with highest z-index');
        debugLog('[INIT] Semi-transparent design');
        debugLog('='.repeat(50));

        setTimeout(checkForVideos, 1000);
        checkInterval = setInterval(checkForVideos, 5000);
    }

    init();

})();