summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArjun Satarkar <me@arjunsatarkar.net>2024-07-29 14:37:17 +0000
committerArjun Satarkar <me@arjunsatarkar.net>2024-07-29 14:50:20 +0000
commit37db2ef91cb806a6ddd123bf7c1974d00699b86a (patch)
tree38dd2a6fc6a7bba5f161856f7264b610dfb6d7d4
parent78808161c6b8726be5e18427a5da4328ab1fa26f (diff)
downloadmediasync-37db2ef91cb806a6ddd123bf7c1974d00699b86a.tar
mediasync-37db2ef91cb806a6ddd123bf7c1974d00699b86a.tar.gz
mediasync-37db2ef91cb806a6ddd123bf7c1974d00699b86a.zip
Change video layout, add alignment button for Discord activity
The button to snap the video to the left or right instead of the default center is because mobile Discord shows the user's avatar as a little floating window over the activity. This can infringe on the video if we're unlucky vis-a-vis screen aspect ratio and video aspect ratio; in practice we can often save ourselves by changing the video alignment so as to create a greater region of unused space in which the avatar can reside. Making this work required changes to how we set video dimensions. Previously we set the player's container to 100% width and height (technically 100svw and 100svh respectively), let video.js fill the container, and let it draw the actual video in the center of this area by itself. But this gives us no control over where the video is drawn, so we couldn't implement snapping. Any attempt to change this runs into the difficulty that it's really hard to substantively control video.js player dimensions without knowing the aspect ratio, which we don't. (Yes, I tried object-fit: contain; it didn't seem to do anything.) The solution is to default to 100% width and height on page load, and set them to better values once we load enough video metadata to know the aspect ratio. (This is all responsive using CSS media queries.)
-rw-r--r--priv/room.html.eex62
-rw-r--r--priv/static/main.css44
-rw-r--r--priv/static/room/main.js59
3 files changed, 130 insertions, 35 deletions
diff --git a/priv/room.html.eex b/priv/room.html.eex
index 012ec0d..c3ae92e 100644
--- a/priv/room.html.eex
+++ b/priv/room.html.eex
@@ -2,15 +2,16 @@
<%
import Mediasync.Constants
-{video_info, websocket_path, state_url, home_button_url} = if in_discord_activity? do
+{video_info, websocket_path, state_url, home_button_url, show_snap_button?} = if in_discord_activity? do
{
%{video_info | url: "/.proxy/room/#{room_id}/video?#{query_param_discord_activity_inner()}"},
"/.proxy/room/#{room_id}/websocket?#{query_param_discord_activity_inner()}",
"/.proxy/room/#{room_id}/state.json?#{query_param_discord_activity_inner()}",
- "/.proxy/?#{query_param_discord_activity_inner()}"
+ "/.proxy/?#{query_param_discord_activity_inner()}",
+ true
}
else
- {video_info, "/room/#{room_id}/websocket", "/room/#{room_id}/state.json", nil}
+ {video_info, "/room/#{room_id}/websocket", "/room/#{room_id}/state.json", nil, false}
end
%>
<html lang="en">
@@ -27,33 +28,31 @@ end
margin: 0;
}
- @font-face {
- font-family: "FontAwesomeSolid";
- src: url("/static/fontawesome-free-6.6.0-web/webfonts/fa-solid-900.woff2") format("woff2");
+ div#outerContainer {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ height: 100svh;
+ width: 100svw;
}
- .icon-home, .icon-users, .icon-left, .icon-right, .icon-left-right {
- font-family: "FontAwesomeSolid";
+ div#playerContainer {
+ /* Default values; JS will adjust depending on actual aspect ratio */
+ height: 100svh;
+ width: 100svw;
+ /* We should be in the (horizontal) center by default */
+ margin: 0 auto;
}
- .icon-home::before {
- content: "\f015";
+ div#playerContainer:has(#snap-button.icon-right) {
+ /* We should be on the left */
+ margin-left: 0;
}
- .icon-users::before {
- content: "\f0c0";
- }
-
- .icon-left::before {
- content: "\f30a";
- }
-
- .icon-right::before {
- content: "\f30b";
- }
-
- .icon-left-right::before {
- content: "\f337";
+ div#playerContainer:has(#snap-button.icon-left-right) {
+ /* We should be on the right */
+ margin-left: auto;
+ margin-right: 0;
}
#state-button-active::after {
@@ -67,14 +66,18 @@ end
top: 0;
}
</style>
+ <!-- Will be modified by JS -->
+ <style id="extra-styles"></style>
</head>
<body>
- <div id="playerContainer">
- <video-js id="player">
- <source src="<%= Plug.HTML.html_escape(video_info.url) %>"
- type="<%= Plug.HTML.html_escape(video_info.content_type) %>">
- </video-js>
+ <div id="outerContainer">
+ <div id="playerContainer">
+ <video-js id="player">
+ <source src="<%= Plug.HTML.html_escape(video_info.url) %>"
+ type="<%= Plug.HTML.html_escape(video_info.content_type) %>">
+ </video-js>
+ </div>
</div>
<script src="/static/video.js/video.min.js"></script>
<script>
@@ -82,6 +85,7 @@ end
const HOME_BUTTON_URL = <%= if home_button_url, do: ~s("#{home_button_url}"), else: "null" %>;
const STATE_ELEMENT_INITIAL_TEXT = "loading...";
const STATE_URL = "<%= state_url %>";
+ const SHOW_SNAP_BUTTON = <%= show_snap_button? %>;
</script>
<script src="/static/room/main.js"></script>
<script src="/static/room/displayState.js"></script>
diff --git a/priv/static/main.css b/priv/static/main.css
index 85906c6..9841b35 100644
--- a/priv/static/main.css
+++ b/priv/static/main.css
@@ -1,3 +1,13 @@
+:root {
+ box-sizing: border-box;
+}
+
+*,
+*::before,
+*::after {
+ box-sizing: inherit;
+}
+
body {
background-color: black;
color: white;
@@ -8,7 +18,35 @@ input {
font-family: inherit;
}
-div#playerContainer {
- width: 100svw;
- height: 100svh;
+@font-face {
+ font-family: "FontAwesomeSolid";
+ src: url("/static/fontawesome-free-6.6.0-web/webfonts/fa-solid-900.woff2") format("woff2");
+}
+
+.icon-home,
+.icon-users,
+.icon-left,
+.icon-right,
+.icon-left-right {
+ font-family: "FontAwesomeSolid";
+}
+
+.icon-home::before {
+ content: "\f015";
+}
+
+.icon-users::before {
+ content: "\f0c0";
+}
+
+.icon-left::before {
+ content: "\f30a";
+}
+
+.icon-right::before {
+ content: "\f30b";
+}
+
+.icon-left-right::before {
+ content: "\f337";
}
diff --git a/priv/static/room/main.js b/priv/static/room/main.js
index 9a0b2ec..8e46f66 100644
--- a/priv/static/room/main.js
+++ b/priv/static/room/main.js
@@ -26,6 +26,21 @@
}
};
+ const setPlayerDimensions = (player) => {
+ document.querySelector("style#extra-styles").textContent = `
+ div#playerContainer {
+ height: 100svh;
+ width: calc(100svh * ${player.videoWidth() / player.videoHeight()});
+ }
+ @media (orientation: portrait) {
+ div#playerContainer {
+ width: 100svw;
+ height: calc(100svw * ${player.videoHeight() / player.videoWidth()});
+ }
+ }
+ `;
+ };
+
const player = videojs("player", {
controls: true,
experimentalSvgIcons: true,
@@ -37,6 +52,14 @@
player.ready(() => {
setControlsEnabled(player, false);
+ if (player.readyState() >= 1) {
+ setPlayerDimensions(player);
+ } else {
+ player.on("loadedmetadata", () => {
+ setPlayerDimensions(player);
+ });
+ }
+
{
let customIconPosition = 1;
const Button = videojs.getComponent("Button");
@@ -52,16 +75,46 @@
}
const stateButton = new Button(player, {
- clickHandler: (event) => {
+ clickHandler: (_) => {
const stateButtonEl = stateButton.el();
- const id = stateButtonEl.getAttribute("id");
- stateButtonEl.setAttribute("id", id ? "" : "state-button-active");
+ stateButtonEl.id = stateButtonEl.id ? "" : "state-button-active";
},
});
stateButton.el().setAttribute("data-text", STATE_ELEMENT_INITIAL_TEXT);
stateButton.addClass("icon-users state-element");
stateButton.controlText("Viewers");
player.controlBar.addChild(stateButton, {}, customIconPosition++);
+
+ if (SHOW_SNAP_BUTTON) {
+ const SNAP_NEXT_L = "icon-left";
+ const SNAP_NEXT_R = "icon-right";
+ const SNAP_NEXT_LR = "icon-left-right";
+ const SNAP_TITLE_L = "Snap Video To Left";
+ const SNAP_TITLE_R = "Snap Video To Right";
+ const SNAP_TITLE_LR = "Snap Video To Center";
+ const snapButton = new Button(player, {
+ clickHandler: (_) => {
+ const snapButtonEl = snapButton.el();
+ if (snapButtonEl.classList.contains(SNAP_NEXT_L)) {
+ snapButtonEl.classList.remove(SNAP_NEXT_L);
+ snapButtonEl.classList.add(SNAP_NEXT_R);
+ snapButtonEl.title = SNAP_TITLE_R;
+ } else if (snapButtonEl.classList.contains(SNAP_NEXT_R)) {
+ snapButtonEl.classList.remove(SNAP_NEXT_R);
+ snapButtonEl.classList.add(SNAP_NEXT_LR);
+ snapButtonEl.title = SNAP_TITLE_LR;
+ } else {
+ snapButtonEl.classList.remove(SNAP_NEXT_LR);
+ snapButtonEl.classList.add(SNAP_NEXT_L);
+ snapButtonEl.title = SNAP_TITLE_L;
+ }
+ },
+ });
+ snapButton.el().id = "snap-button";
+ snapButton.addClass(SNAP_NEXT_L);
+ snapButton.controlText(SNAP_TITLE_L);
+ player.controlBar.addChild(snapButton, {}, customIconPosition++);
+ }
}
const updatePlaybackState = (latestReceivedState, nowMilliseconds) => {