webkitMediaStream Object Type lost while using sendMessage in Chrome Extension webkitMediaStream Object Type lost while using sendMessage in Chrome Extension json json

webkitMediaStream Object Type lost while using sendMessage in Chrome Extension


Extension messages are always JSON-serialized, so it's indeed obvious that you cannot send a MediaStream from the background page to the web page. The question is, do you really need to send the MediaStream from the background to the content script?

  • If you only need to, e.g. display the video, then you can use URL.createObjectURL to get a blob:-URL for the stream and assign it to video.src to see a video. The URL created by URL.createObjectURL can only be used by a page at the same origin, so you need to create the <video> tag in a chrome-extension:// page; either in a tab, or in a frame. If you want to do this in a frame, make sure that the page is listed in web_accessible_resources.

If you DO really need a MediaStream object of the tab in the tab, then RTCPeerConnection can be used to send the stream. This WebRTC API is normally used to exchange media streams between peers in a network, but it can also be used to send streams from one page to another page in another tab or browser.

Here's a full example. Visit any web page, and click on the extension button. Then the extension will insert a video in the page showing the current tab.

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);});

contentscript.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"    ]}