Greasy Fork

Greasy Fork is available in English.

Universal Video Share Button with M3U8 Support

Adds a floating share button that appears when videos are detected. Hold down to copy instead of share. Auto-detects and downloads M3U8 playlists.

当前为 2025-11-06 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Universal Video Share Button with M3U8 Support
// @namespace    http://tampermonkey.net/
// @version      4.6
// @description  Adds a floating share button that appears when videos are detected. Hold down to copy instead of share. Auto-detects and downloads M3U8 playlists.
// @author       Minoa
// @license MIT
// @match        *://*/*
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/m3u8-parser.min.js
// @grant        none
// ==/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();

    // Color scheme
    const COLORS = {
        button: '#55423d',
        buttonHover: '#6b5651',
        icon: '#ffc0ad',
        text: '#fff3ec'
    };

    // 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'];

    // 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 (!url.match(/seg-\d+-.*\.ts/i) && !url.endsWith('.ts')) {
                        checkUrlForVideo(url);
                    }
                    if (url.includes('.m3u8') || url.includes('.m3u')) {
                        detectM3U8(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 (!url.match(/seg-\d+-.*\.ts/i) && !url.endsWith('.ts')) {
                            checkUrlForVideo(url);
                        }
                        if (url.includes('.m3u8') || url.includes('.m3u')) {
                            detectM3U8(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) {
                    checkUrlForVideo(value);
                }
            }
            return originalSetAttribute.call(this, name, value);
        };
    })();

    function checkUrlForVideo(url) {
        try {
            if (url.startsWith('blob:')) return;
            if (url.match(/seg-\d+-.*\.ts/i)) return;
            if (url.endsWith('.ts')) return;
            if (url.includes('.m3u8') || url.includes('.m3u')) 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)) {
                    console.log('[VIDEO DETECTOR] Found video:', fullUrl);
                    allDetectedVideos.set(fullUrl, {
                        url: fullUrl,
                        type: 'video',
                        timestamp: Date.now(),
                        title: 'Video - ' + getFilenameFromUrl(fullUrl)
                    });
                    checkForVideos();
                }
            }
        } catch(e) {}
    }

    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-')) {
                    console.log('[M3U8 FILTER] Filtering out master playlist, found index variant:', url);
                    return true;
                }
            }
        }
        
        return false;
    }

    async function detectM3U8(url) {
        try {
            if (url.startsWith('blob:')) return;
            
            url = new URL(url, location.href).href;
            
            if (detectedM3U8Urls.includes(url)) return;
            detectedM3U8Urls.push(url);

            console.log('[M3U8 DETECTOR] Fetching M3U8:', url);
            const response = await fetch(url);
            const content = await response.text();
            
            const parser = new m3u8Parser.Parser();
            parser.push(content);
            parser.end();
            const manifest = parser.manifest;

            console.log('[M3U8 DETECTOR] Parsed manifest:', manifest);

            let duration = 0;
            if (manifest.segments) {
                for (var s = 0; s < manifest.segments.length; s++) {
                    duration += manifest.segments[s].duration;
                }
                console.log('[M3U8 DETECTOR] Found', manifest.segments.length, 'segments, total duration:', duration);
            } else {
                console.log('[M3U8 DETECTOR] No segments found (might be master playlist)');
            }

            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);
            
            allDetectedVideos.set(url, {
                url: url,
                type: 'm3u8',
                timestamp: Date.now(),
                title: m3u8Data.title,
                m3u8Data: m3u8Data
            });

            checkForVideos();
        } catch(e) {
            console.error("[M3U8 DETECTOR] Parse error:", e);
        }
    }

    function getVideoUrl(videoElement) {
        if (videoElement.tagName === 'VIDEO') {
            if (videoElement.currentSrc && !videoElement.currentSrc.startsWith('blob:')) 
                return videoElement.currentSrc;
            if (videoElement.src && !videoElement.src.startsWith('blob:')) 
                return videoElement.src;
            
            var sources = videoElement.querySelectorAll('source');
            for (var i = 0; i < sources.length; i++) {
                if (sources[i].src && !sources[i].src.startsWith('blob:')) 
                    return sources[i].src;
            }
        }

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

        return null;
    }

    function getUniqueVideos() {
        var videos = [];
        var seenUrls = new Set();
        
        allDetectedVideos.forEach(function(videoData) {
            if (hasM3U8Playlist() && isSegmentOfPlaylist(videoData.url)) {
                return;
            }
            
            if (videoData.type === 'm3u8' && shouldFilterM3U8(videoData.url, videoData.m3u8Data.manifest)) {
                return;
            }
            
            if (!seenUrls.has(videoData.url)) {
                seenUrls.add(videoData.url);
                videos.push(videoData);
            }
        });
        
        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 && !seenUrls.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);
                    }
                }
            }
        }
        
        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 && !seenUrls.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) {}
        }
        
        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';
        container.style.cssText = 'position: fixed; top: 15px; left: 15px; width: 40px; height: 40px; z-index: 999999;';

        var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        svg.setAttribute('width', '40');
        svg.setAttribute('height', '40');
        svg.style.cssText = 'position: absolute; top: 0; left: 0; transform: rotate(-90deg);';
        
        var circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
        circle.setAttribute('cx', '20');
        circle.setAttribute('cy', '20');
        circle.setAttribute('r', '18');
        circle.setAttribute('fill', 'none');
        circle.setAttribute('stroke', '#4ade80');
        circle.setAttribute('stroke-width', '3');
        circle.setAttribute('stroke-dasharray', '113');
        circle.setAttribute('stroke-dashoffset', '113');
        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';
        
        floatingButton.style.cssText = `position: absolute; top: 2px; left: 2px; width: 36px; height: 36px; background: ${COLORS.button}; backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); color: ${COLORS.icon}; border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 50%; font-size: 14px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; user-select: none; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);`;
        
        container.appendChild(floatingButton);
        floatingButton.progressCircle = circle;

        floatingButton.addEventListener('mouseenter', function() {
            this.style.background = COLORS.buttonHover;
            this.style.transform = 'scale(1.05)';
        });

        floatingButton.addEventListener('mouseleave', function() {
            this.style.background = COLORS.button;
            this.style.transform = 'scale(1)';
        });

        floatingButton.addEventListener('mousedown', function(e) {
            e.preventDefault();
            isLongPress = false;
            pressTimer = setTimeout(function() {
                isLongPress = true;
                floatingButton.style.background = 'rgba(74, 222, 128, 0.8)';
                floatingButton.innerHTML = '⎘';
            }, 500);
        });

        floatingButton.addEventListener('mouseup', function(e) {
            e.preventDefault();
            clearTimeout(pressTimer);
            
            var videos = getUniqueVideos();
            floatingButton.style.background = COLORS.button;
            floatingButton.innerHTML = getButtonIcon(videos);
            
            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;
            pressTimer = setTimeout(function() {
                isLongPress = true;
                floatingButton.style.background = 'rgba(74, 222, 128, 0.8)';
                floatingButton.innerHTML = '⎘';
            }, 500);
        });

        floatingButton.addEventListener('touchend', function(e) {
            e.preventDefault();
            clearTimeout(pressTimer);
            
            var videos = getUniqueVideos();
            floatingButton.style.background = COLORS.button;
            floatingButton.innerHTML = getButtonIcon(videos);
            
            if (isLongPress) {
                handleCopy();
            } else {
                handleShare();
            }
        });

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

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

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

    function handleShare() {
        var videos = getUniqueVideos();
        
        console.log('[SHARE HANDLER] Videos found:', videos.length);
        videos.forEach(v => console.log('[SHARE HANDLER] -', v.type, v.url));
        
        if (videos.length === 0) {
            showNotification('No videos found', 'error');
            return;
        }
        
        if (videos.length === 1) {
            shareVideo(videos[0]);
        } else {
            showVideoSelector(videos, 'share');
        }
    }

    function handleCopy() {
        var videos = getUniqueVideos();
        
        console.log('[COPY HANDLER] Videos found:', videos.length);
        
        if (videos.length === 0) {
            showNotification('No videos found', 'error');
            return;
        }
        
        if (videos.length === 1) {
            if (videos[0].type === 'm3u8') {
                console.log('[COPY HANDLER] M3U8 detected, forcing download');
                downloadM3U8(videos[0], true);
            } else {
                copyVideoUrl(videos[0]);
            }
        } else {
            showVideoSelector(videos, 'copy');
        }
    }

    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: 9999999; 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) => (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' : 'VIDEO';
                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: 3px 8px; border-radius: 4px; font-size: 10px; font-weight: 600;`;
                
                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) {
        console.log('[M3U8 DOWNLOAD] Starting download for:', videoData.url);
        console.log('[M3U8 DOWNLOAD] Force download:', forceDownload);
        
        const cachedBlob = downloadedBlobs.get(videoData.url);
        if (cachedBlob && !forceDownload) {
            console.log('[M3U8 DOWNLOAD] Using cached blob');
            showNotification('Using cached video...', 'info');
            const filename = cachedBlob.filename;
            await shareOrDownloadBlob(cachedBlob.blob, filename, videoData, forceDownload);
            return;
        }
        
        showNotification('Starting M3U8 download...', 'info');
        resetProgress();
        
        try {
            const manifest = videoData.m3u8Data.manifest;
            console.log('[M3U8 DOWNLOAD] Manifest:', manifest);
            
            const baseUrl = videoData.url.substring(0, videoData.url.lastIndexOf('/') + 1);
            console.log('[M3U8 DOWNLOAD] Base URL:', baseUrl);
            
            const segments = manifest.segments;
            
            if (!segments || segments.length === 0) {
                const errorMsg = "No segments in playlist! This is a master playlist, not a media playlist.";
                console.error('[M3U8 DOWNLOAD]', errorMsg);
                showNotification(errorMsg, 'error');
                throw new Error(errorMsg);
            }

            console.log('[M3U8 DOWNLOAD] Found', segments.length, 'segments to download');
            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;
                
                console.log(`[M3U8 DOWNLOAD] Downloading segment ${i+1}/${segments.length}:`, segUrl);
                
                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);
                
                console.log(`[M3U8 DOWNLOAD] Progress: ${percent}%, Size: ${(totalSize/1024/1024).toFixed(2)}MB`);
                
                if (percent % 10 === 0 || i === segments.length - 1) {
                    showNotification(`Downloading: ${percent}% (${(totalSize/1024/1024).toFixed(1)}MB)`, 'info');
                }
            }

            console.log('[M3U8 DOWNLOAD] All segments downloaded, merging...');
            showNotification('Merging segments...', 'info');
            
            var merged = new Blob(segmentData, { type: 'video/mp2t' });
            console.log('[M3U8 DOWNLOAD] Merged blob size:', (merged.size/1024/1024).toFixed(2), 'MB');
            
            var urlPath = new URL(videoData.url).pathname;
            var baseName = urlPath.split('/').pop().replace(/\.(m3u8?|ts)$/i, '') || 'video';
            var filename = baseName + '-noenc.ts';
            
            console.log('[M3U8 DOWNLOAD] Filename:', filename);
            
            downloadedBlobs.set(videoData.url, { blob: merged, filename: filename });
            
            showNotification(`Merged! Total: ${(merged.size/1024/1024).toFixed(1)}MB`, 'info');
            
            await shareOrDownloadBlob(merged, filename, videoData, forceDownload);
            
        } catch(e) {
            console.error("[M3U8 DOWNLOAD] Failed:", e);
            showNotification('Download failed: ' + e.message, 'error');
            resetProgress();
        }
    }

    async function shareOrDownloadBlob(blob, filename, videoData, forceDownload = false) {
        console.log('[SHARE/DOWNLOAD] Starting with blob size:', (blob.size/1024/1024).toFixed(2), 'MB');
        console.log('[SHARE/DOWNLOAD] Filename:', filename);
        console.log('[SHARE/DOWNLOAD] Force download:', forceDownload);
        
        const processed = processedVideos.get(videoData.url);
        
        if (processed && !forceDownload) {
            console.log('[SHARE/DOWNLOAD] Already processed, forcing download');
            forceDownload = true;
            showNotification('Re-downloading...', 'info');
        }
        
        if (!forceDownload && navigator.share) {
            console.log('[SHARE/DOWNLOAD] Attempting to share...');
            
            if (navigator.canShare) {
                try {
                    var file = new File([blob], filename, { type: 'video/mp2t' });
                    console.log('[SHARE/DOWNLOAD] Created file:', file.name, file.size, 'bytes');
                    
                    if (navigator.canShare({ files: [file] })) {
                        console.log('[SHARE/DOWNLOAD] Device can share files');
                        showNotification('Opening share dialog...', 'info');
                        
                        var shareStartTime = Date.now();
                        var shareCompleted = false;
                        
                        var shareTimeout = setTimeout(function() {
                            if (!shareCompleted) {
                                console.log("[SHARE/DOWNLOAD] Share dialog still open after 2s");
                            }
                        }, 2000);
                        
                        try {
                            await navigator.share({
                                files: [file],
                                title: filename,
                                text: 'Video from ' + document.title
                            });
                            
                            shareCompleted = true;
                            clearTimeout(shareTimeout);
                            
                            console.log('[SHARE/DOWNLOAD] Share completed successfully');
                            processedVideos.set(videoData.url, { action: 'shared', timestamp: Date.now() });
                            showNotification('✓ Shared successfully!', 'success');
                            resetProgress();
                            return;
                            
                        } catch(e) {
                            shareCompleted = true;
                            clearTimeout(shareTimeout);
                            
                            var timeElapsed = Date.now() - shareStartTime;
                            console.log('[SHARE/DOWNLOAD] Share ended after', timeElapsed, 'ms, error:', e.name, e.message);
                            
                            if (timeElapsed > 2000) {
                                console.log('[SHARE/DOWNLOAD] Assuming share succeeded (dialog was open >2s)');
                                processedVideos.set(videoData.url, { action: 'shared', timestamp: Date.now() });
                                showNotification('✓ Share likely completed', 'success');
                                resetProgress();
                                return;
                            }
                            
                            console.log("[SHARE/DOWNLOAD] Share cancelled/failed quickly, will download instead");
                            showNotification('Share cancelled, downloading...', 'info');
                        }
                    } else {
                        console.log('[SHARE/DOWNLOAD] Device cannot share files');
                        showNotification('Sharing not supported, downloading...', 'info');
                    }
                } catch(e) {
                    console.error("[SHARE/DOWNLOAD] Share API error:", e);
                    showNotification('Share error: ' + e.message, 'error');
                }
            } else {
                console.log('[SHARE/DOWNLOAD] canShare not supported');
            }
        } else {
            console.log('[SHARE/DOWNLOAD] Skipping share (forceDownload or no navigator.share)');
        }
        
        // Download the file
        console.log('[SHARE/DOWNLOAD] Downloading file...');
        showNotification('Downloading ' + filename + '...', 'info');
        
        var blobUrl = URL.createObjectURL(blob);
        console.log('[SHARE/DOWNLOAD] Created blob URL:', blobUrl);
        
        var a = document.createElement('a');
        a.href = blobUrl;
        a.download = filename;
        a.style.display = 'none';
        document.body.appendChild(a);
        
        console.log('[SHARE/DOWNLOAD] Triggering download...');
        a.click();
        
        setTimeout(function() {
            document.body.removeChild(a);
            URL.revokeObjectURL(blobUrl);
            console.log('[SHARE/DOWNLOAD] Cleaned up blob URL');
        }, 100);
        
        processedVideos.set(videoData.url, { action: 'downloaded', timestamp: Date.now() });
        showNotification('✓ Downloaded: ' + filename, 'success');
        resetProgress();
    }

    function shareVideo(videoData) {
        console.log('[SHARE VIDEO] Called with type:', videoData.type, 'URL:', videoData.url);
        
        if (videoData.type === 'm3u8') {
            console.log('[SHARE VIDEO] This is M3U8, calling downloadM3U8');
            const processed = processedVideos.get(videoData.url);
            const forceDownload = processed && (processed.action === 'shared' || processed.action === 'downloaded');
            console.log('[SHARE VIDEO] Force download:', forceDownload);
            downloadM3U8(videoData, forceDownload);
            return;
        }

        console.log('[SHARE VIDEO] Regular video, attempting to share URL');
        const processed = processedVideos.get(videoData.url);
        if (processed && (processed.action === 'shared' || processed.action === 'copied')) {
            downloadVideo(videoData);
            return;
        }

        if (navigator.share) {
            var shareStartTime = Date.now();
            navigator.share({
                title: document.title,
                url: videoData.url
            }).then(function() {
                processedVideos.set(videoData.url, { action: 'shared', timestamp: Date.now() });
                showNotification('Shared!', 'success');
            }).catch(function(error) {
                if (Date.now() - shareStartTime > 2000) {
                    processedVideos.set(videoData.url, { action: 'shared', timestamp: Date.now() });
                    showNotification('Share completed', 'success');
                } else {
                    console.log('Share cancelled:', error);
                    copyVideoUrl(videoData);
                }
            });
        } else {
            copyVideoUrl(videoData);
        }
    }

    function downloadVideo(videoData) {
        showNotification('Opening video...', 'info');
        window.open(videoData.url, '_blank');
        processedVideos.set(videoData.url, { action: 'downloaded', timestamp: Date.now() });
    }

    function copyVideoUrl(videoData) {
        const processed = processedVideos.get(videoData.url);
        if (processed && processed.action === 'copied') {
            downloadVideo(videoData);
            return;
        }
        
        if (navigator.clipboard && navigator.clipboard.writeText) {
            var copyStartTime = Date.now();
            navigator.clipboard.writeText(videoData.url).then(function() {
                processedVideos.set(videoData.url, { action: 'copied', timestamp: Date.now() });
                showNotification('URL copied', 'success');
            }).catch(function(error) {
                if (Date.now() - copyStartTime > 2000) {
                    processedVideos.set(videoData.url, { action: 'copied', timestamp: Date.now() });
                    showNotification('URL copied', 'success');
                } else {
                    fallbackCopy(videoData.url, videoData);
                }
            });
        } else {
            fallbackCopy(videoData.url, videoData);
        }
    }

    function fallbackCopy(url, videoData) {
        try {
            var textArea = document.createElement('textarea');
            textArea.value = url;
            textArea.style.position = 'fixed';
            textArea.style.left = '-999999px';
            document.body.appendChild(textArea);
            textArea.select();
            
            if (document.execCommand('copy')) {
                processedVideos.set(videoData.url, { action: 'copied', timestamp: Date.now() });
                showNotification('URL copied', 'success');
            } else {
                showNotification('Copy failed', 'error');
            }
            
            document.body.removeChild(textArea);
        } catch (err) {
            showNotification('Copy failed', 'error');
        }
    }

    function showNotification(message, type) {
        if (type === undefined) type = 'success';
        
        console.log('[NOTIFICATION]', type.toUpperCase(), ':', 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: 65px; left: 15px; background: ${bgColor}; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); color: ${COLORS.text}; padding: 8px 12px; border-radius: 6px; z-index: 999998; font-weight: 500; font-size: 12px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); max-width: 250px; word-wrap: break-word; transform: translateX(-300px); 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(-300px)';
                setTimeout(function() {
                    notification.remove();
                }, 300);
            }
        }, 3000);
    }

    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() {
        console.log('[INIT] Universal Video Share with M3U8 initialized');
        console.log('[INIT] Open browser console (F12) to see detailed logs');
        setTimeout(checkForVideos, 1000);
        checkInterval = setInterval(checkForVideos, 5000);
    }

    init();

})();