您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Records live Twitch streams directly from the browser
// ==UserScript== // @name Twitch - Recorder // @namespace https://greasyfork.org/ja/users/941284-ぐらんぴ // @version 2025-08-21 // @description Records live Twitch streams directly from the browser // @author ぐらんぴ // @match https://www.twitch.tv/* // @icon https://www.google.com/s2/favicons?sz=64&domain=twitch.tv // @grant none // @run-at document-start // @license MIT // ==/UserScript== let $s = (el) => document.querySelector(el), $sa = (el) => document.querySelectorAll(el), $c = (el) => document.createElement(el) let recorder, chunks = [], isRecording = false, seconds = 0, timerInterval, log = console.log; const origAddEventListener = EventTarget.prototype.addEventListener; EventTarget.prototype.addEventListener = function(type, listener, options) { if(type === "loadstart"){ const recordWrapper = function(e){ if(location.href == "https://www.twitch.tv/") return; record(); }; origAddEventListener.call(this, type, recordWrapper, options); } return origAddEventListener.call(this, type, listener, options); }; function record(){ let awaitAddon = setInterval(() => { let addon = $s(".player-controls__right-control-group") clearInterval(awaitAddon); let btn = $c('button'); btn.textContent = ` [RECORD]`; btn.className = "GRMP"; btn.style.color = "red"; //$s('html.tw-root--theme-light') btn.style.cursor = "pointer"; btn.addEventListener("click", () => { const video = $s("video"); if(!video){ alert("Video element not found."); return; } if(video.paused || video.readyState < 3){ video.play().catch(err => console.warn("Video play failed:", err)); } if (!isRecording) { try { let stream; let recorderStream; if (navigator.userAgent.indexOf('Firefox') > -1) { // Firefox const audioCtx = new AudioContext(); const sourceNode = audioCtx.createMediaElementSource(video); const destinationNode = audioCtx.createMediaStreamDestination(); sourceNode.connect(audioCtx.destination); // keep audio playback sourceNode.connect(destinationNode); // send to recorder stream = video.mozCaptureStream(); recorderStream = new MediaStream([ ...stream.getVideoTracks(), ...destinationNode.stream.getAudioTracks() ]); } else { // Chrome/Edge recorderStream = video.captureStream(); } if (!recorderStream) { alert("Failed to capture stream"); return; } recorder = new MediaRecorder(recorderStream); chunks = []; recorder.ondataavailable = e => chunks.push(e.data); recorder.onstop = () => { clearInterval(timerInterval); btn.textContent = ` [RECORD]`; const blob = new Blob(chunks, { type: 'video/webm' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; // filename generation logic... const now = new Date(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); try { let name = location.pathname.slice(1); let title = $s('[data-a-target="stream-title"]').textContent; if (location.pathname.startsWith('/videos/')) { let videoId = location.pathname.replace('/videos/', ''); a.download = $s('h1.tw-title').textContent + "_" + title + "_" + videoId + ".webm"; } else { a.download = name + "_" + title + "_" + month + "/" + day + ".webm"; } } catch (e) { a.download = name + "_" + month + "/" + day + ".webm"; } a.click(); }; recorder.start(); isRecording = true; seconds = 0; btn.textContent = formatTime(seconds); timerInterval = setInterval(() => { seconds++; btn.textContent = formatTime(seconds); }, 1000); } catch (e) { alert("Recording failed: " + e); } }else{ recorder.stop(); isRecording = false; clearInterval(timerInterval); btn.textContent = ` [RECORD]`; } }); if(!$s('.GRMP')) addon.appendChild(btn); function formatTime(sec){ const m = String(Math.floor(sec / 60)).padStart(2, '0'); const s = String(sec % 60).padStart(2, '0'); return ` [${m}:${s}]`; } }, 500); }