Object type webkitMediaStream lost when using sendMessage in chrome extension
So I'm trying to grab web audio from a tab and pipe it to another script that works with DOM elements on the page.
SCRIPT EXTENSION
In background.js
I am using the following script:
chrome.tabCapture.capture(constraints, function(stream) {
console.log("\ngot stream");
console.log(stream);
chrome.tabs.sendMessage(tabID, {
"message": "stream",
"stream": stream
});
});
The Developer Toolkit shows me that the created object is indeed a MediaStream object. (Which is what I want and seems to be working fine).
EXPANSION DESIGN:
MediaStream {onremovetrack: null, onaddtrack: null, onended: null, ended: false, id: "c0jm4lYJus3XCwQgesUGT9lpyPQiWlGKHb7q"…}
SCRIPT CONTENTS
I am using the content script (injected) in the page itself to pull out the serialized JSON object:
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");
}
});
CONSOLE PAGE
Unfortunately, due to JSON serialization, the object type is lost:
Object {onremovetrack: null, onaddtrack: null, onended: null, ended: false, id: "c0jm4lYJus3XCwQgesUGT9lpyPQiWlGKHb7q"…}
I need a recast object as a MediaStream object and tried the following:
Attempt 1: FAILED
var stream = new webkitMediaStream;
function loadStream(thisStream) {
stream = thisStream;
}
Attempt 2: FAILED
var stream;
function loadStream(thisStream) {
stream = new webkitMediaStream(thisStream);
}
Attempt 3: FAILED
var stream;
function loadStream(thisStream) {
stream = Object.create(webkitMediaStream, thisStream);
}
Note:
Constructor for MediaStream
IS object webkitMediaStream
.
I need either a better method to pass an object from the script extension (the only place the method works from chrome.tab.capture()
) to the content script (the only place that can access and modify the DOM elements of the page)
OR
I need a way to translate a serialized JSON object back into a fully functional object MediaStream
.
Thanks in advance!
JRad the Bad
source to share
The internal messages are always JSON serialized, so it's really obvious that you can't post MediaStream
from a man page to a web page. The question is, do you really need to send MediaStream
from background to content script?
- If you only need to display the video, for example, then you can use
URL.createObjectURL
to getblob:
-URL for the stream and assign itvideo.src
to see the video. The url generatedURL.createObjectURL
can only be used by a page of the same origin, so you need to create a tag<video>
on the pagechrome-extension://
; either in the tab or in the frame. If you want to do this in a frame, make sure the page is listed inweb_accessible_resources
.
If you really want a MediaStream
tab tab object , then RTCPeerConnection
you can use to send a stream.This WebRTC API is usually used to exchange media streams between peers on the network, but it can also be used to send streams from one page to another page in another tab or in a browser.
Here's a complete example. Visit any web page and click the extension button. The extension will then add the video to the page with 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"
]
}
source to share