From 214ec50f0ea00673f6f23d8be573e7e26f034f3e Mon Sep 17 00:00:00 2001 From: Chris McCord Date: Thu, 27 Jan 2022 13:03:42 -0500 Subject: [PATCH] WIP --- assets/js/app.js | 29 ++++++++++++++----- config/dev.exs | 4 ++- config/runtime.exs | 1 - lib/live_beats/media_library.ex | 5 ++-- lib/live_beats/media_library/song.ex | 9 +++++- lib/live_beats/mp3_stat.ex | 12 ++++++-- .../controllers/file_controller.ex | 16 +++++----- lib/live_beats_web/live/player_live.ex | 3 +- 8 files changed, 57 insertions(+), 22 deletions(-) diff --git a/assets/js/app.js b/assets/js/app.js index 4db38a1..d908069 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -4,6 +4,7 @@ import {LiveSocket} from "phoenix_live_view" import topbar from "../vendor/topbar" let nowSeconds = () => Math.round(Date.now() / 1000) +let rand = (min, max) => Math.floor(Math.random() * (max - min) + min) let execJS = (selector, attr) => { document.querySelectorAll(selector).forEach(el => liveSocket.execJS(el, el.getAttribute(attr))) @@ -96,9 +97,13 @@ Hooks.AudioPlayer = { mounted(){ this.playbackBeganAt = null this.player = this.el.querySelector("audio") + this.player.addEventListener("ended", () => console.log("player: ended")) + this.player.addEventListener("stalled", () => console.log("player: stalled")) + this.player.addEventListener("suspend", () => console.log("player: suspend")) + this.player.addEventListener("waiting", () => console.log("player: waiting")) this.playerDuration = 0 this.currentTime = this.el.querySelector("#player-time") - this.duration = this.el.querySelector("#player-duration") + this.durationText = this.el.querySelector("#player-duration") this.progress = this.el.querySelector("#player-progress") let enableAudio = () => { if(this.player.src){ @@ -123,7 +128,7 @@ Hooks.AudioPlayer = { if(currentSrc === url && this.player.paused){ this.play({sync: true}) } else if(currentSrc !== url) { - this.player.src = `${url}?token=${token}` + this.player.src = `${url}?token=${token}&proxy` this.play({sync: true}) } }) @@ -132,9 +137,15 @@ Hooks.AudioPlayer = { }, play(opts = {}){ + console.log("play") let {sync} = opts + clearInterval(this.progressTimer) + clearTimeout(this.nextTimer) this.player.play().then(() => { - if(sync){ this.player.currentTime = nowSeconds() - this.playbackBeganAt } + if(sync){ + console.log("sync", nowSeconds() - this.playbackBeganAt) + this.player.currentTime = nowSeconds() - this.playbackBeganAt + } this.progressTimer = setInterval(() => this.updateProgress(), 100) }, error => { if(error.name === "NotAllowedError"){ @@ -145,28 +156,32 @@ Hooks.AudioPlayer = { pause(){ clearInterval(this.progressTimer) + clearTimeout(this.nextTimer) this.player.pause() }, stop(){ clearInterval(this.progressTimer) + clearTimeout(this.nextTimer) this.player.pause() this.player.currentTime = 0 this.updateProgress() - this.duration.innerText = "" + this.durationText.innerText = "" this.currentTime.innerText = "" }, updateProgress(){ if(this.playerDuration === 0){ return false } if(Math.ceil(this.player.currentTime) >= Math.floor(this.playerDuration)){ - this.playerDuration = 0 - this.pushEvent("next_song_auto") clearInterval(this.progressTimer) + this.player.pause() + this.playerDuration = 0 + console.log("next_song_auto") + this.nextTimer = setTimeout(() => this.pushEvent("next_song_auto"), rand(1000, 3000)) return } this.progress.style.width = `${(this.player.currentTime / (this.playerDuration) * 100)}%` - this.duration.innerText = this.formatTime(this.playerDuration) + this.durationText.innerText = this.formatTime(this.playerDuration) this.currentTime.innerText = this.formatTime(this.player.currentTime) }, diff --git a/config/dev.exs b/config/dev.exs index 4f1093f..807da16 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -3,7 +3,9 @@ import Config config :live_beats, :files, uploads_dir: Path.expand("../priv/uploads", __DIR__), host: [scheme: "http", host: "localhost", port: 4000], - server_ip: "127.0.0.1" + server_ip: "127.0.0.1", + hostname: "localhost", + transport_opts: [] config :live_beats, :github, client_id: System.fetch_env!("LIVE_BEATS_GITHUB_CLIENT_ID"), diff --git a/config/runtime.exs b/config/runtime.exs index bbed52c..89eacf0 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -64,7 +64,6 @@ if config_env() == :prod do client_secret: System.fetch_env!("LIVE_BEATS_GITHUB_CLIENT_SECRET") config :libcluster, - debug: true, topologies: [ fly6pn: [ strategy: Cluster.Strategy.DNSPoll, diff --git a/lib/live_beats/media_library.ex b/lib/live_beats/media_library.ex index fa9392d..0e52082 100644 --- a/lib/live_beats/media_library.ex +++ b/lib/live_beats/media_library.ex @@ -138,7 +138,7 @@ defmodule LiveBeats.MediaLibrary do end def put_stats(%Ecto.Changeset{} = changeset, %MP3Stat{} = stat) do - chset = Song.put_duration(changeset, stat.duration) + chset = Song.put_stats(changeset, stat) if error = chset.errors[:duration] do {:error, %{duration: error}} @@ -423,10 +423,11 @@ defmodule LiveBeats.MediaLibrary do Song.changeset(song, attrs) end + @keep_changes [:duration, :mp3_filesize, :mp3_filepath] def change_song(%Ecto.Changeset{} = prev_changeset, attrs) do %Song{} |> change_song(attrs) - |> Ecto.Changeset.change(Map.take(prev_changeset.changes, [:duration])) + |> Ecto.Changeset.change(Map.take(prev_changeset.changes, @keep_changes)) end defp order_by_playlist(%Ecto.Query{} = query, direction) when direction in [:asc, :desc] do diff --git a/lib/live_beats/media_library/song.ex b/lib/live_beats/media_library/song.ex index 083ad4d..ac68750 100644 --- a/lib/live_beats/media_library/song.ex +++ b/lib/live_beats/media_library/song.ex @@ -19,6 +19,7 @@ defmodule LiveBeats.MediaLibrary.Song do field :mp3_url, :string field :mp3_filepath, :string field :mp3_filename, :string + field :mp3_filesize, :integer, default: 0 field :server_ip, EctoNetwork.INET belongs_to :user, Accounts.User belongs_to :genre, LiveBeats.MediaLibrary.Genre @@ -45,7 +46,13 @@ defmodule LiveBeats.MediaLibrary.Song do put_assoc(changeset, :user, user) end - def put_duration(%Ecto.Changeset{} = changeset, duration) when is_integer(duration) do + def put_stats(%Ecto.Changeset{} = changeset, %LiveBeats.MP3Stat{} = stat) do + changeset + |> put_duration(stat.duration) + |> Ecto.Changeset.put_change(:mp3_filesize, stat.size) + end + + defp put_duration(%Ecto.Changeset{} = changeset, duration) when is_integer(duration) do changeset |> Ecto.Changeset.change(%{duration: duration}) |> Ecto.Changeset.validate_number(:duration, diff --git a/lib/live_beats/mp3_stat.ex b/lib/live_beats/mp3_stat.ex index 1f788f5..a45abd5 100644 --- a/lib/live_beats/mp3_stat.ex +++ b/lib/live_beats/mp3_stat.ex @@ -8,7 +8,7 @@ defmodule LiveBeats.MP3Stat do use Bitwise alias LiveBeats.MP3Stat - defstruct duration: 0, path: nil, title: nil, artist: nil, tags: nil + defstruct duration: 0, size: 0, path: nil, title: nil, artist: nil, tags: nil @declared_frame_ids ~w(AENC APIC ASPI COMM COMR ENCR EQU2 ETCO GEOB GRID LINK MCDI MLLT OWNE PRIV PCNT POPM POSS RBUF RVA2 RVRB SEEK SIGN SYLT SYTC TALB TBPM TCOM TCON TCOP TDEN TDLY TDOR TDRC TDRL TDTG TENC TEXT TFLT TIPL TIT1 TIT2 TIT3 TKEY TLAN TLEN TMCL TMED TMOO TOAL TOFN TOLY TOPE TOWN TPE1 TPE2 TPE3 TPE4 TPOS TPRO TPUB TRCK TRSN TRSO TSOA TSOP TSOT TSRC TSSE TSST TXXX UFID USER USLT WCOM WCOP WOAF WOAR WOAS WORS WPAY WPUB WXXX) @@ -34,6 +34,7 @@ defmodule LiveBeats.MP3Stat do end def parse(path) do + stat = File.stat!(path) {tag_info, rest} = parse_tag(File.read!(path)) duration = parse_frame(rest, 0, 0, 0) @@ -44,7 +45,14 @@ defmodule LiveBeats.MP3Stat do seconds = round(duration) {:ok, - %MP3Stat{duration: seconds, path: path, tags: tag_info, title: title, artist: artist}} + %MP3Stat{ + duration: seconds, + size: stat.size, + path: path, + tags: tag_info, + title: title, + artist: artist + }} _other -> {:error, :bad_file} diff --git a/lib/live_beats_web/controllers/file_controller.ex b/lib/live_beats_web/controllers/file_controller.ex index 390ffb2..01e593a 100644 --- a/lib/live_beats_web/controllers/file_controller.ex +++ b/lib/live_beats_web/controllers/file_controller.ex @@ -8,18 +8,19 @@ defmodule LiveBeatsWeb.FileController do require Logger - def show(conn, %{"id" => filename_uuid, "token" => token}) do + def show(conn, %{"id" => filename_uuid, "token" => token} = params) do path = MediaLibrary.local_filepath(filename_uuid) mime_type = MIME.from_path(path) case Phoenix.Token.decrypt(conn, "file", token, max_age: :timer.minutes(1)) do - {:ok, %{uuid: ^filename_uuid, ip: ip}} -> - if local_file?(filename_uuid, ip) do + {:ok, %{vsn: 1, uuid: ^filename_uuid, ip: ip, size: size}} -> + # if local_file?(filename_uuid, ip) do + if !params["proxy"] do Logger.info("serving file from #{server_ip()}") do_send_file(conn, path) else Logger.info("proxying file to #{ip} from #{server_ip()}") - proxy_file(conn, ip, mime_type) + proxy_file(conn, ip, mime_type, size) end {:ok, _} -> @@ -38,10 +39,11 @@ defmodule LiveBeatsWeb.FileController do |> send_file(200, path) end - defp proxy_file(conn, ip, mime_type) do + defp proxy_file(conn, ip, mime_type, content_length) do uri = conn |> request_url() |> URI.parse() port = LiveBeatsWeb.Endpoint.config(:http)[:port] - path = uri.path <> "?" <> uri.query <> "&from=#{server_ip()}" + # path = uri.path <> "?" <> uri.query <> "&from=#{server_ip()}" + path = uri.path <> "?" <> String.replace(uri.query, "&proxy", "") <> "&from=#{server_ip()}" {:ok, ipv6} = :inet.parse_address(String.to_charlist(ip)) {:ok, req} = Mint.HTTP.connect(:http, ipv6, port, file_server_opts()) {:ok, req, request_ref} = Mint.HTTP.request(req, "GET", path, [], "") @@ -49,7 +51,7 @@ defmodule LiveBeatsWeb.FileController do conn |> put_resp_header("content-type", mime_type) |> put_resp_header("accept-ranges", "bytes") - |> put_resp_header("transfer-encoding", "chunked") + |> put_resp_header("content-length", IO.inspect(to_string(content_length))) |> send_chunked(200) |> stream(req, request_ref) end diff --git a/lib/live_beats_web/live/player_live.ex b/lib/live_beats_web/live/player_live.ex index d486dc5..9226c56 100644 --- a/lib/live_beats_web/live/player_live.ex +++ b/lib/live_beats_web/live/player_live.ex @@ -301,7 +301,8 @@ defmodule LiveBeatsWeb.PlayerLive do Phoenix.Token.encrypt(socket.endpoint, "file", %{ vsn: 1, ip: to_string(song.server_ip), - uuid: song.mp3_filename + size: song.mp3_filesize, + uuid: song.mp3_filename, }) push_event(socket, "play", %{