您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Detect streaming videos and open them in external Android apps or copy to clipboard on desktop
// ==UserScript== // @name Stream To Android Player // @namespace https://github.com/faisalbhuiyan // @version 1.2 // @description Detect streaming videos and open them in external Android apps or copy to clipboard on desktop // @author Faisal Bhuiyan // @match *://*/* // @grant none // @license MIT // @run-at document-start // ==/UserScript== (function () { 'use strict'; let streams = []; let originalXHROpen = XMLHttpRequest.prototype.open; let originalFetchOpen = window.fetch; let isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); // Inject CSS const style = document.createElement('style'); style.textContent = ` #video-handler-button { position: fixed; bottom: 20px; right: 20px; width: 48px; height: 48px; background: none; color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; z-index: 999999; font-size: 24px; transition: opacity 0.3s; } #video-handler-button:hover { opacity: 1; } #video-handler-menu { position: fixed; bottom: 80px; right: 20px; background: rgba(0, 0, 0, 0.9); border-radius: 8px; padding: 8px; z-index: 999998; max-height: 300px; overflow-y: auto; color: white; max-width: 300px; width: auto; } .stream-item { padding: 8px 16px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border-bottom: 1px solid rgba(255, 255, 255, 0.1); } .stream-item:last-child { border-bottom: none; } .stream-item:hover { background: rgba(255, 255, 255, 0.1); } .copy-notification { position: fixed; bottom: 120px; right: 20px; background: rgba(0, 0, 0, 0.7); color: white; padding: 8px 16px; border-radius: 4px; z-index: 999999; transition: opacity 0.3s; } `; document.head.appendChild(style); // Clear streams when page unloads window.addEventListener('beforeunload', () => { streams = []; const menu = document.getElementById('video-handler-menu'); const button = document.getElementById('video-handler-button'); if (menu) menu.remove(); if (button) button.remove(); }); function createStreamMenu() { const menu = document.createElement('div'); menu.id = 'video-handler-menu'; menu.style.display = 'none'; document.body.appendChild(menu); return menu; } function shareUrl(url) { if (isMobile && navigator.share) { navigator.share({ url: url }).catch(console.error); } else { // Desktop: Copy to clipboard navigator.clipboard.writeText(url) .then(() => showNotification('Link copied to clipboard!')) .catch(err => { console.error('Failed to copy: ', err); // Fallback method for clipboard const textarea = document.createElement('textarea'); textarea.value = url; textarea.style.position = 'fixed'; textarea.style.opacity = '0'; document.body.appendChild(textarea); textarea.focus(); textarea.select(); try { document.execCommand('copy'); showNotification('Link copied to clipboard!'); } catch (e) { showNotification('Failed to copy link. Please copy it manually.'); console.error('Fallback copy failed:', e); } document.body.removeChild(textarea); }); } } function showNotification(message) { const notification = document.createElement('div'); notification.className = 'copy-notification'; notification.textContent = message; document.body.appendChild(notification); setTimeout(() => { notification.style.opacity = '0'; setTimeout(() => { document.body.removeChild(notification); }, 300); }, 2000); } function updateMenu() { const menu = document.getElementById('video-handler-menu') || createStreamMenu(); menu.innerHTML = streams.map(stream => ` <div class="stream-item" data-url="${stream.url}" title="${stream.name}"> ${stream.name} </div> `).join(''); menu.querySelectorAll('.stream-item').forEach(item => { item.onclick = () => { shareUrl(item.dataset.url); }; }); } function createFloatingButton() { const button = document.createElement('div'); button.id = 'video-handler-button'; button.innerHTML = '▶️'; button.style.display = 'none'; document.body.appendChild(button); button.onclick = () => { const menu = document.getElementById('video-handler-menu'); menu.style.display = menu.style.display === 'none' ? 'block' : 'none'; }; return button; } // Helper functions for stream detection function isStreamUrl(url) { const videoPatterns = [ '.mp4', '.m3u8', '.m3u', 'urlset', '/video/', '/media/', '/stream/', 'manifest', 'playlist', 'master.json' ]; return videoPatterns.some(pattern => url.toLowerCase().includes(pattern)); } function isStreamContent(contentType) { const streamTypes = [ 'video/', 'application/x-mpegurl', 'application/vnd.apple.mpegurl', 'application/octet-stream' ]; return streamTypes.some(type => contentType?.toLowerCase().includes(type)); } // Enhanced stream detection function function handleStreamDetection(url, contentType, content) { // Skip if already detected if (streams.some(s => s.url === url)) return; const fileName = url.split('/').pop().split('?')[0]; let shouldAdd = false; let type = 'video'; // Check URL patterns if (isStreamUrl(url)) { shouldAdd = true; if (url.includes('.m3u8')) type = 'm3u8'; } // Check content type if (isStreamContent(contentType)) { shouldAdd = true; if (contentType?.includes('mpegurl')) type = 'm3u8'; } // Check for M3U8 content if (content?.trim().startsWith('#EXTM3U')) { shouldAdd = true; type = 'm3u8'; } if (shouldAdd) { streams.push({ url, name: fileName, type }); const button = document.getElementById('video-handler-button') || createFloatingButton(); button.style.display = 'block'; updateMenu(); } } // Extract video sources from the page function extractVideoSources() { // Check video elements document.querySelectorAll('video').forEach(video => { if (video.src) { handleStreamDetection(video.src); } // Check source elements within video video.querySelectorAll('source').forEach(source => { if (source.src) { handleStreamDetection(source.src, source.type); } }); }); // Check data-source attributes document.querySelectorAll('[data-source]').forEach(el => { const source = el.getAttribute('data-source'); if (source) { handleStreamDetection(source); } }); } // Monitor XHR requests with enhanced detection XMLHttpRequest.prototype.open = function () { this.addEventListener('load', function () { try { const contentType = this.getResponseHeader('content-type'); handleStreamDetection( this.responseURL, contentType, this.responseText ); } catch (e) { handleStreamDetection(this.responseURL); } }); originalXHROpen.apply(this, arguments); }; // Monitor fetch requests with enhanced detection window.fetch = async function () { const url = arguments[0]?.url || arguments[0]; const response = await originalFetchOpen.apply(this, arguments); try { const contentType = response.headers.get('content-type'); const clonedResponse = response.clone(); const content = await clonedResponse.text(); handleStreamDetection(url, contentType, content); } catch (e) { handleStreamDetection(url); } return response; }; // Periodic scanning for dynamically added videos setInterval(extractVideoSources, 5000); // Monitor DOM changes for new video elements const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.nodeName === 'VIDEO' || node.nodeName === 'AUDIO') { handleStreamDetection(node.src); } // Check for video elements within added nodes if (node.querySelectorAll) { extractVideoSources(); } }); }); }); // Start observing once DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { observer.observe(document.documentElement, { childList: true, subtree: true }); extractVideoSources(); }); } else { observer.observe(document.documentElement, { childList: true, subtree: true }); extractVideoSources(); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址