From 86f8228d69e9291513ea3b56b7aecbdbac87bc59 Mon Sep 17 00:00:00 2001 From: Romain Beauxis Date: Sat, 14 Dec 2024 16:19:04 +0100 Subject: [PATCH] Send last metadata when connecting to icecast. Fix last metadata. Fixes: #4262, #3906 --- CHANGES.md | 4 +++ src/core/outputs/icecast2.ml | 23 +++++++++++++-- src/core/source.ml | 3 +- tests/streams/dune.inc | 31 +++++++++++++++++++++ tests/streams/icecast_last_meta.liq | 43 +++++++++++++++++++++++++++++ 5 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 tests/streams/icecast_last_meta.liq diff --git a/CHANGES.md b/CHANGES.md index 989fc402c2..769f890787 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -18,11 +18,15 @@ Changed: `input.harbord` disabled when either `"artist"` or `"title"` is also passed. Add a configuration key to disable this mechanism. (#4235, #2676) +- `output.icecast` now re-sends the last metadata when connecting to the + remote server unless explicitly disabled using the `send_last_metadata_on_connect` + option (#3906) Fixed: - Fixed request resolution loop when enabling both `autocue` and `replaygain` metadata resolvers (#4245, fixed in #4246) +- Fixed source `last_metadata` not being properly updated (#4262) - Convert all ICY (icecast) metadata from `input.http` to `utf8`. --- diff --git a/src/core/outputs/icecast2.ml b/src/core/outputs/icecast2.ml index 4ca6d67172..112ce1713b 100644 --- a/src/core/outputs/icecast2.ml +++ b/src/core/outputs/icecast2.ml @@ -234,6 +234,12 @@ let proto frame_t = Some "Timeout for establishing network connections (disabled is negative)." ); + ( "send_last_metadata_on_connect", + Lang.bool_t, + Some (Lang.bool true), + Some + "Send the source's last metadata when connecting to the remote \ + icecast server." ); ( "timeout", Lang.float_t, Some (Lang.float 30.), @@ -341,6 +347,9 @@ class output p = let msg = Printexc.to_string error in Lang.to_float (Lang.apply on_error [("", Lang.string msg)]) in + let send_last_metadata_on_connect = + e Lang.to_bool "send_last_metadata_on_connect" + in let data = encoder_data p in let chunked = Lang.to_bool (List.assoc "chunked" p) in let protocol = @@ -603,7 +612,7 @@ class output p = try List.assoc "User-Agent" headers with Not_found -> Printf.sprintf "liquidsoap %s" Configure.version in - let source = + let handler = Cry.connection ~host ~port ~user ~password ?genre ?url ?description ~name ~public ~protocol ~mount ~chunked ~audio_info ~user_agent ~content_type:data.format () @@ -611,12 +620,20 @@ class output p = List.iter (fun (x, y) -> (* User-Agent has already been passed to Cry.. *) - if x <> "User-Agent" then Hashtbl.replace source.Cry.headers x y) + if x <> "User-Agent" then Hashtbl.replace handler.Cry.headers x y) headers; try - Cry.connect connection source; + Cry.connect connection handler; self#log#important "Connection setup was successful."; + (match (Lang.to_source source)#last_metadata with + | Some m when send_last_metadata_on_connect -> ( + try + self#insert_metadata + (Frame.Metadata.Export.from_metadata ~cover:false m) + with _ -> ()) + | _ -> ()); + (* Execute on_connect hook. *) on_connect () with diff --git a/src/core/source.ml b/src/core/source.ml index 8fe69c738d..7f5852000b 100644 --- a/src/core/source.ml +++ b/src/core/source.ml @@ -563,7 +563,8 @@ class virtual operator ?(stack = []) ?clock ?(name = "src") sources = "generate_frame: got metadata at position %d: calling handlers..." i; List.iter (fun fn -> fn m) on_metadata) metadata; - if has_track_mark then self#execute_on_track buf; + if has_track_mark then self#execute_on_track buf + else self#set_last_metadata buf; self#iter_watchers (fun w -> w.generate_frame ~start_time ~end_time ~length ~has_track_mark ~metadata); diff --git a/tests/streams/dune.inc b/tests/streams/dune.inc index 655060e424..daf97929f3 100644 --- a/tests/streams/dune.inc +++ b/tests/streams/dune.inc @@ -1022,6 +1022,37 @@ (:run_test ../run_test.exe)) (action (run %{run_test} huge-playlist.liq liquidsoap %{test_liq} huge-playlist.liq))) +(rule + (alias citest) + (package liquidsoap) + (deps + icecast_last_meta.liq + ./file1.mp3 + ./file2.mp3 + ./file3.mp3 + ./jingle1.mp3 + ./jingle2.mp3 + ./jingle3.mp3 + ./file1.png + ./file2.png + ./jingles + ./playlist + ./huge_playlist + ./replaygain_track_gain.mp3 + ./r128_track_gain.mp3 + ./replaygain_r128_track_gain.mp3 + ./replaygain_track_gain.opus + ./r128_track_gain.opus + ./replaygain_r128_track_gain.opus + ./without_replaygain_track_gain.mp3 + ./crossfade-plot.old.txt + ./crossfade-plot.new.txt + ../../src/bin/liquidsoap.exe + (package liquidsoap) + (:test_liq ../test.liq) + (:run_test ../run_test.exe)) + (action (run %{run_test} icecast_last_meta.liq liquidsoap %{test_liq} icecast_last_meta.liq))) + (rule (alias citest) (package liquidsoap) diff --git a/tests/streams/icecast_last_meta.liq b/tests/streams/icecast_last_meta.liq new file mode 100644 index 0000000000..ed78945a58 --- /dev/null +++ b/tests/streams/icecast_last_meta.liq @@ -0,0 +1,43 @@ +port = 6723 + +s = sine() + +s = insert_metadata(s) + +insert_metadata = s.insert_metadata + +output.dummy(s) + +thread.run( + delay=0.1, + { + insert_metadata( + [ + ( + "title", + "some title" + ) + ] + ) + } +) + +thread.run( + delay=0.3, {output.icecast(port=port, mount="metadata_test", %mp3, s)} +) + +i = input.harbor(buffer=2., port=port, "metadata_test") + +i = + source.on_metadata( + i, + fun (m) -> + if + m["title"] == + "some title" + then + test.pass() + end + ) + +output.dummy(fallible=true, i)