您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
chromium-based only
// ==UserScript== // @name Screen Recorder // @namespace https://greasyfork.org/ja/users/941284-ぐらんぴ // @version 1.1 // @description chromium-based only // @author ぐらんぴ // @match *://*/* // @grant GM_registerMenuCommand // @grant GM_getResourceText // @grant GM_addStyle // @resource alertifyCSS https://cdn.jsdelivr.net/npm/[email protected]/build/css/alertify.min.css // @resource alertifyTheme https://cdn.jsdelivr.net/npm/[email protected]/build/css/themes/default.min.css // @resource alertifyJS https://cdn.jsdelivr.net/npm/[email protected]/build/alertify.min.js // @license MIT // ==/UserScript== GM_addStyle(GM_getResourceText("alertifyCSS")) GM_addStyle(GM_getResourceText("alertifyTheme")) const alertifyScript = GM_getResourceText("alertifyJS") const scriptTag = document.createElement("script") scriptTag.textContent = alertifyScript document.head.appendChild(scriptTag) let recorder, chunks = [], isRecording = false let combinedStream, logInterval, startTime const registeredVideos = new WeakSet() const $C = (tag, props = {}, styles = {}) => { const el = document.createElement(tag) Object.assign(el, props) Object.assign(el.style, styles) return el } const settings = { mimeType: "video/webm;codecs=vp8,opus", // Highest quality: "video/webm;codecs=vp9,opus", videoBitsPerSecond: 2_500_000, // Highest quality: 10_000_000, frameRate: { ideal: 30, max: 60 }, // Highest quality: { ideal: 60, max: 120 } cursor: "always" // motion, never } const isFirefox = navigator.userAgent.toLowerCase().includes("firefox") async function videoRecord(videoEl) { try { const videoStream = isFirefox ? videoEl.mozCaptureStream(settings.frameRate.ideal) : videoEl.captureStream(settings.frameRate.ideal) const audioCtx = new AudioContext() const srcNode = audioCtx.createMediaElementSource(videoEl) const destNode = audioCtx.createMediaStreamDestination() srcNode.connect(destNode) srcNode.connect(audioCtx.destination) let micStream = null try { micStream = await navigator.mediaDevices.getUserMedia({ audio: true }) } catch (e) { console.warn("Mic access failed:", e) } const tracks = [ ...videoStream.getVideoTracks(), ...destNode.stream.getAudioTracks(), ...(micStream ? micStream.getAudioTracks() : []) ] combinedStream = new MediaStream(tracks) startRecorder() alertify.notify("📹 Video recording started", "success", 5) } catch (err) { alertify.alert("Video record failed: " + err.message) console.error("Video record failed:", err) } } async function screenRecord() { try { const constraints = { video: { cursor: settings.cursor, frameRate: settings.frameRate }, audio: isFirefox ? false : true } const displayStream = await navigator.mediaDevices.getDisplayMedia(constraints) let micStream = null try { micStream = await navigator.mediaDevices.getUserMedia({ audio: true }) } catch (e) { console.warn("Mic access failed:", e) } const tracks = [ ...displayStream.getVideoTracks(), ...displayStream.getAudioTracks(), ...(micStream ? micStream.getAudioTracks() : []) ] combinedStream = new MediaStream(tracks) startRecorder() alertify.notify("📺 Screen recording started", "success", 5) } catch (err) { alertify.alert("Screen record failed: " + err.message) console.error("Screen record failed:", err) } } function startRecorder() { recorder = new MediaRecorder(combinedStream, { mimeType: settings.mimeType, videoBitsPerSecond: settings.videoBitsPerSecond }) recorder.ondataavailable = e => chunks.push(e.data) recorder.onstop = saveRecording recorder.start(1000) startTime = Date.now() logInterval = setInterval(() => { const elapsed = ((Date.now() - startTime) / 1000).toFixed(1) const sizeMB = (chunks.reduce((sum, c) => sum + c.size, 0) / (1024 * 1024)).toFixed(2) console.log(`⏱️ ${elapsed}s elapsed, 💾 ${sizeMB} MB recorded`) }, 10000) isRecording = true } function stopCapture() { if (!recorder || recorder.state === "inactive") return recorder.stop() combinedStream.getTracks().forEach(t => t.stop()) clearInterval(logInterval) isRecording = false console.log("⏹️ Recording stopped") } function saveRecording() { const defaultName = `${new Date().toISOString().replace(/[:.]/g, "-")}` const filename = prompt("Enter filename:", defaultName) || defaultName const blob = new Blob(chunks, { type: settings.mimeType }) const url = URL.createObjectURL(blob) const a = document.createElement("a") a.href = url a.download = `${filename}.webm` document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(url) chunks = [] console.log(`💾 Saved: ${filename}.webm`) } // Firefox only: show floating button function createScreenRecButton() { const btn = $C("button", { textContent: "🖥️ Screen REC/SAVE", onclick: () => isRecording ? stopCapture() : screenRecord() }, { position: "fixed", top: "10px", right: "10px", zIndex: "9999", padding: "8px 12px", fontSize: "14px", background: "#444", color: "#fff", border: "none", borderRadius: "4px", cursor: "pointer", boxShadow: "0 2px 6px rgba(0,0,0,0.3)" }) document.body.appendChild(btn) } // Register screen record command or show button if (!isFirefox) { GM_registerMenuCommand("🖥️ Screen REC/SAVE", () => { isRecording ? stopCapture() : screenRecord() }) } else { createScreenRecButton() } // Scan for <video> tags every 5 seconds setInterval(() => { document.querySelectorAll("video").forEach((videoEl, i) => { if (registeredVideos.has(videoEl)) return const label = videoEl.getAttribute("title") || videoEl.getAttribute("aria-label") || `Video ${i + 1}` GM_registerMenuCommand(`🎥 Video REC/SAVE: ${label}`, () => { isRecording ? stopCapture() : videoRecord(videoEl) }) registeredVideos.add(videoEl) console.log(`🆕 Registered video: ${label}`) }) }, 5000)