Got stuck integrating PeerJS (WebRTC) with my LiveView app

Hi guys,

I’m trying to integrate PeerJS to my Phoenix LiveView App. So, I want to support video calling between users using WebRTC. This is a learning project and I have never done anything like this, there are many new concepts here that I don’t fully understand so naturally, I got stuck.

So, first a user creates a talk, then a show template is rendered where videos should show. Video from a user shows fine, but I’m not sure how to connect it to another user inside a talk.

Can anyone help?

show.ex

defmodule MyAppWeb.TalkLive.Show do
  use MyAppWeb, :live_view

  alias MyApp.Accounts
  alias MyApp.Profiles
  alias MyApp.Talks
  alias MyAppWeb.Presence
  alias Phoenix.Socket.Broadcast

  @impl true
  def mount(%{"slug" => slug}, %{"user_token" => user_token} = _session, socket) do
    current_user = Accounts.get_user_by_session_token(user_token)
    profile = Profiles.get_profile!(current_user.id)

    if connected?(socket) do
      Phoenix.PubSub.subscribe(MyApp.PubSub, "talk:" <> slug)
      Phoenix.PubSub.subscribe(MyApp.PubSub, "talk:" <> slug <> ":" <> to_string(current_user.id))

      {:ok, _} = Presence.track_talk_user(self(), "talk:" <> slug, current_user, profile)
    end

    case Talks.get_talk(slug) do
      nil ->
        {:ok,
          socket
          |> assign(:current_user, current_user)
          |> assign(:profile, profile)
          |> put_flash(:error, "That Talk does not exist.")
          |> push_navigate(to: ~p"/talks/new")
        }
      talk ->
        {:ok,
          socket
          |> assign(:current_user, current_user)
          |> assign(:profile, profile)
          |> assign(:talk, talk)
          |> assign(:online_users, list_online_users(talk))
        }
    end
  end

  @impl true
  def handle_info(%Broadcast{event: "presence_diff"}, socket) do
    talk = socket.assigns.talk

    {:noreply,
      socket
      |> assign(:online_users, list_online_users(talk))}
  end

  defp list_online_users(talk) do
    Presence.list("talk:" <> talk.slug)
    |> Enum.map(fn {_k, %{metas: [v]}} -> v end)
  end
end

show.html.heex

<div class="flex">
  <div id="video-grid" class="">
    <video id="local-video" class="rotate-video" playsinline autoplay muted class="w-1/2 bg-red-800"></video>
    <div class=""><%= @profile.username %></div>
  </div>

  <%= for user <- @online_users do %>
    <%= unless user.user_id == @current_user.id do %>
      <div class="">
        <video id={"remote-video-#{user.user_id}"} data-user-id={user.user_id} playsinline autoplay class="w-96 bg-green-800"></video>
        <div class=""><%= user.username %></div>
      </div>
    <% end %>
  <% end %>
</div>

<.button id="join-call" phx-hook="JoinCall" data-user-id={@current_user.id}>Join Call</.button>

app.js

let Hooks = {}

Hooks.JoinCall = {
  mounted() {
    console.log("Connected to Call")

    const currentUserId = "bljeeee-" + this.el.dataset.userId
    console.log(this.el.dataset)
    console.log("Current User ID: ", currentUserId)

    const videoGrid = document.getElementById("video-grid")
    const localVideo = document.getElementById("local-video")
    const remoteVideo = document.getElementById("remote-video")

    async function initStream() {
      try {
        var peer = new Peer();

        peer.on("open", id => {
          console.log("Peer ID: ", id)
        })
        

        navigator.mediaDevices.getUserMedia({audio: true, video: true}).then(stream => {
          addVideoStream(localVideo, stream)
        })

      } catch (e) {
        console.log("JoinCall ERROR")
        console.log(e)
      }
    }

    this.el.addEventListener("click", e => {
      initStream()
    })

    function addVideoStream(video, stream) {
      video.srcObject = stream
      video.addEventListener("loadedmetadata", () => {
        video.play
      })
      videoGrid.append(video)
    }
  }
}

let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}, hooks: Hooks})