You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
364 lines
10 KiB
364 lines
10 KiB
/* |
|
* Spreed WebRTC. |
|
* Copyright (C) 2013-2014 struktur AG |
|
* |
|
* This file is part of Spreed WebRTC. |
|
* |
|
* This program is free software: you can redistribute it and/or modify |
|
* it under the terms of the GNU Affero General Public License as published by |
|
* the Free Software Foundation, either version 3 of the License, or |
|
* (at your option) any later version. |
|
* |
|
* This program is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
* GNU Affero General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU Affero General Public License |
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
* |
|
*/ |
|
define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/audiovideopeer.html', 'bigscreen', 'webrtc.adapter'], function($, _, template, templatePeer, BigScreen) { |
|
|
|
return ["$window", "$compile", "$filter", "mediaStream", "safeApply", "desktopNotify", "buddyData", "videoWaiter", "videoLayout", "animationFrame", function($window, $compile, $filter, mediaStream, safeApply, desktopNotify, buddyData, videoWaiter, videoLayout, animationFrame) { |
|
|
|
var peerTemplate = $compile(templatePeer); |
|
|
|
var controller = ['$scope', '$element', '$attrs', function($scope, $element, $attrs) { |
|
|
|
var streams = {}; |
|
var getStreamId = function(stream, currentcall) { |
|
var id = currentcall.id + "-" + stream.id; |
|
console.log("Created stream ID", id); |
|
return id; |
|
}; |
|
|
|
$scope.container = $element.get(0); |
|
$scope.layoutparent = $element.parent(); |
|
|
|
$scope.remoteVideos = $element.find(".remoteVideos").get(0); |
|
$scope.localVideo = $element.find(".localVideo").get(0); |
|
$scope.miniVideo = $element.find(".miniVideo").get(0); |
|
$scope.mini = $element.find(".miniContainer").get(0); |
|
|
|
$scope.hasUsermedia = false; |
|
$scope.isActive = false; |
|
$scope.haveStreams = false; |
|
|
|
$scope.peersTalking = {}; |
|
|
|
$scope.rendererName = $scope.defaultRendererName = "democrazy"; |
|
|
|
//console.log("audiovideo", localVideo, miniVideo); |
|
|
|
$scope.addRemoteStream = function(stream, currentcall) { |
|
|
|
var id = getStreamId(stream, currentcall); |
|
|
|
if (streams.hasOwnProperty(id)) { |
|
console.warn("Cowardly refusing to add stream id twice", id, currentcall); |
|
return; |
|
} |
|
|
|
//console.log("Add remote stream to scope", stream.id, stream, currentcall); |
|
// Create scope. |
|
var subscope = $scope.$new(); |
|
var peerid = subscope.peerid = currentcall.id; |
|
buddyData.push(peerid); |
|
subscope.withvideo = false; |
|
subscope.onlyaudio = false; |
|
subscope.destroyed = false; |
|
subscope.$on("active", function() { |
|
console.log("Stream scope is now active", id, peerid); |
|
}); |
|
subscope.$on("$destroy", function() { |
|
console.log("Destroyed scope for stream", id, peerid); |
|
subscope.destroyed = true; |
|
}); |
|
console.log("Created stream scope", id, peerid); |
|
|
|
// Add created scope. |
|
streams[id] = subscope; |
|
|
|
// Render template. |
|
peerTemplate(subscope, function(clonedElement, scope) { |
|
$($scope.remoteVideos).append(clonedElement); |
|
clonedElement.data("peerid", scope.peerid); |
|
scope.element = clonedElement; |
|
var video = clonedElement.find("video").get(0); |
|
$window.attachMediaStream(video, stream); |
|
// Waiter callbacks also count as connected, as browser support (FireFox 25) is not setting state changes properly. |
|
videoWaiter.wait(video, stream, function(withvideo) { |
|
if (scope.destroyed) { |
|
console.log("Abort wait for video on destroyed scope."); |
|
return; |
|
} |
|
if (withvideo) { |
|
scope.$apply(function($scope) { |
|
$scope.withvideo = true; |
|
}); |
|
} else { |
|
console.info("Incoming stream has no video tracks."); |
|
scope.$apply(function($scope) { |
|
$scope.onlyaudio = true; |
|
}); |
|
} |
|
scope.$emit("active", currentcall); |
|
$scope.redraw(); |
|
}, function() { |
|
if (scope.destroyed) { |
|
console.log("No longer wait for video on destroyed scope."); |
|
return; |
|
} |
|
console.warn("We did not receive video data for remote stream", currentcall, stream, video); |
|
scope.$emit("active", currentcall); |
|
$scope.redraw(); |
|
}); |
|
scope.doChat = function() { |
|
$scope.$emit("startchat", currentcall.id, { |
|
autofocus: true, |
|
restore: true |
|
}); |
|
}; |
|
}); |
|
|
|
}; |
|
|
|
$scope.removeRemoteStream = function(stream, currentcall) { |
|
|
|
//console.log("remove stream", stream, stream.id, currentcall); |
|
var id = getStreamId(stream, currentcall); |
|
|
|
var subscope = streams[id]; |
|
if (subscope) { |
|
buddyData.pop(currentcall.id); |
|
delete streams[id]; |
|
//console.log("remove scope", subscope); |
|
if (subscope.element) { |
|
subscope.element.remove(); |
|
} |
|
subscope.$destroy(); |
|
$scope.redraw(); |
|
} |
|
|
|
}; |
|
|
|
// Talking updates receiver. |
|
mediaStream.api.e.on("received.talking", function(event, id, from, talking) { |
|
$scope.$apply(function(scope) { |
|
scope.peersTalking[from] = !!talking; |
|
}); |
|
}); |
|
|
|
$scope.$on("active", function(currentcall) { |
|
|
|
//console.log("active 2"); |
|
if (!$scope.isActive) { |
|
$scope.isActive = true; |
|
$scope.remoteVideos.style.opacity = 1; |
|
$element.addClass("active"); |
|
//console.log("active 3"); |
|
_.delay(function() { |
|
$scope.localVideo.style.opacity = 0; |
|
$scope.localVideo.src = ""; |
|
}, 500); |
|
_.delay(function() { |
|
//console.log("active 4", $scope.mini); |
|
$($scope.mini).addClass("visible"); |
|
}, 1000); |
|
} |
|
|
|
}); |
|
|
|
$scope.toggleFullscreen = function() { |
|
//console.log("Toggle full screen", BigScreen.enabled, $scope.isActive, $scope.hasUsermedia); |
|
if (BigScreen.enabled && ($scope.isActive || $scope.hasUsermedia)) { |
|
$scope.layoutparent.toggleClass("fullscreen"); |
|
BigScreen.toggle($scope.layoutparent.get(0)); |
|
} |
|
}; |
|
|
|
mediaStream.webrtc.e.on("usermedia", function(event, usermedia) { |
|
|
|
//console.log("XXXX XXXXXXXXXXXXXXXXXXXXX usermedia event", usermedia); |
|
if ($scope.haveStreams) { |
|
|
|
usermedia.attachMediaStream($scope.miniVideo); |
|
$scope.redraw(); |
|
|
|
} else { |
|
|
|
$scope.hasUsermedia = true; |
|
usermedia.attachMediaStream($scope.localVideo); |
|
var count = 0; |
|
var waitForLocalVideo = function() { |
|
if (!$scope.hasUsermedia) { |
|
return; |
|
} |
|
if ($scope.localVideo.videoWidth > 0) { |
|
$scope.localVideo.style.opacity = 1; |
|
$scope.redraw(); |
|
} else { |
|
count++; |
|
if (count < 100) { |
|
setTimeout(waitForLocalVideo, 100); |
|
} else { |
|
console.warn("Timeout while waiting for local video.") |
|
} |
|
} |
|
}; |
|
waitForLocalVideo(); |
|
|
|
} |
|
|
|
}); |
|
|
|
mediaStream.webrtc.e.on("done", function() { |
|
|
|
$scope.$apply(function() { |
|
$scope.hasUsermedia = false; |
|
$scope.isActive = false; |
|
$scope.peersTalking = {}; |
|
if (BigScreen.enabled) { |
|
BigScreen.exit(); |
|
} |
|
_.delay(function() { |
|
if ($scope.isActive) { |
|
return; |
|
} |
|
$scope.localVideo.src = ''; |
|
$scope.miniVideo.src = ''; |
|
$($scope.remoteVideos).children(".remoteVideo").remove(); |
|
}, 1500); |
|
$($scope.mini).removeClass("visible"); |
|
$scope.localVideo.style.opacity = 0; |
|
$scope.remoteVideos.style.opacity = 0; |
|
$element.removeClass('active'); |
|
_.each(streams, function(scope, k) { |
|
scope.$destroy(); |
|
delete streams[k]; |
|
}); |
|
$scope.rendererName = $scope.defaultRendererName; |
|
$scope.haveStreams = false; |
|
}); |
|
|
|
}); |
|
|
|
mediaStream.webrtc.e.on("streamadded", function(event, stream, currentcall) { |
|
|
|
console.log("Remote stream added.", stream, currentcall); |
|
if (!$scope.haveStreams) { |
|
//console.log("First stream"); |
|
$window.reattachMediaStream($scope.miniVideo, $scope.localVideo); |
|
$scope.haveStreams = true; |
|
} |
|
$scope.addRemoteStream(stream, currentcall); |
|
|
|
}); |
|
|
|
mediaStream.webrtc.e.on("streamremoved", function(event, stream, currentcall) { |
|
|
|
console.log("Remote stream removed.", stream, currentcall); |
|
$scope.removeRemoteStream(stream, currentcall); |
|
|
|
}); |
|
|
|
return { |
|
streams: streams |
|
}; |
|
|
|
}]; |
|
|
|
var compile = function(tElement, tAttr) { |
|
|
|
return function(scope, iElement, iAttrs, controller) { |
|
|
|
//console.log("compile", arguments) |
|
|
|
iElement.on("doubletap dblclick", _.debounce(scope.toggleFullscreen, 100, true)); |
|
|
|
var rendererName = null; |
|
var getRendererName = function() { |
|
// Return name of current renderer. |
|
if (rendererName !== null) { |
|
return rendererName; |
|
} else { |
|
return scope.rendererName; |
|
} |
|
}; |
|
var forceRendererName = function(name) { |
|
// Allow change between some renderes when forced. |
|
if (name === "classroom") { |
|
rendererName = "classroom"; |
|
} else { |
|
rendererName = "smally"; |
|
} |
|
}; |
|
|
|
scope.setRenderer = function(name) { |
|
scope.rendererName = name; |
|
if (rendererName && rendererName !== name) { |
|
forceRendererName(name); |
|
} |
|
}; |
|
|
|
var needsRedraw = false; |
|
scope.redraw = function() { |
|
needsRedraw = true; |
|
}; |
|
|
|
var redraw = function() { |
|
var size = { |
|
width: scope.layoutparent.width(), |
|
height: scope.layoutparent.height() |
|
} |
|
var again = videoLayout.update(getRendererName(), size, scope, controller); |
|
if (again) { |
|
// Layout needs a redraw. |
|
needsRedraw = true; |
|
} |
|
}; |
|
|
|
// Make sure we draw on resize. |
|
$($window).on("resize", scope.redraw); |
|
scope.$on("mainresize", function(event, main) { |
|
if (main) { |
|
// Force smally renderer or pin classroom when we have a main view. |
|
forceRendererName(scope.rendererName); |
|
} else if (rendererName) { |
|
rendererName = null; |
|
} |
|
_.defer(scope.redraw); |
|
}); |
|
scope.redraw(); |
|
|
|
// Make sure we draw when the renderer was changed. |
|
scope.$watch("rendererName", function() { |
|
_.defer(scope.redraw); |
|
}); |
|
|
|
// Update function run in rendering thread. |
|
var update = function() { |
|
if (needsRedraw) { |
|
needsRedraw = false; |
|
redraw(); |
|
} |
|
} |
|
animationFrame.register(update); |
|
|
|
} |
|
|
|
}; |
|
|
|
return { |
|
restrict: 'E', |
|
replace: true, |
|
scope: true, |
|
template: template, |
|
controller: controller, |
|
compile: compile |
|
} |
|
|
|
}]; |
|
|
|
});
|
|
|