Compare commits

...

3 Commits

Author SHA1 Message Date
maia arson crimew 72c212a100 implement a viewer list 2022-02-13 18:23:20 +01:00
maia arson crimew 951007df2a show where a user seeked from 2022-02-13 18:01:01 +01:00
maia arson crimew 852270c63f add /ping feature
this is useful for ready checks
2022-02-13 17:32:28 +01:00
12 changed files with 171 additions and 38 deletions

View File

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<title>watch party :D</title>
<link rel="stylesheet" href="/styles.css?v=5" />
<link rel="stylesheet" href="/styles.css?v=8" />
</head>
<body>
@ -47,6 +47,6 @@
</p>
</div>
<script type="module" src="/create.mjs?v=7"></script>
<script type="module" src="/create.mjs?v=8"></script>
</body>
</html>

View File

@ -1,4 +1,4 @@
import { setupCreateSessionForm } from "./lib/create-session.mjs?v=7";
import { setupCreateSessionForm } from "./lib/create-session.mjs?v=8";
const main = () => {
setupCreateSessionForm();

View File

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<title>watch party :D</title>
<link rel="stylesheet" href="/styles.css?v=5" />
<link rel="stylesheet" href="/styles.css?v=8" />
</head>
<body>
@ -29,14 +29,9 @@
maxlength="50"
required
/>
<label for="join-session-colour">Colour:</label>
<input
type="color"
id="join-session-colour"
value="#7ed0ff"
required
/>
<input type="color" id="join-session-colour" value="#7ed0ff" required />
<label for="join-session-id">Session ID:</label>
<input
@ -55,12 +50,13 @@
<div id="video-container"></div>
<div id="chatbox-container">
<div id="viewer-list"></div>
<div id="chatbox"></div>
<form id="chatbox-send">
<input type="text" placeholder="Message..." />
</form>
</div>
<script type="module" src="/main.mjs?v=7"></script>
<script type="module" src="/main.mjs?v=8"></script>
</body>
</html>

View File

@ -12,12 +12,24 @@ const setupChatboxEvents = (socket) => {
if (content.trim().length) {
input.value = "";
socket.send(
JSON.stringify({
op: "ChatMessage",
data: content,
})
);
if (
content.toLowerCase() == "/ping" ||
content.toLowerCase().startsWith("/ping ")
) {
socket.send(
JSON.stringify({
op: "Ping",
data: content.slice(5).trim(),
})
);
} else {
socket.send(
JSON.stringify({
op: "ChatMessage",
data: content,
})
);
}
}
});
};
@ -188,10 +200,22 @@ export const logEventToChat = (event) => {
}
case "SetTime": {
const messageContent = document.createElement("span");
messageContent.appendChild(document.createTextNode("set the time to "));
if (event.data.from != undefined) {
messageContent.appendChild(
document.createTextNode("set the time from ")
);
messageContent.appendChild(
document.createTextNode(formatTime(event.data.from))
);
messageContent.appendChild(document.createTextNode(" to "));
} else {
messageContent.appendChild(document.createTextNode("set the time to "));
}
messageContent.appendChild(
document.createTextNode(formatTime(event.data))
document.createTextNode(formatTime(event.data.to))
);
printChatMessage("set-time", event.user, event.colour, messageContent);
@ -212,5 +236,51 @@ export const logEventToChat = (event) => {
printChatMessage("set-playing", event.user, event.colour, messageContent);
break;
}
case "Ping": {
const messageContent = document.createElement("span");
if (event.data) {
messageContent.appendChild(document.createTextNode("pinged saying: "));
messageContent.appendChild(document.createTextNode(event.data));
} else {
messageContent.appendChild(document.createTextNode("pinged"));
}
printChatMessage("ping", event.user, event.colour, messageContent);
beep();
break;
}
}
};
const beep = () => {
const context = new AudioContext();
const gain = context.createGain();
gain.connect(context.destination);
gain.gain.value = 0.1;
const oscillator = context.createOscillator();
oscillator.connect(gain);
oscillator.frequency.value = 520;
oscillator.type = "square";
oscillator.start(context.currentTime);
oscillator.stop(context.currentTime + 0.22);
};
export const updateViewerList = (viewers) => {
const listContainer = document.querySelector("#viewer-list");
// empty out the current list
listContainer.innerHTML = "";
// display the updated list
for (const viewer of viewers) {
const viewerElem = document.createElement("div");
const content = document.createElement("strong");
content.textContent = viewer.nickname;
content.style = `color: #${viewer.colour}`;
viewerElem.appendChild(content);
listContainer.appendChild(viewerElem);
}
};

View File

@ -1,4 +1,4 @@
import { createSession } from "./watch-session.mjs?v=7";
import { createSession } from "./watch-session.mjs?v=8";
export const setupCreateSessionForm = () => {
const form = document.querySelector("#create-session-form");

View File

@ -1,4 +1,4 @@
import { joinSession } from "./watch-session.mjs?v=7";
import { joinSession } from "./watch-session.mjs?v=8";
/**
* @param {HTMLInputElement} field

View File

@ -1,5 +1,5 @@
import { setupVideo } from "./video.mjs?v=7";
import { setupChat, logEventToChat } from "./chat.mjs?v=7";
import { setupVideo } from "./video.mjs?v=8";
import { setupChat, logEventToChat, updateViewerList } from "./chat.mjs?v=8";
/**
* @param {string} sessionId
@ -70,6 +70,9 @@ const setupIncomingEvents = (video, socket) => {
setDebounce();
setVideoTime(event.data);
break;
case "UpdateViewerList":
updateViewerList(event.data);
break;
}
}
@ -137,7 +140,9 @@ const setupOutgoingEvents = (video, socket) => {
socket.send(
JSON.stringify({
op: "SetTime",
data: currentVideoTime(),
data: {
to: currentVideoTime(),
},
})
);
});

View File

@ -1,4 +1,4 @@
import { setupJoinSessionForm } from "./lib/join-session.mjs?v=7";
import { setupJoinSessionForm } from "./lib/join-session.mjs?v=8";
const main = () => {
setupJoinSessionForm();

View File

@ -138,7 +138,8 @@ button.small-button {
}
.chat-message.user-join,
.chat-message.user-leave {
.chat-message.user-leave,
.chat-message.ping {
font-style: italic;
}
@ -160,6 +161,16 @@ button.small-button {
overflow-y: scroll;
}
#viewer-list {
padding: 0.5em 2em;
/* TODO: turn this into max-height instead of fixed height without breaking the chatbox height */
height: 4em;
overflow-y: scroll;
color: rgb(126, 208, 255);
border-bottom: var(--fg);
border-bottom-style: solid;
}
#chatbox-container {
background-color: #222;
}
@ -195,6 +206,6 @@ button.small-button {
}
#chatbox {
height: calc(100vh - 5em) !important;
height: calc(100vh - 5em - 4em) !important;
}
}

View File

@ -1,14 +1,31 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Serialize, Deserialize)]
pub struct Viewer {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub nickname: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub colour: Option<String>,
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(tag = "op", content = "data")]
pub enum WatchEventData {
SetPlaying { playing: bool, time: u64 },
SetTime(u64),
SetPlaying {
playing: bool,
time: u64,
},
SetTime {
#[serde(default, skip_serializing_if = "Option::is_none")]
from: Option<u64>,
to: u64,
},
UserJoin,
UserLeave,
ChatMessage(String),
Ping(String),
UpdateViewerList(Vec<Viewer>),
}
#[derive(Clone, Serialize, Deserialize)]

View File

@ -15,7 +15,7 @@ use uuid::Uuid;
use warp::ws::{Message, WebSocket};
use crate::{
events::{WatchEvent, WatchEventData},
events::{Viewer, WatchEvent, WatchEventData},
utils::truncate_str,
watch_session::{get_session, handle_watch_event_data},
};
@ -74,6 +74,8 @@ pub async fn ws_subscribe(session_uuid: Uuid, nickname: String, colour: String,
)
.await;
update_viewer_list(session_uuid).await;
while let Some(Ok(message)) = viewer_ws_rx.next().await {
let event: WatchEventData = match message
.to_str()
@ -84,11 +86,18 @@ pub async fn ws_subscribe(session_uuid: Uuid, nickname: String, colour: String,
None => continue,
};
handle_watch_event_data(
session_uuid,
&mut get_session(session_uuid).unwrap(),
event.clone(),
);
let session = &mut get_session(session_uuid).unwrap();
// server side event modification where neccessary
let event: WatchEventData = match event {
WatchEventData::SetTime { from: _, to } => WatchEventData::SetTime {
from: Some(session.get_time_ms()),
to: to,
},
_ => event,
};
handle_watch_event_data(session_uuid, session, event.clone());
ws_publish(
session_uuid,
@ -106,6 +115,7 @@ pub async fn ws_subscribe(session_uuid: Uuid, nickname: String, colour: String,
.await;
CONNECTED_VIEWERS.write().await.remove(&viewer_id);
update_viewer_list(session_uuid).await;
}
pub async fn ws_publish(session_uuid: Uuid, skip_viewer_id: Option<usize>, event: WatchEvent) {
@ -120,3 +130,27 @@ pub async fn ws_publish(session_uuid: Uuid, skip_viewer_id: Option<usize>, event
});
}
}
async fn update_viewer_list(session_uuid: Uuid) {
let mut viewers = Vec::new();
for viewer in CONNECTED_VIEWERS.read().await.values() {
if viewer.session == session_uuid {
viewers.push(Viewer {
nickname: viewer.nickname.clone(),
colour: viewer.colour.clone(),
})
}
}
ws_publish(
session_uuid,
None,
WatchEvent::new(
String::from("server"),
String::from(""),
WatchEventData::UpdateViewerList(viewers),
),
)
.await;
}

View File

@ -85,8 +85,8 @@ pub fn handle_watch_event_data(
watch_session.set_playing(playing, time);
}
WatchEventData::SetTime(time) => {
watch_session.set_time_ms(time);
WatchEventData::SetTime { from: _, to } => {
watch_session.set_time_ms(to);
}
_ => {}