Pop-out draggable video support
This commit is contained in:
parent
fb11295168
commit
58264515f9
|
@ -64,11 +64,11 @@ body {
|
||||||
grid-column: 2;
|
grid-column: 2;
|
||||||
grid-row: 2;
|
grid-row: 2;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
/* position: relative; */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Auto-scroll checkbox in the corner */
|
/* Auto-scroll checkbox in the corner */
|
||||||
.chat-column >.autoscroll-field {
|
.autoscroll-field {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 39; /* just below modal shadow */
|
z-index: 39; /* just below modal shadow */
|
||||||
right: 6px;
|
right: 6px;
|
||||||
|
@ -175,7 +175,7 @@ body {
|
||||||
align-items: left; */
|
align-items: left; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-feeds > .feed {
|
.feed {
|
||||||
position: relative;
|
position: relative;
|
||||||
/* flex: 0 0 168px; */
|
/* flex: 0 0 168px; */
|
||||||
float: left;
|
float: left;
|
||||||
|
@ -187,6 +187,17 @@ body {
|
||||||
resize: both;
|
resize: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* A popped-out video feed window */
|
||||||
|
div.feed.popped-out {
|
||||||
|
position: absolute;
|
||||||
|
border: 1px solid #FFF;
|
||||||
|
cursor: move;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
.video-feeds.x1 > .feed {
|
.video-feeds.x1 > .feed {
|
||||||
flex: 0 0 252px;
|
flex: 0 0 252px;
|
||||||
width: 252px;
|
width: 252px;
|
||||||
|
@ -211,25 +222,24 @@ body {
|
||||||
height: 448px;
|
height: 448px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-feeds > .feed > video {
|
.feed > video {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-feeds > .feed > .controls {
|
.feed > .controls {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: rgba(0, 0, 0, 0.75);
|
|
||||||
left: 4px;
|
left: 4px;
|
||||||
bottom: 4px;
|
bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-feeds > .feed > .close {
|
.feed > .close {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 4px;
|
right: 4px;
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-feeds > .feed > .caption {
|
.feed > .caption {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: rgba(0, 0, 0, 0.75);
|
background: rgba(0, 0, 0, 0.75);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
|
@ -18,6 +18,9 @@ function setModalImage(url) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Popped-out video drag functions.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const app = Vue.createApp({
|
const app = Vue.createApp({
|
||||||
delimiters: ['[[', ']]'],
|
delimiters: ['[[', ']]'],
|
||||||
|
@ -120,6 +123,7 @@ const app = Vue.createApp({
|
||||||
// Streams per username.
|
// Streams per username.
|
||||||
streams: {},
|
streams: {},
|
||||||
muted: {}, // muted bool per username
|
muted: {}, // muted bool per username
|
||||||
|
poppedOut: {}, // popped-out video per username
|
||||||
|
|
||||||
// RTCPeerConnections per username.
|
// RTCPeerConnections per username.
|
||||||
pc: {},
|
pc: {},
|
||||||
|
@ -1183,6 +1187,111 @@ const app = Vue.createApp({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Pop out a user's video.
|
||||||
|
popoutVideo(username) {
|
||||||
|
this.WebRTC.poppedOut[username] = !this.WebRTC.poppedOut[username];
|
||||||
|
|
||||||
|
// If not popped out, reset CSS positioning.
|
||||||
|
window.requestAnimationFrame(this.makeDraggableVideos);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Outside of Vue, attach draggable video scripts to DOM.
|
||||||
|
makeDraggableVideos() {
|
||||||
|
let $panel = document.querySelector("#video-feeds");
|
||||||
|
|
||||||
|
interact('.popped-in').unset();
|
||||||
|
|
||||||
|
// Give popped out videos to the root of the DOM so they can
|
||||||
|
// be dragged anywhere on the page.
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
document.querySelectorAll('.popped-out').forEach(node => {
|
||||||
|
// $panel.removeChild(node);
|
||||||
|
document.body.appendChild(node);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.popped-in').forEach(node => {
|
||||||
|
// document.body.removeChild(node);
|
||||||
|
$panel.appendChild(node);
|
||||||
|
node.style.top = null;
|
||||||
|
node.style.left = null;
|
||||||
|
node.setAttribute('data-x', 0);
|
||||||
|
node.setAttribute('data-y', 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
interact('.popped-out').draggable({
|
||||||
|
// enable inertial throwing
|
||||||
|
inertia: true,
|
||||||
|
// keep the element within the area of it's parent
|
||||||
|
modifiers: [
|
||||||
|
interact.modifiers.restrictRect({
|
||||||
|
restriction: 'parent',
|
||||||
|
endOnly: true
|
||||||
|
})
|
||||||
|
],
|
||||||
|
|
||||||
|
listeners: {
|
||||||
|
// call this function on every dragmove event
|
||||||
|
move(event) {
|
||||||
|
let target = event.target;
|
||||||
|
let x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx
|
||||||
|
let y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy
|
||||||
|
|
||||||
|
target.style.top = `${y}px`;
|
||||||
|
target.style.left = `${x}px`;
|
||||||
|
|
||||||
|
target.setAttribute('data-x', x);
|
||||||
|
target.setAttribute('data-y', y);
|
||||||
|
},
|
||||||
|
|
||||||
|
// call this function on every dragend event
|
||||||
|
end (event) {
|
||||||
|
console.log(
|
||||||
|
'moved a distance of ' +
|
||||||
|
(Math.sqrt(Math.pow(event.pageX - event.x0, 2) +
|
||||||
|
Math.pow(event.pageY - event.y0, 2) | 0))
|
||||||
|
.toFixed(2) + 'px')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).resizable({
|
||||||
|
edges: { left: true, right: true, bottom: true, right: true },
|
||||||
|
listeners: {
|
||||||
|
move (event) {
|
||||||
|
var target = event.target
|
||||||
|
var x = (parseFloat(target.getAttribute('data-x')) || 0)
|
||||||
|
var y = (parseFloat(target.getAttribute('data-y')) || 0)
|
||||||
|
|
||||||
|
// update the element's style
|
||||||
|
target.style.width = event.rect.width + 'px'
|
||||||
|
target.style.height = event.rect.height + 'px'
|
||||||
|
|
||||||
|
// translate when resizing from top or left edges
|
||||||
|
x += event.deltaRect.left
|
||||||
|
y += event.deltaRect.top
|
||||||
|
|
||||||
|
target.style.top = `${y}px`;
|
||||||
|
target.style.left = `${x}px`;
|
||||||
|
|
||||||
|
target.setAttribute('data-x', x)
|
||||||
|
target.setAttribute('data-y', y)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifiers: [
|
||||||
|
// keep the edges inside the parent
|
||||||
|
interact.modifiers.restrictEdges({
|
||||||
|
outer: 'parent'
|
||||||
|
}),
|
||||||
|
|
||||||
|
// minimum size
|
||||||
|
interact.modifiers.restrictSize({
|
||||||
|
min: { width: 100, height: 50 }
|
||||||
|
})
|
||||||
|
],
|
||||||
|
|
||||||
|
inertia: true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
initHistory(channel) {
|
initHistory(channel) {
|
||||||
if (this.channels[channel] == undefined) {
|
if (this.channels[channel] == undefined) {
|
||||||
this.channels[channel] = {
|
this.channels[channel] = {
|
||||||
|
|
3
web/static/js/interact.min.js
vendored
Normal file
3
web/static/js/interact.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -451,7 +451,7 @@
|
||||||
<!-- Mute/Unmute my mic buttons (if streaming)-->
|
<!-- Mute/Unmute my mic buttons (if streaming)-->
|
||||||
<button type="button"
|
<button type="button"
|
||||||
v-if="webcam.active && !webcam.muted"
|
v-if="webcam.active && !webcam.muted"
|
||||||
class="button is-small is-danger is-outlined ml-1 px-1"
|
class="button is-small is-success ml-1 px-1"
|
||||||
@click="muteMe()">
|
@click="muteMe()">
|
||||||
<i class="fa fa-microphone mr-2"></i>
|
<i class="fa fa-microphone mr-2"></i>
|
||||||
Mute
|
Mute
|
||||||
|
@ -460,7 +460,7 @@
|
||||||
v-if="webcam.active && webcam.muted"
|
v-if="webcam.active && webcam.muted"
|
||||||
class="button is-small is-danger ml-1 px-1"
|
class="button is-small is-danger ml-1 px-1"
|
||||||
@click="muteMe()">
|
@click="muteMe()">
|
||||||
<i class="fa fa-microphone mr-2"></i>
|
<i class="fa fa-microphone-slash mr-2"></i>
|
||||||
Unmute
|
Unmute
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
@ -561,14 +561,6 @@
|
||||||
|
|
||||||
<!-- Middle Column: Chat Room/History -->
|
<!-- Middle Column: Chat Room/History -->
|
||||||
<div class="chat-column">
|
<div class="chat-column">
|
||||||
<div class="autoscroll-field tag">
|
|
||||||
<label class="checkbox is-size-6" title="Automatically scroll when new chat messages come in.">
|
|
||||||
<input type="checkbox"
|
|
||||||
v-model="autoscroll"
|
|
||||||
:value="true">
|
|
||||||
Auto-scroll
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card grid-card">
|
<div class="card grid-card">
|
||||||
<header class="card-header has-background-link">
|
<header class="card-header has-background-link">
|
||||||
|
@ -615,19 +607,52 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="video-feeds" :class="webcam.videoScale" v-show="webcam.active || Object.keys(WebRTC.streams).length > 0">
|
<div id="video-feeds" class="video-feeds" :class="webcam.videoScale" v-show="webcam.active || Object.keys(WebRTC.streams).length > 0">
|
||||||
<!-- Video Feeds-->
|
<!-- Video Feeds-->
|
||||||
|
|
||||||
<!-- My video -->
|
<!-- My video -->
|
||||||
<div class="feed" v-show="webcam.active">
|
<div class="feed" v-show="webcam.active"
|
||||||
|
:class="{'popped-out': WebRTC.poppedOut[username],
|
||||||
|
'popped-in': !WebRTC.poppedOut[username]}">
|
||||||
<video class="feed"
|
<video class="feed"
|
||||||
id="localVideo"
|
id="localVideo"
|
||||||
autoplay muted>
|
autoplay muted>
|
||||||
</video>
|
</video>
|
||||||
|
|
||||||
|
<div class="caption">
|
||||||
|
[[username]]
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<!-- MY Mute button -->
|
||||||
|
<button type="button"
|
||||||
|
v-if="webcam.active && !webcam.muted"
|
||||||
|
class="button is-small is-success is-outlined ml-1 px-2"
|
||||||
|
@click="muteMe()">
|
||||||
|
<i class="fa fa-microphone"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button"
|
||||||
|
v-if="webcam.active && webcam.muted"
|
||||||
|
class="button is-small is-danger ml-1 px-2"
|
||||||
|
@click="muteMe()">
|
||||||
|
<i class="fa fa-microphone-slash"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Pop-out MY video -->
|
||||||
|
<button type="button"
|
||||||
|
class="button is-small is-light is-outlined p-2 ml-2"
|
||||||
|
title="Pop out"
|
||||||
|
@click="popoutVideo(username)">
|
||||||
|
<i class="fa fa-up-right-from-square"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Others' videos -->
|
<!-- Others' videos -->
|
||||||
<div class="feed" v-for="(stream, username) in WebRTC.streams" v-bind:key="username">
|
<div class="feed" v-for="(stream, username) in WebRTC.streams"
|
||||||
|
v-bind:key="username"
|
||||||
|
:class="{'popped-out': WebRTC.poppedOut[username],
|
||||||
|
'popped-in': !WebRTC.poppedOut[username]}">
|
||||||
<video class="feed"
|
<video class="feed"
|
||||||
:id="'videofeed-'+username"
|
:id="'videofeed-'+username"
|
||||||
autoplay>
|
autoplay>
|
||||||
|
@ -644,20 +669,29 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
|
<!-- Mute button -->
|
||||||
<button type="button"
|
<button type="button"
|
||||||
v-if="isMuted(username)"
|
v-if="isMuted(username)"
|
||||||
class="button is-small is-danger is-outlined p-1"
|
class="button is-small is-danger p-2"
|
||||||
title="Unmute this video"
|
title="Unmute this video"
|
||||||
@click="muteVideo(username)">
|
@click="muteVideo(username)">
|
||||||
<i class="fa fa-volume-xmark"></i>
|
<i class="fa fa-volume-xmark"></i>
|
||||||
</button>
|
</button>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
v-else
|
v-else
|
||||||
class="button is-small is-danger is-outlined p-1"
|
class="button is-small is-success is-outlined p-2"
|
||||||
title="Mute this video"
|
title="Mute this video"
|
||||||
@click="muteVideo(username)">
|
@click="muteVideo(username)">
|
||||||
<i class="fa fa-volume-high"></i>
|
<i class="fa fa-volume-high"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<!-- Pop-out -->
|
||||||
|
<button type="button"
|
||||||
|
class="button is-small is-light is-outlined p-2 ml-2"
|
||||||
|
title="Pop out"
|
||||||
|
@click="popoutVideo(username)">
|
||||||
|
<i class="fa fa-up-right-from-square"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -680,6 +714,15 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content" id="chatHistory" :class="fontSizeClass">
|
<div class="card-content" id="chatHistory" :class="fontSizeClass">
|
||||||
|
|
||||||
|
<div class="autoscroll-field tag">
|
||||||
|
<label class="checkbox is-size-6" title="Automatically scroll when new chat messages come in.">
|
||||||
|
<input type="checkbox"
|
||||||
|
v-model="autoscroll"
|
||||||
|
:value="true">
|
||||||
|
Auto-scroll
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- No history? -->
|
<!-- No history? -->
|
||||||
<div v-if="chatHistory.length === 0">
|
<div v-if="chatHistory.length === 0">
|
||||||
<em v-if="isDM">
|
<em v-if="isDM">
|
||||||
|
@ -806,7 +849,7 @@
|
||||||
<div class="column pl-1 is-narrow">
|
<div class="column pl-1 is-narrow">
|
||||||
<button type="button" class="button"
|
<button type="button" class="button"
|
||||||
@click="uploadFile()">
|
@click="uploadFile()">
|
||||||
<i class="fa fa-upload"></i>
|
<i class="fa fa-image"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -979,6 +1022,7 @@ const UserJWTClaims = {{.JWTClaims.ToJSON}};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="/static/js/vue-3.2.45.js"></script>
|
<script src="/static/js/vue-3.2.45.js"></script>
|
||||||
|
<script src="/static/js/interact.min.js"></script>
|
||||||
<script src="/static/js/sounds.js?{{.CacheHash}}"></script>
|
<script src="/static/js/sounds.js?{{.CacheHash}}"></script>
|
||||||
<script src="/static/js/BareRTC.js?{{.CacheHash}}"></script>
|
<script src="/static/js/BareRTC.js?{{.CacheHash}}"></script>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user