fully working embedded player + chat

This commit is contained in:
Kacper Maj 2021-01-25 19:27:44 +01:00
parent c497a55297
commit 67d81787eb
7 changed files with 219 additions and 26 deletions

58
package-lock.json generated
View File

@ -589,6 +589,11 @@
"unpipe": "~1.0.0" "unpipe": "~1.0.0"
} }
}, },
"foreachasync": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/foreachasync/-/foreachasync-3.0.0.tgz",
"integrity": "sha1-VQKYfchxS+M5IJfzLgBxyd7gfPY="
},
"forwarded": { "forwarded": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
@ -658,6 +663,18 @@
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
"dev": true "dev": true
}, },
"handlebars": {
"version": "4.7.6",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz",
"integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==",
"requires": {
"minimist": "^1.2.5",
"neo-async": "^2.6.0",
"source-map": "^0.6.1",
"uglify-js": "^3.1.4",
"wordwrap": "^1.0.0"
}
},
"has-flag": { "has-flag": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@ -670,6 +687,15 @@
"integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==",
"dev": true "dev": true
}, },
"hbs": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/hbs/-/hbs-4.1.1.tgz",
"integrity": "sha512-6QsbB4RwbpL4cb4DNyjEEPF+suwp+3yZqFVlhILEn92ScC0U4cDCR+FDX53jkfKJPhutcqhAvs+rOLZw5sQrDA==",
"requires": {
"handlebars": "4.7.6",
"walk": "2.3.14"
}
},
"http-cache-semantics": { "http-cache-semantics": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
@ -913,8 +939,7 @@
"minimist": { "minimist": {
"version": "1.2.5", "version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
"dev": true
}, },
"ms": { "ms": {
"version": "2.0.0", "version": "2.0.0",
@ -926,6 +951,11 @@
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
}, },
"neo-async": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
},
"nodemon": { "nodemon": {
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.7.tgz", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.7.tgz",
@ -1293,6 +1323,11 @@
} }
} }
}, },
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"statuses": { "statuses": {
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
@ -1421,6 +1456,12 @@
"is-typedarray": "^1.0.0" "is-typedarray": "^1.0.0"
} }
}, },
"uglify-js": {
"version": "3.12.5",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.5.tgz",
"integrity": "sha512-SgpgScL4T7Hj/w/GexjnBHi3Ien9WS1Rpfg5y91WXMj9SY997ZCQU76mH4TpLwwfmMvoOU8wiaRkIf6NaH3mtg==",
"optional": true
},
"undefsafe": { "undefsafe": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz",
@ -1484,6 +1525,14 @@
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
}, },
"walk": {
"version": "2.3.14",
"resolved": "https://registry.npmjs.org/walk/-/walk-2.3.14.tgz",
"integrity": "sha512-5skcWAUmySj6hkBdH6B6+3ddMjVQYH5Qy9QGbPmN8kVmLteXk+yVXg+yfk1nbX30EYakahLrr8iPcCxJQSCBeg==",
"requires": {
"foreachasync": "^3.0.0"
}
},
"widest-line": { "widest-line": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz",
@ -1493,6 +1542,11 @@
"string-width": "^4.0.0" "string-width": "^4.0.0"
} }
}, },
"wordwrap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
},
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",

View File

@ -13,6 +13,7 @@
"dependencies": { "dependencies": {
"bad-words": "^3.0.4", "bad-words": "^3.0.4",
"express": "^4.17.1", "express": "^4.17.1",
"hbs": "^4.1.1",
"socket.io": "^3.1.0" "socket.io": "^3.1.0"
}, },
"devDependencies": { "devDependencies": {

View File

@ -90,21 +90,41 @@ button:disabled {
} }
.chat__sidebar { .chat__sidebar {
height: 100vh; position: relative;
top: 51vh;
/* left: 1vh; */
/* right: 1vh; */
/* z-index: 2; */
height: 49vh;
color: rgb(223, 223, 223); color: rgb(223, 223, 223);
background: #333744; background: #333744;
width: 225px; width: 50%;
overflow-y: scroll; overflow-y: scroll;
scroll-behavior: smooth; scroll-behavior: smooth;
} }
/* embedded player div */
.player {
position: absolute;
left: 1vh;
top: 1vh;
width: 49%;
height: 49%;
z-index: 1;
/* margin-top: 5px; */
/* margin-left: 5px; */
}
/* Chat styles */ /* Chat styles */
.chat__main { .chat__main {
position: relative;
flex-grow: 1; flex-grow: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-height: 100vh; height: 100vh;
/* margin-left: 20vh; */
background-color: rgb(233, 233, 233);
} }
.chat__messages { .chat__messages {

View File

@ -17,17 +17,25 @@
<div class="centered-form"> <div class="centered-form">
<div class="centered-form__box"> <div class="centered-form__box">
<h1>Join</h1> <h1>Join</h1>
<form action="/chat.html"> <form action="/chat">
<label>Display name</label> <label>Display name</label>
<input type="text" name="username" placeholder="Display name" required> <input type="text" name="username" placeholder="Display name" required>
<label>Room</label> <label>Room</label>
<input type="text" name="room" placeholder="Room" required> <input type="text" id="roomID" name="room" placeholder="Room" required>
<button type="submit">Join</button> <button type="submit">Join or create</button>
</form> </form>
<p class="myPar">or</p> <p class="myPar">or</p>
<button type="submit">Generate room</button> <button type="submit" id="generateIdButton">Generate unique id</button>
</div> </div>
</div> </div>
<script>
const generateIdButton = document.querySelector('#generateIdButton');
const room = document.querySelector('#roomID');
generateIdButton.addEventListener('click', e => {
const id = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
room.value = id;
});
</script>
</body> </body>
</html> </html>

View File

@ -7,6 +7,8 @@ const input = form.childNodes[1];
const button = form.childNodes[3]; const button = form.childNodes[3];
const messageContainer = document.querySelector('#messages-container'); const messageContainer = document.querySelector('#messages-container');
const sidebarContainer = document.querySelector('.chat__sidebar'); const sidebarContainer = document.querySelector('.chat__sidebar');
const changeVideoButton = document.querySelector('#changeVideo');
const syncVideosButton = document.querySelector('#syncVideos');
// TEMPLATES // TEMPLATES
const sidebarTemplate = `<h2 class="room-title">{{room}}</h2> const sidebarTemplate = `<h2 class="room-title">{{room}}</h2>
@ -38,7 +40,83 @@ const htmlTemplate = [
]; ];
let message = ''; let message = '';
// let user = window.prompt('Please state your username', 'User'); let videoId = '';
// YT PLAYER LOGIC
// Load the IFrame Player API code asynchronously.
var tag = document.createElement('script');
tag.src = 'https://www.youtube.com/player_api';
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// Replace the 'ytplayer' element with an <iframe> and
// YouTube player after the API code downloads.
var player;
function onYouTubePlayerAPIReady() {
player = new YT.Player('ytplayer', {
height: '420',
width: '860',
videoId: 'xFqeAUKU09o',
});
// STATE OF PLAYER
player.addEventListener('onStateChange', (e) => {
// 3 bufforing, 1 playing, 2 paused, -1 stopped entirely
if (e.data !== 3) {
const time = e.target.getCurrentTime();
socket.emit('onPlayerState', e.data, time);
}
});
}
// ON PLAYER STATE DATA RECV
socket.on('onPlayerState', (data, time) => {
if (data === 1 || data === -1) {
// player.seekTo(time, true);
player.playVideo();
} else if (data === 2) {
if (Math.abs(player.getCurrentTime() - time) > 2) {
player.seekTo(time, true);
player.playVideo();
} else {
player.pauseVideo();
}
} else if (data === 0) player.stopVideo();
});
const videoIdParse = (link) => {
return link.slice(link.indexOf('v=') + 2);
};
// SYNC VIDEOS
syncVideosButton.addEventListener('click', () => {
// console.log('syncing');
const time = player.getCurrentTime();
const id = videoIdParse(player.getVideoUrl());
// console.log(time);
socket.emit('videoSync', time, id);
});
socket.on('videoSync', (time, id) => {
// console.log(time, id);
player.loadVideoById(id, time);
// player.seekTo(time);
});
// Change video
changeVideoButton.addEventListener('click', () => {
let link = input.value;
if (!link) return alert('You should pass the youtube link');
const videoId = videoIdParse(link);
socket.emit('changeVideo', videoId);
player.loadVideoById(`${videoId}`, 5, 'large');
input.value = '';
});
socket.on('changeVideo', (videoId) => {
player.loadVideoById(`${videoId}`, 5, 'large');
});
const autoscroll = () => { const autoscroll = () => {
// New message elemnt // New message elemnt
@ -59,7 +137,6 @@ const autoscroll = () => {
const scrolloffset = messageContainer.scrollTop + visibleHeight + 15; const scrolloffset = messageContainer.scrollTop + visibleHeight + 15;
if (containerHeight - newMessageHeight <= scrolloffset) { if (containerHeight - newMessageHeight <= scrolloffset) {
console.log('should scroll down');
messageContainer.scrollTop = messageContainer.scrollHeight; messageContainer.scrollTop = messageContainer.scrollHeight;
} }
}; };
@ -67,10 +144,9 @@ const autoscroll = () => {
const { username, room } = Qs.parse(location.search, { const { username, room } = Qs.parse(location.search, {
ignoreQueryPrefix: true, ignoreQueryPrefix: true,
}); });
console.log(username, room);
socket.on('message', (object) => { socket.on('message', (object) => {
console.log(object); // console.log(object);
const string = object.text; const string = object.text;
const user = object.username; const user = object.username;
const createdAt = moment(object.createdAt).format('HH:mm:ss'); const createdAt = moment(object.createdAt).format('HH:mm:ss');
@ -84,7 +160,7 @@ socket.on('message', (object) => {
messageContainer.insertAdjacentHTML('beforeend', html); messageContainer.insertAdjacentHTML('beforeend', html);
autoscroll(); autoscroll();
console.log('not includes'); // console.log('not includes');
} else if (string.includes('Welcome to the server')) { } else if (string.includes('Welcome to the server')) {
const html = Mustache.render(htmlTemplate[2], { const html = Mustache.render(htmlTemplate[2], {
string, string,
@ -115,7 +191,7 @@ form.addEventListener('submit', (e) => {
// Re-enable form // Re-enable form
button.removeAttribute('disabled'); button.removeAttribute('disabled');
if (error) return console.log(error); if (error) return console.log(error);
console.log('Message delivered'); // console.log('Message delivered');
input.value = ''; input.value = '';
input.focus(); input.focus();
}); });
@ -133,7 +209,7 @@ buttonLocation.addEventListener('click', (e) => {
const coordsObj = `https://www.google.com/maps/?q=${latitude},${longitude}`; const coordsObj = `https://www.google.com/maps/?q=${latitude},${longitude}`;
socket.emit('message', coordsObj, () => { socket.emit('message', coordsObj, () => {
console.log('Location delivered'); // console.log('Location delivered');
buttonLocation.removeAttribute('disabled'); buttonLocation.removeAttribute('disabled');
}); });
}); });
@ -144,6 +220,10 @@ socket.on('roomData', ({ room, users }) => {
sidebarContainer.innerHTML = html; sidebarContainer.innerHTML = html;
}); });
socket.on('videoData', (videoData) => {
player.loadVideoById(`${videoData}`, 5, 'large');
});
socket.emit('join', { username, room }, (error) => { socket.emit('join', { username, room }, (error) => {
if (error) { if (error) {
alert(error); alert(error);

View File

@ -12,14 +12,19 @@ const {
} = require('./utils/users'); } = require('./utils/users');
const app = express(); const app = express();
app.set('view engine', 'hbs');
app.engine('html', require('hbs').__express);
const server = http.createServer(app); const server = http.createServer(app);
const io = socketio(server); const io = socketio(server);
const port = process.env.PORT || 3000; const port = process.env.PORT || 3000;
const publicDirectoryPath = path.join(__dirname, '../public'); const publicDirectoryPath = path.join(__dirname, '../public');
app.use(express.static(publicDirectoryPath)); app.use(express.static(publicDirectoryPath));
app.get('/chat', (req, res) => {
res.render('chat.html', {});
});
io.on('connection', (socket) => { io.on('connection', (socket) => {
// Welcome message // Welcome message
@ -34,6 +39,7 @@ io.on('connection', (socket) => {
'message', 'message',
generateMessage(`Welcome to the server, ${username}!`) generateMessage(`Welcome to the server, ${username}!`)
); );
socket.broadcast socket.broadcast
.to(user.room) .to(user.room)
.emit('message', generateMessage(`${user.username} has joined`)); .emit('message', generateMessage(`${user.username} has joined`));
@ -44,14 +50,24 @@ io.on('connection', (socket) => {
callback(); callback();
}); });
// On recv message socket.on('videoSync', (time, id) => {
const user = getUser(socket.id);
// console.log(time);
io.to(user.room).emit('videoSync', time, id);
});
socket.on('changeVideo', (videoId) => {
const user = getUser(socket.id);
if (!user) return;
io.to(user.room).emit('changeVideo', videoId);
});
socket.on('message', (message, callback) => { socket.on('message', (message, callback) => {
const user = getUser(socket.id); const user = getUser(socket.id);
const filter = new Filter(); const filter = new Filter();
if (filter.isProfane(message)) message = filter.clean(message); if (filter.isProfane(message)) message = filter.clean(message);
//callback('Profanity is not allowed'); //callback('Profanity is not allowed');
console.log(user);
try { try {
io.to(user.room).emit( io.to(user.room).emit(
'message', 'message',
@ -64,9 +80,17 @@ io.on('connection', (socket) => {
callback(); callback();
}); });
// Changing state of yt player after recv -> send data.e
socket.on('onPlayerState', (data, time) => {
const user = getUser(socket.id);
if (user) {
io.to(user.room).emit('onPlayerState', data, time);
}
});
socket.on('disconnect', () => { socket.on('disconnect', () => {
const user = removeUser(socket.id); const user = removeUser(socket.id);
if (user) if (user) {
io.to(user.room).emit( io.to(user.room).emit(
'message', 'message',
generateMessage(`${user.username} has left!`) generateMessage(`${user.username} has left!`)
@ -75,6 +99,7 @@ io.on('connection', (socket) => {
room: user.room, room: user.room,
users: getUsersInRoom(user.room), users: getUsersInRoom(user.room),
}); });
}
}); });
socket.on('sendLocation', (object, callback) => { socket.on('sendLocation', (object, callback) => {

View File

@ -20,8 +20,9 @@
<body> <body>
<div class="chat"> <div class="chat">
<div class="chat__sidebar"> <div class = player id="ytplayer"></div>
<div class="chat__sidebar">
</div> </div>
<div class="chat__main"> <div class="chat__main">
<div id="messages-container" class="chat__messages"> </div> <div id="messages-container" class="chat__messages"> </div>
@ -31,7 +32,11 @@
<input placeholder="Write your message"> <input placeholder="Write your message">
<button type="submit" id="formButton">Send</button> <button type="submit" id="formButton">Send</button>
</form> </form>
<button id="sendLocation">Send location!</button> <button id="sendLocation">Send location</button>
<hr style="padding: 6px; border: 0 none; height: 5px; color: rgb(233, 233, 233)">
<button id="changeVideo">Change video</button>
<hr style="padding: 6px; border: 0 none; height: 5px; color: rgb(233, 233, 233)">
<button id="syncVideos">Synchronize videos</button>
</div> </div>
</div> </div>