MJPEG PiP Support

Picture-in-Picture for MJPEG images

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name         MJPEG PiP Support
// @namespace    http://tampermonkey.net/
// @version      1.4
// @author       pooiod7
// @description  Picture-in-Picture for MJPEG images
// @match        *://*/*
// @run-at       document-idle
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    let stream = null;
    let btn = null;
    let video = null;
    let canvas = null;
    let rafId = null;
    let captureStream = null;
    let lastW = 0;
    let lastH = 0;
    let polling = null;

    function findStream() {
        const imgs = document.getElementsByTagName('img');
        for (let i = 0; i < imgs.length; i++) {
            const src = imgs[i].src || '';
            if (src.includes('.mjp')) return imgs[i];
        }
        return null;
    }

    function updateSize(force) {
        const w = stream.naturalWidth || stream.width || 640;
        const h = stream.naturalHeight || stream.height || 360;
        if (force || Math.abs(w - lastW) > 50 || Math.abs(h - lastH) > 50) {
            canvas.width = w;
            canvas.height = h;
            lastW = w;
            lastH = h;
        }
    }

    function startCapture() {
        updateSize(true);
        const ctx = canvas.getContext('2d');

        function draw() {
            updateSize(false);
            try { ctx.drawImage(stream, 0, 0, canvas.width, canvas.height); } catch {}
            rafId = requestAnimationFrame(draw);
        }

        draw();
        captureStream = canvas.captureStream(15);
        video.srcObject = captureStream;
    }

    function stopCapture() {
        if (rafId) {
            cancelAnimationFrame(rafId);
            rafId = null;
        }
        if (captureStream) {
            captureStream.getTracks().forEach(t => t.stop());
            captureStream = null;
        }
        video.pause();
        video.srcObject = null;
        canvas.width = 0;
        canvas.height = 0;
        lastW = 0;
        lastH = 0;
    }

    function createButton() {
        if (btn) return;

        const style = document.createElement('style');
        style.textContent =
            'button#tm-pip-btn{position:fixed;top:10px;right:10px;z-index:2147483647;width:46px;height:46px;border-radius:10px;border:0;background:#111;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 4px 14px rgba(0,0,0,.4)}' +
            'button#tm-pip-btn svg{width:26px;height:26px;fill:#fff}' +
            'video#tm-hidden-video,canvas#tm-hidden-canvas{display:none;visibility:hidden;position:fixed;left:-9999px;top:-9999px}';
        document.head.appendChild(style);

        btn = document.createElement('button');
        btn.id = 'tm-pip-btn';
        btn.innerHTML =
            '<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">' +
            '<path d="M15.694 13.541l2.666 2.665 5.016-5.017 2.59 2.59 0.004-7.734-7.785-0.046 2.526 2.525-5.017 5.017z"></path>' +
            '<path d="M25.926 16.945l-1.92-1.947 0.035 9.007-16.015 0.009 0.016-15.973 8.958-0.040-2-2h-7c-1.104 0-2 0.896-2 2v16c0 1.104 0.896 2 2 2h16c1.104 0 2-0.896 2-2l-0.074-7.056z"></path>' +
            '</svg>';

        document.body.appendChild(btn);

        video = document.createElement('video');
        video.id = 'tm-hidden-video';
        video.muted = true;
        video.playsInline = true;
        document.body.appendChild(video);

        canvas = document.createElement('canvas');
        canvas.id = 'tm-hidden-canvas';
        document.body.appendChild(canvas);

        video.addEventListener('leavepictureinpicture', stopCapture);

        btn.addEventListener('click', async () => {
            if (document.pictureInPictureElement) {
                try { await document.exitPictureInPicture(); } catch {}
                return;
            }
            try {
                startCapture();
                await video.play();
                await video.requestPictureInPicture();
            } catch {
                stopCapture();
            }
        });
    }

    function poll() {
        if (!document.hasFocus()) return;

        if (!stream || !document.contains(stream)) {
            stream = findStream();
            if (stream) createButton();
        }
    }

    function startPolling() {
        if (polling) return;
        polling = setInterval(poll, 1000);
    }

    function stopPolling() {
        if (!polling) return;
        clearInterval(polling);
        polling = null;
    }

    window.addEventListener('focus', startPolling);
    window.addEventListener('blur', stopPolling);
    window.addEventListener('beforeunload', () => {
        try { if (document.pictureInPictureElement) document.exitPictureInPicture(); } catch {}
    });

    if (document.hasFocus()) startPolling();
})();