Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New bout page #730

Open
wants to merge 44 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
37eb44a
Add blank new bout page, non-null category on bout
skanderm Nov 14, 2024
0e4d10d
Merge branch 'main' into skander/new-bout
skanderm Nov 21, 2024
a29780d
Rename ReportsLayout to SimpleLayout
skanderm Nov 21, 2024
e7bab08
Add loading spinner, start of audio_images gql queries
skanderm Nov 25, 2024
6dcd702
Merge branch 'main' into skander/new-bout
skanderm Nov 26, 2024
b735f88
Add skeleton for new bout page, scrollable spectrogram element, feed_…
skanderm Nov 29, 2024
3f35467
Add feedStreams query to frontend, update feed_streams gql client wit…
skanderm Dec 3, 2024
3ccb700
Update feed_streams client query call in GlobalSetup
skanderm Dec 3, 2024
5a09253
Merge branch 'main' into skander/new-bout
skanderm Dec 3, 2024
ad66cd7
Update stream/segment populate script and fix create actions
skanderm Dec 6, 2024
58defa5
Update populate script. Add target time for bout player
skanderm Dec 11, 2024
79f4fd5
Add spectrogram layers, tick marks, update feed stream queries
skanderm Dec 13, 2024
60b6f07
Only show spectrograms for the visible window
skanderm Dec 17, 2024
31169c3
Add more buffer for loading spectrogram images, update background and…
skanderm Dec 18, 2024
60a4b41
Attempt to forward playerTime ref
skanderm Dec 21, 2024
7e5530f
Add working PlayHeadLayer with time updates using a ref
skanderm Dec 31, 2024
60e1aae
Set window scroll based on player time
skanderm Dec 31, 2024
61eb2a5
Add new separate ticker layer
skanderm Jan 6, 2025
0ba36c3
Remove mouse wheel zoom. Replace playhead with sticky element in the …
skanderm Jan 6, 2025
871e66d
Refactor layer components into their own files
skanderm Jan 6, 2025
f8b212f
Add timeline markers, set bout start/end
skanderm Jan 7, 2025
eee9b5a
Add frequency axis, detections table, UI for setting bout start and end
skanderm Jan 23, 2025
437d508
Move zoom, bout start/end controls out of spectrogram
skanderm Jan 24, 2025
218fd8e
Add bout creation with error alerts in new bout page
skanderm Jan 27, 2025
b12319f
Extract bout page into its own component, update path for new bout to…
skanderm Jan 27, 2025
80ccd71
Create bout show page, allow updating existing bout
skanderm Jan 27, 2025
ea57e95
Add success alert on bout save
skanderm Jan 27, 2025
4fb4bab
Add icons for audio category selection and read-only category for non…
skanderm Jan 27, 2025
3c3bc51
Add BoutParts gql fragment
skanderm Jan 28, 2025
f158ea3
Fix unexported type
skanderm Jan 28, 2025
1d12fe6
Redirect to bout show page on create
skanderm Jan 29, 2025
9ec418a
Update spectrogram component colors
skanderm Jan 29, 2025
3360090
Update feed segment downloads in global setup to be more generic
skanderm Feb 24, 2025
f5ae781
Add timeline extensions for spectrogram
skanderm Feb 25, 2025
7030ba0
Remove unnecessary useMemos for const lists
skanderm Feb 25, 2025
2ac8115
Change codegen to output enums as objects for better type safety and DX
paulcretu Feb 25, 2025
22ac3e0
Refactor audioCategories types
paulcretu Feb 25, 2025
363e2d4
Fix scrolling out of bounds
skanderm Feb 25, 2025
98897fe
Remove unused image times
skanderm Feb 25, 2025
f0ec5cb
Make spectrogram gen action in AudioImage synchronous
skanderm Feb 26, 2025
0dd3349
Fix CodeRabbit suggestions - closing intervals, adding loading/saving…
skanderm Feb 27, 2025
eb49e09
Merge branch 'main' into skander/new-bout
skanderm Feb 27, 2025
1b56c1f
Adrian playground (#787)
adrmac Mar 6, 2025
e67238c
Revert "Adrian playground (#787)" (#794)
adrmac Mar 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion server/config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ config :orcasite, Oban,
repo: Orcasite.Repo,
# 7 day job retention
plugins: [{Oban.Plugins.Pruner, max_age: 7 * 24 * 60 * 60}],
queues: [default: 10, email: 10, feeds: 10]
queues: [default: 10, email: 10, feeds: 10, audio_images: 5]

config :spark, :formatter,
remove_parens?: true,
Expand Down
2 changes: 2 additions & 0 deletions server/config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,5 @@ config :ex_aws,
config :orcasite,
audio_image_bucket: System.get_env("ORCASITE_AUDIO_IMAGE_BUCKET", "dev-audio-viz"),
audio_image_bucket_region: System.get_env("ORCASITE_AUDIO_IMAGE_BUCKET_REGION", "us-west-2")

config :orcasite, :env, :dev
2 changes: 2 additions & 0 deletions server/config/prod.exs
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,5 @@ else
config :hammer,
backend: {Hammer.Backend.ETS, [expiry_ms: 60_000 * 60 * 4, cleanup_interval_ms: 60_000 * 10]}
end

config :orcasite, :env, :prod
4 changes: 3 additions & 1 deletion server/config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ if System.get_env("PHX_SERVER") do
config :orcasite, OrcasiteWeb.Endpoint, server: true
end

config :orcasite, :prod_host, System.get_env("HOST_URL", "live.orcasound.net")

if config_env() == :prod do
database_url =
System.get_env("DATABASE_URL") ||
Expand Down Expand Up @@ -49,7 +51,7 @@ if config_env() == :prod do
You can generate one by calling: mix phx.gen.secret
"""

host = System.get_env("HOST_URL") || "live.orcasite.com"
host = System.get_env("HOST_URL") || "live.orcasound.net"
port = String.to_integer(System.get_env("PORT") || "4000")

if System.get_env("FEED_STREAM_QUEUE_URL", "") != "" do
Expand Down
2 changes: 2 additions & 0 deletions server/config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,5 @@ config :ex_aws,
:instance_role
],
region: "us-west-2"

config :orcasite, :env, :test
1 change: 0 additions & 1 deletion server/lib/orcasite/accounts/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ defmodule Orcasite.Accounts.User do
code_interface do
domain Orcasite.Accounts

define :register_with_password
define :sign_in_with_password
define :by_email, args: [:email]
define :request_password_reset_with_password
Expand Down
59 changes: 59 additions & 0 deletions server/lib/orcasite/global_setup.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ defmodule Orcasite.GlobalSetup do
|> case do
{:ok, %{timestamps: [_ | _] = timestamps}} ->
timestamp = List.last(timestamps)

{:ok, feed_stream} =
Orcasite.Radio.FeedStream
|> Ash.Changeset.for_create(:create, %{feed: feed, playlist_timestamp: timestamp})
Expand All @@ -34,4 +35,62 @@ defmodule Orcasite.GlobalSetup do
:ok
end
end

def populate_latest_feed_streams(feed, minutes_ago \\ 10) do
now = DateTime.utc_now()
from_time = now |> DateTime.add(-minutes_ago, :minute)
populate_feed_streams_range(feed, from_time, now)
end

def populate_feed_streams_range(feed, from_time, to_time) do
if Application.get_env(:orcasite, :env) != :prod do
# Get prod feed id for feed
{:ok, feed_resp} = Orcasite.Radio.GraphqlClient.get_feed(feed.slug)

feed_resp
|> get_in(["data", "feed", "id"])
|> case do
nil ->
{:error, :feed_not_found}

feed_id ->
# Get stream for the last `minutes` minutes
{:ok, feed_streams_response} =
Orcasite.Radio.GraphqlClient.get_feed_streams_with_segments(
feed_id,
from_time,
to_time
)

feed_streams = get_in(feed_streams_response, ["data", "feedStreams", "results"])

feed_streams
|> Recase.Enumerable.convert_keys(&Recase.to_snake/1)
|> Enum.map(fn feed_stream ->
feed_stream
|> Map.drop(["id"])
|> Map.put("feed", feed)
|> Map.update(
"feed_segments",
[],
&Enum.map(&1, fn seg ->
seg
|> Map.drop(["id"])
|> Map.put("feed", feed)
|> Recase.Enumerable.atomize_keys()
end)
)
|> Recase.Enumerable.atomize_keys()
end)
|> Ash.bulk_create(
Orcasite.Radio.FeedStream,
:populate_with_segments,
return_errors?: true,
stop_on_error?: true,
upsert?: true,
upsert_identity: :playlist_m3u8_path
)
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ defmodule Orcasite.Notifications.Workers.SendNotificationEmail do
end
|> Enum.filter(&(&1.id != notification_id))

:ok = continue?()
:ok = Orcasite.RateLimiter.continue?("ses_email", 1_000, 14)

%{meta: params}
|> Map.merge(%{
Expand Down Expand Up @@ -95,14 +95,4 @@ defmodule Orcasite.Notifications.Workers.SendNotificationEmail do
end)
end

def continue?() do
case Hammer.check_rate("ses_email", 1_000, 14) do
{:allow, _count} ->
:ok

{:deny, _limit} ->
Process.sleep(250)
continue?()
end
end
end
97 changes: 52 additions & 45 deletions server/lib/orcasite/radio/audio_image.ex
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,18 @@ defmodule Orcasite.Radio.AudioImage do
actions do
defaults [:read, :destroy, create: :*, update: :*]

read :for_feed do
argument :feed_id, :string, allow_nil?: false

pagination do
offset? true
countable true
default_limit 100
end

filter expr(feed_id == ^arg(:feed_id))
end

create :for_feed_segment do
upsert? true
upsert_identity :unique_audio_image
Expand Down Expand Up @@ -170,55 +182,50 @@ defmodule Orcasite.Radio.AudioImage do
require_atomic? false
change set_attribute(:status, :processing)

change after_action(
fn _change, image, _context ->
# Only one feed segment at a time for now
[feed_segment] = image |> Ash.load!(:feed_segments) |> Map.get(:feed_segments)

timeout = :timer.minutes(2)

Task.Supervisor.async_nolink(
Orcasite.TaskSupervisor,
fn ->
%{
image_id: image.id,
audio_bucket: feed_segment.bucket,
audio_key: feed_segment.segment_path,
image_bucket: image.bucket,
image_key: image.object_path
}
|> Orcasite.Radio.AwsClient.generate_spectrogram()
|> case do
{:ok, %{file_size: image_size}} ->
image
|> Ash.Changeset.for_update(:update, %{
status: :complete,
image_size: image_size
})
|> Ash.Changeset.force_change_attribute(:last_error, nil)
|> Ash.update(authorize?: false)

{:error, error} ->
image
|> Ash.Changeset.for_update(:update, %{
status: :failed
})
|> Ash.Changeset.force_change_attribute(:last_error, inspect(error))
|> Ash.update(authorize?: false)
end
end,
timeout: timeout
)

{:ok, image}
end,
prepend?: true
)
change after_action(fn _change, image, _context ->
# Only one feed segment at a time for now
[feed_segment] = image |> Ash.load!(:feed_segments) |> Map.get(:feed_segments)

%{
image_id: image.id,
audio_bucket: feed_segment.bucket,
audio_key: feed_segment.segment_path,
image_bucket: image.bucket,
image_key: image.object_path
}
|> Orcasite.Radio.AwsClient.generate_spectrogram()
|> case do
{:ok, %{file_size: image_size}} ->
image
|> Ash.Changeset.for_update(:update, %{
status: :complete,
image_size: image_size
})
|> Ash.Changeset.force_change_attribute(:last_error, nil)
|> Ash.update(authorize?: false)

{:error, error} ->
image
|> Ash.Changeset.for_update(:update, %{
status: :errored
})
|> Ash.Changeset.force_change_attribute(:last_error, inspect(error))
|> Ash.update(authorize?: false)
end
end)
end

update :set_failed do
change set_attribute(:status, :failed)
end
end

graphql do
type :audio_image
attribute_types [feed_id: :id]
attribute_types feed_id: :id

queries do
list :audio_images, :for_feed
end
end
end
2 changes: 1 addition & 1 deletion server/lib/orcasite/radio/aws_client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ defmodule Orcasite.Radio.AwsClient do
)
|> ExAws.request(
http_opts: [recv_timeout: :timer.minutes(2)],
retries: [max_attempts: 1]
retries: [max_attempts: 3]
)
|> case do
{:ok, %{"image_size" => image_size, "sample_rate" => sample_rate}} ->
Expand Down
58 changes: 53 additions & 5 deletions server/lib/orcasite/radio/bout.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ defmodule Orcasite.Radio.Bout do
attributes do
uuid_attribute :id, prefix: "bout", public?: true

attribute :start_time, :utc_datetime_usec, public?: true
attribute :start_time, :utc_datetime_usec, public?: true, allow_nil?: false
attribute :end_time, :utc_datetime_usec, public?: true
attribute :duration, :decimal, public?: true
attribute :ongoing, :boolean, public?: true

attribute :category, Orcasite.Types.AudioCategory do
public? true
allow_nil? false
end

create_timestamp :inserted_at
Expand All @@ -36,11 +36,15 @@ defmodule Orcasite.Radio.Bout do
relationships do
belongs_to :created_by_user, Orcasite.Accounts.User

belongs_to :feed, Orcasite.Radio.Feed
belongs_to :feed, Orcasite.Radio.Feed do
public? true
end

has_many :bout_feed_streams, Orcasite.Radio.BoutFeedStream

many_to_many :feed_streams, Orcasite.Radio.FeedStream do
through Orcasite.Radio.BoutFeedStream
public? true
end
end

Expand All @@ -63,7 +67,7 @@ defmodule Orcasite.Radio.Bout do
end

actions do
defaults [:read, :update, :destroy]
defaults [:read, :destroy]

read :index do
pagination do
Expand All @@ -74,11 +78,35 @@ defmodule Orcasite.Radio.Bout do

argument :feed_id, :string

filter expr(if not is_nil(^arg(:feed_id), do: feed_id == ^arg(:feed_id)), else: true)
filter expr(if not is_nil(^arg(:feed_id)), do: feed_id == ^arg(:feed_id), else: true)
end

create :create do
primary? true
accept [:category, :start_time, :end_time]

argument :feed_id, :string, allow_nil?: false

change fn changeset, _ ->
changeset
|> Ash.Changeset.manage_relationship(
:feed,
%{id: Ash.Changeset.get_argument(changeset, :feed_id)},
type: :append
)
end

change fn changeset, _ ->
end_time = Ash.Changeset.get_argument_or_attribute(changeset, :end_time)
start_time = Ash.Changeset.get_argument_or_attribute(changeset, :start_time)

if start_time && end_time do
changeset
|> Ash.Changeset.change_attribute(:duration, DateTime.diff(end_time, start_time, :millisecond) / 1000)
else
changeset
end
end

change fn
changeset, %{actor: %Orcasite.Accounts.User{} = actor} ->
Expand All @@ -89,17 +117,37 @@ defmodule Orcasite.Radio.Bout do
changeset
end
end

update :update do
primary? true
accept [:category, :start_time, :end_time]

change fn changeset, _ ->
end_time = Ash.Changeset.get_argument_or_attribute(changeset, :end_time)
start_time = Ash.Changeset.get_argument_or_attribute(changeset, :start_time)

if start_time && end_time do
changeset
|> Ash.Changeset.change_attribute(:duration, DateTime.diff(end_time, start_time, :millisecond) / 1000)
else
changeset
end
end
end
end

graphql do
type :bout
attribute_types [feed_id: :id, feed_stream_id: :id]

queries do
list :bouts, :index
get :bout, :read
end

mutations do
create :create_bout, :create
update :update_bout, :update
end
end
end
Loading
Loading