-
Notifications
You must be signed in to change notification settings - Fork 48
add replaceTrack test #24
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
/* | ||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. | ||
* | ||
* Use of this source code is governed by a BSD-style license | ||
* that can be found in the LICENSE file in the root of the source | ||
* tree. | ||
*/ | ||
button { | ||
margin: 0 20px 0 0; | ||
width: 83px; | ||
} | ||
|
||
button#hangupButton { | ||
margin: 0; | ||
} | ||
|
||
video { | ||
height: 225px; | ||
margin: 0 0 20px 0; | ||
vertical-align: top; | ||
width: calc(50% - 12px); | ||
} | ||
|
||
video#localVideo { | ||
margin: 0 20px 20px 0; | ||
} | ||
|
||
@media screen and (max-width: 400px) { | ||
button { | ||
width: 83px; | ||
} | ||
|
||
button { | ||
margin: 0 11px 10px 0; | ||
} | ||
|
||
|
||
video { | ||
height: 90px; | ||
margin: 0 0 10px 0; | ||
width: calc(50% - 7px); | ||
} | ||
video#localVideo { | ||
margin: 0 10px 20px 0; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
<!DOCTYPE html> | ||
<!-- | ||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. | ||
* | ||
* Use of this source code is governed by a BSD-style license | ||
* that can be found in the LICENSE file in the root of the source | ||
* tree. | ||
--> | ||
<html> | ||
<head> | ||
<title>ReplaceTrack</title> | ||
<link rel="stylesheet" href="css/main.css" /> | ||
</head> | ||
|
||
<body> | ||
|
||
<div id="container"> | ||
<video id="localVideo" autoplay muted></video> | ||
<video id="remoteVideo" autoplay></video> | ||
|
||
<div> | ||
<button id="startButton">Start</button> | ||
<button id="callButton">Call</button> | ||
<button id="restartButton">Restart video with replaceTrack</button> | ||
<button id="muteButton" disabled>toggle audio with replaceTrack</button> | ||
<button id="hangupButton">Hang Up</button> | ||
</div> | ||
|
||
</div> | ||
|
||
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script> | ||
<script src="js/main.js"></script> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,291 @@ | ||
/* | ||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. | ||
* | ||
* Use of this source code is governed by a BSD-style license | ||
* that can be found in the LICENSE file in the root of the source | ||
* tree. | ||
*/ | ||
'use strict'; | ||
|
||
function trace(arg) { | ||
var now = (window.performance.now() / 1000).toFixed(3); | ||
console.log(now + ': ', arg); | ||
} | ||
|
||
var startButton = document.getElementById('startButton'); | ||
var callButton = document.getElementById('callButton'); | ||
var hangupButton = document.getElementById('hangupButton'); | ||
var restartButton = document.getElementById('restartButton'); | ||
var muteButton = document.querySelector('button#muteButton'); | ||
callButton.disabled = true; | ||
hangupButton.disabled = true; | ||
restartButton.disabled = true; | ||
startButton.onclick = start; | ||
callButton.onclick = call; | ||
hangupButton.onclick = hangup; | ||
restartButton.onclick = restartVideo; | ||
muteButton.onclick = toggleMute; | ||
|
||
var supportsReplaceTrack =('RTCRtpSender' in window && | ||
'replaceTrack' in RTCRtpSender.prototype); | ||
|
||
var startTime; | ||
var localVideo = document.getElementById('localVideo'); | ||
var remoteVideo = document.getElementById('remoteVideo'); | ||
|
||
localVideo.addEventListener('loadedmetadata', function() { | ||
trace('Local video videoWidth: ' + this.videoWidth + | ||
'px, videoHeight: ' + this.videoHeight + 'px'); | ||
}); | ||
|
||
remoteVideo.addEventListener('loadedmetadata', function() { | ||
trace('Remote video videoWidth: ' + this.videoWidth + | ||
'px, videoHeight: ' + this.videoHeight + 'px'); | ||
}); | ||
|
||
remoteVideo.onresize = function() { | ||
trace('Remote video size changed to ' + | ||
remoteVideo.videoWidth + 'x' + remoteVideo.videoHeight); | ||
// We'll use the first onsize callback as an indication that video has started | ||
// playing out. | ||
if (startTime) { | ||
var elapsedTime = window.performance.now() - startTime; | ||
trace('Setup time: ' + elapsedTime.toFixed(3) + 'ms'); | ||
startTime = null; | ||
} | ||
}; | ||
|
||
var localStream; | ||
var pc1; | ||
var pc2; | ||
var offerOptions = { | ||
offerToReceiveAudio: 1, | ||
offerToReceiveVideo: 1 | ||
}; | ||
|
||
function getName(pc) { | ||
return (pc === pc1) ? 'pc1' : 'pc2'; | ||
} | ||
|
||
function getOtherPc(pc) { | ||
return (pc === pc1) ? pc2 : pc1; | ||
} | ||
|
||
function gotStream(stream) { | ||
trace('Received local stream'); | ||
localVideo.srcObject = stream; | ||
localStream = stream; | ||
callButton.disabled = false; | ||
} | ||
|
||
function start() { | ||
trace('Requesting local stream'); | ||
startButton.disabled = true; | ||
navigator.mediaDevices.getUserMedia({ | ||
audio: true, | ||
video: true | ||
}) | ||
.then(gotStream) | ||
.catch(function(e) { | ||
alert('getUserMedia() error: ' + e.name); | ||
}); | ||
} | ||
|
||
function call() { | ||
callButton.disabled = true; | ||
hangupButton.disabled = false; | ||
restartButton.disabled = !supportsReplaceTrack; | ||
muteButton.disabled = !supportsReplaceTrack; | ||
|
||
startTime = window.performance.now(); | ||
var videoTracks = localStream.getVideoTracks(); | ||
var audioTracks = localStream.getAudioTracks(); | ||
if (videoTracks.length > 0) { | ||
trace('Using video device: ' + videoTracks[0].label); | ||
} | ||
if (audioTracks.length > 0) { | ||
trace('Using audio device: ' + audioTracks[0].label); | ||
} | ||
var servers = null; | ||
pc1 = new RTCPeerConnection(servers); | ||
trace('Created local peer connection object pc1'); | ||
pc1.onicecandidate = function(e) { | ||
onIceCandidate(pc1, e); | ||
}; | ||
pc2 = new RTCPeerConnection(servers); | ||
trace('Created remote peer connection object pc2'); | ||
pc2.onicecandidate = function(e) { | ||
onIceCandidate(pc2, e); | ||
}; | ||
pc1.oniceconnectionstatechange = function(e) { | ||
onIceStateChange(pc1, e); | ||
}; | ||
pc2.oniceconnectionstatechange = function(e) { | ||
onIceStateChange(pc2, e); | ||
}; | ||
pc2.ontrack = gotRemoteStream; | ||
|
||
localStream.getTracks().forEach( | ||
function(track) { | ||
pc1.addTrack( | ||
track, | ||
localStream | ||
); | ||
} | ||
); | ||
trace('Added local stream to pc1'); | ||
|
||
trace('pc1 createOffer start'); | ||
pc1.createOffer( | ||
offerOptions | ||
).then( | ||
onCreateOfferSuccess, | ||
onCreateSessionDescriptionError | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of a bunch of helper functions could this be more compactly written as?
If more detailed error is desired, more try-catches or a variable keeping track of which stage we're at can be added. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure how comfortable @KaptenJansson feels with es6 and async/await. But I wouldn't want to start the migration here and end up with a weird mix of styles There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK |
||
} | ||
|
||
function onCreateSessionDescriptionError(error) { | ||
trace('Failed to create session description: ' + error.toString()); | ||
} | ||
|
||
function onCreateOfferSuccess(desc) { | ||
trace('Offer from pc1\n' + desc.sdp); | ||
trace('pc1 setLocalDescription start'); | ||
pc1.setLocalDescription(desc).then( | ||
function() { | ||
onSetLocalSuccess(pc1); | ||
}, | ||
onSetSessionDescriptionError | ||
); | ||
trace('pc2 setRemoteDescription start'); | ||
pc2.setRemoteDescription(desc).then( | ||
function() { | ||
onSetRemoteSuccess(pc2); | ||
}, | ||
onSetSessionDescriptionError | ||
); | ||
trace('pc2 createAnswer start'); | ||
// Since the 'remote' side has no media stream we need | ||
// to pass in the right constraints in order for it to | ||
// accept the incoming offer of audio and video. | ||
pc2.createAnswer().then( | ||
onCreateAnswerSuccess, | ||
onCreateSessionDescriptionError | ||
); | ||
} | ||
|
||
function onSetLocalSuccess(pc) { | ||
trace(getName(pc) + ' setLocalDescription complete'); | ||
} | ||
|
||
function onSetRemoteSuccess(pc) { | ||
trace(getName(pc) + ' setRemoteDescription complete'); | ||
} | ||
|
||
function onSetSessionDescriptionError(error) { | ||
trace('Failed to set session description: ' + error.toString()); | ||
} | ||
|
||
function gotRemoteStream(e) { | ||
if (remoteVideo.srcObject !== e.streams[0]) { | ||
remoteVideo.srcObject = e.streams[0]; | ||
trace('pc2 received remote stream'); | ||
} | ||
} | ||
|
||
function onCreateAnswerSuccess(desc) { | ||
trace('Answer from pc2:\n' + desc.sdp); | ||
trace('pc2 setLocalDescription start'); | ||
pc2.setLocalDescription(desc).then( | ||
function() { | ||
onSetLocalSuccess(pc2); | ||
}, | ||
onSetSessionDescriptionError | ||
); | ||
trace('pc1 setRemoteDescription start'); | ||
pc1.setRemoteDescription(desc).then( | ||
function() { | ||
onSetRemoteSuccess(pc1); | ||
}, | ||
onSetSessionDescriptionError | ||
); | ||
} | ||
|
||
function onIceCandidate(pc, event) { | ||
getOtherPc(pc).addIceCandidate(event.candidate) | ||
.then( | ||
function() { | ||
onAddIceCandidateSuccess(pc); | ||
}, | ||
function(err) { | ||
onAddIceCandidateError(pc, err); | ||
} | ||
); | ||
trace(getName(pc) + ' ICE candidate: \n' + (event.candidate ? | ||
event.candidate.candidate : '(null)')); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about using async function and await here too? |
||
|
||
function onAddIceCandidateSuccess(pc) { | ||
trace(getName(pc) + ' addIceCandidate success'); | ||
} | ||
|
||
function onAddIceCandidateError(pc, error) { | ||
trace(getName(pc) + ' failed to add ICE Candidate: ' + error.toString()); | ||
} | ||
|
||
function onIceStateChange(pc, event) { | ||
if (pc) { | ||
trace(getName(pc) + ' ICE state: ' + pc.iceConnectionState); | ||
console.log('ICE state change event: ', event); | ||
} | ||
} | ||
|
||
function hangup() { | ||
trace('Ending call'); | ||
pc1.close(); | ||
pc2.close(); | ||
pc1 = null; | ||
pc2 = null; | ||
hangupButton.disabled = true; | ||
callButton.disabled = false; | ||
} | ||
|
||
// Stops and restarts the video with replaceTrack. | ||
function restartVideo() { | ||
localStream.getVideoTracks()[0].stop(); | ||
localStream.removeTrack(localStream.getVideoTracks()[0]); | ||
window.setTimeout(function() { | ||
navigator.mediaDevices.getUserMedia({video: true}) | ||
.then(function(stream) { | ||
localStream.addTrack(stream.getVideoTracks()[0]); | ||
var sender = pc1.getSenders().find(function(s) { | ||
return s.track && s.track.kind === 'video'; | ||
}); | ||
return sender.replaceTrack(stream.getVideoTracks()[0]); | ||
}) | ||
.then(function() { | ||
console.log('Replaced video track'); | ||
}) | ||
.catch(function(err) { | ||
console.error(err); | ||
}); | ||
}, 5000); | ||
} | ||
|
||
// Toggles audio mute with replaceTrack(null/track) | ||
function toggleMute() { | ||
var sender = pc1.getSenders()[0]; | ||
var p; | ||
if (!sender.track) { | ||
trace('re-adding audio track'); | ||
p = sender.replaceTrack(localStream.getAudioTracks()[0]); | ||
} else { | ||
trace('replacing audio track with null'); | ||
p = sender.replaceTrack(null); | ||
} | ||
p.then(function() { | ||
console.log('replaced track'); | ||
}).catch(function(err) { | ||
console.error('during replaceTrack', err); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Remove extra indentation of then()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with you but eslint does not sadly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:'( saddest story