// -----------------------------------------------------------------------------
// WebRTC Engine
//
window.RTCPeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
window.RTCSessionDescription = window.mozRTCSessionDescription || window.RTCSessionDescription;
window.RTCIceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate;
navigator.getUserMedia = navigator.mozGetUserMedia || navigator.webkitGetUserMedia;
window.URL = window.webkitURL || window.URL;
var myLocalStream = undefined;
var isVideoMuted = false;
var isAudioMuted = false;
Symple.Media.registerEngine({
id: 'WebRTC',
name: 'WebRTC Player',
formats: 'VP8, Opus',
preference: 100,
support: (function() {
return typeof RTCPeerConnection != "undefined";
})()
});
Symple.Player.Engine.WebRTC = Symple.Player.Engine.extend({
init: function(player) {
Symple.log("SympleWebRTC: Init");
this._super(player);
this.rtcConfig = player.options.rtcConfig || {
iceServers: [
{ url: "stun:stun.l.google.com:19302" }
]
}
this.rtcOptions = player.options.rtcOptions || {
optional: [
{DtlsSrtpKeyAgreement: true} // FF <=> Chrome interop
]
}
this.mediaConstraints = player.options.mediaConstraints || {}
//this.mediaConstraints = player.options.mediaConstraints || {
// 'mandatory': {
// 'OfferToReceiveAudio':true,
// 'OfferToReceiveVideo':true
// }
//};
},
setup: function() {
Symple.log("SympleWebRTC: Setup");
this._createPeerConnection();
// Note: Absolutely position video element so it scales to
// the parent element size. Need to test in other browsers.
if (typeof(this.video) == 'undefined') {
Symple.log("SympleWebRTC: Setup: Peer video");
this.video = $('')
this.player.screen.prepend(this.video);
}
//this.video = $(''); // Chrome
//this.selfVideo = typeof(this.selfVideo) == 'undefined' ? $('') : this.selfVideo;
//this.video = typeof(this.video) == 'undefined' ? $('') : this.video; // style="position:absolute;left:0;top:0;" width="100%" height="100%" style="max-width:100%;height:auto;"
},
destroy: function() {
Symple.log("SympleWebRTC: Destroy");
this.sendLocalSDP = null;
this.sendLocalCandidate = null;
if (this.video) {
this.video[0].src = '';
this.video[0] = null;
this.video = null;
// Anything else required for video cleanup?
}
if (this.pc) {
this.pc.close();
this.pc = null;
// Anything else required for peer connection cleanup?
}
},
play: function(params) {
Symple.log("SympleWebRTC: Play", params);
// The 'playing' state will be set when candidates
// gathering is complete.
// TODO: Get state events from the video element
// to shift from local loading to playing state.
if (params && params.localMedia) {
// Get the local stream, show it in the local video element and send it
var self = this;
navigator.getUserMedia({ audio: !params.disableAudio, video: !params.disableVideo },
// successCallback
function (localStream) {
//self._createPeerConnection();
myLocalStream = localStream;
// Play the local stream
self.video[0].src = URL.createObjectURL(localStream);
self.pc.addStream(localStream);
//if (params.caller)
self.pc.createOffer(
function(desc) { self._onLocalSDP(desc); }, function(err) { self.setError("createOffer() Failed: " + err);});
//else
// self.pc.createAnswer(
// function(desc) { self._onLocalSDP(desc); },
// function() { // error
// self.setError("Cannot create local SDP answer");
// },
// null //this.mediaConstraints;
// )
//function gotDescription(desc) {
// pc.setLocalDescription(desc);
// signalingChannel.send(JSON.stringify({ "sdp": desc }));
//}
},
// errorCallback
function(err) {
self.setError("getUserMedia() Failed: " + err);
});
}
},
stop: function() {
if (this.video) {
this.video[0].src = '';
// Do not nullify
}
// TODO: Close peer connection?
if (this.pc) {
this.pc.close();
this.pc = null;
}
if (myLocalStream) {
myLocalStream.stop();
myLocalStream = undefined;
}
this.setState('stopped');
},
mute: function(flag) {
// Mute unless explicit false given
flag = flag === false ? false : true;
Symple.log("SympleWebRTC: Mute:", flag);
if (this.video) {
this.video.prop('muted', flag); //mute
}
},
muteLocal: function() {
Symple.log('mute');
if (this.video) {
this.video[0].muted = true;
}
},
toggleVideo: function() {
videoTracks = myLocalStream.getVideoTracks();
if (videoTracks.length === 0) {
Symple.log('No local video available.');
return;
}
if (isVideoMuted) {
for (i = 0; i < videoTracks.length; i++) {
videoTracks[i].enabled = true;
}
Symple.log('Video unmuted.');
} else {
for (i = 0; i < videoTracks.length; i++) {
videoTracks[i].enabled = false;
}
Symple.log('Video muted.');
}
isVideoMuted = !isVideoMuted;
},
toggleAudio: function() {
audioTracks = myLocalStream.getAudioTracks();
if (audioTracks.length === 0) {
Symple.log('No local audio available.');
return;
}
if (isAudioMuted) {
for (i = 0; i < audioTracks.length; i++) {
audioTracks[i].enabled = true;
}
Symple.log('Audio unmuted.');
} else {
for (i = 0; i < audioTracks.length; i++){
audioTracks[i].enabled = false;
}
Symple.log('Audio muted.');
}
isAudioMuted = !isAudioMuted;
},
fullscreenVideo: function() {
if (this.video[0].requestFullscreen) {
this.video[0].requestFullscreen();
} else if (this.video[0].mozRequestFullScreen) {
this.video[0].mozRequestFullScreen();
} else if (this.video[0].webkitRequestFullscreen) {
this.video[0].webkitRequestFullscreen();
}
},
// Initiates the player with local media capture
//startLocalMedia: function(params) {
//Symple.log("SympleWebRTC: Play", params);
// The 'playing' state will be set when candidates
// gathering is complete.
// TODO: Get state events from the video element
// to shift from local loading to playing state.
//},
//
// Called when local SDP is ready to be sent to the peer.
sendLocalSDP: new Function,
//
// Called when a local candidate is ready to be sent to the peer.
sendLocalCandidate: new Function,
//
// Called when remote SDP is received from the peer.
onRemoteSDP: function(desc) {
Symple.log('SympleWebRTC: Recieve remote SDP:', desc)
if (!desc || !desc.type || !desc.sdp)
throw "Invalid SDP data"
//if (desc.type != "offer")
// throw "Only SDP offers are supported"
var self = this;
this.pc.setRemoteDescription(new RTCSessionDescription(desc),
function() {
Symple.log('SympleWebRTC: SDP success');
//alert('success')
},
function(message) {
console.error('SympleWebRTC: SDP error:', message);
self.setError("Cannot parse remote SDP offer");
}
);
if (desc.type == "offer") {
this.pc.createAnswer(
function(answer) { // success
self._onLocalSDP(answer);
//alert('answer')
},
function() { // error
self.setError("Cannot create local SDP answer");
},
null //this.mediaConstraints
);
}
},
//
// Called when remote candidate is received from the peer.
onRemoteCandidate: function(candidate) {
//Symple.log("SympleWebRTC: Recieve remote candiate ", candidate);
if (!this.pc)
throw 'The peer connection is not initialized' // call onRemoteSDP first
this.pc.addIceCandidate(new RTCIceCandidate({
//sdpMid: candidate.sdpMid,
sdpMLineIndex: candidate.sdpMLineIndex,
candidate: candidate.candidate
}));
},
//
// Private methods
//
//
// Called when local SDP is received from the peer.
_onLocalSDP: function(desc) {
try {
this.pc.setLocalDescription(desc);
this.sendLocalSDP(desc);
}
catch (e) {
Symple.log("Failed to send local SDP:", e);
}
},
_createPeerConnection: function() {
if (this.pc)
throw 'The peer connection is already initialized'
Symple.log("SympleWebRTC: Creating peer connection: ", this.rtcConfig);
var self = this;
this.pc = new RTCPeerConnection(this.rtcConfig, this.rtcOptions);
this.pc.onicecandidate = function(event) {
if (event.candidate) {
//Symple.log("SympleWebRTC: Local candidate gathered:", event.candidate);
self.sendLocalCandidate(event.candidate);
}
else {
Symple.log("SympleWebRTC: Local candidate gathering complete");
}
};
this.pc.onaddstream = function(event) {
Symple.log("SympleWebRTC: Remote stream added:", URL.createObjectURL(event.stream));
// Set the state to playing once candidates have completed gathering.
// This is the best we can do until ICE onstatechange is implemented.
self.setState('playing');
self.video[0].src = URL.createObjectURL(event.stream);
self.video[0].play();
};
this.pc.onremovestream = function(event) {
Symple.log("SympleWebRTC: Remote stream removed:", event);
self.video[0].stop();
};
// Note: The following state events are completely unreliable.
// Hopefully when the spec is complete this will change, but
// until then we need to "guess" the state.
//this.pc.onconnecting = function(event) { Symple.log("SympleWebRTC: onconnecting:", event); };
//this.pc.onopen = function(event) { Symple.log("SympleWebRTC: onopen:", event); };
//this.pc.onicechange = function(event) { Symple.log("SympleWebRTC: onicechange :", event); };
//this.pc.onstatechange = function(event) { Symple.log("SympleWebRTC: onstatechange :", event); };
Symple.log("SympleWebRTC: Setupd RTCPeerConnnection with config: " + JSON.stringify(this.rtcConfig));
}
});
//
// Helpers
Symple.Media.iceCandidateType = function(candidateSDP) {
if (candidateSDP.indexOf("typ relay") != -1)
return "turn";
if (candidateSDP.indexOf("typ srflx") != -1)
return "stun";
if (candidateSDP.indexOf("typ host") != -1)
return "host";
return "unknown";
}