From c04d3470615a78bb1adac8cf15634fbf812f2fe8 Mon Sep 17 00:00:00 2001 From: Philipp Hancke Date: Tue, 13 Feb 2018 21:05:27 +0100 Subject: [PATCH] add replaceTrack test based on pc1 sample and the following two PRs: https://github.com/webrtc/samples/pull/1009 https://github.com/webrtc/samples/pull/996 --- src/replaceTrack/css/main.css | 47 ++++++ src/replaceTrack/index.html | 34 ++++ src/replaceTrack/js/main.js | 291 ++++++++++++++++++++++++++++++++++ 3 files changed, 372 insertions(+) create mode 100644 src/replaceTrack/css/main.css create mode 100644 src/replaceTrack/index.html create mode 100644 src/replaceTrack/js/main.js diff --git a/src/replaceTrack/css/main.css b/src/replaceTrack/css/main.css new file mode 100644 index 0000000..fc7dbda --- /dev/null +++ b/src/replaceTrack/css/main.css @@ -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; + } + +} diff --git a/src/replaceTrack/index.html b/src/replaceTrack/index.html new file mode 100644 index 0000000..cb48ed9 --- /dev/null +++ b/src/replaceTrack/index.html @@ -0,0 +1,34 @@ + + + + + ReplaceTrack + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + diff --git a/src/replaceTrack/js/main.js b/src/replaceTrack/js/main.js new file mode 100644 index 0000000..4435a48 --- /dev/null +++ b/src/replaceTrack/js/main.js @@ -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 + ); +} + +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)')); +} + +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); + }); +}