查看: 367|回复: 0

零度HTML版的录屏小工具

[复制链接]

89

主题

8

回帖

31

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
31
发表于 2025-10-2 00:10:40 | 显示全部楼层 |阅读模式
[HTML] 纯文本查看 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>录屏工具</title>
</head>
<body>
    <div>
        <h3>录屏工具</h3>
        <div>
            <label><input type="checkbox" id="sysAudio" checked>系统声音</label>
            <label style="margin-left:10px;"><input type="checkbox" id="micAudio" checked>麦克风</label>
        </div>
        <div id="status">点击开始</div>
        <div id="timer">00:00</div>
        <button id="start">开始录制</button>
        <button id="stop" disabled>停止录制</button>
    </div>
    <script>
        const state = { rec: null, screen: null, stream: null, aCtx: null, writer: null, timerId: null, sec: 0 };
        const el = Object.fromEntries(['start', 'stop', 'sysAudio', 'micAudio', 'status', 'timer'].map(id => [id, document.getElementById(id)]));
        const fmt = sec => `${(sec / 60 | 0).toString().padStart(2, 0)}:${(sec % 60).toString().padStart(2, 0)}`;
        const reset = () => {
            el.start.disabled = el.sysAudio.disabled = el.micAudio.disabled = false;
            el.stop.disabled = true;
            el.timer.textContent = '00:00';
            state.aCtx?.close();
            Object.keys(state).forEach(k => state[k] = null);
            state.sec = 0;
        };
        el.start.addEventListener('click', async () => {
            try {
                el.start.disabled = el.sysAudio.disabled = el.micAudio.disabled = true;
                el.stop.disabled = false;
                el.status.textContent = '选择路径...';
                state.writer = await (await showSaveFilePicker({ suggestedName: '录屏.webm', types: [{ description: 'WebM', accept: { 'video/webm': ['.webm'] } }] })).createWritable();
                el.status.textContent = '准备录制...';
                state.screen = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: el.sysAudio.checked });
                const audioStreams = [
                    ...(el.micAudio.checked ? [await navigator.mediaDevices.getUserMedia({ audio: true })] : []),
                    ...(el.sysAudio.checked && state.screen.getAudioTracks().length ? [new MediaStream(state.screen.getAudioTracks())] : [])
                ];
                state.stream = audioStreams.length 
                    ? (() => {
                        state.aCtx = new AudioContext();
                        const destination = state.aCtx.createMediaStreamDestination();
                        audioStreams.forEach(stream => { state.aCtx.createMediaStreamSource(stream).connect(destination); });
                        return new MediaStream([...state.screen.getVideoTracks(), ...destination.stream.getAudioTracks()]);
                    })()
                    : state.screen;
                state.rec = new MediaRecorder(state.stream, { mimeType: 'video/webm; codecs=vp8,opus' });
                state.rec.ondataavailable = e => { if (e.data.size) { state.writer.write(e.data); } };
                state.rec.onstart = () => {
                    el.status.textContent = '正在录制...';
                    const startTime = performance.now();
                    state.timerId = requestAnimationFrame(function updateTimer() {
                        const currentTime = Math.floor((performance.now() - startTime) / 1000);
                        if (currentTime !== state.sec) { state.sec = currentTime; el.timer.textContent = fmt(state.sec); }
                        state.timerId = requestAnimationFrame(updateTimer);
                    });
                };
                (state.screen.getVideoTracks()[0] || {}).onended = stopRec;
                state.rec.start(5000);
            } catch (err) {
                alert(err.message || '已取消');
                el.status.textContent = '已取消';
                reset();
            }
        });
        const stopRec = async () => {
            try {
                if (state.rec?.state === 'inactive') return;
                state.rec.stop();
                cancelAnimationFrame(state.timerId);
                const duration = fmt(state.sec);
                [state.stream, state.screen].forEach(stream => { if (stream) { stream.getTracks().forEach(track => track.stop()); } });
                await state.writer?.close();
                el.status.textContent = `录制完成!${duration}`;
                alert(`已保存\n时长:${duration}`);
            } catch (err) {
                el.status.textContent = '操作出错';
                console.error(err);
            } finally {
                reset();
            }
        };
        el.stop.addEventListener('click', stopRec);
    </script>
</body>
</html>

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表