diff --git a/html5/verto/js/src/jquery.verto.js b/html5/verto/js/src/jquery.verto.js index 0528652673..792331f18a 100644 --- a/html5/verto/js/src/jquery.verto.js +++ b/html5/verto/js/src/jquery.verto.js @@ -1697,7 +1697,22 @@ $(vlselect_id).append(new Option("Choose a Layout", "none")); if (e.data.responseData) { - options = e.data.responseData.sort(); + var rdata = []; + + for (var i in e.data.responseData) { + rdata.push(e.data.responseData[i].name); + } + + options = rdata.sort(function(a, b) { + var ga = a.substring(0, 6) == "group:" ? true : false; + var gb = b.substring(0, 6) == "group:" ? true : false; + + if ((ga || gb) && ga != gb) { + return ga ? -1 : 1; + } + + return ( ( a == b ) ? 0 : ( ( a > b ) ? 1 : -1 ) ); + }); for (var i in options) { $(vlselect_id).append(new Option(options[i], options[i])); diff --git a/html5/verto/verto_communicator/src/css/verto.css b/html5/verto/verto_communicator/src/css/verto.css index 4e0175d583..89ffc0e7ca 100644 --- a/html5/verto/verto_communicator/src/css/verto.css +++ b/html5/verto/verto_communicator/src/css/verto.css @@ -21,7 +21,6 @@ body { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; - width: 160px; display: inline-block; } @@ -604,12 +603,24 @@ body .modal-body .btn-group .btn.active { transition-delay:0s; } + +#incall .dropdown-menu .selected { + background-color: #ccc; + color: white; +} + +#incall .dropdown-menu .selected:hover { + background-color: #ccc; + color: white; + cursor: pointer; +} + #incall .video-hover-buttons .btn-group .dropdown-menu { max-height: 200px; overflow: auto; } -#incall .video-hover-buttons .btn-group ul li a:hover { +#incall .video-hover-buttons .btn-group ul li a:not(.selected):hover { background-color: #EEE; cursor: pointer; } @@ -906,13 +917,11 @@ body .modal-body .btn-group .btn.active { } .members-name { - width: 160px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + width: 140px; } .members-number { + width: 140px; font-size: 10px; } @@ -970,6 +979,7 @@ body .modal-body .btn-group .btn.active { display: inline-block; line-height: 16px; margin-top: -3px; + width: 140px; } .chat-members .chat-members-status i { @@ -1018,6 +1028,17 @@ body .modal-body .btn-group .btn.active { vertical-align: -2px; } +.chat-members .resevartion-menu .icon { + color: #C5C5C5; +} + +.chat-members .resevartion-menu .dropdown-menu { + min-width: 0; +} + +.chat-members .resevartion-menu .dropdown-menu .selected { + font-weight: bold; +} /*.chat-messages {*/ /*border-top: 1px solid #E5E5E5;*/ diff --git a/html5/verto/verto_communicator/src/partials/chat.html b/html5/verto/verto_communicator/src/partials/chat.html index 23797d0b51..9cefd72615 100644 --- a/html5/verto/verto_communicator/src/partials/chat.html +++ b/html5/verto/verto_communicator/src/partials/chat.html @@ -23,12 +23,12 @@

-
{{ member.name }}
+
{{ member.name }}
({{ member.number }})
Floor
-
Presenter
+
{{ member.status.video.reservationID }}
Screen Share

@@ -57,12 +57,6 @@ Mute/Unmute Video -
  • - - - Presenter - -
  • @@ -103,6 +97,21 @@ +
    + +
    + diff --git a/html5/verto/verto_communicator/src/partials/video_call.html b/html5/verto/verto_communicator/src/partials/video_call.html index 9633149c77..7fcb9b71b2 100644 --- a/html5/verto/verto_communicator/src/partials/video_call.html +++ b/html5/verto/verto_communicator/src/partials/video_call.html @@ -31,7 +31,7 @@ diff --git a/html5/verto/verto_communicator/src/vertoControllers/controllers/ChatController.js b/html5/verto/verto_communicator/src/vertoControllers/controllers/ChatController.js index 3129db87c7..c990e0d5c2 100644 --- a/html5/verto/verto_communicator/src/vertoControllers/controllers/ChatController.js +++ b/html5/verto/verto_communicator/src/vertoControllers/controllers/ChatController.js @@ -49,6 +49,39 @@ }); }); + + $rootScope.$on('changedVideoLayout', function(event, layout) { + $scope.resIDs = getResByLayout(layout); + + // remove resIDs param to clear every members resID. + // passing $scope.resIDs results in preserving resIDs compatible + // with the current layout + clearMembersResID($scope.resIDs); + }); + + $rootScope.$on('conference.canvasInfo', function(event, data) { + $scope.currentLayout = data[0].layoutName; + $scope.resIDs = getResByLayout($scope.currentLayout); + }); + + function getResByLayout(layout) { + var layoutsData = verto.data.confLayoutsData; + for (var i = 0; i < layoutsData.length; i++) { + if (layoutsData[i].name === layout) { + return layoutsData[i].resIDS; + } + } + } + + // @preserve - a array of values to be preserved + function clearMembersResID(preserve) { + $scope.members.forEach(function(member) { + var resID = member.status.video.reservationID; + if (preserve && preserve.indexOf(resID) !== -1) return; + $scope.confResID(member.id, resID); + }); + }; + function findMemberByUUID(uuid) { var found = false; for (var idx in $scope.members) { @@ -171,6 +204,11 @@ verto.data.conf.presenter(memberID); }; + $scope.confResID = function(memberID, resID) { + console.log('Set', memberID, 'to', resID); + verto.setResevartionId(memberID, resID); + }; + $scope.confVideoFloor = function(memberID) { console.log('$scope.confVideoFloor'); verto.data.conf.videoFloor(memberID); diff --git a/html5/verto/verto_communicator/src/vertoControllers/controllers/InCallController.js b/html5/verto/verto_communicator/src/vertoControllers/controllers/InCallController.js index 93e510d5e3..4285ace586 100644 --- a/html5/verto/verto_communicator/src/vertoControllers/controllers/InCallController.js +++ b/html5/verto/verto_communicator/src/vertoControllers/controllers/InCallController.js @@ -73,6 +73,8 @@ $scope.confChangeVideoLayout = function(layout) { verto.data.conf.setVideoLayout(layout); + $scope.videoLayout = layout; + $rootScope.$emit('changedVideoLayout', layout); }; $scope.confChangeSpeaker = function(speakerId) { diff --git a/html5/verto/verto_communicator/src/vertoService/services/vertoService.js b/html5/verto/verto_communicator/src/vertoService/services/vertoService.js index 9bf54c3cbd..4a46b09066 100644 --- a/html5/verto/verto_communicator/src/vertoService/services/vertoService.js +++ b/html5/verto/verto_communicator/src/vertoService/services/vertoService.js @@ -391,7 +391,27 @@ vertoService.service('verto', ['$rootScope', '$cookieStore', '$location', 'stora if (message.action == 'response') { // This is a response with the video layouts list. if (message['conf-command'] == 'list-videoLayouts') { - data.confLayouts = message.responseData.sort(); + var rdata = []; + + for (var i in message.responseData) { + rdata.push(message.responseData[i].name); + } + + var options = rdata.sort(function(a, b) { + var ga = a.substring(0, 6) == "group:" ? true : false; + var gb = b.substring(0, 6) == "group:" ? true : false; + + if ((ga || gb) && ga != gb) { + return ga ? -1 : 1; + } + + return ( ( a == b ) ? 0 : ( ( a > b ) ? 1 : -1 ) ); + }); + data.confLayoutsData = message.responseData; + data.confLayouts = options; + } else if (message['conf-command'] == 'canvasInfo') { + data.canvasInfo = message.responseData; + $rootScope.$emit('conference.canvasInfo', message.responseData); } else { $rootScope.$emit('conference.broadcast', message); } @@ -402,6 +422,7 @@ vertoService.service('verto', ['$rootScope', '$cookieStore', '$location', 'stora if (data.confRole == "moderator") { console.log('>>> conf.listVideoLayouts();'); conf.listVideoLayouts(); + conf.modCommand('canvasInfo'); } data.conf = conf; @@ -913,6 +934,12 @@ vertoService.service('verto', ['$rootScope', '$cookieStore', '$location', 'stora data.conf.sendChat(message, "message"); }, /* + * Method is used to set a member's resevartion Id. + */ + setResevartionId: function(memberID, resID) { + data.conf.modCommand('vid-res-id', memberID, resID); + }, + /* * Method is used to send user2user chats. * VC does not yet support that. */ diff --git a/html5/verto/video_demo/js/verto-min.js b/html5/verto/video_demo/js/verto-min.js index f4e362be1b..0e5580299f 100644 --- a/html5/verto/video_demo/js/verto-min.js +++ b/html5/verto/video_demo/js/verto-min.js @@ -238,7 +238,9 @@ jq.mouseover(function(e){jq.data({"mouse":true});$("#"+box_id).show();});jq.mous $("#"+volup_id).click(function(){confMan.modCommand("volume_in",x,"up");});$("#"+voldn_id).click(function(){confMan.modCommand("volume_in",x,"down");});return html;} var atitle="";var awidth=0;verto.subscribe(confMan.params.laData.chatChannel,{handler:function(v,e){if(typeof(confMan.params.chatCallback)==="function"){confMan.params.chatCallback(v,e);}}});if(confMan.params.laData.role==="moderator"){atitle="Action";awidth=600;if(confMan.params.mainModID){genMainMod($(confMan.params.mainModID));$(confMan.params.displayID).html("Moderator Controls Ready

    ");}else{$(confMan.params.mainModID).html("");} verto.subscribe(confMan.params.laData.modChannel,{handler:function(v,e){if(confMan.params.onBroadcast){confMan.params.onBroadcast(verto,confMan,e.data);} -if(e.data["conf-command"]==="list-videoLayouts"){for(var j=0;jb)?1:-1));});for(var i in options){$(vlselect_id).append(new Option(options[i],options[i]));x++;}} if(x){$(vlselect_id).selectmenu('refresh',true);}else{$(vlayout_id).hide();}}}else{if(!confMan.destroyed&&confMan.params.displayID){$(confMan.params.displayID).html(e.data.response+"

    ");if(confMan.lastTimeout){clearTimeout(confMan.lastTimeout);confMan.lastTimeout=0;} confMan.lastTimeout=setTimeout(function(){$(confMan.params.displayID).html(confMan.destroyed?"":"Moderator Controls Ready

    ");},4000);}}}});if(confMan.params.hasVid){confMan.modCommand("list-videoLayouts",null,null);}} var row_callback=null;if(confMan.params.laData.role==="moderator"){row_callback=function(nRow,aData,iDisplayIndex,iDisplayIndexFull){if(!aData[5]){var $row=$('td:eq(5)',nRow);genControls($row,aData);if(confMan.params.onLaRow){confMan.params.onLaRow(verto,confMan,$row,aData);}}};} @@ -259,15 +261,15 @@ dialog.verto.rpcClient.call(method,obj,function(e){dialog.processReply(method,tr return false;} function find_name(id){for(var i in $.verto.audioOutDevices){var source=$.verto.audioOutDevices[i];if(source.id===id){return(source.label);}} return id;} -$.verto.dialog.prototype.setAudioDevice=function(sinkId,callback,arg){var dialog=this;var element=dialog.audioStream;if(typeof element.sinkId!=='undefined'){console.info("Dialog: "+dialog.callID+" Setting speaker:",element,find_name(sinkId));element.setSinkId(sinkId).then(function(){console.log("Dialog: "+dialog.callID+' Success, audio output device attached: '+sinkId);if(callback){callback(true,arg);}}).catch(function(error){var errorMessage=error;if(error.name==='SecurityError'){errorMessage="Dialog: "+dialog.callID+' You need to use HTTPS for selecting audio output '+'device: '+error;} -if(callback){callback(false,arg);} -console.error(errorMessage);});}else{console.warn("Dialog: "+dialog.callID+' Browser does not support output device selection.');if(callback){callback(false,arg);}}} +$.verto.dialog.prototype.setAudioPlaybackDevice=function(sinkId,callback,arg){var dialog=this;var element=dialog.audioStream;if(typeof element.sinkId!=='undefined'){var devname=find_name(sinkId);console.info("Dialog: "+dialog.callID+" Setting speaker:",element,devname);element.setSinkId(sinkId).then(function(){console.log("Dialog: "+dialog.callID+' Success, audio output device attached: '+sinkId);if(callback){callback(true,devname,arg);}}).catch(function(error){var errorMessage=error;if(error.name==='SecurityError'){errorMessage="Dialog: "+dialog.callID+' You need to use HTTPS for selecting audio output '+'device: '+error;} +if(callback){callback(false,null,arg);} +console.error(errorMessage);});}else{console.warn("Dialog: "+dialog.callID+' Browser does not support output device selection.');if(callback){callback(false,null,arg);}}} $.verto.dialog.prototype.setState=function(state){var dialog=this;if(dialog.state==$.verto.enum.state.ringing){dialog.stopRinging();} if(dialog.state==state||!checkStateChange(dialog.state,state)){console.error("Dialog "+dialog.callID+": INVALID state change from "+dialog.state.name+" to "+state.name);dialog.hangup();return false;} console.log("Dialog "+dialog.callID+": state change from "+dialog.state.name+" to "+state.name);dialog.lastState=dialog.state;dialog.state=state;if(!dialog.causeCode){dialog.causeCode=16;} if(!dialog.cause){dialog.cause="NORMAL CLEARING";} if(dialog.callbacks.onDialogState){dialog.callbacks.onDialogState(this);} -switch(dialog.state){case $.verto.enum.state.early:case $.verto.enum.state.active:var speaker=dialog.useSpeak;console.info("Using Speaker: ",speaker);if(speaker&&speaker!=="any"){setTimeout(function(){dialog.setAudioDevice(speaker);},500);} +switch(dialog.state){case $.verto.enum.state.early:case $.verto.enum.state.active:var speaker=dialog.useSpeak;console.info("Using Speaker: ",speaker);if(speaker&&speaker!=="any"){setTimeout(function(){dialog.setAudioPlaybackDevice(speaker);},500);} break;case $.verto.enum.state.trying:setTimeout(function(){if(dialog.state==$.verto.enum.state.trying){dialog.setState($.verto.enum.state.hangup);}},30000);break;case $.verto.enum.state.purge:dialog.setState($.verto.enum.state.destroy);break;case $.verto.enum.state.hangup:if(dialog.lastState.val>$.verto.enum.state.requesting.val&&dialog.lastState.val<$.verto.enum.state.hangup.val){dialog.sendMethod("verto.bye",{});} dialog.setState($.verto.enum.state.destroy);break;case $.verto.enum.state.destroy:delete dialog.verto.dialogs[dialog.callID];if(dialog.params.screenShare){dialog.rtc.stopPeer();}else{dialog.rtc.stop();} break;} diff --git a/src/mod/applications/mod_conference/conference_event.c b/src/mod/applications/mod_conference/conference_event.c index d5b094e978..2a1b2df93d 100644 --- a/src/mod/applications/mod_conference/conference_event.c +++ b/src/mod/applications/mod_conference/conference_event.c @@ -42,6 +42,21 @@ #include +static cJSON *get_canvas_info(mcu_canvas_t *canvas) +{ + cJSON *obj = cJSON_CreateObject(); + + cJSON_AddItemToObject(obj, "canvasID", cJSON_CreateNumber(canvas->canvas_id)); + cJSON_AddItemToObject(obj, "totalLayers", cJSON_CreateNumber(canvas->total_layers)); + cJSON_AddItemToObject(obj, "layersUsed", cJSON_CreateNumber(canvas->layers_used)); + cJSON_AddItemToObject(obj, "layoutFloorID", cJSON_CreateNumber(canvas->layout_floor_id)); + if (canvas->vlayout) { + cJSON_AddItemToObject(obj, "layoutName", cJSON_CreateString(canvas->vlayout->name)); + } + + return obj; +} + void conference_event_mod_channel_handler(const char *event_channel, cJSON *json, const char *key, switch_event_channel_id_t id) { cJSON *data, *addobj = NULL; @@ -63,6 +78,7 @@ void conference_event_mod_channel_handler(const char *event_channel, cJSON *json if ((data = cJSON_GetObjectItem(json, "data"))) { action = cJSON_GetObjectCstr(data, "command"); + if ((jid = cJSON_GetObjectItem(data, "id"))) { if (jid->valueint) { switch_snprintf(cid, sizeof(cid), "%d", jid->valueint); @@ -151,6 +167,68 @@ void conference_event_mod_channel_handler(const char *event_channel, cJSON *json switch_thread_rwlock_unlock(conference->rwlock); } goto end; + } else if (!strcasecmp(action, "canvasInfo")) { + cJSON *j_member_id; + int member_id = 0; + int i = 0; + cJSON *array = cJSON_CreateArray(); + conference_obj_t *conference; + + if ((conference = conference_find(conference_name, NULL))) { + + if ((j_member_id = cJSON_GetObjectItem(data, "memberID"))) { + if (j_member_id->valueint) { + member_id = j_member_id->valueint; + } else if (j_member_id->valuedouble) { + member_id = (int) j_member_id->valuedouble; + } else if (j_member_id->valuestring) { + member_id = atoi(j_member_id->valuestring); + } + if (member_id < 0) member_id = 0; + } + + if (member_id > 0) { + conference_member_t *member; + + if ((member = conference_member_get(conference, member_id))) { + mcu_canvas_t *canvas; + + if ((canvas = conference_video_get_canvas_locked(member))) { + cJSON *obj; + + if ((obj = get_canvas_info(canvas))) { + cJSON_AddItemToObject(obj, "layerID", cJSON_CreateNumber(member->video_layer_id)); + cJSON_AddItemToArray(array, obj); + } + + conference_video_release_canvas(&canvas); + } + + switch_thread_rwlock_unlock(member->rwlock); + } + + } else { + switch_mutex_lock(conference->canvas_mutex); + + for (i = 0; i <= conference->canvas_count; i++) { + mcu_canvas_t *canvas = conference->canvases[i]; + if (canvas) { + cJSON *obj; + + if ((obj = get_canvas_info(canvas))) { + cJSON_AddItemToArray(array, obj); + } + } + } + + switch_mutex_unlock(conference->canvas_mutex); + } + + switch_thread_rwlock_unlock(conference->rwlock); + } + + addobj = array; + } else if (!strcasecmp(action, "list-videoLayouts")) { switch_hash_index_t *hi; void *val; @@ -161,17 +239,49 @@ void conference_event_mod_channel_handler(const char *event_channel, cJSON *json switch_mutex_lock(conference_globals.setup_mutex); if (conference->layout_hash) { for (hi = switch_core_hash_first(conference->layout_hash); hi; hi = switch_core_hash_next(&hi)) { + video_layout_t *vlayout; + cJSON *obj = cJSON_CreateObject(); + cJSON *resarray = cJSON_CreateArray(); + int i; + switch_core_hash_this(hi, &vvar, NULL, &val); - cJSON_AddItemToArray(array, cJSON_CreateString((char *)vvar)); + vlayout = (video_layout_t *)val; + for (i = 0; i < vlayout->layers; i++) { + if (vlayout->images[i].res_id) { + cJSON_AddItemToArray(resarray, cJSON_CreateString((char *)vlayout->images[i].res_id)); + } + } + + cJSON_AddItemToObject(obj, "type", cJSON_CreateString("layout")); + cJSON_AddItemToObject(obj, "name", cJSON_CreateString((char *)vvar)); + cJSON_AddItemToObject(obj, "resIDS", resarray); + + cJSON_AddItemToArray(array, obj); } } if (conference->layout_group_hash) { for (hi = switch_core_hash_first(conference->layout_group_hash); hi; hi = switch_core_hash_next(&hi)) { char *name; + cJSON *obj = cJSON_CreateObject(); + cJSON *grouparray = cJSON_CreateArray(); + layout_group_t *lg; + video_layout_node_t *vlnode; + switch_core_hash_this(hi, &vvar, NULL, &val); + lg = (layout_group_t *) val; + name = switch_mprintf("group:%s", (char *)vvar); - cJSON_AddItemToArray(array, cJSON_CreateString(name)); + + for (vlnode = lg->layouts; vlnode; vlnode = vlnode->next) { + cJSON_AddItemToArray(grouparray, cJSON_CreateString(vlnode->vlayout->name)); + } + + cJSON_AddItemToObject(obj, "type", cJSON_CreateString("layoutGroup")); + cJSON_AddItemToObject(obj, "name", cJSON_CreateString(name)); + cJSON_AddItemToObject(obj, "groupLayouts", grouparray); + + cJSON_AddItemToArray(array, obj); free(name); } }