Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions broadcaster/assets/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,39 @@
@import "tailwindcss/utilities";

/* This file is for your main application CSS */
.invalid-input {
border-color: #d52b4d;
}

.chat-message {
display: flex;
flex-direction: column;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}

.chat-nickname {
font-weight: 600;
}

/* from https://stackoverflow.com/a/38994837/9620900 */
/* Hiding scrollbar for Chrome, Safari and Opera */
#chat-messages::-webkit-scrollbar {
display: none;
}

/* Hiding scrollbar for IE, Edge and Firefox */
#chat-messages {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
}

#chat-input::-webkit-scrollbar {
display: none;
}

/* Hiding scrollbar for IE, Edge and Firefox */
#chat-input {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
}
14 changes: 7 additions & 7 deletions broadcaster/assets/js/app.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
// to get started and then uncomment the line below.
import "./user_socket.js"
// import "./user_socket.js"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Importing user_socket this way, we count our player panel into the viewers. It could also be tricky to make sure that HTML chat elements are already loaded so I've renamed this file to chat.js, wrapped everything into a function and import this function in home.js.

This way, chat.js is only loaded and used when needed.


// You can include dependencies in two ways.
//
Expand All @@ -18,12 +18,12 @@ import "./user_socket.js"
// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
import "phoenix_html"
// Establish Phoenix Socket and LiveView configuration.
import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view"
import { Socket } from "phoenix"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vscode formatts this by default. I will try to add linter in a separate PR

import { LiveSocket } from "phoenix_live_view"
import topbar from "../vendor/topbar"

import {Home} from "./home.js"
import {Player} from "./player.js"
import { Home } from "./home.js"
import { Player } from "./player.js"

let Hooks = {}
Hooks.Home = Home
Expand All @@ -32,12 +32,12 @@ Hooks.Player = Player
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {
longPollFallbackMs: 2500,
params: {_csrf_token: csrfToken},
params: { _csrf_token: csrfToken },
hooks: Hooks,
})

// Show progress bar on live navigation and form submits
topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
topbar.config({ barColors: { 0: "#29d" }, shadowColor: "rgba(0, 0, 0, .3)" })
window.addEventListener("phx:page-loading-start", _info => topbar.show(300))
window.addEventListener("phx:page-loading-stop", _info => topbar.hide())

Expand Down
105 changes: 105 additions & 0 deletions broadcaster/assets/js/chat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { Socket, Presence } from "phoenix"

export async function connectChat() {
const viewercount = document.getElementById("viewercount");
const chatToggler = document.getElementById("chat-toggler");
const chat = document.getElementById("chat");
const chatMessages = document.getElementById("chat-messages");
const chatInput = document.getElementById("chat-input");
const chatNickname = document.getElementById("chat-nickname");
const chatButton = document.getElementById("chat-button");

let socket = new Socket("/socket", { params: { token: window.userToken } });

socket.connect();

const channel = socket.channel("stream:chat");
const presence = new Presence(channel);

send = function() {
body = chatInput.value.trim();
if (body != "") {
channel.push("chat_msg", { body: body });
chatInput.value = "";
}
}

presence.onSync(() => {
viewercount.innerText = presence.list().length;
});

channel.join()
.receive("ok", resp => { console.log("Joined chat channel successfully", resp) })
.receive("error", resp => { console.log("Unable to join chat channel", resp) });

channel.on("join_chat_resp", resp => {
if (resp.result === 'success') {
chatButton.innerText = "Send";
chatButton.onclick = send;
chatNickname.disabled = true;
chatInput.disabled = false;
chatInput.onkeydown = (ev) => {
if (ev.key === 'Enter') {
// prevent from adding a new line in our text area
ev.preventDefault();
send();
}
}
} else {
chatNickname.classList.add('invalid-input');
}
});

channel.on("chat_msg", msg => {
if (msg.nickname == undefined || msg.body == undefined) return;

const chatMessage = document.createElement('div');
chatMessage.classList.add('chat-message');

const nickname = document.createElement('div');
nickname.classList.add('chat-nickname');
nickname.innerText = msg.nickname;

const body = document.createElement('div');
body.innerText = msg.body;

chatMessage.appendChild(nickname);
chatMessage.appendChild(body);

chatMessages.appendChild(chatMessage);

// scroll to the bottom after adding a message
chatMessages.scrollTop = chatMessages.scrollHeight;

// allow for 1 scroll history
if (chatMessages.scrollHeight > 2 * chatMessages.clientHeight) {
chatMessages.removeChild(chatMessages.children[0]);
}
})

chatButton.onclick = () => {
channel.push("join_chat", { nickname: chatNickname.value });
};

chatNickname.onclick = () => {
chatNickname.classList.remove("invalid-input");
}

chatToggler.onclick = () => {
if (window.getComputedStyle(chat).display === "none") {
chat.style.display = "flex";

// For screen's width lower than 1024,
// eiter show video player or chat at the same time.
if (window.innerWidth < 1024) {
document.getElementById("videoplayer-wrapper").style.display = "none";
}
} else {
chat.style.display = "none";

if (window.innerWidth < 1024) {
document.getElementById("videoplayer-wrapper").style.display = "block";
}
}
}
}
9 changes: 6 additions & 3 deletions broadcaster/assets/js/home.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { connectChat } from "./chat.js"

const pcConfig = { iceServers: [{ urls: "stun:stun.l.google.com:19302" }] };
const whepEndpoint = `${window.location.origin}/api/whep`
const videoPlayer = document.getElementById("videoPlayer");
const videoPlayer = document.getElementById("videoplayer");
const candidates = [];
let patchEndpoint;

Expand All @@ -21,7 +23,7 @@ async function sendCandidate(candidate) {
}
}

async function connect() {
async function connectMedia() {
const pc = new RTCPeerConnection(pcConfig);

pc.ontrack = event => videoPlayer.srcObject = event.streams[0];
Expand Down Expand Up @@ -75,6 +77,7 @@ async function connect() {

export const Home = {
mounted() {
connect()
connectMedia()
connectChat()
}
}
27 changes: 0 additions & 27 deletions broadcaster/assets/js/user_socket.js

This file was deleted.

3 changes: 2 additions & 1 deletion broadcaster/lib/broadcaster/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ defmodule Broadcaster.Application do
BroadcasterWeb.Presence,
Broadcaster.PeerSupervisor,
Broadcaster.Forwarder,
{Registry, name: Broadcaster.PeerRegistry, keys: :unique}
{Registry, name: Broadcaster.PeerRegistry, keys: :unique},
{Registry, name: Broadcaster.ChatNicknamesRegistry, keys: :unique}
]

# See https://hexdocs.pm/elixir/Supervisor.html
Expand Down
36 changes: 33 additions & 3 deletions broadcaster/lib/broadcaster_web/channels/stream_channel.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,51 @@ defmodule BroadcasterWeb.StreamChannel do
alias BroadcasterWeb.Presence

@impl true
def join("stream:chat", %{"name" => name}, socket) do
def join("stream:chat", _, socket) do
send(self(), :after_join)
{:ok, assign(socket, :name, name)}
{:ok, assign(socket, :nickname, nil)}
end

@impl true
def handle_in("chat_msg", _, %{assigns: %{nickname: nil}} = socket) do
{:noreply, socket}
end

@impl true
def handle_in("chat_msg", %{"body" => body}, socket) do
broadcast!(socket, "chat_msg", %{body: body, name: socket.assigns.name})
broadcast!(socket, "chat_msg", %{body: body, nickname: socket.assigns.nickname})
{:noreply, socket}
end

@impl true
def handle_in("join_chat", %{"nickname" => nickname}, socket) do
case register(nickname) do
:ok ->
socket = assign(socket, :nickname, nickname)
:ok = push(socket, "join_chat_resp", %{"result" => "success"})
{:noreply, socket}

:error ->
:ok = push(socket, "join_chat_resp", %{"result" => "error"})
{:noreply, socket}
end
end

@impl true
def handle_info(:after_join, socket) do
{:ok, _} = Presence.track(socket, socket.assigns.user_id, %{})
push(socket, "presence_state", Presence.list(socket))
{:noreply, socket}
end

defp register(nickname), do: do_register(String.trim(nickname))

defp do_register(""), do: :error

defp do_register(nickname) do
case Registry.register(Broadcaster.ChatNicknamesRegistry, nickname, nil) do
{:ok, _} -> :ok
{:error, _} -> :error
end
end
end
49 changes: 33 additions & 16 deletions broadcaster/lib/broadcaster_web/components/layouts/app.html.heex
Original file line number Diff line number Diff line change
@@ -1,29 +1,46 @@
<header class="px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between border-b border-brand/15 py-3 text-sm">
<div class="flex flex-col lg:flex-row items-center justify-between border-b border-brand/15 py-3 text-sm">
<div class="flex items-center gap-4">
<a href="/">
<img src={~p"/images/logo.svg"} width="225" />
</a>
</div>
<div class="flex items-center gap-4 font-semibold leading-6 text-brand/80">
<div class="flex gap-1 px-4">
<p id="viewercount">0</p>
<.icon name="hero-user-solid" />
<div class="flex">
<div class="flex items-center gap-4 font-semibold leading-6 text-brand/80">
<div class="flex gap-1">
<p id="viewercount">0</p>
<.icon name="hero-user-solid" />
</div>
<div id="chat-toggler">
<.icon name="hero-chat-bubble-bottom-center" />
</div>
<a href="https://github.com/elixir-webrtc/ex_webrtc" class="hover:text-brand">
GitHub
</a>
<a
href="https://hexdocs.pm/ex_webrtc/readme.html"
class="rounded-lg bg-brand/10 px-2 py-1 hover:bg-brand/20"
>
Docs <span aria-hidden="true">&rarr;</span>
</a>
</div>
<a href="https://github.com/elixir-webrtc/ex_webrtc" class="hover:text-brand">
GitHub
</a>
<a
href="https://hexdocs.pm/ex_webrtc/readme.html"
class="rounded-lg bg-brand/10 px-2 py-1 hover:bg-brand/20"
>
Docs <span aria-hidden="true">&rarr;</span>
</a>
</div>
</div>
</header>
<main class="flex-1">
<div class="m-auto py-7">
<!--
flex-1 will make our main element grow as much as its parent allows it to do so.
However, if one of main's children grows more than main, main will also grow.
That's because parent cannot be smaller than its content.
This is enforced by min-h and min-w set to auto by default.
As a consequence, we will never experience overflow in children elements.
In particular, overflow-scroll on chat div won't have any effect.
To change this behaviour, we set min-h-0 to allow our main element to shrink to the size
smaller than its content. Setting overflow-hidden or overflow-auto has the same effect.
I put this comment as it took me at least 5 hours to understand what's going on.
See https://stackoverflow.com/a/36247448/9620900
-->
<main class="flex-1 min-h-0">
<div class="h-full py-7 px-7">
<.flash_group flash={@flash} />
<%= @inner_content %>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
</script>
</head>
<body class="bg-white antialiased w-screen h-screen flex flex-col">
<body class="bg-white antialiased h-svh flex flex-col">
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

h-svh so we don't have any issues with address bar on mobile phones

<%= @inner_content %>
</body>
</html>
Loading