diff --git a/lib/music_library/scrobble_activity.ex b/lib/music_library/scrobble_activity.ex
index 45eaef91..36839e14 100644
--- a/lib/music_library/scrobble_activity.ex
+++ b/lib/music_library/scrobble_activity.ex
@@ -97,6 +97,54 @@ defmodule MusicLibrary.ScrobbleActivity do
end
end
+ def scrobble_tracks(selected_track_ids, release_with_tracks, opts) when is_list(opts) do
+ case Enum.sort(opts) do
+ [finished_at: _, started_at: _] ->
+ raise ArgumentError, """
+ Cannot scrobble tracks with both started_at and finished_at.
+ Remove either of them.
+ """
+
+ [started_at: started_at] ->
+ scrobble_tracks(selected_track_ids, release_with_tracks, {:started_at, started_at})
+
+ [finished_at: finished_at] ->
+ scrobble_tracks(selected_track_ids, release_with_tracks, {:finished_at, finished_at})
+ end
+ end
+
+ def scrobble_tracks(selected_track_ids, release_with_tracks, {:finished_at, finished_at}) do
+ all_tracks = Release.tracks(release_with_tracks)
+
+ selected_tracks =
+ Enum.filter(all_tracks, fn track -> MapSet.member?(selected_track_ids, track.id) end)
+
+ tracks_duration = Enum.sum_by(selected_tracks, fn track -> track.length || 0 end)
+ started_at = DateTime.add(finished_at, -tracks_duration, :millisecond)
+ scrobble_tracks(selected_track_ids, release_with_tracks, {:started_at, started_at})
+ end
+
+ def scrobble_tracks(selected_track_ids, release_with_tracks, {:started_at, started_at}) do
+ all_tracks = Release.tracks(release_with_tracks)
+
+ selected_tracks =
+ Enum.filter(all_tracks, fn track -> MapSet.member?(selected_track_ids, track.id) end)
+
+ tracks_duration = Enum.sum_by(selected_tracks, fn track -> track.length || 0 end)
+
+ if tracks_duration == 0 do
+ {:error, :no_duration}
+ else
+ session_key = Secrets.get!("last_fm_session_key").value
+
+ {scrobbles, _finished_at} =
+ selected_tracks
+ |> to_scrobbles(release_with_tracks, started_at)
+
+ LastFm.scrobble(scrobbles, session_key)
+ end
+ end
+
defp to_scrobbles(tracks, release_with_tracks, started_at) do
tracks
|> Enum.map_reduce(started_at, fn track, time ->
diff --git a/lib/music_library_web/components/release.ex b/lib/music_library_web/components/release.ex
index 5fa9b1ca..7e6548de 100644
--- a/lib/music_library_web/components/release.ex
+++ b/lib/music_library_web/components/release.ex
@@ -14,7 +14,8 @@ defmodule MusicLibraryWeb.Components.Release do
socket
|> assign(:can_scrobble?, ScrobbleActivity.can_scrobble?())
|> assign(:release_with_tracks, AsyncResult.loading())
- |> assign(:already_scrobbled, false)}
+ |> assign(:already_scrobbled, false)
+ |> assign(:selected_tracks, MapSet.new())}
end
@impl true
@@ -49,11 +50,15 @@ defmodule MusicLibraryWeb.Components.Release do
:if={@can_scrobble? && @release_with_tracks.ok?}
size="sm"
disabled={@already_scrobbled}
- phx-click="scrobble_release"
+ phx-click={
+ if MapSet.size(@selected_tracks) > 0,
+ do: "scrobble_selected_tracks",
+ else: "scrobble_release"
+ }
phx-target={@myself}
phx-disable-with={gettext("Scrobbling...")}
>
- {gettext("Scrobble release")}
+ {scrobble_button_label(@selected_tracks)}
<.button :if={!@can_scrobble?} size="sm" href={LastFm.auth_url()}>
{gettext("Connect your Last.fm account")}
@@ -86,6 +91,7 @@ defmodule MusicLibraryWeb.Components.Release do
medium={medium}
release_artists={release_with_tracks.artists}
media_count={MusicBrainz.Release.media_count(release_with_tracks)}
+ selected_tracks={@selected_tracks}
myself={@myself}
/>
@@ -100,6 +106,7 @@ defmodule MusicLibraryWeb.Components.Release do
attr :media_count, :integer, required: true
attr :can_scrobble?, :boolean, required: true
attr :already_scrobbled, :boolean, required: true
+ attr :selected_tracks, :any, required: true
attr :myself, :any, required: true
def medium(assigns) do
@@ -127,6 +134,9 @@ defmodule MusicLibraryWeb.Components.Release do
medium_number={@medium.number}
tracks={@medium.tracks}
release_artists={@release_artists}
+ selected_tracks={@selected_tracks}
+ can_scrobble?={@can_scrobble?}
+ myself={@myself}
/>
<.separator />
@@ -146,6 +156,9 @@ defmodule MusicLibraryWeb.Components.Release do
attr :medium_number, :integer, required: true
attr :tracks, :list, required: true
attr :release_artists, :list, required: true
+ attr :selected_tracks, :any, required: true
+ attr :can_scrobble?, :boolean, required: true
+ attr :myself, :any, required: true
def track_list(assigns) do
~H"""
@@ -155,6 +168,17 @@ defmodule MusicLibraryWeb.Components.Release do
class="contents leading-5 text-zinc-700 dark:text-zinc-300 list-none"
>
+
+
+
{track.number || track.position}
@@ -169,6 +193,7 @@ defmodule MusicLibraryWeb.Components.Release do
:if={@release_artists !== track.artists}
class="table-row text-xs md:text-sm"
>
+
{Enum.map_join(track.artists, ", ", fn artist -> artist.name end)}
@@ -238,6 +263,56 @@ defmodule MusicLibraryWeb.Components.Release do
{:noreply, socket |> put_toast(:error, gettext("Error scrobbling disc"))}
end
+ def handle_event("toggle_track", %{"track-id" => track_id}, socket) do
+ selected_tracks = socket.assigns.selected_tracks
+
+ updated_tracks =
+ if MapSet.member?(selected_tracks, track_id) do
+ MapSet.delete(selected_tracks, track_id)
+ else
+ MapSet.put(selected_tracks, track_id)
+ end
+
+ {:noreply, assign(socket, :selected_tracks, updated_tracks)}
+ end
+
+ def handle_event("scrobble_selected_tracks", _params, socket)
+ when release_loaded?(socket.assigns) do
+ release_with_tracks = socket.assigns.release_with_tracks.result
+ selected_track_ids = socket.assigns.selected_tracks
+
+ if MapSet.size(selected_track_ids) == 0 do
+ {:noreply, socket |> put_toast(:error, gettext("No tracks selected"))}
+ else
+ case ScrobbleActivity.scrobble_tracks(
+ selected_track_ids,
+ release_with_tracks,
+ finished_at: DateTime.utc_now()
+ ) do
+ {:ok, _} ->
+ send_update_after(socket.assigns.myself, %{already_scrobbled: false}, 3000)
+
+ {:noreply,
+ socket
+ |> assign(:already_scrobbled, true)
+ |> assign(:selected_tracks, MapSet.new())
+ |> put_toast(:info, gettext("Selected tracks scrobbled successfully"))}
+
+ {:error, reason} ->
+ {:noreply,
+ socket
+ |> put_toast(
+ :error,
+ gettext("Error scrobbling selected tracks") <> "," <> inspect(reason)
+ )}
+ end
+ end
+ end
+
+ def handle_event("scrobble_selected_tracks", _params, socket) do
+ {:noreply, socket |> put_toast(:error, gettext("Error scrobbling selected tracks"))}
+ end
+
defp medium_duration(medium) do
medium
|> MusicBrainz.Release.medium_duration()
@@ -251,4 +326,12 @@ defmodule MusicLibraryWeb.Components.Release do
medium.title
end
end
+
+ defp scrobble_button_label(selected_tracks) do
+ if MapSet.size(selected_tracks) > 0 do
+ gettext("Scrobble selected tracks")
+ else
+ gettext("Scrobble release")
+ end
+ end
end
diff --git a/lib/music_library_web/live/scrobble_live/show.html.heex b/lib/music_library_web/live/scrobble_live/show.html.heex
index d9dc5d24..b29dd409 100644
--- a/lib/music_library_web/live/scrobble_live/show.html.heex
+++ b/lib/music_library_web/live/scrobble_live/show.html.heex
@@ -94,6 +94,7 @@
already_scrobbled={false}
medium={medium}
release_artists={@release.artists}
+ selected_tracks={MapSet.new()}
media_count={MusicBrainz.Release.media_count(@release)}
myself={nil}
/>
diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot
index 3fc7b093..6b6debb5 100644
--- a/priv/gettext/default.pot
+++ b/priv/gettext/default.pot
@@ -766,6 +766,26 @@ msgstr ""
msgid "Scrobble release"
msgstr ""
+#: lib/music_library_web/components/release.ex
+#, elixir-autogen, elixir-format
+msgid "Scrobble selected tracks"
+msgstr ""
+
+#: lib/music_library_web/components/release.ex
+#, elixir-autogen, elixir-format
+msgid "Selected tracks scrobbled successfully"
+msgstr ""
+
+#: lib/music_library_web/components/release.ex
+#, elixir-autogen, elixir-format
+msgid "Error scrobbling selected tracks"
+msgstr ""
+
+#: lib/music_library_web/components/release.ex
+#, elixir-autogen, elixir-format
+msgid "No tracks selected"
+msgstr ""
+
#: lib/music_library_web/components/release.ex
#: lib/music_library_web/live/scrobble_live/show.ex
#, elixir-autogen, elixir-format
diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po
index 5398091d..27cd3766 100644
--- a/priv/gettext/en/LC_MESSAGES/default.po
+++ b/priv/gettext/en/LC_MESSAGES/default.po
@@ -766,6 +766,26 @@ msgstr ""
msgid "Scrobble release"
msgstr ""
+#: lib/music_library_web/components/release.ex
+#, elixir-autogen, elixir-format
+msgid "Scrobble selected tracks"
+msgstr ""
+
+#: lib/music_library_web/components/release.ex
+#, elixir-autogen, elixir-format
+msgid "Selected tracks scrobbled successfully"
+msgstr ""
+
+#: lib/music_library_web/components/release.ex
+#, elixir-autogen, elixir-format
+msgid "Error scrobbling selected tracks"
+msgstr ""
+
+#: lib/music_library_web/components/release.ex
+#, elixir-autogen, elixir-format
+msgid "No tracks selected"
+msgstr ""
+
#: lib/music_library_web/components/release.ex
#: lib/music_library_web/live/scrobble_live/show.ex
#, elixir-autogen, elixir-format