Synced playing fixup

This commit is contained in:
Chris McCord 2021-11-05 15:57:33 -04:00
parent 3ff4ae2bea
commit 287f78ab2a
13 changed files with 322 additions and 167 deletions

View file

@ -3,15 +3,7 @@ import {Socket} from "phoenix"
import {LiveSocket} from "./phoenix_live_view"
import topbar from "../vendor/topbar"
let render = (webComponent, html) => {
let shadow = webComponent.attachShadow({mode: "open"})
document.querySelectorAll("link").forEach(link => shadow.appendChild(link.cloneNode()))
let div = document.createElement("div")
div.setAttribute("class", webComponent.getAttribute("class"))
div.innerHTML = html || webComponent.innerHTML
shadow.appendChild(div)
return div
}
let nowSeconds = () => Math.round(Date.now() / 1000)
let Hooks = {}
@ -40,23 +32,30 @@ Hooks.AudioPlayer = {
this.player.pause()
}
document.addEventListener("click", enableAudio)
this.el.addEventListener("js:listen_now", () => this.play({sync: true}))
this.el.addEventListener("js:play_pause", () => {
this.play()
if(this.player.paused){
this.play()
}
})
this.handleEvent("play", ({url, began_at}) => {
this.playbackBeganAt = began_at
this.player.src = url
this.play()
this.handleEvent("play", ({url, elapsed}) => {
this.playbackBeganAt = nowSeconds() - elapsed
if(this.player.src === url && this.player.paused){
this.play({sync: true})
} else if(this.player.src !== url) {
this.player.src = url
this.play({sync: true})
}
})
this.handleEvent("pause", () => {
console.log("Server Pause!")
this.pause()
})
},
play(){
play(opts = {}){
let {sync} = opts
this.player.play().then(() => {
this.player.currentTime = (Date.now() - this.playbackBeganAt) / 1000
if(sync){ this.player.currentTime = nowSeconds() - this.playbackBeganAt }
this.progressTimer = setInterval(() => this.updateProgress(), 100)
this.pushEvent("audio-accepted", {})
}, error => {
@ -65,8 +64,8 @@ Hooks.AudioPlayer = {
},
pause(){
this.player.pause()
clearInterval(this.progressTimer)
this.player.pause()
},
updateProgress(){

View file

@ -1,25 +0,0 @@
defmodule LiveBeats.ID3 do
alias LiveBeats.ID3
defstruct title: nil,
artist: nil,
album: nil,
year: nil
def parse(path) do
with {:ok, parsed} <- :id3_tag_reader.read_tag(path) do
{:ok, parsed}
# %ID3{
# title: strip(title),
# artist: strip(artist),
# album: strip(album),
# year: 2028
# }}
else
other ->
{:error, other}
end
end
defp strip(binary), do: String.trim_trailing(binary, <<0>>)
end

View file

@ -3,12 +3,18 @@ defmodule LiveBeats.MediaLibrary do
The MediaLibrary context.
"""
require Logger
import Ecto.Query, warn: false
alias LiveBeats.{Repo, MP3Stat, Accounts}
alias LiveBeats.MediaLibrary.{Song, Genre}
alias Ecto.{Multi, Changeset}
@pubsub LiveBeats.PubSub
defdelegate stopped?(song), to: Song
defdelegate playing?(song), to: Song
defdelegate paused?(song), to: Song
def subscribe(%Accounts.User{} = user) do
Phoenix.PubSub.subscribe(@pubsub, topic(user.id))
end
@ -17,19 +23,65 @@ defmodule LiveBeats.MediaLibrary do
def play_song(id) do
song = get_song!(id)
Phoenix.PubSub.broadcast!(@pubsub, topic(song.user_id), {:play, song, %{began_at: now_ms()}})
played_at =
cond do
playing?(song) ->
song.played_at
paused?(song) ->
elapsed = DateTime.diff(song.paused_at, song.played_at, :second)
DateTime.add(DateTime.utc_now(), -elapsed)
true ->
DateTime.utc_now()
end
changeset =
Changeset.change(song, %{
played_at: DateTime.truncate(played_at, :second),
status: :playing
})
stopped_query =
from s in Song,
where: s.user_id == ^song.user_id and s.status == :playing,
update: [set: [status: :stopped]]
{:ok, %{now_playing: new_song}} =
Multi.new()
|> Multi.update_all(:now_stopped, fn _ -> stopped_query end, [])
|> Multi.update(:now_playing, changeset)
|> Repo.transaction()
elapsed = elapsed_playback(new_song)
Phoenix.PubSub.broadcast!(@pubsub, topic(song.user_id), {:play, song, %{elapsed: elapsed}})
end
def pause_song(%Song{} = song) do
now = DateTime.truncate(DateTime.utc_now(), :second)
set = [status: :paused, paused_at: now]
pause_query = from(s in Song, where: s.id == ^song.id, update: [set: ^set])
stopped_query =
from s in Song,
where: s.user_id == ^song.user_id and s.status in [:playing, :paused],
update: [set: [status: :stopped]]
{:ok, _} =
Multi.new()
|> Multi.update_all(:now_stopped, fn _ -> stopped_query end, [])
|> Multi.update_all(:now_paused, fn _ -> pause_query end, [])
|> Repo.transaction()
Phoenix.PubSub.broadcast!(@pubsub, topic(song.user_id), {:pause, song})
end
defp topic(user_id), do: "room:#{user_id}"
def store_mp3(%Song{} = song, tmp_path) do
dir = "priv/static/uploads/songs"
File.mkdir_p!(dir)
File.cp!(tmp_path, Path.join(dir, song.mp3_filename))
File.mkdir_p!(Path.dirname(song.mp3_filepath))
File.cp!(tmp_path, song.mp3_filepath)
end
def put_stats(%Ecto.Changeset{} = changeset, %MP3Stat{} = stat) do
@ -38,18 +90,17 @@ defmodule LiveBeats.MediaLibrary do
def import_songs(%Accounts.User{} = user, changesets, consume_file)
when is_map(changesets) and is_function(consume_file, 2) do
changesets
|> Enum.reduce(Ecto.Multi.new(), fn {ref, chset}, acc ->
chset =
chset
|> Song.put_user(user)
|> Song.put_mp3_path()
|> Map.put(:action, nil)
multi =
Enum.reduce(changesets, Ecto.Multi.new(), fn {ref, chset}, acc ->
chset =
chset
|> Song.put_user(user)
|> Song.put_mp3_path()
Ecto.Multi.insert(acc, {:song, ref}, chset)
end)
|> LiveBeats.Repo.transaction()
|> case do
Ecto.Multi.insert(acc, {:song, ref}, chset)
end)
case LiveBeats.Repo.transaction(multi) do
{:ok, results} ->
{:ok,
results
@ -83,7 +134,25 @@ defmodule LiveBeats.MediaLibrary do
end
def list_songs(limit \\ 100) do
Repo.all(from s in Song, limit: ^limit, order_by: [asc: s.inserted_at])
Repo.all(from s in Song, limit: ^limit, order_by: [asc: s.inserted_at, asc: s.id])
end
def get_current_active_song(user_id) do
Repo.one(from s in Song, where: s.user_id == ^user_id and s.status in [:playing, :paused])
end
def elapsed_playback(%Song{} = song) do
cond do
playing?(song) ->
start_seconds = song.played_at |> DateTime.to_unix()
System.os_time(:second) - start_seconds
paused?(song) ->
DateTime.diff(song.paused_at, song.played_at, :second)
stopped?(song) ->
0
end
end
def get_song!(id), do: Repo.get!(Song, id)
@ -101,12 +170,20 @@ defmodule LiveBeats.MediaLibrary do
end
def delete_song(%Song{} = song) do
case File.rm(song.mp3_filepath) do
:ok ->
:ok
{:error, reason} ->
Logger.info(
"unable to delete song #{song.id} at #{song.mp3_filepath}, got: #{inspect(reason)}"
)
end
Repo.delete(song)
end
def change_song(%Song{} = song, attrs \\ %{}) do
Song.changeset(song, attrs)
end
defp now_ms, do: System.system_time() |> System.convert_time_unit(:native, :millisecond)
end

View file

@ -2,23 +2,31 @@ defmodule LiveBeats.MediaLibrary.Song do
use Ecto.Schema
import Ecto.Changeset
alias LiveBeats.MediaLibrary.Song
alias LiveBeats.Accounts
schema "songs" do
field :album_artist, :string
field :artist, :string
field :played_at, :utc_datetime
field :paused_at, :utc_datetime
field :date_recorded, :naive_datetime
field :date_released, :naive_datetime
field :duration, :integer
field :status, Ecto.Enum, values: [stopped: 1, playing: 2, paused: 3]
field :title, :string
field :mp3_path, :string
field :mp3_filename, :string
field :mp3_filepath, :string
belongs_to :user, Accounts.User
belongs_to :genre, LiveBeats.MediaLibrary.Genre
timestamps()
end
def playing?(%Song{} = song), do: song.status == :playing
def paused?(%Song{} = song), do: song.status == :paused
def stopped?(%Song{} = song), do: song.status == :stopped
@doc false
def changeset(song, attrs) do
song
@ -34,10 +42,11 @@ defmodule LiveBeats.MediaLibrary.Song do
def put_mp3_path(%Ecto.Changeset{} = changeset) do
if changeset.valid? do
filename = Ecto.UUID.generate() <> ".mp3"
filepath = Path.join("priv/static/uploads/songs", filename)
changeset
|> Ecto.Changeset.put_change(:mp3_filename, filename)
|> Ecto.Changeset.put_change(:mp3_path, "uploads/songs/#{filename}")
|> Ecto.Changeset.put_change(:mp3_filepath, filepath)
|> Ecto.Changeset.put_change(:mp3_path, Path.join("uploads/songs", filename))
else
changeset
end

View file

@ -87,7 +87,7 @@ defmodule LiveBeatsWeb.PlayerLive do
</div>
<%= if @error do %>
<.modal show id="enable-audio" on_confirm={js_play_pause() |> hide_modal("enable-audio")}>
<.modal show id="enable-audio" on_confirm={js_listen_now() |> hide_modal("enable-audio")}>
<:title>Start Listening now</:title>
Your browser needs a click event to enable playback
<:confirm>Listen Now</:confirm>
@ -101,16 +101,25 @@ defmodule LiveBeatsWeb.PlayerLive do
def mount(_parmas, _session, socket) do
if connected?(socket) and socket.assigns.current_user do
MediaLibrary.subscribe(socket.assigns.current_user)
send(self(), :play_current)
end
{:ok, assign(socket, song: nil, playing: false, error: false), layout: false}
socket =
assign(socket,
song: nil,
playing: false,
error: false,
current_user_id: socket.assigns.current_user.id,
# todo use actual room user id
room_user_id: socket.assigns.current_user.id
)
{:ok, socket, layout: false, temporary_assigns: [current_user: nil]}
end
def handle_event("play_pause", _, socket) do
%{song: song, playing: playing} = socket.assigns
IO.inspect({:play_pause, playing})
cond do
song && playing ->
MediaLibrary.pause_song(song)
@ -133,6 +142,15 @@ defmodule LiveBeatsWeb.PlayerLive do
{:noreply, assign(socket, error: false)}
end
def handle_info(:play_current, socket) do
# we raced a pubsub, noop
if socket.assigns.song do
{:noreply, socket}
else
{:noreply, play_current_song(socket)}
end
end
def handle_info({:pause, _}, socket) do
{:noreply,
socket
@ -140,14 +158,44 @@ defmodule LiveBeatsWeb.PlayerLive do
|> assign(playing: false)}
end
def handle_info({:play, %Song{} = song, %{began_at: at}}, socket) do
{:noreply,
socket
|> push_event("play", %{began_at: at, url: Path.join(LiveBeatsWeb.Endpoint.url(), song.mp3_path)})
|> assign(song: song, playing: true)}
def handle_info({:play, %Song{} = song, %{elapsed: elapsed}}, socket) do
{:noreply, play_song(socket, song, elapsed)}
end
defp js_play_pause(js \\ %JS{}) do
defp play_song(socket, %Song{} = song, elapsed) do
socket
|> push_play(song, elapsed)
|> assign(song: song, playing: true)
end
defp js_play_pause(%JS{} = js) do
JS.dispatch(js, "js:play_pause", to: "#audio-player")
end
defp js_listen_now(js \\ %JS{}) do
JS.dispatch(js, "js:listen_now", to: "#audio-player")
end
defp play_current_song(socket) do
song = MediaLibrary.get_current_active_song(socket.assigns.room_user_id)
cond do
song && MediaLibrary.playing?(song) ->
play_song(socket, song, MediaLibrary.elapsed_playback(song))
song && MediaLibrary.paused?(song) ->
assign(socket, song: song, playing: false)
true ->
socket
end
end
defp push_play(socket, %Song{} = song, elapsed) do
push_event(socket, "play", %{
paused: Song.paused?(song),
elapsed: elapsed,
url: Path.join(LiveBeatsWeb.Endpoint.url(), song.mp3_path)
})
end
end

View file

@ -2,8 +2,8 @@ defmodule LiveBeatsWeb.SongLive.Index do
use LiveBeatsWeb, :live_view
alias LiveBeats.{MediaLibrary, MP3Stat}
alias LiveBeats.MediaLibrary.Song
alias LiveBeatsWeb.LayoutComponent
alias LiveBeatsWeb.SongLive.{SongRowComponent, UploadFormComponent}
def render(assigns) do
~H"""
@ -27,7 +27,7 @@ defmodule LiveBeatsWeb.SongLive.Index do
<% end %>
<.live_table
module={LiveBeatsWeb.SongLive.SongRow}
module={SongRowComponent}
rows={@songs}
row_id={fn song -> "song-#{song.id}" end}
>
@ -45,19 +45,33 @@ defmodule LiveBeatsWeb.SongLive.Index do
end
def mount(_params, _session, socket) do
%{current_user: current_user} = socket.assigns
if connected?(socket) do
MediaLibrary.subscribe(socket.assigns.current_user)
MediaLibrary.subscribe(current_user)
end
{:ok, assign(socket, songs: list_songs(), active_id: nil), temporary_assigns: [songs: []]}
active_id =
if song = MediaLibrary.get_current_active_song(current_user.id) do
SongRowComponent.send_status(song.id, song.status)
song.id
end
{:ok, assign(socket, songs: list_songs(), active_id: active_id), temporary_assigns: [songs: []]}
end
def handle_params(params, _url, socket) do
{:noreply, socket |> apply_action(socket.assigns.live_action, params) |> maybe_show_modal()}
end
def handle_event("play-song", %{"id" => id}, socket) do
MediaLibrary.play_song(id)
def handle_event("play_or_pause", %{"id" => id}, socket) do
song = MediaLibrary.get_song!(id)
if socket.assigns.active_id == id and MediaLibrary.playing?(song) do
MediaLibrary.pause_song(song)
else
MediaLibrary.play_song(id)
end
{:noreply, socket}
end
@ -67,42 +81,59 @@ defmodule LiveBeatsWeb.SongLive.Index do
{:noreply, socket}
end
def handle_info({:play, %Song{} = song, _meta}, socket) do
def handle_info({:play, %MediaLibrary.Song{} = song, _meta}, socket) do
{:noreply, play_song(socket, song)}
end
def handle_info({:pause, %Song{} = song}, socket) do
def handle_info({:pause, %MediaLibrary.Song{} = song}, socket) do
{:noreply, pause_song(socket, song.id)}
end
defp stop_song(socket, song_id) do
SongRowComponent.send_status(song_id, :stopped)
if socket.assigns.active_id == song_id do
assign(socket, :active_id, nil)
else
socket
end
end
defp pause_song(socket, song_id) do
send_update(LiveBeatsWeb.SongLive.SongRow, id: "song-#{song_id}", action: :deactivate)
SongRowComponent.send_status(song_id, :paused)
socket
end
defp play_song(socket, %Song{} = song) do
send_update(LiveBeatsWeb.SongLive.SongRow, id: "song-#{song.id}", action: :activate)
defp play_song(socket, %MediaLibrary.Song{} = song) do
%{active_id: active_id} = socket.assigns
if socket.assigns.active_id do
socket
|> pause_song(socket.assigns.active_id)
|> assign(active_id: song.id)
else
assign(socket, active_id: song.id)
cond do
active_id == song.id ->
SongRowComponent.send_status(song.id, :playing)
socket
active_id ->
SongRowComponent.send_status(song.id, :playing)
socket
|> stop_song(active_id)
|> assign(active_id: song.id)
true ->
SongRowComponent.send_status(song.id, :playing)
assign(socket, active_id: song.id)
end
end
defp maybe_show_modal(socket) do
if socket.assigns.live_action in [:new, :edit] do
LayoutComponent.show_modal(LiveBeatsWeb.SongLive.UploadFormComponent, %{
if socket.assigns.live_action in [:new] do
LayoutComponent.show_modal(UploadFormComponent, %{
id: :new,
confirm: {"Save", type: "submit", form: "song-form"},
patch_to: Routes.song_index_path(socket, :index),
id: socket.assigns.song.id || :new,
title: socket.assigns.page_title,
action: socket.assigns.live_action,
song: socket.assigns.song,
current_user: socket.assigns.current_user,
genres: socket.assigns.genres
title: socket.assigns.page_title,
current_user: socket.assigns.current_user
})
else
LayoutComponent.hide_modal()
@ -114,7 +145,7 @@ defmodule LiveBeatsWeb.SongLive.Index do
defp apply_action(socket, :new, _params) do
socket
|> assign(:page_title, "Add Songs")
|> assign(:song, %Song{})
|> assign(:song, %MediaLibrary.Song{})
end
defp apply_action(socket, :index, _params) do

View file

@ -1,4 +1,4 @@
defmodule LiveBeatsWeb.SongLive.SongEntryComponent do
defmodule LiveBeatsWeb.SongLive.SongEntry do
use LiveBeatsWeb, :live_component
alias LiveBeats.MP3Stat

View file

@ -1,22 +1,32 @@
defmodule LiveBeatsWeb.SongLive.SongRow do
defmodule LiveBeatsWeb.SongLive.SongRowComponent do
use LiveBeatsWeb, :live_component
def send_status(id, status) when status in [:playing, :paused, :stopped] do
send_update(__MODULE__, id: "song-#{id}", action: :send, status: status)
end
def render(assigns) do
~H"""
<tr id={@id} class={@class}}>
<%= for {col, i} <- Enum.with_index(@col) do %>
<td
class={"px-6 py-3 whitespace-nowrap text-sm font-medium text-gray-900 #{if i == 0, do: "max-w-0 w-full cursor-pointer"}"}
phx-click={JS.push("play-song", value: %{id: @song.id})}
phx-click={JS.push("play_or_pause", value: %{id: @song.id})}
>
<div class="flex items-center space-x-3 lg:pl-2">
<%= if i == 0 do %>
<%= if @active do %>
<%= if @status == :playing do %>
<span class="flex pt-1 relative mr-2 w-4">
<span class="w-3 h-3 animate-ping bg-purple-400 rounded-full absolute"></span>
<.icon name={:volume_up} class="h-5 w-5 -mt-1 -ml-1"/>
</span>
<% else %>
<% end %>
<%= if @status == :paused do %>
<span class="flex pt-1 relative mr-2 w-4">
<.icon name={:volume_up} class="h-5 w-5 -mt-1 -ml-1 text-gray-400"/>
</span>
<% end %>
<%= if @status == :stopped do %>
<span class="flex relative w-6 -translate-x-1">
<.icon name={:play} class="h-5 w-5 text-gray-400"/>
</span>
@ -30,16 +40,8 @@ defmodule LiveBeatsWeb.SongLive.SongRow do
"""
end
def update(%{action: :activate}, socket) do
{:ok, assign(socket, active: true)}
end
def update(%{action: :deactivate}, socket) do
{:ok, assign(socket, active: false)}
end
def update(%{action: action}, _socket) do
raise ArgumentError, "unkown action #{inspect(action)}"
def update(%{action: :send, status: status}, socket) when status in [:playing, :paused, :stopped] do
{:ok, assign(socket, status: status)}
end
def update(assigns, socket) do
@ -50,7 +52,7 @@ defmodule LiveBeatsWeb.SongLive.SongRow do
col: assigns.col,
class: assigns.class,
index: assigns.index,
active: false
status: :stopped
)}
end
end

View file

@ -2,7 +2,7 @@ defmodule LiveBeatsWeb.SongLive.UploadFormComponent do
use LiveBeatsWeb, :live_component
alias LiveBeats.{MediaLibrary, MP3Stat}
alias LiveBeatsWeb.SongLive.SongEntryComponent
alias LiveBeatsWeb.SongLive.SongEntry
@max_songs 10
@ -37,29 +37,12 @@ defmodule LiveBeatsWeb.SongLive.UploadFormComponent do
{:noreply, drop_invalid_uploads(socket)}
end
def handle_event("validate", %{"songs" => songs_params, "_target" => ["songs", _, _]}, socket) do
new_socket =
Enum.reduce(songs_params, socket, fn {ref, song_params}, acc ->
new_changeset =
acc
|> get_changeset(ref)
|> Ecto.Changeset.apply_changes()
|> MediaLibrary.change_song(song_params)
|> Map.put(:action, :validate)
update_changeset(acc, new_changeset, ref)
end)
{:noreply, new_socket}
def handle_event("validate", %{"songs" => params, "_target" => ["songs", _, _]}, socket) do
{:noreply, apply_params(socket, params, :validate)}
end
defp consume_entry(socket, ref, store_func) when is_function(store_func) do
{entries, []} = uploaded_entries(socket, :mp3)
entry = Enum.find(entries, fn entry -> entry.ref == ref end)
consume_uploaded_entry(socket, entry, fn meta -> {:ok, store_func.(meta.path)} end)
end
def handle_event("save", %{"songs" => song_params}, socket) do
def handle_event("save", %{"songs" => params}, socket) do
socket = apply_params(socket, params, :insert)
%{current_user: current_user} = socket.assigns
changesets = socket.assigns.changesets
@ -75,6 +58,25 @@ defmodule LiveBeatsWeb.SongLive.UploadFormComponent do
end
end
defp consume_entry(socket, ref, store_func) when is_function(store_func) do
{entries, []} = uploaded_entries(socket, :mp3)
entry = Enum.find(entries, fn entry -> entry.ref == ref end)
consume_uploaded_entry(socket, entry, fn meta -> {:ok, store_func.(meta.path)} end)
end
defp apply_params(socket, params, action) when action in [:validate, :insert] do
Enum.reduce(params, socket, fn {ref, song_params}, acc ->
new_changeset =
acc
|> get_changeset(ref)
|> Ecto.Changeset.apply_changes()
|> MediaLibrary.change_song(song_params)
|> Map.put(:action, action)
update_changeset(acc, new_changeset, ref)
end)
end
defp get_changeset(socket, entry_ref) do
case Enum.find(socket.assigns.changesets, fn {ref, _changeset} -> ref === entry_ref end) do
{^entry_ref, changeset} -> changeset
@ -102,27 +104,30 @@ defmodule LiveBeatsWeb.SongLive.UploadFormComponent do
end
defp handle_progress(:mp3, entry, socket) do
send_update(SongEntryComponent, id: entry.ref, progress: entry.progress)
lv = self()
send_update(SongEntry, id: entry.ref, progress: entry.progress)
if entry.done? do
consume_uploaded_entry(socket, entry, fn %{path: path} ->
Task.Supervisor.start_child(LiveBeats.TaskSupervisor, fn ->
result = LiveBeats.MP3Stat.parse(path)
send_update(lv, __MODULE__,
id: socket.assigns.id,
action: {:duration, entry.ref, result}
)
end)
{:postpone, :ok}
end)
async_calculate_duration(socket, entry)
end
{:noreply, put_new_changeset(socket, entry)}
end
defp async_calculate_duration(socket, %Phoenix.LiveView.UploadEntry{} = entry) do
lv = self()
consume_uploaded_entry(socket, entry, fn %{path: path} ->
Task.Supervisor.start_child(LiveBeats.TaskSupervisor, fn ->
send_update(lv, __MODULE__,
id: socket.assigns.id,
action: {:duration, entry.ref, LiveBeats.MP3Stat.parse(path)}
)
end)
{:postpone, :ok}
end)
end
defp file_error(%{kind: :dropped} = assigns), do: ~H|dropped (exceeds limit of 10 files)|
defp file_error(%{kind: :too_large} = assigns), do: ~H|larger than 10MB|
defp file_error(%{kind: :not_accepted} = assigns), do: ~H|not a valid MP3 file|

View file

@ -2,7 +2,6 @@
<h2><%= @title %></h2>
<.form
let={f}
for={:songs}
id="song-form"
class="space-y-8"
@ -13,7 +12,7 @@
<div class="space-y-8 divide-y divide-gray-200 sm:space-y-5">
<div class="space-y-2 sm:space-y-2">
<%= for {ref, changeset} <- @changesets do %>
<.live_component id={ref} module={SongEntryComponent} changeset={changeset} />
<.live_component id={ref} module={SongEntry} changeset={changeset} />
<% end %>
<!-- upload -->

View file

@ -307,7 +307,9 @@
</div>
<main class="flex-1 relative z-0 overflow-y-auto focus:outline-none">
<%= live_render(@conn, LiveBeatsWeb.PlayerLive, session: %{}) %>
<%= if @current_user do %>
<%= live_render(@conn, LiveBeatsWeb.PlayerLive, session: %{}) %>
<% end %>
<%= @inner_content %>
</main>
</div>

View file

@ -4,11 +4,14 @@ defmodule LiveBeats.Repo.Migrations.CreateSongs do
def change do
create table(:songs) do
add :album_artist, :string
add :artist, :string
add :duration, :integer
add :title, :string
add :mp3_path, :string
add :mp3_filename, :string
add :artist, :string, null: false
add :duration, :integer, default: 0, null: false
add :status, :integer, null: false, default: 1
add :played_at, :utc_datetime
add :paused_at, :utc_datetime
add :title, :string, null: false
add :mp3_path, :string, null: false
add :mp3_filepath, :string, null: false
add :date_recorded, :naive_datetime
add :date_released, :naive_datetime
add :user_id, references(:users, on_delete: :nothing)
@ -20,5 +23,6 @@ defmodule LiveBeats.Repo.Migrations.CreateSongs do
create unique_index(:songs, [:user_id, :title, :artist])
create index(:songs, [:user_id])
create index(:songs, [:genre_id])
create index(:songs, [:status])
end
end

View file

@ -14,11 +14,15 @@
# {:ok, _} = LiveBeats.MediaLibrary.create_genre(%{title: title})
# end
for i <- 1..200 do
{:ok, _} =
LiveBeats.MediaLibrary.create_song(%{
artist: "Bonobo",
title: "Black Sands #{i}",
duration: 180_000
})
end
# for i <- 1..200 do
# filename = Ecto.UUID.generate()
# {:ok, _} =
# LiveBeats.Repo.insert(%LiveBeats.MediaLibrary.Song{
# artist: "Bonobo",
# title: "Black Sands #{i}",
# duration: 180_000,
# mp3_filename: filename,
# mp3_path: "uploads/songs/#{filename}"
# })
# end