]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
FS-8400 [verto_communicator] Added Camera and microphone preview after the splash...
authorJaon EarlWolf <jamonsterr@gmail.com>
Fri, 6 Nov 2015 18:50:51 +0000 (15:50 -0300)
committerJaon EarlWolf <jamonsterr@gmail.com>
Fri, 6 Nov 2015 18:50:51 +0000 (15:50 -0300)
html5/verto/verto_communicator/js/3rd-party/volume-meter.js [new file with mode: 0644]
html5/verto/verto_communicator/src/css/verto.css
html5/verto/verto_communicator/src/index.html
html5/verto/verto_communicator/src/partials/preview.html [new file with mode: 0644]
html5/verto/verto_communicator/src/storageService/services/storage.js
html5/verto/verto_communicator/src/vertoApp/vertoApp.module.js
html5/verto/verto_communicator/src/vertoControllers/controllers/DialPadController.js
html5/verto/verto_communicator/src/vertoControllers/controllers/MainController.js
html5/verto/verto_communicator/src/vertoControllers/controllers/PreviewController.js [new file with mode: 0644]
html5/verto/verto_communicator/src/vertoControllers/controllers/SplashScreenController.js

diff --git a/html5/verto/verto_communicator/js/3rd-party/volume-meter.js b/html5/verto/verto_communicator/js/3rd-party/volume-meter.js
new file mode 100644 (file)
index 0000000..a4ac33a
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+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);
+}
index 9d75a3203790bfbd025ad8a696b9fe5a398780b5..c9694e15d331041b0dc5d7e255817781ad9c5e34 100644 (file)
@@ -8,6 +8,10 @@ body {
   padding-top: 60px;
 }
 
+.panel.panel-material-blue-900 .panel-heading {
+  background-color: #0d47a1;
+}
+
 .install {
   color: white;
   text-decoration: underline;
@@ -1469,3 +1473,39 @@ body:-webkit-full-screen #incall .video-footer {
   }
 
 }
+
+.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;
+}
index c450607c9fafe8a641d6eed1ea665e9585b59286..47bdf740cdd334c0700155110df13bbdefda809b 100644 (file)
@@ -95,6 +95,7 @@
 
     <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>
diff --git a/html5/verto/verto_communicator/src/partials/preview.html b/html5/verto/verto_communicator/src/partials/preview.html
new file mode 100644 (file)
index 0000000..947b0af
--- /dev/null
@@ -0,0 +1,70 @@
+<!-- <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>
index f0b8bb1b8090e57da81a639fb4c08b891a565384..2ac76532f6ec8b2b9314e5d75e68e688bbabc626 100644 (file)
@@ -21,6 +21,7 @@
           userStatus: 'disconnected',
           mutedVideo: false,
           mutedMic: false,
+          preview: true,
           selectedVideo: null,
           selectedAudio: null,
           selectedShare: null,
index 966a7c1f7e0a3f00ce117cfc9be678228f2adc03..2962301362856ea0243a678240f98f46e3d0e69f 100644 (file)
           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',
index 2944cae46df7b98fe4b2622c2424c379f14a76db..14a8846f45d9104112a1f9d01b526fae4e6540c4 100644 (file)
@@ -7,9 +7,9 @@
       '$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;
index 571558c780957defe90980461e046d5a80d90145..7bd5cb68190bfd977459b77d6b11341ad5fd8f17 100644 (file)
             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');
             }
           }
diff --git a/html5/verto/verto_communicator/src/vertoControllers/controllers/PreviewController.js b/html5/verto/verto_communicator/src/vertoControllers/controllers/PreviewController.js
new file mode 100644 (file)
index 0000000..61fbc5f
--- /dev/null
@@ -0,0 +1,142 @@
+(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();
+      }
+    ]);
+})();
index fdc96efd59b9c9cf1c4aecd97bb360efd62df9b3..6fd32f9de7a39409484fca81da7a66a0956e869c 100644 (file)
@@ -3,10 +3,10 @@
 
   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) {
@@ -48,7 +48,7 @@
 
         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');