Adds IE support through Temasys WebRTC plugin.

This commit is contained in:
paweldomas 2015-07-10 11:57:20 +02:00
parent cd6928d770
commit ae759fab5b
25 changed files with 4009 additions and 582 deletions

5
app.js
View File

@ -21,14 +21,11 @@ var APP =
function init() { function init() {
APP.desktopsharing.init();
APP.RTC.start(); APP.RTC.start();
APP.xmpp.start(); APP.xmpp.start();
APP.statistics.start(); APP.statistics.start();
APP.connectionquality.init(); APP.connectionquality.init();
// Set default desktop sharing method
APP.desktopsharing.init();
APP.keyboardshortcut.init(); APP.keyboardshortcut.init();
APP.members.start(); APP.members.start();
} }

View File

@ -60,28 +60,31 @@
} }
#remoteVideos .videocontainer:hover { #remoteVideos .videocontainer:hover {
-webkit-box-shadow: inset 0 0 10px #FFFFFF, 0 0 10px #FFFFFF; box-shadow: inset 0 0 10px #FFFFFF, 0 0 10px #FFFFFF;
border: 2px solid #FFFFFF; border: 2px solid #FFFFFF;
} }
#remoteVideos .videocontainer.videoContainerFocused { #remoteVideos .videocontainer.videoContainerFocused {
-webkit-box-shadow: inset 0 0 28px #006d91; box-shadow: inset 0 0 28px #006d91;
border: 2px solid #006d91; border: 2px solid #006d91;
} }
#remoteVideos .videocontainer.videoContainerFocused:hover { #remoteVideos .videocontainer.videoContainerFocused:hover {
-webkit-box-shadow: inset 0 0 5px #FFFFFF, 0 0 10px #FFFFFF, inset 0 0 60px #006d91; box-shadow: inset 0 0 5px #FFFFFF, 0 0 10px #FFFFFF, inset 0 0 60px #006d91;
border: 2px solid #FFFFFF; border: 2px solid #FFFFFF;
} }
#localVideoWrapper { #localVideoWrapper {
display:inline-block; display:inline-block;
-webkit-mask-box-image: url(../images/videomask.svg); -webkit-mask-box-image: url(../images/videomask.svg);
border-radius:0px !important; border-radius:4px !important;
border: 0px !important; border: 0px !important;
} }
#remoteVideos .videocontainer>video { /* With TemasysWebRTC plugin <object/> element is used
instead of <video/> */
#remoteVideos .videocontainer>video,
#remoteVideos .videocontainer>object {
border-radius:4px; border-radius:4px;
} }
@ -92,8 +95,9 @@
-o-transform: scale(-1, 1); -o-transform: scale(-1, 1);
} }
#localVideoWrapper>video { #localVideoWrapper>video,
border-radius:0px !important; #localVideoWrapper>object {
border-radius:4px !important;
} }
#largeVideo, #largeVideo,
@ -110,8 +114,10 @@
#presentation, #presentation,
#etherpad, #etherpad,
#localVideoWrapper>video, #localVideoWrapper>video,
#localVideoWrapper>object,
#localVideoWrapper, #localVideoWrapper,
.videocontainer>video { .videocontainer>video,
.videocontainer>object {
position: absolute; position: absolute;
left: 0; left: 0;
top: 0; top: 0;
@ -363,7 +369,7 @@
margin-right: 40%; margin-right: 40%;
text-align: center; text-align: center;
background: linear-gradient(to bottom, rgba(255,255,255,.85) , rgba(255,255,255,.35)); background: linear-gradient(to bottom, rgba(255,255,255,.85) , rgba(255,255,255,.35));
-webkit-box-shadow: 0 0 2px #000000, 0 0 10px #000000; box-shadow: 0 0 2px #000000, 0 0 10px #000000;
border-bottom-left-radius: 12px; border-bottom-left-radius: 12px;
border-bottom-right-radius: 12px; border-bottom-right-radius: 12px;
display: none; display: none;

View File

@ -22,12 +22,12 @@
<script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib --> <script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
<script src="libs/toastr.js?v=1"></script><!-- notifications lib --> <script src="libs/toastr.js?v=1"></script><!-- notifications lib -->
<script src="interface_config.js?v=5"></script> <script src="interface_config.js?v=5"></script>
<script src="libs/app.bundle.js?v=98"></script> <script src="libs/app.bundle.js?v=99"></script>
<script src="analytics.js?v=1"></script><!-- google analytics plugin --> <script src="analytics.js?v=1"></script><!-- google analytics plugin -->
<link rel="stylesheet" href="css/font.css?v=7"/> <link rel="stylesheet" href="css/font.css?v=7"/>
<link rel="stylesheet" href="css/toastr.css?v=1"> <link rel="stylesheet" href="css/toastr.css?v=1">
<link rel="stylesheet" type="text/css" media="screen" href="css/main.css?v=30"/> <link rel="stylesheet" type="text/css" media="screen" href="css/main.css?v=30"/>
<link rel="stylesheet" type="text/css" media="screen" href="css/videolayout_default.css?v=17" id="videolayout_default"/> <link rel="stylesheet" type="text/css" media="screen" href="css/videolayout_default.css?v=18" id="videolayout_default"/>
<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet"> <link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
<link rel="stylesheet" href="css/jquery-impromptu.css?v=4"> <link rel="stylesheet" href="css/jquery-impromptu.css?v=4">
<link rel="stylesheet" href="css/modaldialog.css?v=3"> <link rel="stylesheet" href="css/modaldialog.css?v=3">

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,5 @@
var EventEmitter = require("events"); var EventEmitter = require("events");
var RTCBrowserType = require("./RTCBrowserType");
var RTCUtils = require("./RTCUtils.js"); var RTCUtils = require("./RTCUtils.js");
var LocalStream = require("./LocalStream.js"); var LocalStream = require("./LocalStream.js");
var DataChannels = require("./DataChannels"); var DataChannels = require("./DataChannels");
@ -99,7 +100,7 @@ var RTC = {
}, },
createRemoteStream: function (data, sid, thessrc) { createRemoteStream: function (data, sid, thessrc) {
var remoteStream = new MediaStream(data, sid, thessrc, var remoteStream = new MediaStream(data, sid, thessrc,
this.getBrowserType(), eventEmitter); RTCBrowserType.getBrowserType(), eventEmitter);
var jid = data.peerjid || APP.xmpp.myJid(); var jid = data.peerjid || APP.xmpp.myJid();
if(!this.remoteStreams[jid]) { if(!this.remoteStreams[jid]) {
this.remoteStreams[jid] = {}; this.remoteStreams[jid] = {};
@ -108,9 +109,6 @@ var RTC = {
eventEmitter.emit(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, remoteStream); eventEmitter.emit(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, remoteStream);
return remoteStream; return remoteStream;
}, },
getBrowserType: function () {
return this.rtcUtils.browser;
},
getPCConstraints: function () { getPCConstraints: function () {
return this.rtcUtils.pc_constraints; return this.rtcUtils.pc_constraints;
}, },
@ -133,6 +131,9 @@ var RTC = {
setVideoSrc: function (element, src) { setVideoSrc: function (element, src) {
this.rtcUtils.setVideoSrc(element, src); this.rtcUtils.setVideoSrc(element, src);
}, },
getVideoElementName: function () {
return RTCBrowserType.isTemasysPluginUsed() ? 'object' : 'video';
},
dispose: function() { dispose: function() {
if (this.rtcUtils) { if (this.rtcUtils) {
this.rtcUtils = null; this.rtcUtils = null;
@ -168,9 +169,22 @@ var RTC = {
DataChannels.handleSelectedEndpointEvent); DataChannels.handleSelectedEndpointEvent);
APP.UI.addListener(UIEvents.PINNED_ENDPOINT, APP.UI.addListener(UIEvents.PINNED_ENDPOINT,
DataChannels.handlePinnedEndpointEvent); DataChannels.handlePinnedEndpointEvent);
this.rtcUtils = new RTCUtils(this);
this.rtcUtils.obtainAudioAndVideoPermissions( // In case of IE we continue from 'onReady' callback
null, null, getMediaStreamUsage()); // passed to RTCUtils constructor. It will be invoked by Temasys plugin
// once it is initialized.
var onReady = function () {
eventEmitter.emit(RTCEvents.RTC_READY, true);
self.rtcUtils.obtainAudioAndVideoPermissions(
null, null, getMediaStreamUsage());
};
this.rtcUtils = new RTCUtils(this, onReady);
// Call onReady() if Temasys plugin is not used
if (!RTCBrowserType.isTemasysPluginUsed()) {
onReady();
}
}, },
muteRemoteVideoStream: function (jid, value) { muteRemoteVideoStream: function (jid, value) {
var stream; var stream;
@ -211,6 +225,10 @@ var RTC = {
callback(); callback();
}; };
} }
// FIXME: Workaround for FF/IE/Safari
if (stream && stream.videoStream) {
stream = stream.videoStream;
}
var videoStream = this.rtcUtils.createStream(stream, true); var videoStream = this.rtcUtils.createStream(stream, true);
this.localVideo = this.createLocalStream(videoStream, "video", true, type); this.localVideo = this.createLocalStream(videoStream, "video", true, type);
// Stop the stream to trigger onended event for old stream // Stop the stream to trigger onended event for old stream

View File

@ -0,0 +1,152 @@
var currentBrowser;
var browserVersion;
var RTCBrowserType = {
RTC_BROWSER_CHROME: "rtc_browser.chrome",
RTC_BROWSER_OPERA: "rtc_browser.opera",
RTC_BROWSER_FIREFOX: "rtc_browser.firefox",
RTC_BROWSER_IEXPLORER: "rtc_browser.iexplorer",
RTC_BROWSER_SAFARI: "rtc_browser.safari",
getBrowserType: function () {
return currentBrowser;
},
isChrome: function () {
return currentBrowser === RTCBrowserType.RTC_BROWSER_CHROME;
},
isOpera: function () {
return currentBrowser === RTCBrowserType.RTC_BROWSER_OPERA;
},
isFirefox: function () {
return currentBrowser === RTCBrowserType.RTC_BROWSER_FIREFOX;
},
isIExplorer: function () {
return currentBrowser === RTCBrowserType.RTC_BROWSER_IEXPLORER;
},
isSafari: function () {
return currentBrowser === RTCBrowserType.RTC_BROWSER_SAFARI;
},
isTemasysPluginUsed: function () {
return RTCBrowserType.isIExplorer() || RTCBrowserType.isSafari();
},
getFirefoxVersion: function () {
return RTCBrowserType.isFirefox() ? browserVersion : null;
},
getChromeVersion: function () {
return RTCBrowserType.isChrome() ? browserVersion : null;
}
// Add version getters for other browsers when needed
};
// detectOpera() must be called before detectChrome() !!!
// otherwise Opera wil be detected as Chrome
function detectChrome() {
if (navigator.webkitGetUserMedia) {
currentBrowser = RTCBrowserType.RTC_BROWSER_CHROME;
var userAgent = navigator.userAgent.toLowerCase();
// We can assume that user agent is chrome, because it's
// enforced when 'ext' streaming method is set
var ver = parseInt(userAgent.match(/chrome\/(\d+)\./)[1], 10);
console.log("This appears to be Chrome, ver: " + ver);
return ver;
}
return null;
}
function detectOpera() {
var userAgent = navigator.userAgent;
if (userAgent.match(/Opera|OPR/)) {
currentBrowser = RTCBrowserType.RTC_BROWSER_OPERA;
var version = userAgent.match(/(Opera|OPR) ?\/?(\d+)\.?/)[2];
console.info("This appears to be Opera, ver: " + version);
return version;
}
return null;
}
function detectFirefox() {
if (navigator.mozGetUserMedia) {
currentBrowser = RTCBrowserType.RTC_BROWSER_FIREFOX;
var version = parseInt(
navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
console.log('This appears to be Firefox, ver: ' + version);
return version;
}
return null;
}
function detectSafari() {
if ((/^((?!chrome).)*safari/i.test(navigator.userAgent))) {
currentBrowser = RTCBrowserType.RTC_BROWSER_SAFARI;
console.info("This appears to be Safari");
// FIXME detect Safari version when needed
return 1;
}
return null;
}
function detectIE() {
var version;
var ua = window.navigator.userAgent;
var msie = ua.indexOf('MSIE ');
if (msie > 0) {
// IE 10 or older => return version number
version = parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
}
var trident = ua.indexOf('Trident/');
if (!version && trident > 0) {
// IE 11 => return version number
var rv = ua.indexOf('rv:');
version = parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
}
var edge = ua.indexOf('Edge/');
if (!version && edge > 0) {
// IE 12 => return version number
version = parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
}
if (version) {
currentBrowser = RTCBrowserType.RTC_BROWSER_IEXPLORER;
console.info("This appears to be IExplorer, ver: " + version);
}
return version;
}
function detectBrowser() {
var version;
var detectors = [
detectOpera,
detectChrome,
detectFirefox,
detectIE,
detectSafari
];
// Try all browser detectors
for (var i = 0; i < detectors.length; i++) {
version = detectors[i]();
if (version)
return version;
}
console.error("Failed to detect browser type");
return undefined;
}
browserVersion = detectBrowser();
module.exports = RTCBrowserType;

View File

@ -1,5 +1,7 @@
var RTCBrowserType = require("../../service/RTC/RTCBrowserType.js"); var RTCBrowserType = require("./RTCBrowserType");
var Resolutions = require("../../service/RTC/Resolutions"); var Resolutions = require("../../service/RTC/Resolutions");
var AdapterJS = require("./adapter.screenshare");
var SDPUtil = require("../xmpp/SDPUtil");
var currentResolution = null; var currentResolution = null;
@ -58,16 +60,30 @@ function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid
constraints.audio = { mandatory: {}, optional: []};// same behaviour as true constraints.audio = { mandatory: {}, optional: []};// same behaviour as true
} }
if (um.indexOf('screen') >= 0) { if (um.indexOf('screen') >= 0) {
constraints.video = { if (RTCBrowserType.isChrome()) {
mandatory: { constraints.video = {
chromeMediaSource: "screen", mandatory: {
googLeakyBucket: true, chromeMediaSource: "screen",
maxWidth: window.screen.width, googLeakyBucket: true,
maxHeight: window.screen.height, maxWidth: window.screen.width,
maxFrameRate: 3 maxHeight: window.screen.height,
}, maxFrameRate: 3
optional: [] },
}; optional: []
};
} else if (RTCBrowserType.isTemasysPluginUsed()) {
constraints.video = {
optional: [
{
sourceId: AdapterJS.WebRTCPlugin.plugin.screensharingKey
}
]
};
} else {
console.error(
"'screen' WebRTC media source is supported only in Chrome" +
" and with Temasys plugin");
}
} }
if (um.indexOf('desktop') >= 0) { if (um.indexOf('desktop') >= 0) {
constraints.video = { constraints.video = {
@ -124,16 +140,14 @@ function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid
} }
function RTCUtils(RTCService) function RTCUtils(RTCService, onTemasysPluginReady)
{ {
var self = this;
this.service = RTCService; this.service = RTCService;
if (navigator.mozGetUserMedia) { if (RTCBrowserType.isFirefox()) {
console.log('This appears to be Firefox'); var FFversion = RTCBrowserType.getFirefoxVersion();
var version = parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10); if (FFversion >= 40 && config.useBundle && config.useRtcpMux) {
if (version >= 40
&& config.useBundle && config.useRtcpMux) {
this.peerconnection = mozRTCPeerConnection; this.peerconnection = mozRTCPeerConnection;
this.browser = RTCBrowserType.RTC_BROWSER_FIREFOX;
this.getUserMedia = navigator.mozGetUserMedia.bind(navigator); this.getUserMedia = navigator.mozGetUserMedia.bind(navigator);
this.pc_constraints = {}; this.pc_constraints = {};
this.attachMediaStream = function (element, stream) { this.attachMediaStream = function (element, stream) {
@ -155,7 +169,7 @@ function RTCUtils(RTCService)
{ {
tracks = stream.getAudioTracks(); tracks = stream.getAudioTracks();
} }
return tracks[0].id.replace(/[\{,\}]/g,""); return SDPUtil.filter_special_chars(tracks[0].id);
}; };
this.getVideoSrc = function (element) { this.getVideoSrc = function (element) {
if(!element) if(!element)
@ -169,14 +183,16 @@ function RTCUtils(RTCService)
RTCSessionDescription = mozRTCSessionDescription; RTCSessionDescription = mozRTCSessionDescription;
RTCIceCandidate = mozRTCIceCandidate; RTCIceCandidate = mozRTCIceCandidate;
} else { } else {
console.error(
"Firefox requirements not met, ver: " + FFversion +
", bundle: " + config.useBundle +
", rtcp-mux: " + config.useRtcpMux);
window.location.href = 'unsupported_browser.html'; window.location.href = 'unsupported_browser.html';
return; return;
} }
} else if (navigator.webkitGetUserMedia) { } else if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) {
console.log('This appears to be Chrome');
this.peerconnection = webkitRTCPeerConnection; this.peerconnection = webkitRTCPeerConnection;
this.browser = RTCBrowserType.RTC_BROWSER_CHROME;
this.getUserMedia = navigator.webkitGetUserMedia.bind(navigator); this.getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
this.attachMediaStream = function (element, stream) { this.attachMediaStream = function (element, stream) {
element.attr('src', webkitURL.createObjectURL(stream)); element.attr('src', webkitURL.createObjectURL(stream));
@ -184,7 +200,7 @@ function RTCUtils(RTCService)
this.getStreamID = function (stream) { this.getStreamID = function (stream) {
// streams from FF endpoints have the characters '{' and '}' // streams from FF endpoints have the characters '{' and '}'
// that make jQuery choke. // that make jQuery choke.
return stream.id.replace(/[\{,\}]/g,""); return SDPUtil.filter_special_chars(stream.id);
}; };
this.getVideoSrc = function (element) { this.getVideoSrc = function (element) {
if(!element) if(!element)
@ -211,12 +227,62 @@ function RTCUtils(RTCService)
}; };
} }
} }
else // Detect IE/Safari
{ else if (RTCBrowserType.isTemasysPluginUsed()) {
try { console.log('Browser does not appear to be WebRTC-capable'); } catch (e) { }
AdapterJS.WebRTCPlugin.setLogLevel(
AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS.VERBOSE);
AdapterJS.webRTCReady(function (isPlugin) {
self.peerconnection = RTCPeerConnection;
self.getUserMedia = getUserMedia;
self.attachMediaStream = function (element, stream) {
if (stream.id === "dummyAudio" || stream.id === "dummyVideo") {
return;
}
attachMediaStream(element[0], stream);
};
self.getStreamID = function (stream) {
var id = SDPUtil.filter_special_chars(stream.label);
return id;
};
self.getVideoSrc = function (element) {
if (!element) {
console.warn("Attempt to get video SRC of null element");
return null;
}
var src = null;
var children = element.children;
for (var i = 0; i !== children.length; ++i) {
if (children[i].name === 'streamId') {
return children[i].value;
}
}
//console.info(element.id + " SRC: " + src);
return null;
};
self.setVideoSrc = function (element, src) {
//console.info("Set video src: ", element, src);
if (!src) {
console.warn("Not attaching video stream, 'src' is null");
return;
}
AdapterJS.WebRTCPlugin.WaitForPluginReady();
var stream = AdapterJS.WebRTCPlugin.plugin
.getStreamWithId(AdapterJS.WebRTCPlugin.pageId, src);
attachMediaStream(element, stream);
};
onTemasysPluginReady(isPlugin);
});
} else {
try {
console.log('Browser does not appear to be WebRTC-capable');
} catch (e) { }
window.location.href = 'unsupported_browser.html'; window.location.href = 'unsupported_browser.html';
return;
} }
} }
@ -232,7 +298,7 @@ RTCUtils.prototype.getUserMediaWithConstraints = function(
var constraints = getConstraints( var constraints = getConstraints(
um, resolution, bandwidth, fps, desktopStream, isAndroid); um, resolution, bandwidth, fps, desktopStream, isAndroid);
var isFF = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; console.info("Get media constraints", constraints);
var self = this; var self = this;
@ -311,9 +377,9 @@ RTCUtils.prototype.obtainAudioAndVideoPermissions =
return; return;
} }
if (navigator.mozGetUserMedia) { if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed()) {
// With FF we can't split the stream into audio and video because FF // With FF/IE we can't split the stream into audio and video because FF
// doesn't support media stream constructors. So, we need to get the // doesn't support media stream constructors. So, we need to get the
// audio stream separately from the video stream using two distinct GUM // audio stream separately from the video stream using two distinct GUM
// calls. Not very user friendly :-( but we don't have many other // calls. Not very user friendly :-( but we don't have many other
@ -321,31 +387,41 @@ RTCUtils.prototype.obtainAudioAndVideoPermissions =
// //
// Note that we pack those 2 streams in a single object and pass it to // Note that we pack those 2 streams in a single object and pass it to
// the successCallback method. // the successCallback method.
var obtainVideo = function (audioStream) {
self.getUserMediaWithConstraints( self.getUserMediaWithConstraints(
['audio'], ['video'],
function (audioStream) { function (videoStream) {
self.getUserMediaWithConstraints( return successCallback({
['video'], audioStream: audioStream,
function (videoStream) { videoStream: videoStream
return self.successCallback({ });
audioStream: audioStream, },
videoStream: videoStream function (error) {
}); console.error(
}, 'failed to obtain video stream - stop', error);
function (error) { self.errorCallback(error);
console.error('failed to obtain video stream - stop', },
error); config.resolution || '360');
return self.successCallback(null); };
}, var obtainAudio = function () {
config.resolution || '360'); self.getUserMediaWithConstraints(
}, ['audio'],
function (error) { function (audioStream) {
console.error('failed to obtain audio stream - stop', if (newDevices.indexOf('video') !== -1)
error); obtainVideo(audioStream);
return self.successCallback(null); },
} function (error) {
); console.error(
'failed to obtain audio stream - stop', error);
self.errorCallback(error);
}
);
};
if (newDevices.indexOf('audio') !== -1) {
obtainAudio();
} else {
obtainVideo(null);
}
} else { } else {
this.getUserMediaWithConstraints( this.getUserMediaWithConstraints(
newDevices, newDevices,
@ -358,12 +434,12 @@ RTCUtils.prototype.obtainAudioAndVideoPermissions =
config.resolution || '360'); config.resolution || '360');
} }
} };
RTCUtils.prototype.successCallback = function (stream, usageOptions) { RTCUtils.prototype.successCallback = function (stream, usageOptions) {
// If this is FF, the stream parameter is *not* a MediaStream object, it's // If this is FF or IE, the stream parameter is *not* a MediaStream object,
// an object with two properties: audioStream, videoStream. // it's an object with two properties: audioStream, videoStream.
if(stream && !navigator.mozGetUserMedia) if (stream && stream.getAudioTracks && stream.getVideoTracks)
console.log('got', stream, stream.getAudioTracks().length, console.log('got', stream, stream.getAudioTracks().length,
stream.getVideoTracks().length); stream.getVideoTracks().length);
this.handleLocalStream(stream, usageOptions); this.handleLocalStream(stream, usageOptions);
@ -427,10 +503,17 @@ RTCUtils.prototype.handleLocalStream = function(stream, usageOptions)
} }
} }
} }
else else if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed())
{//firefox { // Firefox and Temasys plugin
audioStream = stream.audioStream; if (stream && stream.audioStream)
videoStream = stream.videoStream; audioStream = stream.audioStream;
else
audioStream = new DummyMediaStream("dummyAudio");
if (stream && stream.videoStream)
videoStream = stream.videoStream;
else
videoStream = new DummyMediaStream("dummyVideo");
} }
var audioMuted = (usageOptions && usageOptions.audio === false), var audioMuted = (usageOptions && usageOptions.audio === false),
@ -447,15 +530,20 @@ RTCUtils.prototype.handleLocalStream = function(stream, usageOptions)
videoMuted, videoGUM); videoMuted, videoGUM);
}; };
RTCUtils.prototype.createStream = function(stream, isVideo) function DummyMediaStream(id) {
{ this.id = id;
this.label = id;
this.stop = function() { };
this.getAudioTracks = function() { return []; }
this.getVideoTracks = function() { return []; }
}
RTCUtils.prototype.createStream = function(stream, isVideo) {
var newStream = null; var newStream = null;
if(window.webkitMediaStream) if (window.webkitMediaStream) {
{
newStream = new webkitMediaStream(); newStream = new webkitMediaStream();
if(newStream) if (newStream) {
{ var tracks = (isVideo ? stream.getVideoTracks() : stream.getAudioTracks());
var tracks = (isVideo? stream.getVideoTracks() : stream.getAudioTracks());
for (i = 0; i < tracks.length; i++) { for (i = 0; i < tracks.length; i++) {
newStream.addTrack(tracks[i]); newStream.addTrack(tracks[i]);
@ -463,8 +551,14 @@ RTCUtils.prototype.createStream = function(stream, isVideo)
} }
} }
else else {
newStream = stream; // FIXME: this is duplicated with 'handleLocalStream' !!!
if (stream) {
newStream = stream;
} else {
newStream = new DummyMediaStream(isVideo ? "dummyVideo" : "dummyAudio");
}
}
return newStream; return newStream;
}; };

File diff suppressed because it is too large Load Diff

View File

@ -24,6 +24,7 @@ var CQEvents = require("../../service/connectionquality/CQEvents");
var DesktopSharingEventTypes var DesktopSharingEventTypes
= require("../../service/desktopsharing/DesktopSharingEventTypes"); = require("../../service/desktopsharing/DesktopSharingEventTypes");
var RTCEvents = require("../../service/RTC/RTCEvents"); var RTCEvents = require("../../service/RTC/RTCEvents");
var RTCBrowserType = require("../RTC/RTCBrowserType");
var StreamEventTypes = require("../../service/RTC/StreamEventTypes"); var StreamEventTypes = require("../../service/RTC/StreamEventTypes");
var XMPPEvents = require("../../service/xmpp/XMPPEvents"); var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var MemberEvents = require("../../service/members/Events"); var MemberEvents = require("../../service/members/Events");
@ -253,7 +254,7 @@ function registerListeners() {
{ {
// might need to update the direction if participant just went from sendrecv to recvonly // might need to update the direction if participant just went from sendrecv to recvonly
if (stream.type === 'video' || stream.type === 'screen') { if (stream.type === 'video' || stream.type === 'screen') {
var el = $('#participant_' + Strophe.getResourceFromJid(jid) + '>video'); var el = $('#participant_' + Strophe.getResourceFromJid(jid) + '>' + APP.RTC.getVideoElementName());
switch (stream.direction) { switch (stream.direction) {
case 'sendrecv': case 'sendrecv':
el.show(); el.show();
@ -405,7 +406,9 @@ UI.start = function (init) {
$('#notice').css({display: 'block'}); $('#notice').css({display: 'block'});
} }
document.getElementById('largeVideo').volume = 0; if (!RTCBrowserType.isIExplorer()) {
document.getElementById('largeVideo').volume = 0;
}
if(config.requireDisplayName) { if(config.requireDisplayName) {
var currentSettings = Settings.getSettings(); var currentSettings = Settings.getSettings();

View File

@ -336,7 +336,10 @@ ConnectionIndicator.prototype.create = function () {
*/ */
ConnectionIndicator.prototype.remove = function() ConnectionIndicator.prototype.remove = function()
{ {
this.connectionIndicatorContainer.remove(); if (this.connectionIndicatorContainer.parentNode) {
this.connectionIndicatorContainer.parentNode.removeChild(
this.connectionIndicatorContainer);
}
this.popover.forceHide(); this.popover.forceHide();
}; };

View File

@ -1,9 +1,11 @@
var Avatar = require("../avatar/Avatar"); var Avatar = require("../avatar/Avatar");
var RTCBrowserType = require("../../RTC/RTCBrowserType");
var UIUtil = require("../util/UIUtil"); var UIUtil = require("../util/UIUtil");
var UIEvents = require("../../../service/UI/UIEvents"); var UIEvents = require("../../../service/UI/UIEvents");
var xmpp = require("../../xmpp/xmpp"); var xmpp = require("../../xmpp/xmpp");
var video = $('#largeVideo'); // FIXME: With Temasys we have to re-select everytime
//var video = $('#largeVideo');
var currentVideoWidth = null; var currentVideoWidth = null;
var currentVideoHeight = null; var currentVideoHeight = null;
@ -244,9 +246,7 @@ function changeVideo(isVisible) {
} }
if (isVisible) { if (isVisible) {
// using "this" should be ok because we're called $('#largeVideo').fadeIn(300);
// from within the fadeOut event.
$(this).fadeIn(300);
} }
if(oldSmallVideo) if(oldSmallVideo)
@ -260,12 +260,16 @@ var LargeVideo = {
this.eventEmitter = emitter; this.eventEmitter = emitter;
var self = this; var self = this;
// Listen for large video size updates // Listen for large video size updates
document.getElementById('largeVideo') var largeVideo = $('#largeVideo')[0];
.addEventListener('loadedmetadata', function (e) { var onplaying = function (arg1, arg2, arg3) {
currentVideoWidth = this.videoWidth; // re-select
currentVideoHeight = this.videoHeight; if (RTCBrowserType.isTemasysPluginUsed())
self.position(currentVideoWidth, currentVideoHeight); largeVideo = $('#largeVideo')[0];
}); currentVideoWidth = largeVideo.videoWidth;
currentVideoHeight = largeVideo.videoHeight;
self.position(currentVideoWidth, currentVideoHeight);
};
largeVideo.onplaying = onplaying;
}, },
/** /**
* Indicates if the large video is currently visible. * Indicates if the large video is currently visible.
@ -273,14 +277,14 @@ var LargeVideo = {
* @return <tt>true</tt> if visible, <tt>false</tt> - otherwise * @return <tt>true</tt> if visible, <tt>false</tt> - otherwise
*/ */
isLargeVideoVisible: function() { isLargeVideoVisible: function() {
return video.is(':visible'); return $('#largeVideo').is(':visible');
}, },
/** /**
* Updates the large video with the given new video source. * Updates the large video with the given new video source.
*/ */
updateLargeVideo: function(resourceJid, forceUpdate) { updateLargeVideo: function(resourceJid, forceUpdate) {
console.log('hover in', resourceJid);
var newSmallVideo = this.VideoLayout.getSmallVideo(resourceJid); var newSmallVideo = this.VideoLayout.getSmallVideo(resourceJid);
console.log('hover in ' + resourceJid + ', video: ', newSmallVideo);
if ((currentSmallVideo && currentSmallVideo.resourceJid !== resourceJid) if ((currentSmallVideo && currentSmallVideo.resourceJid !== resourceJid)
|| forceUpdate) { || forceUpdate) {
@ -303,8 +307,8 @@ var LargeVideo = {
this.eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, this.eventEmitter.emit(UIEvents.SELECTED_ENDPOINT,
resourceJid); resourceJid);
} }
$('#largeVideo').fadeOut(300,
video.fadeOut(300, changeVideo.bind(video, this.isLargeVideoVisible())); changeVideo.bind($('#largeVideo'), this.isLargeVideoVisible()));
} else { } else {
if(currentSmallVideo) { if(currentSmallVideo) {
currentSmallVideo.showAvatar(); currentSmallVideo.showAvatar();

View File

@ -3,6 +3,7 @@ var ConnectionIndicator = require("./ConnectionIndicator");
var NicknameHandler = require("../util/NicknameHandler"); var NicknameHandler = require("../util/NicknameHandler");
var UIUtil = require("../util/UIUtil"); var UIUtil = require("../util/UIUtil");
var LargeVideo = require("./LargeVideo"); var LargeVideo = require("./LargeVideo");
var RTCBrowserType = require("../../RTC/RTCBrowserType");
function LocalVideo(VideoLayout) function LocalVideo(VideoLayout)
{ {
@ -163,13 +164,18 @@ LocalVideo.prototype.changeVideo = function (stream, isMuted) {
var self = this; var self = this;
function localVideoClick(event) { function localVideoClick(event) {
event.stopPropagation(); // FIXME: with Temasys plugin event arg is not an event, but
// the clicked object itself, so we have to skip this call
if (event.stopPropagation) {
event.stopPropagation();
}
self.VideoLayout.handleVideoThumbClicked( self.VideoLayout.handleVideoThumbClicked(
false, false,
APP.xmpp.myResource()); APP.xmpp.myResource());
} }
$('#localVideoContainer').click(localVideoClick); $('#localVideoContainer').off('click');
$('#localVideoContainer').on('click', localVideoClick);
// Add hover handler // Add hover handler
$('#localVideoContainer').hover( $('#localVideoContainer').hover(
@ -192,8 +198,10 @@ LocalVideo.prototype.changeVideo = function (stream, isMuted) {
var localVideo = document.createElement('video'); var localVideo = document.createElement('video');
localVideo.id = 'localVideo_' + localVideo.id = 'localVideo_' +
APP.RTC.getStreamID(stream.getOriginalStream()); APP.RTC.getStreamID(stream.getOriginalStream());
localVideo.autoplay = true; if (!RTCBrowserType.isIExplorer()) {
localVideo.volume = 0; // is it required if audio is separated ? localVideo.autoplay = true;
localVideo.volume = 0; // is it required if audio is separated ?
}
localVideo.oncontextmenu = function () { return false; }; localVideo.oncontextmenu = function () { return false; };
var localVideoContainer = document.getElementById('localVideoWrapper'); var localVideoContainer = document.getElementById('localVideoWrapper');
@ -203,7 +211,9 @@ LocalVideo.prototype.changeVideo = function (stream, isMuted) {
// Add click handler to both video and video wrapper elements in case // Add click handler to both video and video wrapper elements in case
// there's no video. // there's no video.
localVideoSelector.click(localVideoClick);
// onclick has to be used with Temasys plugin
localVideo.onclick = localVideoClick;
if (this.flipX) { if (this.flipX) {
localVideoSelector.addClass("flipVideoX"); localVideoSelector.addClass("flipVideoX");
@ -214,6 +224,9 @@ LocalVideo.prototype.changeVideo = function (stream, isMuted) {
// Add stream ended handler // Add stream ended handler
stream.getOriginalStream().onended = function () { stream.getOriginalStream().onended = function () {
// We have to re-select after attach when Temasys plugin is used,
// because <video> element is replaced with <object>
localVideo = $('#' + localVideo.id)[0];
localVideoContainer.removeChild(localVideo); localVideoContainer.removeChild(localVideo);
self.VideoLayout.updateRemovedVideo(APP.xmpp.myResource()); self.VideoLayout.updateRemovedVideo(APP.xmpp.myResource());
}; };

View File

@ -3,6 +3,7 @@ var SmallVideo = require("./SmallVideo");
var AudioLevels = require("../audio_levels/AudioLevels"); var AudioLevels = require("../audio_levels/AudioLevels");
var LargeVideo = require("./LargeVideo"); var LargeVideo = require("./LargeVideo");
var Avatar = require("../avatar/Avatar"); var Avatar = require("../avatar/Avatar");
var RTCBrowserType = require("../../RTC/RTCBrowserType");
function RemoteVideo(peerJid, VideoLayout) function RemoteVideo(peerJid, VideoLayout)
{ {
@ -146,14 +147,15 @@ RemoteVideo.prototype.removeRemoteStreamElement = function (stream, isVideo, id)
select.remove(); select.remove();
var audioCount = $('#' + this.videoSpanId + '>audio').length; var audioCount = $('#' + this.videoSpanId + '>audio').length;
var videoCount = $('#' + this.videoSpanId + '>video').length; var videoCount = $('#' + this.videoSpanId + '>' + APP.RTC.getVideoElementName()).length;
if (!audioCount && !videoCount) { if (!audioCount && !videoCount) {
console.log("Remove whole user", this.videoSpanId); console.log("Remove whole user", this.videoSpanId);
if(this.connectionIndicator) if(this.connectionIndicator)
this.connectionIndicator.remove(); this.connectionIndicator.remove();
// Remove whole container // Remove whole container
this.container.remove(); if (this.container.parentNode)
this.container.parentNode.removeChild(this.container);
this.VideoLayout.resizeThumbnails(); this.VideoLayout.resizeThumbnails();
} }
@ -185,11 +187,21 @@ RemoteVideo.prototype.addRemoteStreamElement = function (sid, stream, thessrc) {
if (isVideo && stream.id !== 'mixedmslabel') { if (isVideo && stream.id !== 'mixedmslabel') {
var onPlayingHandler = function () { var onPlayingHandler = function () {
// FIXME: why do i have to do this for FF? // FIXME: why do i have to do this for FF?
APP.RTC.attachMediaStream(sel, stream); if (RTCBrowserType.isFirefox()) {
APP.RTC.attachMediaStream(sel, stream);
}
if (RTCBrowserType.isTemasysPluginUsed()) {
sel = $('#' + newElementId);
}
self.VideoLayout.videoactive(sel, self.resourceJid); self.VideoLayout.videoactive(sel, self.resourceJid);
sel.off("playing", onPlayingHandler); sel[0].onplaying = null;
if (RTCBrowserType.isTemasysPluginUsed()) {
// 'currentTime' is used to check if the video has started
// and the value is not set by the plugin, so we do it
sel[0].currentTime = 1;
}
}; };
sel.on("playing", onPlayingHandler); sel[0].onplaying = onPlayingHandler;
} }
APP.RTC.attachMediaStream(sel, stream); APP.RTC.attachMediaStream(sel, stream);
@ -203,23 +215,35 @@ RemoteVideo.prototype.addRemoteStreamElement = function (sid, stream, thessrc) {
}; };
// Name of video element name is different for IE/Safari
var videoElem = APP.RTC.getVideoElementName();
// Add click handler. // Add click handler.
this.container.onclick = function (event) { var onClickHandler = function (event) {
/* /*
* FIXME It turns out that videoThumb may not exist (if there is * FIXME It turns out that videoThumb may not exist (if there is
* no actual video). * no actual video).
*/ */
var videoThumb = $('#' + self.videoSpanId + '>video').get(0); var videoThumb = $('#' + self.videoSpanId + '>' + videoElem).get(0);
if (videoThumb) { if (videoThumb) {
self.VideoLayout.handleVideoThumbClicked( self.VideoLayout.handleVideoThumbClicked(
false, false,
self.resourceJid); self.resourceJid);
} }
// On IE we need to populate this handler on video <object>
event.stopPropagation(); // and it does not give event instance as an argument,
event.preventDefault(); // so we check here for methods.
if (event.stopPropagation && event.preventDefault) {
event.stopPropagation();
event.preventDefault();
}
return false; return false;
}; };
this.container.onclick = onClickHandler;
// reselect
if (RTCBrowserType.isTemasysPluginUsed())
sel = $('#' + newElementId);
sel[0].onclick = onClickHandler;
//FIXME //FIXME
// Add hover handler // Add hover handler
@ -229,9 +253,9 @@ RemoteVideo.prototype.addRemoteStreamElement = function (sid, stream, thessrc) {
}, },
function() { function() {
var videoSrc = null; var videoSrc = null;
if ($('#' + self.videoSpanId + '>video') var videoSelector = $('#' + self.videoSpanId + '>' + videoElem);
&& $('#' + self.videoSpanId + '>video').length > 0) { if (videoSelector && videoSelector.length > 0) {
videoSrc = APP.RTC.getVideoSrc($('#' + self.videoSpanId + '>video').get(0)); videoSrc = APP.RTC.getVideoSrc(videoSelector.get(0));
} }
// If the video has been "pinned" by the user we want to // If the video has been "pinned" by the user we want to
@ -241,7 +265,7 @@ RemoteVideo.prototype.addRemoteStreamElement = function (sid, stream, thessrc) {
self.showDisplayName(false); self.showDisplayName(false);
} }
); );
} },
/** /**
* Show/hide peer container for the given resourceJid. * Show/hide peer container for the given resourceJid.

View File

@ -1,5 +1,6 @@
var UIUtil = require("../util/UIUtil"); var UIUtil = require("../util/UIUtil");
var LargeVideo = require("./LargeVideo"); var LargeVideo = require("./LargeVideo");
var RTCBrowserType = require("../../RTC/RTCBrowserType");
function SmallVideo(){ function SmallVideo(){
this.isMuted = false; this.isMuted = false;
@ -96,7 +97,9 @@ SmallVideo.createStreamElement = function (sid, stream) {
+ sid + '_' + APP.RTC.getStreamID(stream); + sid + '_' + APP.RTC.getStreamID(stream);
element.id = id; element.id = id;
element.autoplay = true; if (!RTCBrowserType.isIExplorer()) {
element.autoplay = true;
}
element.oncontextmenu = function () { return false; }; element.oncontextmenu = function () { return false; };
return element; return element;
@ -204,7 +207,7 @@ SmallVideo.prototype.enableDominantSpeaker = function (isEnable)
return; return;
} }
var video = $('#' + this.videoSpanId + '>video'); var video = $('#' + this.videoSpanId + '>' + APP.RTC.getVideoElementName());
if (video && video.length > 0) { if (video && video.length > 0) {
if (isEnable) { if (isEnable) {
@ -275,8 +278,10 @@ SmallVideo.prototype.createModeratorIndicatorElement = function () {
SmallVideo.prototype.getSrc = function () { SmallVideo.prototype.getSrc = function () {
return APP.RTC.getVideoSrc($('#' + this.videoSpanId).find("video").get(0)); var videoElement = APP.RTC.getVideoElementName();
} return APP.RTC.getVideoSrc(
$('#' + this.videoSpanId).find(videoElement).get(0));
},
SmallVideo.prototype.focus = function(isFocused) SmallVideo.prototype.focus = function(isFocused)
{ {
@ -290,7 +295,8 @@ SmallVideo.prototype.focus = function(isFocused)
} }
SmallVideo.prototype.hasVideo = function () { SmallVideo.prototype.hasVideo = function () {
return $("#" + this.videoSpanId).find("video").length !== 0; return $("#" + this.videoSpanId).find(
APP.RTC.getVideoElementName()).length !== 0;
} }
/** /**
@ -302,7 +308,8 @@ SmallVideo.prototype.showAvatar = function (show) {
if(!this.hasAvatar) if(!this.hasAvatar)
return; return;
var video = $('#' + this.videoSpanId).find("video"); var videoElem = APP.RTC.getVideoElementName();
var video = $('#' + this.videoSpanId).find(videoElem);
var avatar = $('#avatar_' + this.resourceJid); var avatar = $('#avatar_' + this.resourceJid);
if (show === undefined || show === null) { if (show === undefined || show === null) {

View File

@ -3,6 +3,10 @@ var Avatar = require("../avatar/Avatar");
var ContactList = require("../side_pannels/contactlist/ContactList"); var ContactList = require("../side_pannels/contactlist/ContactList");
var MediaStreamType = require("../../../service/RTC/MediaStreamTypes"); var MediaStreamType = require("../../../service/RTC/MediaStreamTypes");
var UIEvents = require("../../../service/UI/UIEvents"); var UIEvents = require("../../../service/UI/UIEvents");
var RTC = require("../../RTC/RTC");
var RTCBrowserType = require('../../RTC/RTCBrowserType');
var RemoteVideo = require("./RemoteVideo"); var RemoteVideo = require("./RemoteVideo");
var LargeVideo = require("./LargeVideo"); var LargeVideo = require("./LargeVideo");
var LocalVideo = require("./LocalVideo"); var LocalVideo = require("./LocalVideo");
@ -48,11 +52,15 @@ var VideoLayout = (function (my) {
}; };
my.changeLocalAudio = function(stream, isMuted) { my.changeLocalAudio = function(stream, isMuted) {
if(isMuted) if (isMuted)
APP.UI.setAudioMuted(true, true); APP.UI.setAudioMuted(true, true);
APP.RTC.attachMediaStream($('#localAudio'), stream.getOriginalStream()); APP.RTC.attachMediaStream($('#localAudio'), stream.getOriginalStream());
document.getElementById('localAudio').autoplay = true; var localAudio = document.getElementById('localAudio');
document.getElementById('localAudio').volume = 0; // Writing volume not allowed in IE
if (!RTCBrowserType.isIExplorer()) {
localAudio.autoplay = true;
localAudio.volume = 0;
}
}; };
my.changeLocalVideo = function(stream, isMuted) { my.changeLocalVideo = function(stream, isMuted) {
@ -107,22 +115,26 @@ var VideoLayout = (function (my) {
* @param removedVideoSrc src stream identifier of the video. * @param removedVideoSrc src stream identifier of the video.
*/ */
my.updateRemovedVideo = function(resourceJid) { my.updateRemovedVideo = function(resourceJid) {
var videoElem = RTC.getVideoElementName();
if (resourceJid === LargeVideo.getResourceJid()) { if (resourceJid === LargeVideo.getResourceJid()) {
// this is currently displayed as large // this is currently displayed as large
// pick the last visible video in the row // pick the last visible video in the row
// if nobody else is left, this picks the local video // if nobody else is left, this picks the local video
var pick var pick
= $('#remoteVideos>span[id!="mixedstream"]:visible:last>video') = $('#remoteVideos>' +
.get(0); 'span[id!="mixedstream"]:visible:last>' + videoElem).get(0);
if (!pick) { if (!pick) {
console.info("Last visible video no longer exists"); console.info("Last visible video no longer exists");
pick = $('#remoteVideos>span[id!="mixedstream"]>video').get(0); pick = $('#remoteVideos>' +
'span[id!="mixedstream"]>' + videoElem).get(0);
if (!pick || !APP.RTC.getVideoSrc(pick)) { if (!pick || !APP.RTC.getVideoSrc(pick)) {
// Try local video // Try local video
console.info("Fallback to local video..."); console.info("Fallback to local video...");
pick = $('#remoteVideos>span>span>video').get(0); pick = $('#remoteVideos>span>span>' + videoElem).get(0);
} }
} }
@ -223,10 +235,13 @@ var VideoLayout = (function (my) {
LargeVideo.updateLargeVideo(resourceJid); LargeVideo.updateLargeVideo(resourceJid);
$('audio').each(function (idx, el) { // Writing volume not allowed in IE
el.volume = 0; if (!RTCBrowserType.isIExplorer()) {
el.volume = 1; $('audio').each(function (idx, el) {
}); el.volume = 0;
el.volume = 1;
});
}
}; };
@ -452,7 +467,8 @@ var VideoLayout = (function (my) {
var resource = Strophe.getResourceFromJid(jid); var resource = Strophe.getResourceFromJid(jid);
var videoContainer = $("#participant_" + resource); var videoContainer = $("#participant_" + resource);
if (videoContainer.length > 0) { if (videoContainer.length > 0) {
var videoThumb = $('video', videoContainer).get(0); var videoThumb
= $(RTC.getVideoElementName(), videoContainer).get(0);
// It is not always the case that a videoThumb exists (if there is // It is not always the case that a videoThumb exists (if there is
// no actual video). // no actual video).
if (videoThumb) { if (videoThumb) {
@ -571,7 +587,8 @@ var VideoLayout = (function (my) {
// since we don't want to switch to local video. // since we don't want to switch to local video.
if (container && !focusedVideoResourceJid) if (container && !focusedVideoResourceJid)
{ {
var video = container.getElementsByTagName("video"); var video
= container.getElementsByTagName(RTC.getVideoElementName());
// Update the large video if the video source is already available, // Update the large video if the video source is already available,
// otherwise wait for the "videoactive.jingle" event. // otherwise wait for the "videoactive.jingle" event.
@ -673,7 +690,8 @@ var VideoLayout = (function (my) {
var jid = APP.xmpp.findJidFromResource(resourceJid); var jid = APP.xmpp.findJidFromResource(resourceJid);
var mediaStream = APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]; var mediaStream = APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
var sel = $('#participant_' + resourceJid + '>video'); var sel = $('#participant_' + resourceJid +
'>' + RTC.getVideoElementName());
APP.RTC.attachMediaStream(sel, mediaStream.stream); APP.RTC.attachMediaStream(sel, mediaStream.stream);
if (lastNPickupJid == mediaStream.peerjid) { if (lastNPickupJid == mediaStream.peerjid) {

View File

@ -31,13 +31,7 @@ var extInstalled = false;
*/ */
var extUpdateRequired = false; var extUpdateRequired = false;
/** var AdapterJS = require("../RTC/adapter.screenshare");
* Flag used to cache desktop sharing enabled state. Do not use directly as
* it can be <tt>null</tt>.
*
* @type {null|boolean}
*/
var _desktopSharingEnabled = null;
var EventEmitter = require("events"); var EventEmitter = require("events");
@ -46,6 +40,10 @@ var eventEmitter = new EventEmitter();
var DesktopSharingEventTypes var DesktopSharingEventTypes
= require("../../service/desktopsharing/DesktopSharingEventTypes"); = require("../../service/desktopsharing/DesktopSharingEventTypes");
var RTCBrowserType = require("../RTC/RTCBrowserType");
var RTCEvents = require("../../service/RTC/RTCEvents");
/** /**
* Method obtains desktop stream from WebRTC 'screen' source. * Method obtains desktop stream from WebRTC 'screen' source.
* Flag 'chrome://flags/#enable-usermedia-screen-capture' must be enabled. * Flag 'chrome://flags/#enable-usermedia-screen-capture' must be enabled.
@ -116,7 +114,7 @@ function isUpdateRequired(minVersion, extVersion)
} }
} }
function checkExtInstalled(callback) { function checkChromeExtInstalled(callback) {
if (!chrome.runtime) { if (!chrome.runtime) {
// No API, so no extension for sure // No API, so no extension for sure
callback(false, false); callback(false, false);
@ -213,20 +211,39 @@ function obtainScreenFromExtension(streamCallback, failCallback) {
* feature completely. * feature completely.
*/ */
function setDesktopSharing(method) { function setDesktopSharing(method) {
// Check if we are running chrome
if (!navigator.webkitGetUserMedia) { obtainDesktopStream = null;
obtainDesktopStream = null;
console.info("Desktop sharing disabled"); // When TemasysWebRTC plugin is used we always use getUserMedia, so we don't
} else if (method == "ext") { // care about 'method' parameter
obtainDesktopStream = obtainScreenFromExtension; if (RTCBrowserType.isTemasysPluginUsed()) {
console.info("Using Chrome extension for desktop sharing"); if (!AdapterJS.WebRTCPlugin.plugin.HasScreensharingFeature) {
} else if (method == "webrtc") { console.info("Screensharing not supported by this plugin version");
obtainDesktopStream = obtainWebRTCScreen; } else if (!AdapterJS.WebRTCPlugin.plugin.isScreensharingAvailable) {
console.info("Using Chrome WebRTC for desktop sharing"); console.info(
"Screensharing not available with Temasys plugin on this site");
} else {
obtainDesktopStream = obtainWebRTCScreen;
console.info("Using Temasys plugin for desktop sharing");
}
} else if (RTCBrowserType.isChrome()) {
if (method == "ext") {
if (RTCBrowserType.getChromeVersion() >= 34) {
obtainDesktopStream = obtainScreenFromExtension;
console.info("Using Chrome extension for desktop sharing");
initChromeExtension();
} else {
console.info("Chrome extension not supported until ver 34");
}
} else if (method == "webrtc") {
obtainDesktopStream = obtainWebRTCScreen;
console.info("Using Chrome WebRTC for desktop sharing");
}
} }
// Reset enabled cache if (!obtainDesktopStream) {
_desktopSharingEnabled = null; console.info("Desktop sharing disabled");
}
} }
/** /**
@ -239,6 +256,19 @@ function initInlineInstalls()
$("link[rel=chrome-webstore-item]").attr("href", getWebStoreInstallUrl()); $("link[rel=chrome-webstore-item]").attr("href", getWebStoreInstallUrl());
} }
function initChromeExtension() {
// Initialize Chrome extension inline installs
initInlineInstalls();
// Check if extension is installed
checkChromeExtInstalled(function (installed, updateRequired) {
extInstalled = installed;
extUpdateRequired = updateRequired;
console.info(
"Chrome extension installed: " + extInstalled +
" updateRequired: " + extUpdateRequired);
});
}
function getVideoStreamFailed(error) { function getVideoStreamFailed(error) {
console.error("Failed to obtain the stream to switch to", error); console.error("Failed to obtain the stream to switch to", error);
switchInProgress = false; switchInProgress = false;
@ -264,6 +294,25 @@ function newStreamCreated(stream)
stream, isUsingScreenStream, streamSwitchDone); stream, isUsingScreenStream, streamSwitchDone);
} }
function onEndedHandler(stream) {
if (!switchInProgress && isUsingScreenStream) {
APP.desktopsharing.toggleScreenSharing();
}
//FIXME: to be verified
if (stream.removeEventListener) {
stream.removeEventListener('ended', onEndedHandler);
} else {
stream.detachEvent('ended', onEndedHandler);
}
}
// Called when RTC finishes initialization
function onWebRtcReady() {
setDesktopSharing(config.desktopSharing);
eventEmitter.emit(DesktopSharingEventTypes.INIT);
}
module.exports = { module.exports = {
isUsingScreenStream: function () { isUsingScreenStream: function () {
@ -274,43 +323,10 @@ module.exports = {
* @returns {boolean} <tt>true</tt> if desktop sharing feature is available * @returns {boolean} <tt>true</tt> if desktop sharing feature is available
* and enabled. * and enabled.
*/ */
isDesktopSharingEnabled: function () { isDesktopSharingEnabled: function () { return !!obtainDesktopStream; },
if (_desktopSharingEnabled === null) {
if (obtainDesktopStream === obtainScreenFromExtension) {
// Parse chrome version
var userAgent = navigator.userAgent.toLowerCase();
// We can assume that user agent is chrome, because it's
// enforced when 'ext' streaming method is set
var ver = parseInt(userAgent.match(/chrome\/(\d+)\./)[1], 10);
console.log("Chrome version" + userAgent, ver);
_desktopSharingEnabled = ver >= 34;
} else {
_desktopSharingEnabled =
obtainDesktopStream === obtainWebRTCScreen;
}
}
return _desktopSharingEnabled;
},
init: function () { init: function () {
setDesktopSharing(config.desktopSharing); APP.RTC.addListener(RTCEvents.RTC_READY, onWebRtcReady);
// Initialize Chrome extension inline installs
if (config.chromeExtensionId) {
initInlineInstalls();
// Check if extension is installed
checkExtInstalled(function (installed, updateRequired) {
extInstalled = installed;
extUpdateRequired = updateRequired;
console.info(
"Chrome extension installed: " + extInstalled +
" updateRequired: " + extUpdateRequired);
});
}
eventEmitter.emit(DesktopSharingEventTypes.INIT);
}, },
addListener: function (listener, type) addListener: function (listener, type)
@ -341,13 +357,16 @@ module.exports = {
isUsingScreenStream = true; isUsingScreenStream = true;
// Hook 'ended' event to restore camera // Hook 'ended' event to restore camera
// when screen stream stops // when screen stream stops
stream.addEventListener('ended', //FIXME: to be verified
function (e) { if (stream.addEventListener) {
if (!switchInProgress && isUsingScreenStream) { stream.addEventListener('ended', function () {
APP.desktopsharing.toggleScreenSharing(); onEndedHandler(stream);
} });
} } else {
); stream.attachEvent('ended', function () {
onEndedHandler(stream);
});
}
newStreamCreated(stream); newStreamCreated(stream);
}, },
getDesktopStreamFailed); getDesktopStreamFailed);

View File

@ -1,6 +1,6 @@
/* global ssrc2jid */ /* global ssrc2jid */
/* jshint -W117 */ /* jshint -W117 */
var RTCBrowserType = require("../../service/RTC/RTCBrowserType"); var RTCBrowserType = require("../RTC/RTCBrowserType");
/** /**
@ -17,10 +17,12 @@ function calculatePacketLoss(lostPackets, totalPackets) {
} }
function getStatValue(item, name) { function getStatValue(item, name) {
if(!keyMap[APP.RTC.getBrowserType()][name]) var browserType = RTCBrowserType.getBrowserType();
if (!keyMap[browserType][name])
throw "The property isn't supported!"; throw "The property isn't supported!";
var key = keyMap[APP.RTC.getBrowserType()][name]; var key = keyMap[browserType][name];
return APP.RTC.getBrowserType() == RTCBrowserType.RTC_BROWSER_CHROME? item.stat(key) : item[key]; return (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) ?
item.stat(key) : item[key];
} }
/** /**
@ -415,6 +417,8 @@ keyMap[RTCBrowserType.RTC_BROWSER_CHROME] = {
"audioInputLevel": "audioInputLevel", "audioInputLevel": "audioInputLevel",
"audioOutputLevel": "audioOutputLevel" "audioOutputLevel": "audioOutputLevel"
}; };
keyMap[RTCBrowserType.RTC_BROWSER_OPERA] =
keyMap[RTCBrowserType.RTC_BROWSER_CHROME];
/** /**

View File

@ -6,6 +6,7 @@ var SDP = require("./SDP");
var async = require("async"); var async = require("async");
var transform = require("sdp-transform"); var transform = require("sdp-transform");
var XMPPEvents = require("../../service/xmpp/XMPPEvents"); var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var RTCBrowserType = require("../RTC/RTCBrowserType");
// Jingle stuff // Jingle stuff
function JingleSession(me, sid, connection, service, eventEmitter) { function JingleSession(me, sid, connection, service, eventEmitter) {
@ -1332,9 +1333,10 @@ JingleSession.prototype.remoteStreamAdded = function (data, times) {
var self = this; var self = this;
var thessrc; var thessrc;
var ssrc2jid = this.connection.emuc.ssrc2jid; var ssrc2jid = this.connection.emuc.ssrc2jid;
var streamId = APP.RTC.getStreamID(data.stream);
// look up an associated JID for a stream id // look up an associated JID for a stream id
if (data.stream.id && data.stream.id.indexOf('mixedmslabel') === -1) { if (streamId && streamId.indexOf('mixedmslabel') === -1) {
// look only at a=ssrc: and _not_ at a=ssrc-group: lines // look only at a=ssrc: and _not_ at a=ssrc-group: lines
var ssrclines var ssrclines
@ -1344,7 +1346,11 @@ JingleSession.prototype.remoteStreamAdded = function (data, times) {
// is not always present. // is not always present.
// return line.indexOf('mslabel:' + data.stream.label) !== -1; // return line.indexOf('mslabel:' + data.stream.label) !== -1;
return ((line.indexOf('msid:' + data.stream.id) !== -1)); if (RTCBrowserType.isTemasysPluginUsed()) {
return ((line.indexOf('mslabel:' + streamId) !== -1));
} else {
return ((line.indexOf('msid:' + streamId) !== -1));
}
}); });
if (ssrclines.length) { if (ssrclines.length) {
thessrc = ssrclines[0].substring(7).split(' ')[0]; thessrc = ssrclines[0].substring(7).split(' ')[0];
@ -1379,7 +1385,7 @@ JingleSession.prototype.remoteStreamAdded = function (data, times) {
} }
// ok to overwrite the one from focus? might save work in colibri.js // ok to overwrite the one from focus? might save work in colibri.js
console.log('associated jid', ssrc2jid[thessrc], data.peerjid); console.log('associated jid', ssrc2jid[thessrc], thessrc);
if (ssrc2jid[thessrc]) { if (ssrc2jid[thessrc]) {
data.peerjid = ssrc2jid[thessrc]; data.peerjid = ssrc2jid[thessrc];
} }

View File

@ -218,8 +218,12 @@ SDP.prototype.toJingle = function (elem, thecreator, ssrcs) {
if (kv.indexOf(':') == -1) { if (kv.indexOf(':') == -1) {
elem.attrs({ name: kv }); elem.attrs({ name: kv });
} else { } else {
elem.attrs({ name: kv.split(':', 2)[0] }); var k = kv.split(':', 2)[0];
elem.attrs({ value: kv.split(':', 2)[1] }); elem.attrs({ name: k });
var v = kv.split(':', 2)[1];
v = SDPUtil.filter_special_chars(v);
elem.attrs({ value: v });
} }
elem.up(); elem.up();
}); });
@ -243,7 +247,7 @@ SDP.prototype.toJingle = function (elem, thecreator, ssrcs) {
} }
if(msid != null) if(msid != null)
{ {
msid = msid.replace(/[\{,\}]/g,""); msid = SDPUtil.filter_special_chars(msid);
elem.c('parameter'); elem.c('parameter');
elem.attrs({name: "msid", value:msid}); elem.attrs({name: "msid", value:msid});
elem.up(); elem.up();
@ -605,9 +609,12 @@ SDP.prototype.jingle2media = function (content) {
tmp.each(function () { tmp.each(function () {
var ssrc = this.getAttribute('ssrc'); var ssrc = this.getAttribute('ssrc');
$(this).find('>parameter').each(function () { $(this).find('>parameter').each(function () {
media += 'a=ssrc:' + ssrc + ' ' + this.getAttribute('name'); var name = this.getAttribute('name');
if (this.getAttribute('value') && this.getAttribute('value').length) var value = this.getAttribute('value');
media += ':' + this.getAttribute('value'); value = SDPUtil.filter_special_chars(value);
media += 'a=ssrc:' + ssrc + ' ' + name;
if (value && value.length)
media += ':' + value;
media += '\r\n'; media += '\r\n';
}); });
}); });

View File

@ -1,3 +1,6 @@
var SDPUtil = require("./SDPUtil");
function SDPDiffer(mySDP, otherSDP) { function SDPDiffer(mySDP, otherSDP) {
this.mySDP = mySDP; this.mySDP = mySDP;
this.otherSDP = otherSDP; this.otherSDP = otherSDP;
@ -130,8 +133,11 @@ SDPDiffer.prototype.toJingle = function(modify) {
if (kv.indexOf(':') == -1) { if (kv.indexOf(':') == -1) {
modify.attrs({ name: kv }); modify.attrs({ name: kv });
} else { } else {
modify.attrs({ name: kv.split(':', 2)[0] }); var nv = kv.split(':', 2);
modify.attrs({ value: kv.split(':', 2)[1] }); var name = nv[0];
var value = SDPUtil.filter_special_chars(nv[1]);
modify.attrs({ name: name });
modify.attrs({ value: value });
} }
modify.up(); // end of parameter modify.up(); // end of parameter
}); });

View File

@ -1,4 +1,7 @@
SDPUtil = { SDPUtil = {
filter_special_chars: function (text) {
return text.replace(/[\\\/\{,\}\+]/g, "");
},
iceparams: function (mediadesc, sessiondesc) { iceparams: function (mediadesc, sessiondesc) {
var data = null; var data = null;
if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) && if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&

View File

@ -1,9 +1,18 @@
var RTC = require('../RTC/RTC');
var RTCBrowserType = require("../RTC/RTCBrowserType.js");
var XMPPEvents = require("../../service/xmpp/XMPPEvents"); var XMPPEvents = require("../../service/xmpp/XMPPEvents");
function TraceablePeerConnection(ice_config, constraints, session) { function TraceablePeerConnection(ice_config, constraints, session) {
var self = this; var self = this;
var RTCPeerconnection = navigator.mozGetUserMedia ? mozRTCPeerConnection : webkitRTCPeerConnection; var RTCPeerconnectionType = null;
this.peerconnection = new RTCPeerconnection(ice_config, constraints); if (RTCBrowserType.isFirefox()) {
RTCPeerconnectionType = mozRTCPeerConnection;
} else if (RTCBrowserType.isTemasysPluginUsed()) {
RTCPeerconnectionType = RTCPeerConnection;
} else {
RTCPeerconnectionType = webkitRTCPeerConnection;
}
this.peerconnection = new RTCPeerconnectionType(ice_config, constraints);
this.updateLog = []; this.updateLog = [];
this.stats = {}; this.stats = {};
this.statsinterval = null; this.statsinterval = null;
@ -15,7 +24,15 @@ function TraceablePeerConnection(ice_config, constraints, session) {
// override as desired // override as desired
this.trace = function (what, info) { this.trace = function (what, info) {
//console.warn('WTRACE', what, info); /*console.warn('WTRACE', what, info);
if (info && RTCBrowserType.isIExplorer()) {
if (info.length > 1024) {
console.warn('WTRACE', what, info.substr(1024));
}
if (info.length > 2048) {
console.warn('WTRACE', what, info.substr(2048));
}
}*/
self.updateLog.push({ self.updateLog.push({
time: new Date(), time: new Date(),
type: what, type: what,
@ -24,7 +41,9 @@ function TraceablePeerConnection(ice_config, constraints, session) {
}; };
this.onicecandidate = null; this.onicecandidate = null;
this.peerconnection.onicecandidate = function (event) { this.peerconnection.onicecandidate = function (event) {
self.trace('onicecandidate', JSON.stringify(event.candidate, null, ' ')); // FIXME: this causes stack overflow with Temasys Plugin
if (!RTCBrowserType.isTemasysPluginUsed())
self.trace('onicecandidate', JSON.stringify(event.candidate, null, ' '));
if (self.onicecandidate !== null) { if (self.onicecandidate !== null) {
self.onicecandidate(event); self.onicecandidate(event);
} }
@ -155,16 +174,26 @@ TraceablePeerConnection.prototype.removeStream = function (stream, stopStreams)
this.trace('removeStream', stream.id); this.trace('removeStream', stream.id);
if(stopStreams) { if(stopStreams) {
stream.getAudioTracks().forEach(function (track) { stream.getAudioTracks().forEach(function (track) {
track.stop(); // stop() not supported with IE
if (track.stop) {
track.stop();
}
}); });
stream.getVideoTracks().forEach(function (track) { stream.getVideoTracks().forEach(function (track) {
track.stop(); // stop() not supported with IE
if (track.stop) {
track.stop();
}
}); });
if (stream.stop) {
stream.stop();
}
} }
try { try {
// FF doesn't support this yet. // FF doesn't support this yet.
this.peerconnection.removeStream(stream); if (this.peerconnection.removeStream)
this.peerconnection.removeStream(stream);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }

View File

@ -28,12 +28,11 @@ module.exports = function(XMPP, eventEmitter) {
initPresenceMap: function (myroomjid) { initPresenceMap: function (myroomjid) {
this.presMap['to'] = myroomjid; this.presMap['to'] = myroomjid;
this.presMap['xns'] = 'http://jabber.org/protocol/muc'; this.presMap['xns'] = 'http://jabber.org/protocol/muc';
if(APP.RTC.localAudio.isMuted()) if (APP.RTC.localAudio && APP.RTC.localAudio.isMuted())
{ {
this.addAudioInfoToPresence(true); this.addAudioInfoToPresence(true);
} }
if (APP.RTC.localVideo && APP.RTC.localVideo.isMuted())
if(APP.RTC.localVideo.isMuted())
{ {
this.addVideoInfoToPresence(true); this.addVideoInfoToPresence(true);
} }

View File

@ -1,7 +0,0 @@
var RTCBrowserType = {
RTC_BROWSER_CHROME: "rtc_browser.chrome",
RTC_BROWSER_FIREFOX: "rtc_browser.firefox"
};
module.exports = RTCBrowserType;

View File

@ -1,4 +1,5 @@
var RTCEvents = { var RTCEvents = {
RTC_READY: "rtc.ready",
LASTN_CHANGED: "rtc.lastn_changed", LASTN_CHANGED: "rtc.lastn_changed",
DOMINANTSPEAKER_CHANGED: "rtc.dominantspeaker_changed", DOMINANTSPEAKER_CHANGED: "rtc.dominantspeaker_changed",
LASTN_ENDPOINT_CHANGED: "rtc.lastn_endpoint_changed", LASTN_ENDPOINT_CHANGED: "rtc.lastn_endpoint_changed",