mirror of
https://github.com/fly-apps/live_beats.git
synced 2024-11-21 23:50:59 +00:00
Add attribution field and handle async duration race
This commit is contained in:
parent
ff7b064660
commit
eda99fa903
11 changed files with 72 additions and 21 deletions
|
@ -49,7 +49,7 @@ defmodule LiveBeats.MediaLibrary do
|
|||
|
||||
stopped_query =
|
||||
from s in Song,
|
||||
where: s.user_id == ^song.user_id and s.status == :playing,
|
||||
where: s.user_id == ^song.user_id and s.status in [:playing, :paused],
|
||||
update: [set: [status: :stopped]]
|
||||
|
||||
{:ok, %{now_playing: new_song}} =
|
||||
|
@ -89,7 +89,12 @@ defmodule LiveBeats.MediaLibrary do
|
|||
end
|
||||
|
||||
def put_stats(%Ecto.Changeset{} = changeset, %MP3Stat{} = stat) do
|
||||
Ecto.Changeset.put_change(changeset, :duration, stat.duration)
|
||||
chset = Song.put_duration(changeset, stat.duration)
|
||||
if error = chset.errors[:duration] do
|
||||
{:error, %{duration: error}}
|
||||
else
|
||||
{:ok, chset}
|
||||
end
|
||||
end
|
||||
|
||||
def import_songs(%Accounts.User{} = user, changesets, consume_file)
|
||||
|
|
|
@ -15,6 +15,7 @@ defmodule LiveBeats.MediaLibrary.Song do
|
|||
field :duration, :integer
|
||||
field :status, Ecto.Enum, values: [stopped: 1, playing: 2, paused: 3]
|
||||
field :title, :string
|
||||
field :attribution, :string
|
||||
field :mp3_url, :string
|
||||
field :mp3_filepath, :string
|
||||
field :mp3_filename, :string
|
||||
|
@ -31,15 +32,24 @@ defmodule LiveBeats.MediaLibrary.Song do
|
|||
@doc false
|
||||
def changeset(song, attrs) do
|
||||
song
|
||||
|> cast(attrs, [:album_artist, :artist, :title, :date_recorded, :date_released])
|
||||
|> cast(attrs, [:album_artist, :artist, :title, :attribution, :date_recorded, :date_released])
|
||||
|> validate_required([:artist, :title])
|
||||
|> validate_number(:duration, greater_than: 0, less_than: 1200)
|
||||
end
|
||||
|
||||
def put_user(%Ecto.Changeset{} = changeset, %Accounts.User{} = user) do
|
||||
put_assoc(changeset, :user, user)
|
||||
end
|
||||
|
||||
def put_duration(%Ecto.Changeset{} = changeset, duration) when is_integer(duration) do
|
||||
changeset
|
||||
|> Ecto.Changeset.change(%{duration: duration})
|
||||
|> Ecto.Changeset.validate_number(:duration,
|
||||
greater_than: 0,
|
||||
less_than: 1200,
|
||||
message: "must be less than 20 minutes"
|
||||
)
|
||||
end
|
||||
|
||||
def put_mp3_path(%Ecto.Changeset{} = changeset) do
|
||||
if changeset.valid? do
|
||||
filename = Ecto.UUID.generate() <> ".mp3"
|
||||
|
|
|
@ -315,7 +315,7 @@ defmodule LiveBeatsWeb.LiveHelpers do
|
|||
<%= for {row, i} <- Enum.with_index(@rows) do %>
|
||||
<tr id={@row_id && @row_id.(row)} class="hover:bg-gray-50">
|
||||
<%= for col <- @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"}"}>
|
||||
<td class={"px-6 py-3 whitespace-nowrap text-sm font-medium text-gray-900 #{if i == 0, do: "max-w-0 w-full"} #{col[:class]}"}>
|
||||
<div class="flex items-center space-x-3 lg:pl-2">
|
||||
<%= render_slot(col, row) %>
|
||||
</div>
|
||||
|
|
|
@ -16,7 +16,7 @@ defmodule LiveBeatsWeb.PlayerLive do
|
|||
<div class="bg-white dark:bg-gray-800 p-4">
|
||||
<div class="flex items-center space-x-3.5 sm:space-x-5 lg:space-x-3.5 xl:space-x-5">
|
||||
<div class="pr-5">
|
||||
<div class="min-w-0 flex-col space-y-0.5">
|
||||
<div class="min-w-0 max-w-xs flex-col space-y-0.5">
|
||||
<h2 class="text-black dark:text-white text-sm sm:text-sm lg:text-sm xl:text-sm font-semibold truncate">
|
||||
<%= if @song, do: @song.title, else: raw(" ") %>
|
||||
</h2>
|
||||
|
|
|
@ -33,6 +33,7 @@ defmodule LiveBeatsWeb.SongLive.Index do
|
|||
>
|
||||
<:col let={%{song: song}} label="Title"><%= song.title %></:col>
|
||||
<:col let={%{song: song}} label="Artist"><%= song.artist %></:col>
|
||||
<:col let={%{song: song}} label="Attribution" class="max-w-5xl break-words text-gray-600 font-light"><%= song.attribution %></:col>
|
||||
<:col let={%{song: song}} label="Duration"><%= MP3Stat.to_mmss(song.duration) %></:col>
|
||||
<:col let={%{song: song}} label="">
|
||||
<.link phx-click={show_modal("delete-modal-#{song.id}")} class="inline-flex items-center px-3 py-2 text-sm leading-4 font-medium">
|
||||
|
|
|
@ -33,6 +33,18 @@ defmodule LiveBeatsWeb.SongLive.SongEntryComponent do
|
|||
<.error input_name={"songs[#{@ref}][title]"} field={:title} errors={@errors} class="-mt-1"/>
|
||||
<.error input_name={"songs[#{@ref}][artist]"} field={:artist} errors={@errors} class="-mt-1"/>
|
||||
</div>
|
||||
<div class="border col-span-full border-gray-300 rounded-md px-3 py-2 mt-2 shadow-sm focus-within:ring-1 focus-within:ring-indigo-600 focus-within:border-indigo-600">
|
||||
<label for="name" class="block text-xs font-medium text-gray-900">
|
||||
License Attribution <span class="text-gray-400">(as required by artist)</span>
|
||||
</label>
|
||||
<textarea
|
||||
name={"songs[#{@ref}][attribution]"}
|
||||
class="block w-full border-0 p-0 text-gray-900 placeholder-gray-500 focus:ring-0 sm:text-xs"
|
||||
><%= @attribution %></textarea>
|
||||
</div>
|
||||
<div class="col-span-full sm:grid sm:grid-cols-2 sm:gap-2 sm:items-start">
|
||||
<.error input_name={"songs[#{@ref}][attribution]"} field={:attribution} errors={@errors} class="-mt-1"/>
|
||||
</div>
|
||||
<div style={"width: #{@progress}%;"} class="col-span-full bg-purple-500 dark:bg-purple-400 h-1.5 w-0 p-0">
|
||||
</div>
|
||||
</div>
|
||||
|
@ -51,6 +63,7 @@ defmodule LiveBeatsWeb.SongLive.SongEntryComponent do
|
|||
|> assign(title: Ecto.Changeset.get_field(changeset, :title))
|
||||
|> assign(artist: Ecto.Changeset.get_field(changeset, :artist))
|
||||
|> assign(duration: Ecto.Changeset.get_field(changeset, :duration))
|
||||
|> assign(attribution: Ecto.Changeset.get_field(changeset, :attribution))
|
||||
|> assign_new(:progress, fn -> 0 end)}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ defmodule LiveBeatsWeb.SongLive.SongRowComponent do
|
|||
<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"}"}
|
||||
class={"px-6 py-3 text-sm font-medium text-gray-900 #{if i == 0, do: "w-80 cursor-pointer"} #{col[:class]}"}
|
||||
phx-click={JS.push("play_or_pause", value: %{id: @song.id})}
|
||||
>
|
||||
<div class="flex items-center space-x-3 lg:pl-2">
|
||||
|
|
|
@ -46,18 +46,26 @@ defmodule LiveBeatsWeb.SongLive.UploadFormComponent do
|
|||
%{current_user: current_user} = socket.assigns
|
||||
changesets = socket.assigns.changesets
|
||||
|
||||
case MediaLibrary.import_songs(current_user, changesets, &consume_entry(socket, &1, &2)) do
|
||||
{:ok, songs} ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, "#{map_size(songs)} song(s) uploaded")
|
||||
|> push_redirect(to: Routes.song_index_path(socket, :index))}
|
||||
if pending_stats?(socket) do
|
||||
{:noreply, socket}
|
||||
else
|
||||
case MediaLibrary.import_songs(current_user, changesets, &consume_entry(socket, &1, &2)) do
|
||||
{:ok, songs} ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, "#{map_size(songs)} song(s) uploaded")
|
||||
|> push_redirect(to: home_path(socket))}
|
||||
|
||||
{:error, _reason} ->
|
||||
{:noreply, socket}
|
||||
{:error, _reason} ->
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp pending_stats?(socket) do
|
||||
Enum.find(socket.assigns.changesets, fn {_ref, chset} -> !chset.changes[:duration] 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)
|
||||
|
@ -121,6 +129,7 @@ defmodule LiveBeatsWeb.SongLive.UploadFormComponent do
|
|||
|
||||
consume_uploaded_entry(socket, entry, fn %{path: path} ->
|
||||
Task.Supervisor.start_child(LiveBeats.TaskSupervisor, fn ->
|
||||
Process.sleep(5000)
|
||||
send_update(lv, __MODULE__,
|
||||
id: socket.assigns.id,
|
||||
action: {:duration, entry.ref, LiveBeats.MP3Stat.parse(path)}
|
||||
|
@ -136,9 +145,20 @@ defmodule LiveBeatsWeb.SongLive.UploadFormComponent do
|
|||
defp file_error(%{kind: :not_accepted} = assigns), do: ~H|not a valid MP3 file|
|
||||
defp file_error(%{kind: :too_many_files} = assigns), do: ~H|too many files|
|
||||
|
||||
defp file_error(%{kind: {msg, opts}} = assigns) when is_binary(msg) and is_list(opts) do
|
||||
~H|<%= LiveBeatsWeb.ErrorHelpers.translate_error(@kind) %>|
|
||||
end
|
||||
|
||||
defp put_stats(socket, entry_ref, %MP3Stat{} = stat) do
|
||||
if changeset = get_changeset(socket, entry_ref) do
|
||||
update_changeset(socket, MediaLibrary.put_stats(changeset, stat), entry_ref)
|
||||
case MediaLibrary.put_stats(changeset, stat) do
|
||||
{:ok, new_changeset} ->
|
||||
update_changeset(socket, new_changeset, entry_ref)
|
||||
|
||||
{:error, %{duration: error}} ->
|
||||
IO.inspect({:duration, error})
|
||||
cancel_changeset_upload(socket, entry_ref, error)
|
||||
end
|
||||
else
|
||||
socket
|
||||
end
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
<main>
|
||||
<.live_component module={LiveBeatsWeb.LayoutComponent} id="layout" />
|
||||
|
||||
<%= if @current_user do %>
|
||||
<%= live_render(@socket, LiveBeatsWeb.PlayerLive, id: "player", session: %{}, sticky: true) %>
|
||||
<% end %>
|
||||
|
||||
<p class="alert alert-info fade-in-scale" role="alert"
|
||||
phx-click="lv:clear-flash"
|
||||
phx-value-key="info"><%= live_flash(@flash, :info) %></p>
|
||||
|
@ -7,7 +13,5 @@
|
|||
phx-click="lv:clear-flash"
|
||||
phx-value-key="error"><%= live_flash(@flash, :error) %></p>
|
||||
|
||||
<.live_component module={LiveBeatsWeb.LayoutComponent} id="layout" />
|
||||
|
||||
<%= @inner_content %>
|
||||
</main>
|
||||
|
|
|
@ -301,9 +301,6 @@
|
|||
</div>
|
||||
|
||||
<main class="flex-1 relative z-0 overflow-y-auto focus:outline-none">
|
||||
<%= if @current_user do %>
|
||||
<%= live_render(@conn, LiveBeatsWeb.PlayerLive, session: %{}) %>
|
||||
<% end %>
|
||||
<%= @inner_content %>
|
||||
</main>
|
||||
</div>
|
||||
|
|
|
@ -10,6 +10,7 @@ defmodule LiveBeats.Repo.Migrations.CreateSongs do
|
|||
add :played_at, :utc_datetime
|
||||
add :paused_at, :utc_datetime
|
||||
add :title, :string, null: false
|
||||
add :attribution, :string
|
||||
add :mp3_url, :string, null: false
|
||||
add :mp3_filename, :string, null: false
|
||||
add :mp3_filepath, :string, null: false
|
||||
|
|
Loading…
Reference in a new issue