diff --git a/public/css/vroom.css b/public/css/vroom.css index d0a0c9c..8e84ff5 100644 --- a/public/css/vroom.css +++ b/public/css/vroom.css @@ -99,6 +99,27 @@ .unauthEl{ display: none; } +.ownerActions { + position: absolute; + left: 15px; + top: 0px; + display: none; +} +.actionMute:before{ + font-family: 'Glyphicons Halflings'; + color: red; + content: "\e036"; +} +.actionPause:before{ + font-family: 'Glyphicons Halflings'; + color: red; + content: "\e106"; +} +.actionKick:before{ + font-family: 'Glyphicons Halflings'; + color: red; + content: "\e020"; +} #chatBox { max-height:300px; resize:none; diff --git a/public/js/vroom.js b/public/js/vroom.js index 55724f2..3f57619 100644 --- a/public/js/vroom.js +++ b/public/js/vroom.js @@ -35,7 +35,12 @@ var locale = { SCREEN_s: '', TO_INVITE_SHARE_THIS_URL: '', NO_SOUND_DETECTED: '', - DISPLAY_NAME_TOO_LONG: '' + DISPLAY_NAME_TOO_LONG: '', + s_IS_MUTING_YOU: '', + s_IS_MUTING_s: '', + s_IS_SUSPENDING_YOU: '', + s_IS_SUSPENDING_s: '', + s_IS_KICKING_s: '' }; // Localize the strings we need @@ -162,12 +167,14 @@ function initVroom(room) { $.notify(locale.ERROR_OCCURED, 'error'); }, success: function(data){ - peers[id].role = data.role; - if (data.role == 'owner'){ - $("#overlay_" + id).append('
'); - } - else{ - $('#owner_' + id).remove(); + if (peers[id]){ + peers[id].role = data.role; + if (data.role == 'owner'){ + $("#overlay_" + id).append(''); + } + else{ + $('#owner_' + id).remove(); + } } } }); @@ -241,6 +248,35 @@ function initVroom(room) { $('').addClass('volumeBar').attr('id', 'volume_' + id).appendTo(div); $('').addClass('displayName').attr('id', 'name_' + id).appendTo(div); $('').attr('id', 'overlay_' + id).appendTo(div); + $('').addClass('ownerActions').attr('id', 'ownerActions_' + id).appendTo(div) + .append($('',{ + class: 'btn-group' + }) + .append($('', { + class: 'actionMute btn btn-default btn-sm', + id: 'actionMute_' + id, + click: function() { mutePeer(id) }, + })) + .append($('', { + class: 'actionPause btn btn-default btn-sm', + id: 'actionPause_' + id, + click: function() { pausePeer(id) }, + })) + .append($('', { + class: 'actionKick btn btn-default btn-sm', + id: 'actionKick_' + id, + click: function() { kickPeer(id) }, + }))); + $(div).hover( + function(){ + if (peers.local.role == 'owner'){ + $('#ownerActions_' + id).show(200); + } + }, + function(){ + $('#ownerActions_' + id).hide(200); + } + ); // Create a new dataChannel // will be used for text chat and displayName var color = chooseColor(); @@ -389,6 +425,104 @@ function initVroom(room) { saveAs(blob, filename); } + // Mute a peer + function mutePeer(id){ + webrtc.sendToAll('owner_mute', {peer: id}); + } + // Puase a peer + function pausePeer(id){ + webrtc.sendToAll('owner_pause', {peer: id}); + } + // Kick a peer + function kickPeer(id){ + webrtc.sendToAll('owner_kick', {peer: id}); + // Wait a bit for the peer to leave, but end connection if it's still here + // after 2 seconds + setTimeout(function(){ + if (peers[id]){ + peers[id].obj.end(); + } + }, 2000); + } + + // Mute our mic + function muteMic(){ + webrtc.mute(); + peers.local.micMuted = true; + showVolume($('#localVolume'), -45); + } + // Unmute + function unmuteMic(){ + webrtc.unmute(); + peers.local.micMuted = false; + } + // Suspend or webcam + function suspendCam(){ + webrtc.pauseVideo(); + peers.local.videoPaused = true; + } + // Resume webcam + function resumeCam(){ + webrtc.resumeVideo(); + peers.local.videoPaused = false; + } + + // An owner is muting ourself + webrtc.on('owner_mute', function(data){ + if (peers[data.id].role != 'owner'){ + return; + } + if (data.payload.peer && data.payload.peer == peers.local.id){ + // Ignore this if the remote peer isn't the owner of the room + if (!peers.local.micMuted){ + muteMic(); + $("#muteMicLabel").addClass('btn-danger active'); + $('#muteMicButton').prop('checked', true); + $.notify(sprintf(locale.s_IS_MUTING_YOU, peers[data.id].displayName), 'info'); + } + } + else{ + $.notify(sprintf(locale.s_IS_MUTING_s, peers[data.id].displayName, peers[data.payload.peer].displayName), 'info'); + } + }); + // An owner is pausing our webcam + webrtc.on('owner_pause', function(data){ + if (peers[data.id].role != 'owner'){ + return; + } + if (data.payload.peer && data.payload.peer == peers.local.id){ + if (!peers.local.paused){ + suspendCam(); + $("#suspendCamLabel").addClass('btn-danger active'); + $('#suspendCamButton').prop('checked', true); + $.notify(sprintf(locale.s_IS_SUSPENDING_YOU, peers[data.id].displayName), 'info'); + } + } + else{ + $.notify(sprintf(locale.s_IS_SUSPENDING_s, peers[data.id].displayName, peers[data.payload.peer].displayName), 'info'); + } + }); + // An owner is kicking us from the room + webrtc.on('owner_kick', function(data){ + if (peers[data.id].role != 'owner'){ + return; + } + if (data.payload.peer && data.payload.peer == peers.local.id){ + hangupCall; + window.location.assign(rootUrl + 'kicked/' + roomName); + } + else{ + $.notify(sprintf(locale.s_IS_KICKING_s, peers[data.id].displayName, peers[data.payload.peer].displayName), 'info'); + // Wait a bit for the peer to leave, but end connection if it's still here + // after 2 seconds + setTimeout(function(){ + if (peers[data.id]){ + peers[data.id].obj.end(); + } + }, 2000); + } + }); + // Handle volume changes from our own mic webrtc.on('volumeChange', function (volume, treshold) { if (volume > maxVol){ @@ -558,6 +692,9 @@ function initVroom(room) { $('#mainVideo').html(''); mainVid = false; } + if (peer && peers[peer.id]){ + delete peers[peer.id]; + } }); // Error sending something through dataChannel @@ -709,15 +846,12 @@ function initVroom(room) { $('#muteMicButton').change(function() { var action = ($(this).is(":checked")) ? 'mute':'unmute'; if (action === 'mute'){ - webrtc.mute(); - peers.local.micMuted = true; - showVolume($('#localVolume'), -45); + muteMic(); $("#muteMicLabel").addClass('btn-danger'); $.notify(locale.MIC_MUTED, 'info'); } else{ - webrtc.unmute(); - peers.local.micMuted = false; + unmuteMic(); $("#muteMicLabel").removeClass('btn-danger'); $.notify(locale.MIC_UNMUTED, 'info'); } @@ -727,14 +861,12 @@ function initVroom(room) { $('#suspendCamButton').change(function() { var action = ($(this).is(":checked")) ? 'pause':'resume'; if (action === 'pause'){ - webrtc.pauseVideo(); - peers.local.videoPaused = true; + suspendCam(); $("#suspendCamLabel").addClass('btn-danger'); $.notify(locale.CAM_SUSPENDED, 'info'); } else{ - webrtc.resumeVideo(); - peers.local.videoPaused = false; + resumeCam(); $("#suspendCamLabel").removeClass('btn-danger'); $.notify(locale.CAM_RESUMED, 'info'); } diff --git a/public/vroom.pl b/public/vroom.pl index 8c498f5..99e4689 100755 --- a/public/vroom.pl +++ b/public/vroom.pl @@ -270,7 +270,7 @@ helper valid_room_name => sub { my ($name) = @_; my $ret = undef; # A few names are reserved - my @reserved = qw(about help feedback goodbye admin create localize action missing dies password); + my @reserved = qw(about help feedback goodbye admin create localize action missing dies password kicked); if ($name =~ m/^[\w\-]{1,49}$/ && !grep { $name eq $_ } @reserved){ $ret = 1; } @@ -397,6 +397,22 @@ get '/goodby/(:room)' => sub { $self->logout; } => 'goodby'; +# Route for the kicked page +# Should be merged with the goodby route +get '/kicked/(:room)' => sub { + my $self = shift; + my $room = $self->stash('room'); + if (!$self->get_room($room)){ + return $self->render('error', + err => 'ERROR_ROOM_s_DOESNT_EXIST', + msg => sprintf ($self->l("ERROR_ROOM_s_DOESNT_EXIST"), $room), + room => $room + ); + } + $self->remove_participant($room,$self->session('name')); + $self->logout; +} => 'kicked'; + # This handler creates a new room post '/create' => sub { my $self = shift; diff --git a/templates/default/kicked.html.ep b/templates/default/kicked.html.ep new file mode 100644 index 0000000..baf9134 --- /dev/null +++ b/templates/default/kicked.html.ep @@ -0,0 +1,14 @@ +% title $self->l('KICKED'); +%= include 'header' +%= include 'public_toolbar' +<%=l 'AN_ADMIN_HAS_KICKED_YOU' %>
+ +