Búsqueda de sitios web

javascript - Tipo de objeto webkitMediaStream perdido al usar sendMessage en la extensión de Chrome


Así que estoy tratando de capturar el audio web de una pestaña y pasarlo a otro script que funciona con elementos DOM en la página.

SCRIPT DE EXTENSIÓN

En el background.js, uso el siguiente script:

    chrome.tabCapture.capture(constraints, function(stream) {
        console.log("\ngot stream");
        console.log(stream);

        chrome.tabs.sendMessage(tabID, {
            "message": "stream",
            "stream": stream
        });
    });

El Kit de Herramientas para Desarrolladores me muestra que el objeto creado es efectivamente un objeto MediaStream. (Lo cual quiero y parece estar funcionando bien).

CONSOLA DE EXTENSIÓN:

MediaStream {onremovetrack: null, onaddtrack: null, onended: null, ended: false, id: "c0jm4lYJus3XCwQgesUGT9lpyPQiWlGKHb7q"…}

GUION DE CONTENIDO

Uso un script de contenido (inyectado), en la propia página para luego extraer de nuevo el objeto serializado en JSON:

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
  if (request.message === "stream") {
    var thisStream = request.stream;
    console.log(thisStream);
    if (!thisStream) {
      console.log("stream is null");
      return;
    }    
    loadStream(thisStream);
  }
  else if (request.message === "statusChanged") {
    console.log("statusChanged");
  }
});

CONSOLA DE PÁGINA

Desafortunadamente, debido a la serialización JSON, se pierde el tipo de objeto:

Object {onremovetrack: null, onaddtrack: null, onended: null, ended: false, id: "c0jm4lYJus3XCwQgesUGT9lpyPQiWlGKHb7q"…}

Necesito que el objeto se vuelva a crear como un objeto MediaStream y he intentado las siguientes cosas, las cuales todas fallaron:

Intento 1: FALLIDO

var stream = new webkitMediaStream;
function loadStream(thisStream) {
    stream = thisStream;
}

Intento 2: FALLIDO

var stream;
function loadStream(thisStream) {
    stream = new webkitMediaStream(thisStream);
}

Intento 3: FALLIDO

var stream;
function loadStream(thisStream) {
    stream = Object.create(webkitMediaStream, thisStream);
}

NOTA: El constructor para el objeto MediaStream ES webkitMediaStream.

Necesito un mejor método para pasar el objeto desde el script de extensión (el único lugar desde el que funciona el método chrome.tab.capture()) al script de contenido (el único lugar que tiene acceso y puede modificar los elementos DOM de la página),

O

Necesito una forma de reconstruir el objeto serializado en JSON de nuevo en un objeto MediaStream completamente funcional.

¡Gracias de antemano!

JRad el Malo

Respuesta #1

Los mensajes de la extensión siempre se serializan en JSON, por lo que es realmente obvio que no se puede enviar un MediaStream desde la página de fondo a la página web. La pregunta es, ¿realmente necesitas enviar el MediaStream desde el fondo al script de contenido?

  • Si solo necesitas, por ejemplo, mostrar el video, entonces puedes usar URL.createObjectURL para obtener una URL blob: del flujo y asignarla a video.src para ver un video. La URL creada por URL.createObjectURL solo puede ser usada por una página con el mismo origen, así que necesitas crear la etiqueta <video> en una página chrome-extension://; ya sea en una pestaña o en un marco. Si quieres hacer esto en un marco, asegúrate de que la página esté listada en web_accessible_resources.

Si REALMENTE necesitas un objeto MediaStream de la pestaña en la pestaña, entonces se puede usar RTCPeerConnection para enviar el flujo. Esta API de WebRTC normalmente se usa para intercambiar flujos de medios entre pares en una red, pero también se puede usar para enviar flujos de una página a otra página en otra pestaña o navegador.

Aquí hay un ejemplo completo. Visita cualquier página web y haz clic en el botón de la extensión. Luego, la extensión insertará un video en la página mostrando la pestaña actual.

background.js

function sendStreamToTab(tabId, stream) {
    var pc = new webkitRTCPeerConnection({iceServers:[]});
    pc.addStream(stream);
    pc.createOffer(function(offer) {
        pc.setLocalDescription(offer, function() {
            // Use chrome.tabs.connect instead of sendMessage
            // to make sure that the lifetime of the stream
            // is tied to the lifetime of the consumer (tab).
            var port = chrome.tabs.connect(tabId, {name: 'tabCaptureSDP'});
            port.onDisconnect.addListener(function() {
                stopStream(stream);
            });
            port.onMessage.addListener(function(sdp) {
                pc.setRemoteDescription(new RTCSessionDescription(sdp));
            });
            port.postMessage(pc.localDescription);
        });
    });
}

function stopStream(stream) {
    var tracks = this.getTracks();
    for (var i = 0; i < tracks.length; ++i) {
        tracks[i].stop();
    }
}

function captureTab(tabId) {
    // Note: this method must be invoked by the user as defined
    // in https://crbug.com/489258, e.g. chrome.browserAction.onClicked.
    chrome.tabCapture.capture({
        audio: true,
        video: true,
        audioConstraints: {
            mandatory: {
                chromeMediaSource: 'tab',
            },
        },
        videoConstraints: {
            mandatory: {
                chromeMediaSource: 'tab',
            },
        },
    }, function(stream) {
        if (!stream) {
            alert('Stream creation failed: ' + chrome.runtime.lastError.message);
        }
        chrome.tabs.executeScript(tabId, {file: 'contentscript.js'}, function() {
            if (chrome.runtime.lastError) {
                stopStream(stream);
                alert('Script injection failed:' + chrome.runtime.lastError.message);
            } else {
                sendStreamToTab(tabId, stream);
            }
        });
    });
}

chrome.browserAction.onClicked.addListener(function(tab) {
    captureTab(tab.id);
});

scriptcontenido.js

function onReceiveStream(stream) {
    // Just to show that we can receive streams:
    var video = document.createElement('video');
    video.style.border = '1px solid black';
    video.src = URL.createObjectURL(stream);
    document.body.insertBefore(video, document.body.firstChild);
}

function onReceiveOfferSDP(sdp, sendResponse) {
    var pc = new webkitRTCPeerConnection({iceServers:[]});
    pc.onaddstream = function(event) {
        onReceiveStream(event.stream);
    };
    pc.setRemoteDescription(new RTCSessionDescription(sdp), function() {
        pc.createAnswer(function(answer) {
            pc.setLocalDescription(answer);
            sendResponse(pc.localDescription);
        });
    });
}

// Run once to prevent the message from being handled twice when
// executeScript is called multiple times.
if (!window.hasRun) {
    window.hasRun = 1;
    chrome.runtime.onConnect.addListener(function(port) {
        if (port.name === 'tabCaptureSDP') {
            port.onMessage.addListener(function(remoteDescription) {
                onReceiveOfferSDP(remoteDescription, function(sdp) {
                    port.postMessage(sdp);
                });
            });
        }
    });
}

manifest.json

{
    "name": "tabCapture to tab",
    "version": "1",
    "manifest_version": 2,
    "background": {
        "scripts": ["background.js"],
        "persistent": false
    },
    "browser_action": {
        "default_title": "Capture tab"
    },
    "permissions": [
        "activeTab",
        "tabCapture"
    ]
}