--- /dev/null
+/*
+The MIT License (MIT)
+
+Copyright (c) 2014 Chris Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+/*
+
+Usage:
+audioNode = createAudioMeter(audioContext,clipLevel,averaging,clipLag);
+
+audioContext: the AudioContext you're using.
+clipLevel: the level (0 to 1) that you would consider "clipping".
+ Defaults to 0.98.
+averaging: how "smoothed" you would like the meter to be over time.
+ Should be between 0 and less than 1. Defaults to 0.95.
+clipLag: how long you would like the "clipping" indicator to show
+ after clipping has occured, in milliseconds. Defaults to 750ms.
+
+Access the clipping through node.checkClipping(); use node.shutdown to get rid of it.
+*/
+
+function createAudioMeter(audioContext,clipLevel,averaging,clipLag) {
+ var processor = audioContext.createScriptProcessor(512);
+ processor.onaudioprocess = volumeAudioProcess;
+ processor.clipping = false;
+ processor.lastClip = 0;
+ processor.volume = 0;
+ processor.clipLevel = clipLevel || 0.98;
+ processor.averaging = averaging || 0.95;
+ processor.clipLag = clipLag || 750;
+
+ // this will have no effect, since we don't copy the input to the output,
+ // but works around a current Chrome bug.
+ processor.connect(audioContext.destination);
+
+ processor.checkClipping =
+ function(){
+ if (!this.clipping)
+ return false;
+ if ((this.lastClip + this.clipLag) < window.performance.now())
+ this.clipping = false;
+ return this.clipping;
+ };
+
+ processor.shutdown =
+ function(){
+ this.disconnect();
+ this.onaudioprocess = null;
+ };
+
+ return processor;
+}
+
+function volumeAudioProcess( event ) {
+ var buf = event.inputBuffer.getChannelData(0);
+ var bufLength = buf.length;
+ var sum = 0;
+ var x;
+
+ // Do a root-mean-square on the samples: sum up the squares...
+ for (var i=0; i<bufLength; i++) {
+ x = buf[i];
+ if (Math.abs(x)>=this.clipLevel) {
+ this.clipping = true;
+ this.lastClip = window.performance.now();
+ }
+ sum += x * x;
+ }
+
+ // ... then take the square root of the sum.
+ var rms = Math.sqrt(sum / bufLength);
+
+ // Now smooth this out with the averaging factor applied
+ // to the previous sample - take the max here because we
+ // want "fast attack, slow release."
+ this.volume = Math.max(rms, this.volume*this.averaging);
+}
padding-top: 60px;
}
+.panel.panel-material-blue-900 .panel-heading {
+ background-color: #0d47a1;
+}
+
.install {
color: white;
text-decoration: underline;
}
}
+
+.preview-wrapper {
+ position: relative;
+}
+
+.preview-wrapper video {
+ transform: scaleX(-1);
+}
+#mic-meter {
+ position: absolute;
+ bottom: 5px;
+ left: 10px;
+}
+#mic-meter .icon {
+ margin-left: 3px;
+ color: white;
+}
+#mic-meter .volumes {
+ width: 30px;
+}
+#mic-meter .volumes .volume-segment {
+ height: 10px;
+ width: 100%;
+ border-radius: 5px;
+ border: 2px solid white;
+ display: block;
+ margin-top: 1.5px;
+}
+
+#mic-meter .volumes .volume-segment.active {
+ background-color: white;
+}
+
+#preview .refresh {
+ margin: 15px 0px 0px 0px;
+}
<script type="text/javascript" src="js/3rd-party/getScreenId.js"></script>
<script type="text/javascript" src="js/3rd-party/md5.min.js"></script>
+ <script type="text/javascript" src="js/3rd-party/volume-meter.js"></script>
<script type="text/javascript" src="src/vertoApp/vertoApp.module.js"></script>
<script type="text/javascript" src="src/vertoControllers/controllers/ModalWsReconnectController.js"></script>
<script type="text/javascript" src="src/vertoControllers/controllers/ModalLoginInformationController.js"></script>
<script type="text/javascript" src="src/vertoControllers/controllers/ModalSettingsController.js"></script>
+ <script type="text/javascript" src="src/vertoControllers/controllers/PreviewController.js"></script>
<script type="text/javascript" src="src/vertoDirectives/vertoDirectives.module.js"></script>
<script type="text/javascript" src="src/vertoDirectives/directives/autofocus.js"></script>
--- /dev/null
+<!-- <div class="panel panel-default shadow-z-0">
+ <div class="" style="width: 100%; height: 100%;">
+ <div class="" ng-dblclick="goFullscreen()">
+ <video id="preview" style="width: 400px;"></video>
+ <svg ng-show="video != 'active'" class="spinner" width="65px" height="65px" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg">
+ <circle class="path" fill="none" stroke-width="6" stroke-linecap="round" cx="33" cy="33" r="30"></circle>
+ </svg>
+ </div>
+ <div class="video-footer panel-body">
+ <div class="row">
+ <div class="col-md-6 col-xs-6 text-left">
+ </div>
+ <div class="col-md-6 col-xs-6 text-right">
+ <button class="btn btn-primary" ng-click="localVideo()">
+ <i class="mdi-communication-call-end"></i>
+ Get
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+</div> -->
+
+<div class="centered-block-frame" id="preview">
+ <div class="col-md-4 col-sm-12 col-xs-12 centered-block">
+ <div class="panel panel-material-blue-900 shadow-z-2 ">
+ <div class="panel-heading">
+ <h3 class="panel-title text-center">Setup your camera and microphone settings</h3>
+ </div>
+ <div class="panel-body">
+ <div class="preview-wrapper">
+ <video id="videopreview" muted autoplay style="width: 100%;"></video>
+ <div id="mic-meter">
+ <div class="volumes">
+ <div class="volume-segment"></div>
+ <div class="volume-segment"></div>
+ <div class="volume-segment"></div>
+ <div class="volume-segment"></div>
+ <div class="volume-segment"></div>
+ </div>
+ <i class="icon mdi-hardware-keyboard-voice"></i>
+ </div>
+ </div>
+ <form name="form">
+ <div class="form-group col-md-5 col-sm-12 col-xs-12" ng-show="true">
+ <label for="settings-camera">Camera:</label>
+ <select name="camera" id="settings-camera" class="form-control" ng-model="storage.data.selectedVideo"
+ ng-options="item.id as item.label for item in verto.data.videoDevices" ng-change="localVideo()" >
+ </select>
+ </div>
+ <div class="form-group col-md-5 col-sm-12 col-xs-12" ng-show="true">
+ <label for="settings-microphone">Microphone:</label>
+ <select name="microphone" id="settings-microphone" class="form-control" ng-model="storage.data.selectedAudio"
+ ng-options="item.id as item.label for item in verto.data.audioDevices" ng-change="localVideo()">
+ </select>
+ </div>
+ <a class="btn btn-primary btn-sm col-md-2 refresh" ng-click="refreshDeviceList()">
+ <i class="icon mdi-action-autorenew"></i>
+ </a>
+
+ <div class="form-group text-center">
+ <button type="submit" class="btn btn-success" ng-click="endPreview()" title="Save">
+ Save
+ </button>
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+</div>
userStatus: 'disconnected',
mutedVideo: false,
mutedMic: false,
+ preview: true,
selectedVideo: null,
selectedAudio: null,
selectedShare: null,
templateUrl: 'partials/incall.html',
controller: 'InCallController'
}).
+ when('/preview', {
+ title: 'Preview Video',
+ templateUrl: 'partials/preview.html',
+ controller: 'PreviewController'
+ }).
when('/browser-upgrade', {
title: '',
templateUrl: 'partials/browser_upgrade.html',
'$http', '$location', 'toastr', 'verto', 'storage', 'CallHistory', 'eventQueue',
function($rootScope, $scope, $http, $location, toastr, verto, storage, CallHistory, eventQueue) {
console.debug('Executing DialPadController.');
-
+
eventQueue.process();
-
+
$scope.call_history = CallHistory.all();
$scope.history_control = CallHistory.all_control();
$scope.has_history = Object.keys($scope.call_history).length;
$rootScope.dialpadNumber = number;
};
+ $scope.preview = function() {
+ $location.path('/preview');
+ };
+
$rootScope.transfer = function() {
if (!$rootScope.dialpadNumber) {
return false;
storage.data.email = verto.data.email;
storage.data.login = verto.data.login;
storage.data.password = verto.data.password;
- if (redirect) {
+ if (redirect && storage.data.preview) {
+ $location.path('/preview');
+ }
+ else if (redirect) {
$location.path('/dialpad');
}
}
--- /dev/null
+(function() {
+ 'use strict';
+
+ angular
+ .module('vertoControllers')
+ .controller('PreviewController', ['$rootScope', '$scope',
+ '$http', '$location', '$modal', '$timeout', 'toastr', 'verto', 'storage', 'prompt', 'Fullscreen',
+ function($rootScope, $scope, $http, $location, $modal, $timeout, toastr,
+ verto, storage, prompt, Fullscreen) {
+
+ $scope.storage = storage;
+ console.debug('Executing PreviewController.');
+ var localVideo = document.getElementById('videopreview');
+ var volumes = document.querySelector('#mic-meter .volumes').children;
+
+ $scope.localVideo = function() {
+ var constraints = {
+ mirrored: true,
+ audio: {
+ optional: [{ sourceId: storage.data.selectedAudio }]
+ }
+ };
+
+ if (storage.data.selectedVideo !== 'none') {
+ constraints.video = {
+ optional: [{ sourceId: storage.data.selectedVideo }]
+ };
+ }
+
+ navigator.getUserMedia(constraints, handleMedia, function(err, data) {
+
+ });
+ };
+
+ var audioContext = new AudioContext();
+ var mediaStreamSource = null;
+ var meter;
+ var streamObj = {};
+
+ function handleMedia(stream) {
+ streamObj.stop ? streamObj.stop() : streamObj.active = false;
+
+ streamObj = stream;
+ localVideo.src = window.URL.createObjectURL(stream);
+
+ mediaStreamSource = audioContext.createMediaStreamSource(stream);
+ meter = createAudioMeter(audioContext);
+ mediaStreamSource.connect(meter);
+
+ renderMic();
+ }
+
+ function renderMic() {
+ // meter.volume;
+ var n = Math.round(meter.volume * 25);
+ for(var i = volumes.length -1, j = 0; i >= 0; i--, j++) {
+ var el = angular.element(volumes[j]);
+ if (i >= n) el.removeClass('active');
+ else el.addClass('active');
+ }
+
+ if(!verto.data.call) {
+ window.requestAnimationFrame(renderMic);
+ }
+ }
+ /**
+ * TODO: useless?
+ */
+
+ $scope.refreshDeviceList = function() {
+ return verto.refreshDevices();
+ };
+
+ $scope.videoCall = function() {
+ prompt({
+ title: 'Would you like to activate video for this call?',
+ message: 'Video will be active during the next calls.'
+ }).then(function() {
+ storage.data.videoCall = true;
+ $scope.callTemplate = 'partials/video_call.html';
+ });
+ };
+
+ $scope.cbMuteVideo = function(event, data) {
+ storage.data.mutedVideo = !storage.data.mutedVideo;
+ }
+
+ $scope.cbMuteMic = function(event, data) {
+ storage.data.mutedMic = !storage.data.mutedMic;
+ }
+
+ $scope.confChangeVideoLayout = function(layout) {
+ verto.data.conf.setVideoLayout(layout);
+ };
+
+ $scope.endPreview = function() {
+ localVideo.src = null;
+ meter.shutdown();
+ meter.onaudioprocess = null;
+ streamObj.stop();
+ $location.path('/dialpad');
+ storage.data.preview = false;
+ };
+
+ $scope.screenshare = function() {
+ if(verto.data.shareCall) {
+ verto.screenshareHangup();
+ return false;
+ }
+ verto.screenshare(storage.data.called_number);
+ };
+
+ $scope.call = function() {
+ if($rootScope.dialpadNumber) {
+ localVideo.src = null;
+ meter.shutdown();
+ meter.onaudioprocess = null;
+ streamObj.stop();
+ }
+ $rootScope.call($rootScope.dialpadNumber);
+ };
+
+ $scope.muteMic = verto.muteMic;
+ $scope.muteVideo = verto.muteVideo;
+
+ $rootScope.$on('ScreenShareExtensionStatus', function(event, error) {
+ var pluginUrl = 'https://chrome.google.com/webstore/detail/screen-capturing/ajhifddimkapgcifgcodmmfdlknahffk';
+ switch(error) {
+ case 'permission-denied':
+ toastr.info('Please allow the plugin in order to use Screen Share', 'Error'); break;
+ case 'not-installed':
+ toastr.warning('Please <a target="_blank" class="install" href="'+ pluginUrl +'">install</a> the plugin in order to use Screen Share', 'Warning', { allowHtml: true }); break;
+ case 'installed-disabled':
+ toastr.info('Please enable the plugin in order to use Screen Share', 'Error'); break;
+ // case 'not-chrome'
+ // toastr.info('Chrome', 'Error');
+ }
+ });
+ $scope.localVideo();
+ }
+ ]);
+})();
angular
.module('vertoControllers')
- .controller('SplashScreenController', ['$scope', '$rootScope', '$location', '$timeout', 'splashscreen', 'prompt', 'verto',
- function($scope, $rootScope, $location, $timeout, splashscreen, prompt, verto) {
+ .controller('SplashScreenController', ['$scope', '$rootScope', '$location', '$timeout', 'storage', 'splashscreen', 'prompt', 'verto',
+ function($scope, $rootScope, $location, $timeout, storage, splashscreen, prompt, verto) {
console.debug('Executing SplashScreenController.');
-
+
$scope.progress_percentage = splashscreen.progress_percentage;
$scope.message = '';
$scope.interrupt_next = false;
link = activity;
}
}
-
+
$location.path(link);
}
var checkProgressState = function(current_progress, status, promise, activity, soft, interrupt, message) {
- $scope.progress_percentage = splashscreen.calculate(current_progress);
+ $scope.progress_percentage = splashscreen.calculate(current_progress);
$scope.message = message;
if(interrupt && status == 'error') {
$scope.errors.push(message);
if(!soft) {
- redirectTo('', activity);
+ redirectTo('', activity);
return;
} else {
- message = message + '. Continue?';
+ message = message + '. Continue?';
};
if(!confirm(message)) {
- $scope.interrupt_next = true;
- };
+ $scope.interrupt_next = true;
+ };
};
if($scope.interrupt_next) {
return true;
};
-
+
$rootScope.$on('progress.next', function(ev, current_progress, status, promise, activity, soft, interrupt, message) {
$timeout(function() {
if(promise) {
return;
}
-
+
if(!checkProgressState(current_progress, status, promise, activity, soft, interrupt, message)) {
return;
}
-
+
splashscreen.next();
}, 400);
});
$rootScope.$on('progress.complete', function(ev, current_progress) {
$scope.message = 'Complete';
if(verto.data.connected) {
- redirectTo('/dialpad');
+ if (storage.data.preview) {
+ $location.path('/preview');
+ }
+ else {
+ $location.path('/dialpad');
+ }
} else {
redirectTo('/login');
$location.path('/login');