haikuwebkit/ManualTests/webrtc-one-tab-p2p.html

345 lines
11 KiB
HTML
Raw Permalink Normal View History

WebRTC: [GTK] Add MediaEndpointOwr - an OpenWebRTC WebRTC backend https://bugs.webkit.org/show_bug.cgi?id=163327 Reviewed by Philippe Normand. .: Add manual WebRTC test. Test features: - Two RTCPeerConnection instances communicate in a single browser tab. - Supports setting up bidirectional media with a single SDP dialog, as well as one direction at a time. - Strips vendor prefixes (runs in Chrome and Firefox as well) - Supports modern as well as legacy APIs (mainly to make the test run in Chrome) * ManualTests/webrtc-one-tab-p2p.html: Added. Source/WebCore: Add MediaEndpointOwr which is a MediaEndpoint implementation (WebRTC backend) based on OpenWebRTC [1]. The WebRTC backend can be tested with a manual test. Automatic testing is still done with MockMediaEndpoint. [1] http://www.openwebrtc.org/ Testing: Added manual test (webrtc-one-tab-p2p.html) * CMakeLists.txt: * platform/GStreamer.cmake: * platform/mediastream/openwebrtc/MediaEndpointOwr.cpp: Added. (WebCore::createMediaEndpointOwr): (WebCore::MediaEndpointOwr::MediaEndpointOwr): (WebCore::MediaEndpointOwr::~MediaEndpointOwr): (WebCore::MediaEndpointOwr::setConfiguration): (WebCore::cryptoDataCallback): (WebCore::MediaEndpointOwr::generateDtlsInfo): (WebCore::MediaEndpointOwr::getDefaultAudioPayloads): (WebCore::MediaEndpointOwr::getDefaultVideoPayloads): (WebCore::payloadsContainType): (WebCore::MediaEndpointOwr::filterPayloads): (WebCore::MediaEndpointOwr::updateReceiveConfiguration): (WebCore::findRtxPayload): (WebCore::MediaEndpointOwr::updateSendConfiguration): (WebCore::MediaEndpointOwr::addRemoteCandidate): (WebCore::MediaEndpointOwr::replaceMutedRemoteSourceMid): (WebCore::MediaEndpointOwr::createMutedRemoteSource): (WebCore::MediaEndpointOwr::replaceSendSource): (WebCore::MediaEndpointOwr::stop): (WebCore::MediaEndpointOwr::transceiverIndexForSession): (WebCore::MediaEndpointOwr::sessionMid): (WebCore::MediaEndpointOwr::matchTransceiverByMid): (WebCore::MediaEndpointOwr::dispatchNewIceCandidate): (WebCore::MediaEndpointOwr::dispatchGatheringDone): (WebCore::MediaEndpointOwr::processIceTransportStateChange): (WebCore::MediaEndpointOwr::dispatchDtlsFingerprint): (WebCore::MediaEndpointOwr::unmuteRemoteSource): (WebCore::MediaEndpointOwr::prepareSession): (WebCore::MediaEndpointOwr::prepareMediaSession): (WebCore::parseHelperServerUrl): (WebCore::MediaEndpointOwr::ensureTransportAgentAndTransceivers): (WebCore::MediaEndpointOwr::internalAddRemoteCandidate): (WebCore::gotCandidate): (WebCore::candidateGatheringDone): (WebCore::iceConnectionStateChange): (WebCore::gotIncomingSource): * platform/mediastream/openwebrtc/MediaEndpointOwr.h: Added. (WebCore::OwrTransceiver::create): (WebCore::OwrTransceiver::~OwrTransceiver): (WebCore::OwrTransceiver::mid): (WebCore::OwrTransceiver::session): (WebCore::OwrTransceiver::owrIceState): (WebCore::OwrTransceiver::setOwrIceState): (WebCore::OwrTransceiver::gotEndOfRemoteCandidates): (WebCore::OwrTransceiver::markGotEndOfRemoteCandidates): (WebCore::OwrTransceiver::OwrTransceiver): * platform/mediastream/openwebrtc/RealtimeMediaSourceOwr.h: (WebCore::RealtimeMediaSourceOwr::RealtimeMediaSourceOwr): (WebCore::RealtimeMediaSourceOwr::swapOutShallowSource): Add support for an initially muted source. This is used for early creation of remote sources. Canonical link: https://commits.webkit.org/181545@main git-svn-id: https://svn.webkit.org/repository/webkit/trunk@207665 268f45cc-cd09-0410-ab3c-d52691b4dbfc
2016-10-21 10:20:23 +00:00
<!doctype html>
<html>
<head>
<title>One tab p2p</title>
<style type="text/css">
video { width: 240px; height: 160px; border: black 1px dashed; }
input { margin: 2px }
</style>
<script>
// Make use of prefixed APIs to run this test in Chrome and Firefox
self.RTCPeerConnection = self.RTCPeerConnection || self.webkitRTCPeerConnection || self.mozRTCPeerConnection;
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
let legacyCheckBox;
let closeButton;
let pcA;
let pcB;
let localStream;
const pcNames = {
first: "A",
second: "B"
};
// FIXME: We should be able to use an empty configuration (bug: http://webkit.org/b/158936)
const configuration = { "iceServers": [{ "urls": "stun:mmt-stun.verkstad.net" }] };
document.addEventListener("DOMContentLoaded", function () {
legacyCheckBox = document.querySelector("#legacy_check");
const audioCheckBox = document.querySelector("#audio_check");
const videoCheckBox = document.querySelector("#video_check");
const startButton = document.querySelector("#start_but");
closeButton = document.querySelector("#close_but");
const testButtons = {
"single": document.querySelector("#single_but"),
"mediaAtoB": document.querySelector("#media_A_to_B_but"),
"mediaBtoA": document.querySelector("#media_B_to_A_but")
};
function setTestButtonsDisabled(isDisabled) {
for (let p in testButtons)
testButtons[p].disabled = isDisabled;
}
startButton.onclick = function () {
navigator.getUserMedia({
"audio": audioCheckBox.checked,
"video": videoCheckBox.checked
}, function (stream) {
audioCheckBox.disabled = videoCheckBox.disabled = true;
localStream = stream;
startButton.disabled = true;
setTestButtonsDisabled(false);
}, logError);
};
closeButton.onclick = function (evt) {
evt.target.disabled = true;
console.log("Closing");
pcA.close();
pcB.close();
pcA = null;
pcB = null;
setTestButtonsDisabled(false);
}
testButtons.single.onclick = function (evt) {
setTestButtonsDisabled(true);
getTestFunction("singleDialog")();
}
testButtons.mediaAtoB.onclick = function (evt) {
setTestButtonsDisabled(true);
if (!pcA)
commonSetup();
getTestFunction("addOneWayMedia")(pcA, pcB, testButtons.mediaBtoA);
}
testButtons.mediaBtoA.onclick = function (evt) {
setTestButtonsDisabled(true);
if (!pcA)
commonSetup();
getTestFunction("addOneWayMedia")(pcB, pcA, testButtons.mediaAtoB);
}
});
function getTestFunction(name) {
const functionName = legacyCheckBox.checked ? name : `${name}Promise`;
return self[functionName];
}
function singleDialog() {
commonSetup();
renderStream(localStream, document.querySelector("#self_viewA"));
pcA.addStream(localStream);
pcA.createOffer(function (offer) {
pcA.setLocalDescription(offer, function () {
offerToB(pcA.localDescription);
}, logError);
}, logError);
function offerToB(offer) {
logSignalling(offer, pcA, pcB);
pcB.setRemoteDescription(offer, function () {
addStoredCandidates(pcB);
renderStream(localStream, document.querySelector("#self_viewB"));
pcB.addStream(localStream);
pcB.createAnswer(function (answer) {
pcB.setLocalDescription(answer, function () {
answerToA(pcB.localDescription);
}, logError);
}, logError);
}, logError);
}
function answerToA(answer) {
logSignalling(answer, pcB, pcA);
pcA.setRemoteDescription(answer, function () {
console.log("Initiator got answer, O/A dialog completed");
addStoredCandidates(pcA);
closeButton.disabled = false;
}, logError);
}
}
function singleDialogPromise() {
commonSetup();
renderStream(localStream, document.querySelector("#self_viewA"));
localStream.getTracks().forEach(track => {
pcA.addTrack(track, localStream);
});
pcA.createOffer().then(function (offer) {
return pcA.setLocalDescription(offer);
})
.then(function () {
logSignalling(pcA.localDescription, pcA, pcB);
return pcB.setRemoteDescription(pcA.localDescription);
})
.then(function () {
addStoredCandidates(pcB);
renderStream(localStream, document.querySelector("#self_viewB"));
localStream.getTracks().forEach(track => {
pcB.addTrack(track, localStream);
});
return pcB.createAnswer();
})
.then(function (answer) {
return pcB.setLocalDescription(answer);
})
.then(function () {
logSignalling(pcB.localDescription, pcB, pcA);
return pcA.setRemoteDescription(pcB.localDescription);
})
.then(function () {
addStoredCandidates(pcA);
console.log("Initiator got answer, O/A dialog completed");
closeButton.disabled = false;
})
.catch(logError);
}
function addOneWayMedia(offeringPc, answeringPc, continueButton) {
renderStream(localStream, document.querySelector(`#self_view${offeringPc.name}`));
offeringPc.addStream(localStream);
offeringPc.createOffer(function (offer) {
offeringPc.setLocalDescription(offer, function () {
offerToAnsweringPc(offeringPc.localDescription);
}, logError);
}, logError);
function offerToAnsweringPc(offer) {
logSignalling(offer, offeringPc, answeringPc);
answeringPc.setRemoteDescription(offer, function () {
addStoredCandidates(answeringPc);
answeringPc.createAnswer(function (answer) {
answeringPc.setLocalDescription(answer, function () {
answerToOfferingPc(answeringPc.localDescription);
}, logError);
}, logError);
}, logError);
}
function answerToOfferingPc(answer) {
logSignalling(answer, answeringPc, offeringPc);
offeringPc.setRemoteDescription(answer, function () {
console.log("Initiator side got answer, single way O/A dialog completed");
addStoredCandidates(offeringPc);
continueButton.disabled = false;
closeButton.disabled = false;
}, logError);
}
}
function addOneWayMediaPromise(offeringPc, answeringPc, continueButton) {
renderStream(localStream, document.querySelector(`#self_view${offeringPc.name}`));
localStream.getTracks().forEach(track => {
offeringPc.addTrack(track, localStream);
});
offeringPc.createOffer().then(function (offer) {
return offeringPc.setLocalDescription(offer);
})
.then(function () {
logSignalling(offeringPc.localDescription, offeringPc, answeringPc);
return answeringPc.setRemoteDescription(offeringPc.localDescription);
})
.then(function () {
addStoredCandidates(answeringPc);
return answeringPc.createAnswer();
})
.then(function (answer) {
return answeringPc.setLocalDescription(answer)
})
.then(function () {
logSignalling(answeringPc.localDescription, answeringPc, offeringPc);
return offeringPc.setRemoteDescription(answeringPc.localDescription)
})
.then(function () {
console.log("Initiator side got answer, single way O/A dialog completed");
addStoredCandidates(offeringPc);
continueButton.disabled = false;
closeButton.disabled = false;
})
.catch(logError);
}
function commonSetup() {
pcA = new RTCPeerConnection(configuration);
pcB = new RTCPeerConnection(configuration);
pcA.name = pcNames.first;
pcB.name = pcNames.second;
symetricSetup(pcA, pcB);
symetricSetup(pcB, pcA);
}
function addStoredCandidates(pc) {
if (!pc.storedCandidates)
return;
pc.storedCandidates.forEach(candidate => {
pc.addIceCandidate(candidate).catch(logError);
});
console.log(`Added ${pc.storedCandidates.length} stored candidates (arrived before remote description was set)`);
pc.storedCandidates = null;
}
function symetricSetup(pc, otherPc) {
pc.onicecandidate = function (evt) {
if (evt.candidate) {
logSignalling(evt.candidate, pc, otherPc);
// If the remote description isn't set yet, store the candidate
if (!otherPc.remoteDescription) {
if (!otherPc.storedCandidates)
otherPc.storedCandidates = [];
otherPc.storedCandidates.push(evt.candidate);
} else
otherPc.addIceCandidate(evt.candidate).catch(logError);
}
};
pc.onaddstream = function (evt) {
renderStream(evt.stream, document.querySelector(`#remote_view${pc.name}`));
};
}
function renderStream(stream, video) {
if (typeof video.srcObject !== "undefined")
video.srcObject = stream;
else
video.src = URL.createObjectURL(stream);
}
function logSignalling(msg, fromPc, toPc) {
const type = msg.candidate ? "Candidate" : msg.type.replace(/^[a-z]/, s => s.toUpperCase());
let header = `${type} `;
header += fromPc.name == pcNames.first ? `${fromPc.name} -> ${toPc.name}` : `${toPc.name} <- ${fromPc.name}`;
console.groupCollapsed(header);
console.log(msg.candidate || msg.sdp);
console.groupEnd();
}
function logError(error) {
if (error) {
if (error.name || error.message)
console.error(`logError: ${error.name || "-"}: ${error.message || "-"}`);
else
console.error(`logError: ${error}`);
} else
console.error("logError: (no error message)");
}
</script>
</head>
<body>
<h3>One Tab P2P - Test Different Signaling Schemas</h3>
<p>Click start to request user media. The same stream is sent in both directions so a successful
bidirectional media setup shows the same output in all four video elements. Open console to view
signaling details. Some browsers only allow access to user media via a secure origin (e.g.
localhost).</p>
<input type="checkbox" id="legacy_check">Use Legacy APIs (Chrome compatible)<br>
<input type="checkbox" id="audio_check">Audio<br>
<input type="checkbox" id="video_check" checked>Video<br>
<input type="button" id="start_but" value="Start">
<input type="button" id="close_but" value="Close Connections" disabled>
<br>
Setup bidirectional media: <input type="button" id="single_but" value="Single SDP dialog" disabled>
<br>
Setup media in one direction at a time: <input type="button" id="media_A_to_B_but" value="Media A to B" disabled>
<input type="button" id="media_B_to_A_but" value="Media B to A" disabled>
<br>
<table>
<tr>
<td>Local (A)</td><td>Remote (A)</td>
</tr>
<tr>
<td><video id="self_viewA" autoplay muted></video></td>
<td><video id="remote_viewA" autoplay></video></td>
</tr>
<tr>
<td>Local (B)</td><td>Remote (B)</td>
</tr>
<tr>
<td><video id="self_viewB" autoplay muted></video></td>
<td><video id="remote_viewB" autoplay></video></td>
</tr>
</table>
</body>
</html>