mirror of
https://github.com/fly-apps/live_beats.git
synced 2024-11-25 01:10:59 +00:00
Add profiles
This commit is contained in:
parent
5fa2944627
commit
c45510cb6e
11 changed files with 350 additions and 109 deletions
|
@ -1,7 +1,7 @@
|
||||||
import "phoenix_html"
|
import "phoenix_html"
|
||||||
import {Socket} from "phoenix"
|
import {Socket} from "phoenix"
|
||||||
// import {LiveSocket} from "./phoenix_live_view"
|
import {LiveSocket} from "./phoenix_live_view"
|
||||||
import {LiveSocket} from "phoenix_live_view"
|
// import {LiveSocket} from "phoenix_live_view"
|
||||||
import topbar from "../vendor/topbar"
|
import topbar from "../vendor/topbar"
|
||||||
|
|
||||||
let nowSeconds = () => Math.round(Date.now() / 1000)
|
let nowSeconds = () => Math.round(Date.now() / 1000)
|
||||||
|
@ -54,9 +54,8 @@ Hooks.AudioPlayer = {
|
||||||
this.play({sync: true})
|
this.play({sync: true})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.handleEvent("pause", () => {
|
this.handleEvent("pause", () => this.pause())
|
||||||
this.pause()
|
this.handleEvent("stop", () => this.stop())
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
play(opts = {}){
|
play(opts = {}){
|
||||||
|
@ -76,10 +75,19 @@ Hooks.AudioPlayer = {
|
||||||
this.player.pause()
|
this.player.pause()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
stop(){
|
||||||
|
clearInterval(this.progressTimer)
|
||||||
|
this.player.pause()
|
||||||
|
this.player.currentTime = 0
|
||||||
|
this.updateProgress()
|
||||||
|
this.duration.innerText = ""
|
||||||
|
this.currentTime.innerText = ""
|
||||||
|
},
|
||||||
|
|
||||||
updateProgress(){
|
updateProgress(){
|
||||||
if(isNaN(this.player.duration)){ return false }
|
if(isNaN(this.player.duration)){ return false }
|
||||||
if(this.player.currentTime >= this.player.duration){
|
if(this.player.currentTime >= this.player.duration){
|
||||||
this.pushEvent("next-song-auto")
|
this.pushEvent("next_song_auto")
|
||||||
clearInterval(this.progressTimer)
|
clearInterval(this.progressTimer)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,17 @@ defmodule LiveBeats.Accounts do
|
||||||
alias LiveBeats.Accounts.{User, Identity}
|
alias LiveBeats.Accounts.{User, Identity}
|
||||||
|
|
||||||
@admin_emails ["chris@chrismccord.com"]
|
@admin_emails ["chris@chrismccord.com"]
|
||||||
|
@pubsub LiveBeats.PubSub
|
||||||
|
|
||||||
|
def subscribe(user_id) do
|
||||||
|
Phoenix.PubSub.subscribe(@pubsub, topic(user_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
def unsubscribe(user_id) do
|
||||||
|
Phoenix.PubSub.unsubscribe(@pubsub, topic(user_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp topic(user_id), do: "user:#{user_id}"
|
||||||
|
|
||||||
def list_users(opts) do
|
def list_users(opts) do
|
||||||
Repo.all(from u in User, limit: ^Keyword.fetch!(opts, :limit))
|
Repo.all(from u in User, limit: ^Keyword.fetch!(opts, :limit))
|
||||||
|
@ -47,6 +58,27 @@ defmodule LiveBeats.Accounts do
|
||||||
"""
|
"""
|
||||||
def get_user!(id), do: Repo.get!(User, id)
|
def get_user!(id), do: Repo.get!(User, id)
|
||||||
|
|
||||||
|
def get_user_by!(fields), do: Repo.get_by!(User, fields)
|
||||||
|
|
||||||
|
def update_active_profile(%User{active_profile_user_id: same_id} = current_user, same_id) do
|
||||||
|
current_user
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_active_profile(%User{} = current_user, profile_user_id) do
|
||||||
|
{1, _} =
|
||||||
|
Repo.update_all(from(u in User, where: u.id == ^current_user.id),
|
||||||
|
set: [active_profile_user_id: profile_user_id]
|
||||||
|
)
|
||||||
|
|
||||||
|
Phoenix.PubSub.broadcast!(
|
||||||
|
@pubsub,
|
||||||
|
topic(current_user.id),
|
||||||
|
{__MODULE__, :active_profile_changed, current_user, %{user_id: profile_user_id}}
|
||||||
|
)
|
||||||
|
|
||||||
|
%User{current_user | active_profile_user_id: profile_user_id}
|
||||||
|
end
|
||||||
|
|
||||||
## User registration
|
## User registration
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
|
@ -11,6 +11,7 @@ defmodule LiveBeats.Accounts.User do
|
||||||
field :confirmed_at, :naive_datetime
|
field :confirmed_at, :naive_datetime
|
||||||
field :role, :string, default: "subscriber"
|
field :role, :string, default: "subscriber"
|
||||||
field :profile_tagline, :string
|
field :profile_tagline, :string
|
||||||
|
field :active_profile_user_id, :id
|
||||||
|
|
||||||
has_many :identities, Identity
|
has_many :identities, Identity
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ defmodule LiveBeats.MediaLibrary do
|
||||||
require Logger
|
require Logger
|
||||||
import Ecto.Query, warn: false
|
import Ecto.Query, warn: false
|
||||||
alias LiveBeats.{Repo, MP3Stat, Accounts}
|
alias LiveBeats.{Repo, MP3Stat, Accounts}
|
||||||
alias LiveBeats.MediaLibrary.{Song, Genre}
|
alias LiveBeats.MediaLibrary.{Profile, Song, Genre}
|
||||||
alias Ecto.{Multi, Changeset}
|
alias Ecto.{Multi, Changeset}
|
||||||
|
|
||||||
@pubsub LiveBeats.PubSub
|
@pubsub LiveBeats.PubSub
|
||||||
|
@ -16,15 +16,25 @@ defmodule LiveBeats.MediaLibrary do
|
||||||
defdelegate playing?(song), to: Song
|
defdelegate playing?(song), to: Song
|
||||||
defdelegate paused?(song), to: Song
|
defdelegate paused?(song), to: Song
|
||||||
|
|
||||||
def subscribe(%Accounts.User{} = user) do
|
def subscribe_to_profile(%Profile{} = profile, from \\ nil) do
|
||||||
Phoenix.PubSub.subscribe(@pubsub, topic(user.id))
|
Phoenix.PubSub.subscribe(@pubsub, topic(profile.user_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
def unsubscribe_to_profile(%Profile{} = profile) do
|
||||||
|
Phoenix.PubSub.unsubscribe(@pubsub, topic(profile.user_id))
|
||||||
end
|
end
|
||||||
|
|
||||||
def local_filepath(filename_uuid) when is_binary(filename_uuid) do
|
def local_filepath(filename_uuid) when is_binary(filename_uuid) do
|
||||||
Path.join("priv/uploads/songs", filename_uuid)
|
Path.join("priv/uploads/songs", filename_uuid)
|
||||||
end
|
end
|
||||||
|
|
||||||
def play_song(%Song{id: id}), do: play_song(id)
|
def can_control_playback?(%Accounts.User{} = user, %Song{} = song) do
|
||||||
|
user.id == song.user_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def play_song(%Song{id: id}) do
|
||||||
|
play_song(id)
|
||||||
|
end
|
||||||
|
|
||||||
def play_song(id) do
|
def play_song(id) do
|
||||||
song = get_song!(id)
|
song = get_song!(id)
|
||||||
|
@ -60,7 +70,14 @@ defmodule LiveBeats.MediaLibrary do
|
||||||
|> Repo.transaction()
|
|> Repo.transaction()
|
||||||
|
|
||||||
elapsed = elapsed_playback(new_song)
|
elapsed = elapsed_playback(new_song)
|
||||||
Phoenix.PubSub.broadcast!(@pubsub, topic(song.user_id), {:play, song, %{elapsed: elapsed}})
|
|
||||||
|
Phoenix.PubSub.broadcast!(
|
||||||
|
@pubsub,
|
||||||
|
topic(song.user_id),
|
||||||
|
{__MODULE__, :play, song, %{elapsed: elapsed}}
|
||||||
|
)
|
||||||
|
|
||||||
|
new_song
|
||||||
end
|
end
|
||||||
|
|
||||||
def pause_song(%Song{} = song) do
|
def pause_song(%Song{} = song) do
|
||||||
|
@ -79,36 +96,36 @@ defmodule LiveBeats.MediaLibrary do
|
||||||
|> Multi.update_all(:now_paused, fn _ -> pause_query end, [])
|
|> Multi.update_all(:now_paused, fn _ -> pause_query end, [])
|
||||||
|> Repo.transaction()
|
|> Repo.transaction()
|
||||||
|
|
||||||
Phoenix.PubSub.broadcast!(@pubsub, topic(song.user_id), {:pause, song})
|
Phoenix.PubSub.broadcast!(@pubsub, topic(song.user_id), {__MODULE__, :pause, song})
|
||||||
end
|
end
|
||||||
|
|
||||||
def play_next_song_auto(user_id) do
|
def play_next_song_auto(%Profile{} = profile) do
|
||||||
song = get_current_active_song(user_id) || get_first_song(user_id)
|
song = get_current_active_song(profile) || get_first_song(profile)
|
||||||
|
|
||||||
if song && elapsed_playback(song) >= song.duration - @auto_next_threshold_seconds do
|
if song && elapsed_playback(song) >= song.duration - @auto_next_threshold_seconds do
|
||||||
song
|
song
|
||||||
|> get_next_song()
|
|> get_next_song(profile)
|
||||||
|> play_song()
|
|> play_song()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def play_prev_song(user_id) do
|
def play_prev_song(%Profile{} = profile) do
|
||||||
song = get_current_active_song(user_id) || get_first_song(user_id)
|
song = get_current_active_song(profile) || get_first_song(profile)
|
||||||
|
|
||||||
if prev_song = get_prev_song(song) do
|
if prev_song = get_prev_song(song, profile) do
|
||||||
play_song(prev_song)
|
play_song(prev_song)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def play_next_song(user_id) do
|
def play_next_song(%Profile{} = profile) do
|
||||||
song = get_current_active_song(user_id) || get_first_song(user_id)
|
song = get_current_active_song(profile) || get_first_song(profile)
|
||||||
|
|
||||||
if next_song = get_next_song(song) do
|
if next_song = get_next_song(song, profile) do
|
||||||
play_song(next_song)
|
play_song(next_song)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp topic(user_id), do: "room:#{user_id}"
|
defp topic(user_id) when is_integer(user_id), do: "profile:#{user_id}"
|
||||||
|
|
||||||
def store_mp3(%Song{} = song, tmp_path) do
|
def store_mp3(%Song{} = song, tmp_path) do
|
||||||
File.mkdir_p!(Path.dirname(song.mp3_filepath))
|
File.mkdir_p!(Path.dirname(song.mp3_filepath))
|
||||||
|
@ -170,16 +187,28 @@ defmodule LiveBeats.MediaLibrary do
|
||||||
Repo.all(Genre, order_by: [asc: :title])
|
Repo.all(Genre, order_by: [asc: :title])
|
||||||
end
|
end
|
||||||
|
|
||||||
def list_songs(limit \\ 100) do
|
def list_profile_songs(%Profile{} = profile, limit \\ 100) do
|
||||||
from(s in Song, limit: ^limit)
|
from(s in Song, where: s.user_id == ^profile.user_id, limit: ^limit)
|
||||||
|> order_by_playlist(:asc)
|
|> order_by_playlist(:asc)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_current_active_song(user_id) do
|
def get_current_active_song(%Profile{user_id: user_id}) do
|
||||||
Repo.one(from s in Song, where: s.user_id == ^user_id and s.status in [:playing, :paused])
|
Repo.one(from s in Song, where: s.user_id == ^user_id and s.status in [:playing, :paused])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_profile!(%Accounts.User{} = user) do
|
||||||
|
%Profile{user_id: user.id, username: user.username, tagline: user.profile_tagline}
|
||||||
|
end
|
||||||
|
|
||||||
|
def owns_profile?(%Accounts.User{} = user, %Profile{} = profile) do
|
||||||
|
user.id == profile.user_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def owns_song?(%Profile{} = profile, %Song{} = song) do
|
||||||
|
profile.user_id == song.user_id
|
||||||
|
end
|
||||||
|
|
||||||
def elapsed_playback(%Song{} = song) do
|
def elapsed_playback(%Song{} = song) do
|
||||||
cond do
|
cond do
|
||||||
playing?(song) ->
|
playing?(song) ->
|
||||||
|
@ -196,7 +225,7 @@ defmodule LiveBeats.MediaLibrary do
|
||||||
|
|
||||||
def get_song!(id), do: Repo.get!(Song, id)
|
def get_song!(id), do: Repo.get!(Song, id)
|
||||||
|
|
||||||
def get_first_song(user_id) do
|
def get_first_song(%Profile{user_id: user_id}) do
|
||||||
from(s in Song,
|
from(s in Song,
|
||||||
where: s.user_id == ^user_id,
|
where: s.user_id == ^user_id,
|
||||||
limit: 1
|
limit: 1
|
||||||
|
@ -205,7 +234,7 @@ defmodule LiveBeats.MediaLibrary do
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_last_song(user_id) do
|
def get_last_song(%Profile{user_id: user_id}) do
|
||||||
from(s in Song,
|
from(s in Song,
|
||||||
where: s.user_id == ^user_id,
|
where: s.user_id == ^user_id,
|
||||||
limit: 1
|
limit: 1
|
||||||
|
@ -214,7 +243,7 @@ defmodule LiveBeats.MediaLibrary do
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_next_song(%Song{} = song) do
|
def get_next_song(%Song{} = song, %Profile{} = profile) do
|
||||||
next =
|
next =
|
||||||
from(s in Song,
|
from(s in Song,
|
||||||
where: s.user_id == ^song.user_id and s.id > ^song.id,
|
where: s.user_id == ^song.user_id and s.id > ^song.id,
|
||||||
|
@ -223,10 +252,10 @@ defmodule LiveBeats.MediaLibrary do
|
||||||
|> order_by_playlist(:asc)
|
|> order_by_playlist(:asc)
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
|
|
||||||
next || get_first_song(song.user_id)
|
next || get_first_song(profile)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_prev_song(%Song{} = song) do
|
def get_prev_song(%Song{} = song, %Profile{} = profile) do
|
||||||
prev =
|
prev =
|
||||||
from(s in Song,
|
from(s in Song,
|
||||||
where: s.user_id == ^song.user_id and s.id < ^song.id,
|
where: s.user_id == ^song.user_id and s.id < ^song.id,
|
||||||
|
@ -236,7 +265,7 @@ defmodule LiveBeats.MediaLibrary do
|
||||||
|> order_by_playlist(:desc)
|
|> order_by_playlist(:desc)
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
|
|
||||||
prev || get_last_song(song.user_id)
|
prev || get_last_song(profile)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_song(attrs \\ %{}) do
|
def create_song(attrs \\ %{}) do
|
||||||
|
|
3
lib/live_beats/media_library/profile.ex
Normal file
3
lib/live_beats/media_library/profile.ex
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule LiveBeats.MediaLibrary.Profile do
|
||||||
|
defstruct user_id: nil, username: nil, tagline: nil
|
||||||
|
end
|
|
@ -5,9 +5,17 @@ defmodule LiveBeatsWeb.FileController do
|
||||||
|
|
||||||
def show(conn, %{"id" => filename_uuid, "token" => token}) do
|
def show(conn, %{"id" => filename_uuid, "token" => token}) do
|
||||||
case Phoenix.Token.verify(conn, "file", token, max_age: :timer.minutes(10)) do
|
case Phoenix.Token.verify(conn, "file", token, max_age: :timer.minutes(10)) do
|
||||||
{:ok, ^filename_uuid} -> send_file(conn, 200, MediaLibrary.local_filepath(filename_uuid))
|
{:ok, ^filename_uuid} -> do_send_file(conn, MediaLibrary.local_filepath(filename_uuid))
|
||||||
{:ok, _} -> send_resp(conn, :unauthorized, "")
|
{:ok, _} -> send_resp(conn, :unauthorized, "")
|
||||||
{:error, _} -> send_resp(conn, :unauthorized, "")
|
{:error, _} -> send_resp(conn, :unauthorized, "")
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_send_file(conn, path) do
|
||||||
|
# accept-ranges headers required for chrome to seek via currentTime
|
||||||
|
conn
|
||||||
|
|> put_resp_header("content-type", "audio/mp3")
|
||||||
|
|> put_resp_header("accept-ranges", "bytes")
|
||||||
|
|> send_file(200, path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
defmodule LiveBeatsWeb.HomeLive do
|
defmodule LiveBeatsWeb.HomeLive do
|
||||||
use LiveBeatsWeb, :live_view
|
use LiveBeatsWeb, :live_view
|
||||||
|
|
||||||
alias LiveBeats.MediaLibrary
|
# alias LiveBeats.MediaLibrary
|
||||||
|
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
|
@ -346,6 +346,6 @@ defmodule LiveBeatsWeb.HomeLive do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp fetch_songs(_socket) do
|
defp fetch_songs(_socket) do
|
||||||
MediaLibrary.list_songs()
|
[]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
defmodule LiveBeatsWeb.PlayerLive do
|
defmodule LiveBeatsWeb.PlayerLive do
|
||||||
use LiveBeatsWeb, {:live_view, container: {:div, []}}
|
use LiveBeatsWeb, {:live_view, container: {:div, []}}
|
||||||
|
|
||||||
alias LiveBeats.MediaLibrary
|
alias LiveBeats.{Accounts, MediaLibrary}
|
||||||
alias LiveBeats.MediaLibrary.Song
|
alias LiveBeats.MediaLibrary.Song
|
||||||
|
|
||||||
on_mount {LiveBeatsWeb.UserAuth, :current_user}
|
on_mount {LiveBeatsWeb.UserAuth, :current_user}
|
||||||
|
@ -36,14 +36,20 @@ defmodule LiveBeatsWeb.PlayerLive do
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gray-50 text-black dark:bg-gray-900 dark:text-white px-1 sm:px-3 lg:px-1 xl:px-3 grid grid-cols-5 items-center">
|
<div class="bg-gray-50 text-black dark:bg-gray-900 dark:text-white px-1 sm:px-3 lg:px-1 xl:px-3 grid grid-cols-5 items-center">
|
||||||
<button type="button" class="mx-auto scale-75">
|
<%= if @profile do %>
|
||||||
<svg width="24" height="24" fill="none">
|
<.link
|
||||||
<path d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
redirect_to={Routes.song_index_path(@socket, :index, @profile.username)}
|
||||||
</svg>
|
class="mx-auto flex outline border-2 border-white border-opacity-20 rounded-md p-1 pr-2"
|
||||||
</button>
|
>
|
||||||
|
<span class="mt-1"><.icon name={:user_circle}/></span>
|
||||||
|
<p class="ml-2"><%= @profile.username %></p>
|
||||||
|
</.link>
|
||||||
|
<% else %>
|
||||||
|
<div class="mx-auto flex"></div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<!-- prev -->
|
<!-- prev -->
|
||||||
<button type="button" class="sm:block xl:block mx-auto scale-75" phx-click="prev-song">
|
<button type="button" class="sm:block xl:block mx-auto scale-75" phx-click={js_prev(@own_profile?)}>
|
||||||
<svg width="17" height="18">
|
<svg width="17" height="18">
|
||||||
<path d="M0 0h2v18H0V0zM4 9l13-9v18L4 9z" fill="currentColor" />
|
<path d="M0 0h2v18H0V0zM4 9l13-9v18L4 9z" fill="currentColor" />
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -51,7 +57,7 @@ defmodule LiveBeatsWeb.PlayerLive do
|
||||||
<!-- /prev -->
|
<!-- /prev -->
|
||||||
|
|
||||||
<!-- pause -->
|
<!-- pause -->
|
||||||
<button type="button" class="mx-auto scale-75" phx-click={JS.push("play_pause") |> js_play_pause()}>
|
<button type="button" class="mx-auto scale-75" phx-click={js_play_pause(@own_profile?)}>
|
||||||
<%= if @playing do %>
|
<%= if @playing do %>
|
||||||
<svg id="player-pause" width="50" height="50" fill="none">
|
<svg id="player-pause" width="50" height="50" fill="none">
|
||||||
<circle class="text-gray-300 dark:text-gray-500" cx="25" cy="25" r="24" stroke="currentColor" stroke-width="1.5" />
|
<circle class="text-gray-300 dark:text-gray-500" cx="25" cy="25" r="24" stroke="currentColor" stroke-width="1.5" />
|
||||||
|
@ -67,7 +73,7 @@ defmodule LiveBeatsWeb.PlayerLive do
|
||||||
<!-- /pause -->
|
<!-- /pause -->
|
||||||
|
|
||||||
<!-- next -->
|
<!-- next -->
|
||||||
<button type="button" class="mx-auto scale-75" phx-click="next-song">
|
<button type="button" class="mx-auto scale-75" phx-click={js_next(@own_profile?)}>
|
||||||
<svg width="17" height="18" viewBox="0 0 17 18" fill="none">
|
<svg width="17" height="18" viewBox="0 0 17 18" fill="none">
|
||||||
<path d="M17 0H15V18H17V0Z" fill="currentColor" />
|
<path d="M17 0H15V18H17V0Z" fill="currentColor" />
|
||||||
<path d="M13 9L0 0V18L13 9Z" fill="currentColor" />
|
<path d="M13 9L0 0V18L13 9Z" fill="currentColor" />
|
||||||
|
@ -85,84 +91,153 @@ defmodule LiveBeatsWeb.PlayerLive do
|
||||||
Your browser needs a click event to enable playback
|
Your browser needs a click event to enable playback
|
||||||
<:confirm>Listen Now</:confirm>
|
<:confirm>Listen Now</:confirm>
|
||||||
</.modal>
|
</.modal>
|
||||||
|
|
||||||
|
<%= if @profile do %>
|
||||||
|
<.modal id="not-authorized" on_confirm={hide_modal("not-authorized")}>
|
||||||
|
<:title>You can't do that</:title>
|
||||||
|
|
||||||
|
Only <%= @profile.username %> can control playback
|
||||||
|
|
||||||
|
<:confirm>Ok</:confirm>
|
||||||
|
</.modal>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<!-- /player -->
|
<!-- /player -->
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
def mount(_parmas, _session, socket) do
|
def mount(_parmas, _session, socket) do
|
||||||
if connected?(socket) and socket.assigns.current_user do
|
%{current_user: current_user} = socket.assigns
|
||||||
MediaLibrary.subscribe(socket.assigns.current_user)
|
|
||||||
send(self(), :play_current)
|
if connected?(socket) do
|
||||||
|
Accounts.subscribe(current_user.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
socket =
|
socket =
|
||||||
assign(socket,
|
socket
|
||||||
|
|> assign(
|
||||||
song: nil,
|
song: nil,
|
||||||
playing: false,
|
playing: false,
|
||||||
current_user_id: socket.assigns.current_user.id,
|
profile: nil,
|
||||||
# todo use actual room user id
|
current_user_id: current_user.id,
|
||||||
room_user_id: socket.assigns.current_user.id
|
own_profile?: false
|
||||||
)
|
)
|
||||||
|
|> switch_profile(current_user.active_profile_user_id || current_user.id)
|
||||||
|
|
||||||
{:ok, socket, layout: false, temporary_assigns: [current_user: nil]}
|
{:ok, socket, layout: false, temporary_assigns: []}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp switch_profile(socket, nil) do
|
||||||
|
current_user = Accounts.update_active_profile(socket.assigns.current_user, nil)
|
||||||
|
|
||||||
|
socket
|
||||||
|
|> assign(current_user: current_user)
|
||||||
|
|> assign_profile(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp switch_profile(socket, profile_user_id) do
|
||||||
|
profile = get_profile(profile_user_id)
|
||||||
|
|
||||||
|
if connected?(socket) and profile do
|
||||||
|
current_user = Accounts.update_active_profile(socket.assigns.current_user, profile.user_id)
|
||||||
|
|
||||||
|
send(self(), :play_current)
|
||||||
|
|
||||||
|
socket
|
||||||
|
|> assign(current_user: current_user)
|
||||||
|
|> assign_profile(profile)
|
||||||
|
else
|
||||||
|
assign_profile(socket, nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp assign_profile(socket, profile) do
|
||||||
|
if prev_profile = connected?(socket) && socket.assigns.profile do
|
||||||
|
MediaLibrary.unsubscribe_to_profile(prev_profile)
|
||||||
|
end
|
||||||
|
if profile, do: MediaLibrary.subscribe_to_profile(profile)
|
||||||
|
|
||||||
|
assign(socket,
|
||||||
|
profile: profile,
|
||||||
|
own_profile?: profile && MediaLibrary.owns_profile?(socket.assigns.current_user, profile)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp assign_inactive_profile(socket, %Song{} = song) do
|
||||||
|
if socket.assigns.profile && MediaLibrary.owns_song?(socket.assigns.profile, song) do
|
||||||
|
socket
|
||||||
|
else
|
||||||
|
switch_profile(socket, song.user_id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("play_pause", _, socket) do
|
def handle_event("play_pause", _, socket) do
|
||||||
%{song: song, playing: playing} = socket.assigns
|
%{song: song, playing: playing, current_user: current_user} = socket.assigns
|
||||||
|
song = MediaLibrary.get_song!(song.id)
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
song && playing ->
|
song && playing and MediaLibrary.can_control_playback?(current_user, song) ->
|
||||||
MediaLibrary.pause_song(song)
|
MediaLibrary.pause_song(song)
|
||||||
{:noreply, assign(socket, playing: false)}
|
{:noreply, assign(socket, playing: false)}
|
||||||
|
|
||||||
song ->
|
song && MediaLibrary.can_control_playback?(current_user, song) ->
|
||||||
MediaLibrary.play_song(song)
|
MediaLibrary.play_song(song)
|
||||||
{:noreply, assign(socket, playing: true)}
|
{:noreply, assign(socket, playing: true)}
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
{:noreply, assign(socket, playing: false)}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("next-song", _, socket) do
|
def handle_event("switch_profile", %{"user_id" => user_id}, socket) do
|
||||||
if socket.assigns.song do
|
{:noreply, switch_profile(socket, user_id)}
|
||||||
MediaLibrary.play_next_song(socket.assigns.song.user_id)
|
end
|
||||||
|
|
||||||
|
def handle_event("next_song", _, socket) do
|
||||||
|
%{song: song, current_user: current_user} = socket.assigns
|
||||||
|
|
||||||
|
if song && MediaLibrary.can_control_playback?(current_user, song) do
|
||||||
|
MediaLibrary.play_next_song(socket.assigns.profile)
|
||||||
end
|
end
|
||||||
|
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("prev-song", _, socket) do
|
def handle_event("prev_song", _, socket) do
|
||||||
if socket.assigns.song do
|
%{song: song, current_user: current_user} = socket.assigns
|
||||||
MediaLibrary.play_prev_song(socket.assigns.song.user_id)
|
|
||||||
|
if song && MediaLibrary.can_control_playback?(current_user, song) do
|
||||||
|
MediaLibrary.play_prev_song(socket.assigns.profile)
|
||||||
end
|
end
|
||||||
|
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("next-song-auto", _, socket) do
|
def handle_event("next_song_auto", _, socket) do
|
||||||
if socket.assigns.song do
|
if socket.assigns.song do
|
||||||
MediaLibrary.play_next_song_auto(socket.assigns.song.user_id)
|
MediaLibrary.play_next_song_auto(socket.assigns.profile)
|
||||||
end
|
end
|
||||||
|
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_info({Accounts, :active_profile_changed, _cur_user, %{user_id: user_id}}, socket) do
|
||||||
|
if user_id do
|
||||||
|
{:noreply, assign(socket, profile: get_profile(user_id))}
|
||||||
|
else
|
||||||
|
{:noreply, socket |> assign_profile(nil) |> stop_song()}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def handle_info(:play_current, socket) do
|
def handle_info(:play_current, socket) do
|
||||||
# we raced a pubsub, noop
|
{:noreply, play_current_song(socket)}
|
||||||
if socket.assigns.song do
|
|
||||||
{:noreply, socket}
|
|
||||||
else
|
|
||||||
{:noreply, play_current_song(socket)}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:pause, _}, socket) do
|
def handle_info({MediaLibrary, :pause, _}, socket) do
|
||||||
{:noreply,
|
{:noreply, push_pause(socket)}
|
||||||
socket
|
|
||||||
|> push_event("pause", %{})
|
|
||||||
|> assign(playing: false)}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:play, %Song{} = song, %{elapsed: elapsed}}, socket) do
|
def handle_info({MediaLibrary, :play, %Song{} = song, %{elapsed: elapsed}}, socket) do
|
||||||
{:noreply, play_song(socket, song, elapsed)}
|
{:noreply, play_song(socket, song, elapsed)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -170,18 +245,17 @@ defmodule LiveBeatsWeb.PlayerLive do
|
||||||
socket
|
socket
|
||||||
|> push_play(song, elapsed)
|
|> push_play(song, elapsed)
|
||||||
|> assign(song: song, playing: true)
|
|> assign(song: song, playing: true)
|
||||||
|
|> assign_inactive_profile(song)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp js_play_pause(%JS{} = js) do
|
defp stop_song(socket) do
|
||||||
JS.dispatch(js, "js:play_pause", to: "#audio-player")
|
socket
|
||||||
end
|
|> push_event("stop", %{})
|
||||||
|
|> assign(song: nil, playing: false)
|
||||||
defp js_listen_now(js \\ %JS{}) do
|
|
||||||
JS.dispatch(js, "js:listen_now", to: "#audio-player")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp play_current_song(socket) do
|
defp play_current_song(socket) do
|
||||||
song = MediaLibrary.get_current_active_song(socket.assigns.room_user_id)
|
song = MediaLibrary.get_current_active_song(socket.assigns.profile)
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
song && MediaLibrary.playing?(song) ->
|
song && MediaLibrary.playing?(song) ->
|
||||||
|
@ -197,6 +271,7 @@ defmodule LiveBeatsWeb.PlayerLive do
|
||||||
|
|
||||||
defp push_play(socket, %Song{} = song, elapsed) do
|
defp push_play(socket, %Song{} = song, elapsed) do
|
||||||
token = Phoenix.Token.sign(socket.endpoint, "file", song.mp3_filename)
|
token = Phoenix.Token.sign(socket.endpoint, "file", song.mp3_filename)
|
||||||
|
|
||||||
push_event(socket, "play", %{
|
push_event(socket, "play", %{
|
||||||
paused: Song.paused?(song),
|
paused: Song.paused?(song),
|
||||||
elapsed: elapsed,
|
elapsed: elapsed,
|
||||||
|
@ -204,4 +279,43 @@ defmodule LiveBeatsWeb.PlayerLive do
|
||||||
url: song.mp3_url
|
url: song.mp3_url
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp push_pause(socket) do
|
||||||
|
socket
|
||||||
|
|> push_event("pause", %{})
|
||||||
|
|> assign(playing: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp js_play_pause(own_profile?) do
|
||||||
|
if own_profile? do
|
||||||
|
JS.push("play_pause")
|
||||||
|
|> JS.dispatch("js:play_pause", to: "#audio-player")
|
||||||
|
else
|
||||||
|
show_modal("not-authorized")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp js_prev(own_profile?) do
|
||||||
|
if own_profile? do
|
||||||
|
JS.push("prev_song")
|
||||||
|
else
|
||||||
|
show_modal("not-authorized")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp js_next(own_profile?) do
|
||||||
|
if own_profile? do
|
||||||
|
JS.push("next_song")
|
||||||
|
else
|
||||||
|
show_modal("not-authorized")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp js_listen_now(js \\ %JS{}) do
|
||||||
|
JS.dispatch(js, "js:listen_now", to: "#audio-player")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_profile(user_id) do
|
||||||
|
user_id && Accounts.get_user!(user_id) |> MediaLibrary.get_profile!()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,17 +1,34 @@
|
||||||
defmodule LiveBeatsWeb.SongLive.Index do
|
defmodule LiveBeatsWeb.SongLive.Index do
|
||||||
use LiveBeatsWeb, :live_view
|
use LiveBeatsWeb, :live_view
|
||||||
|
|
||||||
alias LiveBeats.{MediaLibrary, MP3Stat}
|
alias LiveBeats.{Accounts, MediaLibrary, MP3Stat}
|
||||||
alias LiveBeatsWeb.LayoutComponent
|
alias LiveBeatsWeb.LayoutComponent
|
||||||
alias LiveBeatsWeb.SongLive.{SongRowComponent, UploadFormComponent}
|
alias LiveBeatsWeb.SongLive.{SongRowComponent, UploadFormComponent}
|
||||||
|
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<.title_bar>
|
<.title_bar>
|
||||||
Listing Songs
|
<%= @profile.tagline %> <%= if @owns_profile? do %>(you)<% end %>
|
||||||
|
|
||||||
<:actions>
|
<:actions>
|
||||||
<.button primary patch_to={Routes.song_index_path(@socket, :new)}>Upload Songs</.button>
|
<%= if @active_profile_id == @profile.user_id do %>
|
||||||
|
<.button primary
|
||||||
|
phx-click={JS.push("switch_profile", value: %{user_id: nil}, target: "#player", loading: "#player")}
|
||||||
|
>
|
||||||
|
<.icon name={:stop}/><span class="ml-2">Stop Listening</span>
|
||||||
|
</.button>
|
||||||
|
<% else %>
|
||||||
|
<.button primary
|
||||||
|
phx-click={JS.push("switch_profile", value: %{user_id: @profile.user_id}, target: "#player", loading: "#player")}
|
||||||
|
>
|
||||||
|
<.icon name={:play}/><span class="ml-2">Listen</span>
|
||||||
|
</.button>
|
||||||
|
<% end %>
|
||||||
|
<%= if @owns_profile? do %>
|
||||||
|
<.button primary patch_to={Routes.song_index_path(@socket, :new)}>
|
||||||
|
<.icon name={:upload}/><span class="ml-2">Upload Songs</span>
|
||||||
|
</.button>
|
||||||
|
<% end %>
|
||||||
</:actions>
|
</:actions>
|
||||||
</.title_bar>
|
</.title_bar>
|
||||||
|
|
||||||
|
@ -45,20 +62,35 @@ defmodule LiveBeatsWeb.SongLive.Index do
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
def mount(_params, _session, socket) do
|
def mount(%{"profile_username" => profile_username}, _session, socket) do
|
||||||
%{current_user: current_user} = socket.assigns
|
%{current_user: current_user} = socket.assigns
|
||||||
|
|
||||||
|
profile =
|
||||||
|
Accounts.get_user_by!(username: profile_username)
|
||||||
|
|> MediaLibrary.get_profile!()
|
||||||
|
|
||||||
if connected?(socket) do
|
if connected?(socket) do
|
||||||
MediaLibrary.subscribe(current_user)
|
MediaLibrary.subscribe_to_profile(profile, __MODULE__)
|
||||||
|
Accounts.subscribe(current_user.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
active_id =
|
active_song_id =
|
||||||
if song = MediaLibrary.get_current_active_song(current_user.id) do
|
if song = MediaLibrary.get_current_active_song(profile) do
|
||||||
SongRowComponent.send_status(song.id, song.status)
|
SongRowComponent.send_status(song.id, song.status)
|
||||||
song.id
|
song.id
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok, assign(socket, songs: list_songs(), active_id: active_id), temporary_assigns: [songs: []]}
|
socket =
|
||||||
|
socket
|
||||||
|
|> assign(
|
||||||
|
active_song_id: active_song_id,
|
||||||
|
active_profile_id: current_user.active_profile_user_id,
|
||||||
|
profile: profile,
|
||||||
|
owns_profile?: MediaLibrary.owns_profile?(current_user, profile)
|
||||||
|
)
|
||||||
|
|> list_songs()
|
||||||
|
|
||||||
|
{:ok, socket, temporary_assigns: [songs: []]}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_params(params, _url, socket) do
|
def handle_params(params, _url, socket) do
|
||||||
|
@ -67,10 +99,17 @@ defmodule LiveBeatsWeb.SongLive.Index do
|
||||||
|
|
||||||
def handle_event("play_or_pause", %{"id" => id}, socket) do
|
def handle_event("play_or_pause", %{"id" => id}, socket) do
|
||||||
song = MediaLibrary.get_song!(id)
|
song = MediaLibrary.get_song!(id)
|
||||||
if socket.assigns.active_id == id and MediaLibrary.playing?(song) do
|
can_playback? = MediaLibrary.can_control_playback?(socket.assigns.current_user, song)
|
||||||
MediaLibrary.pause_song(song)
|
|
||||||
else
|
cond do
|
||||||
MediaLibrary.play_song(id)
|
can_playback? and socket.assigns.active_song_id == id and MediaLibrary.playing?(song) ->
|
||||||
|
MediaLibrary.pause_song(song)
|
||||||
|
|
||||||
|
can_playback? ->
|
||||||
|
MediaLibrary.play_song(id)
|
||||||
|
|
||||||
|
true ->
|
||||||
|
:noop
|
||||||
end
|
end
|
||||||
|
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
|
@ -78,23 +117,29 @@ defmodule LiveBeatsWeb.SongLive.Index do
|
||||||
|
|
||||||
def handle_event("delete", %{"id" => id}, socket) do
|
def handle_event("delete", %{"id" => id}, socket) do
|
||||||
song = MediaLibrary.get_song!(id)
|
song = MediaLibrary.get_song!(id)
|
||||||
{:ok, _} = MediaLibrary.delete_song(song)
|
if song.user_id == socket.assigns.current_user.id do
|
||||||
|
{:ok, _} = MediaLibrary.delete_song(song)
|
||||||
|
end
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:play, %MediaLibrary.Song{} = song, _meta}, socket) do
|
def handle_info({Accounts, :active_profile_changed, _cur_user, %{user_id: user_id}}, socket) do
|
||||||
|
{:noreply, assign(socket, active_profile_id: user_id)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info({MediaLibrary, :play, %MediaLibrary.Song{} = song, _meta}, socket) do
|
||||||
{:noreply, play_song(socket, song)}
|
{:noreply, play_song(socket, song)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:pause, %MediaLibrary.Song{} = song}, socket) do
|
def handle_info({MediaLibrary, :pause, %MediaLibrary.Song{} = song}, socket) do
|
||||||
{:noreply, pause_song(socket, song.id)}
|
{:noreply, pause_song(socket, song.id)}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp stop_song(socket, song_id) do
|
defp stop_song(socket, song_id) do
|
||||||
SongRowComponent.send_status(song_id, :stopped)
|
SongRowComponent.send_status(song_id, :stopped)
|
||||||
|
|
||||||
if socket.assigns.active_id == song_id do
|
if socket.assigns.active_song_id == song_id do
|
||||||
assign(socket, :active_id, nil)
|
assign(socket, :active_song_id, nil)
|
||||||
else
|
else
|
||||||
socket
|
socket
|
||||||
end
|
end
|
||||||
|
@ -106,23 +151,23 @@ defmodule LiveBeatsWeb.SongLive.Index do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp play_song(socket, %MediaLibrary.Song{} = song) do
|
defp play_song(socket, %MediaLibrary.Song{} = song) do
|
||||||
%{active_id: active_id} = socket.assigns
|
%{active_song_id: active_song_id} = socket.assigns
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
active_id == song.id ->
|
active_song_id == song.id ->
|
||||||
SongRowComponent.send_status(song.id, :playing)
|
SongRowComponent.send_status(song.id, :playing)
|
||||||
socket
|
socket
|
||||||
|
|
||||||
active_id ->
|
active_song_id ->
|
||||||
SongRowComponent.send_status(song.id, :playing)
|
SongRowComponent.send_status(song.id, :playing)
|
||||||
|
|
||||||
socket
|
socket
|
||||||
|> stop_song(active_id)
|
|> stop_song(active_song_id)
|
||||||
|> assign(active_id: song.id)
|
|> assign(active_song_id: song.id)
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
SongRowComponent.send_status(song.id, :playing)
|
SongRowComponent.send_status(song.id, :playing)
|
||||||
assign(socket, active_id: song.id)
|
assign(socket, active_song_id: song.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -155,7 +200,7 @@ defmodule LiveBeatsWeb.SongLive.Index do
|
||||||
|> assign(:song, nil)
|
|> assign(:song, nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp list_songs do
|
defp list_songs(socket) do
|
||||||
MediaLibrary.list_songs(50)
|
assign(socket, songs: MediaLibrary.list_profile_songs(socket.assigns.profile, 50))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -56,7 +56,7 @@ defmodule LiveBeatsWeb.Router do
|
||||||
live_session :authenticated,
|
live_session :authenticated,
|
||||||
on_mount: [{LiveBeatsWeb.UserAuth, :ensure_authenticated}, LiveBeatsWeb.Nav] do
|
on_mount: [{LiveBeatsWeb.UserAuth, :ensure_authenticated}, LiveBeatsWeb.Nav] do
|
||||||
live "/songs/new", SongLive.Index, :new
|
live "/songs/new", SongLive.Index, :new
|
||||||
live "/:user_id", SongLive.Index, :index
|
live "/:profile_username", SongLive.Index, :index
|
||||||
live "/profile/settings", SettingsLive, :edit
|
live "/profile/settings", SettingsLive, :edit
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,6 +11,7 @@ defmodule LiveBeats.Repo.Migrations.CreateUserAuth do
|
||||||
add :role, :string, null: false
|
add :role, :string, null: false
|
||||||
add :confirmed_at, :naive_datetime
|
add :confirmed_at, :naive_datetime
|
||||||
add :profile_tagline, :string
|
add :profile_tagline, :string
|
||||||
|
add :active_profile_user_id, references(:users, on_delete: :nilify_all)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue