navidrome / navidrome

🎧☁️ Modern Music Server and Streamer compatible with Subsonic/Airsonic
https://www.navidrome.org
GNU General Public License v3.0
10.35k stars 797 forks source link

Jukebox: set audio output to server local output device #364

Closed certuna closed 6 months ago

certuna commented 3 years ago

Currently the web UI allows clients to navigate the library, but always streams to the client browser and outputs the sound there. Feature request: add the option (speaker icon on the bottom) to output the sound on the server instead.

deluan commented 3 years ago

This is the implementation of the Jukebox mode I talk about in other issues (ex: #250), but didn't have an open issue for it. I'll keep this open to track the Jukebox work.

bitva77 commented 3 years ago

for what it's worth, Jukebox mode would be amazing.

I'd love to be able to have my server playing musically locally (goes to an FM transmitter) that I control remotely but if I could also stream my music as well to my phone for when I'm remote....it would really be a game changer (for me :)

going back into my hole now...

deluan commented 3 years ago

@bitva77, why not use a Subsonic client on your phone?

bitva77 commented 3 years ago

Oh I do and it works for one or the other. I can use two different clients to get that effect when using Airsonic (for example).

I'm wondering if using MPD for jukebox mode and then Navidrome (*sonic) for streaming is really the best option at the moment. All MPD clients pretty much suck. What you've done with Navidrome is really nice and ticks off most feature boxes :)

To play Spotify in jukebox mode would be the ultimate as I could then get my wife using all this as well (mopidy comes close but it's buggy).

I'm also wondering if a combination of pulse and jack may be an option to get the sound going everywhere. Need to research that some more as well

Sanskar95 commented 3 years ago

Hi I have ample knowledge of web dev that will be required for this project Can I pick some open issue to get familiar with the codebase?

jvoisin commented 3 years ago

Sure

crisclacerda commented 2 years ago

More than jukebox mode, IMHO this should be modeled after logitech media server, which has the paradigm of 'control from anywhere, play from anywhere'. The react web UI should be able not only control the 'jukebox client' but actually any connected client to the server.

For what is worth here is a minimal go subsonic client that could be used for this. https://github.com/wildeyedskies/stmp

@deluan has any work been done on this? Would you have the time and interest to mentor me in this project?

deluan commented 2 years ago

More than jukebox mode, IMHO this should be modeled after logitech media server, which has the paradigm of 'control from anywhere, play from anywhere'. The react web UI should be able not only control the 'jukebox client' but actually any connected client to the server.

Hey @crisclacerda , yes, thats the plan.

And yes, this contribution would be awesome, ping me on Discord and we can chat about this.

gitbreaker222 commented 2 years ago

hello, today I got Navidrome running on my pi3 and I'd like to join this topic :headphones:

Implementing the frontend part to select the jukebox mode and control the subsonic client – gsoc

In the past month I tested kodi again and had a look at the web-ui Chorus 2. Maybe it serves as a good demo, on how the UI could be presented:

kodi webui chorus 2

1: The Playlist-Panel has two tabs. Remote (blue) and Local (red). Switching target sets the highlight color 2: The Player shows the active song + controls for the selected target 3: An additional visual indicator shows by highlight color what mode is active. This can prevent accidentally changing remote playback when intention is to act locally

A similar strategy should work with Navidrome - what do you think?

I have 5+ yrs hypertext frontend exp. React, Angular :unamused: , Svelte :relaxed:; See you in discord ^^

crisclacerda commented 2 years ago

That's awesome. I also run navidrome on a pi3. I also have a pi1 that runs squeezeplay quite smoothly. So with LMS, I can make a neat multi-room setup. That go client I mentioned above should also do the trick. Maybe we can have a tab for each player @gitbreaker222 ?

gitbreaker222 commented 2 years ago

I'll try getting the development environment running upcoming weekend. The documentation looks exceptionally well, so I'm confident that it will be fun and successful :smiley: If not, I'll get easily distracted and will replay 6 months later ^^

Hukuma1 commented 2 years ago

Any updates on this? Or any builds to test this feature?

TomDakan commented 2 years ago

I'm also interested to know if there are any updates on this. I'd be interested in working on it if other folks haven't been able to get to it. @deluan do you know what the status is?

myfairx commented 1 year ago

Hi, today I test navidrome on my nas and works nicely. I am however looking for MoodeAudio replacement that can play music locally preferably using attached dac so I can control music from my phone or tablet. Moode audio also have some Impressive parametric and convolver plugin which I like a lot. however, they are primarily intended for RPi. Navidrome looks promising. looking forward to this jukebox function. TQ

enigmaoftech commented 1 year ago

I'm running Navidrome on my docker host. I use a subsonic client on my phone while I'm on the go but I am looking at doing whole house audio with raspberry pi's with DAC hats. Need a way to have some type of client that can be controlled and synced for different zones. It looks like this thread is working on something like this. If there are any suggestions on how to accomplish this please let me know.

enigmaoftech commented 1 year ago

Plexamp has a nice app and a raspberry pi install that you can control. Looking to do this same thing with the freedom of Navidrome instead of Plex.

TemplarB commented 1 year ago

I wanted to jump in to say that streaming from a server is a very interesting option for me and I'll be glad iff it is released. I use Navidrome on a Docker in my Synology NAS - initially, Synology had its own player/organizer that streamed via USB but since version 7 of their OS they cut off all USB support (there are informal methods to add serial USB drivers back but not audio). Actually, that's how I went seeking for alternatives and after several unsuccessful tries ended up with Navidrome on PC and playing via PC USB on DAC - not the best solution but workable

chiefy commented 1 year ago

Just chiming in here because I haven't seen much talk of Snapcast which is what I am currently using in my home to play from my NAS via Mopidy. I do really love the Navidrome UI though, IMO it's a bit cleaner than some of the Mopidy front-ends (no disrespect to those projects).

Anyhow, this weekend I was messing around with transcoding commands and got Navidrome to successfully output to a pipe file located on my nas which streamed to the snapserver. Unfortunately because of how the UI works you can't pause/stop the stream and when you select another song to play the stream gets muxed all weird.

ms140569 commented 1 year ago

I would propose to create a plugin-system here to register various output systems and a moniker-scheme to address the system in the configuration file.

Something like:

alsa:hw:0,0 osx:Cambridge Audio USB 1.0 Audio Out win:BEHRINGER UMC 202HD 192k mpd:soundserver.exmple.com:6600

There must be a level of indirection to avoid hardcoding the first sound-output-device.

RollingStar commented 1 year ago

Perhaps relevant, another *sonic compatible (paid) app is doing it: https://support.symfonium.app/t/wiki-symfonium-api-allow-control-from-other-apps-like-tasker/643

deluan commented 1 year ago

Perhaps relevant, another *sonic compatible (paid) app is doing it: https://support.symfonium.app/t/wiki-symfonium-api-allow-control-from-other-apps-like-tasker/643

Not really relevant, Symfonium is a Subsonic client, not a server as Navidrome. Both actually works well together, but what Synfonium is implementing is something else, not related to what we call "jukebox mode" in the context of Subsonic applications.

GGORG0 commented 1 year ago

More than jukebox mode, IMHO this should be modeled after logitech media server, which has the paradigm of 'control from anywhere, play from anywhere'. The react web UI should be able not only control the 'jukebox client' but actually any connected client to the server.

I have my Navidrome server running on a Raspberry Pi 4 NAS (on Docker) and I'd like to have another Raspberry Pi zero W connected to some speakers. It would probably run some kind of headless client and output audio via the audio jack of a USB dongle. It would be cool to control it from the webui of Navidrome (just like things like Spotify Connect or LMS).

For now, I think the only option is to create a subsonic client with its own webui or control an existing terminal client via ssh.

deluan commented 8 months ago

Merged! For those waiting for this, please let me know if you bump into any issues, thanks for the patience :)

stratosgear commented 8 months ago

Hmmm, I pulled the latest deluan/navidrome:develop docker image, but I do not see anywhere how to activate the jukebox mode. Also checked the https://github.com/navidrome/navidrome/pull/2289 merge request but it's only code changes, no help files edited... :(

How do I try this out? (Btw, I'm running on a PI4) Thanks!!!

bitva77 commented 8 months ago

it got merged but doesn't look like a new build happened

sabatmonk commented 8 months ago

Downloaded develop, but i do not see a change. Also, is there a timeline for a new latest?

afontenot commented 8 months ago

From what I can tell with some testing and looking at the code, much of the backend work for a jukebox mode was done, but it's not really complete yet. A partial list of issues:

jukebox.enabled = true
jukebox.devices = [["default", "default", "default"]]
jukebox.default = "default"

Unfortunately, I have no idea what appropriate values for a device would be. Since navidrome is just using mpv under the hood, I kind of hoped it would work with no configuration.

I edited the user endpoint to enable the jukebox role and recompiled navidrome, but it doesn't play any music in jukebox mode (probably unsurprising).

@deluan if I've accurately described the state of this feature, could we get this issue re-opened until some of the problems are fixed? Apologies if I've misunderstood something, I know I'm tinkering with brand new work here but I really wanted to get it working.

deluan commented 8 months ago

Thanks for the review!

  • It's not implemented in the web UI yet.

And won't be there any time soon, unfortunately. I need to finish the new UI, and adding Jukebox support to it is not a priority ATM. The feature will be available only from Subsonic clients for now.

  • It's not available to any clients because the getUser endpoint returns a hardcoded "jukeboxRole": false. Clients I've tested (e.g. Ultrasonic) interpret this as jukebox mode being unavailable or not allowed for the user. This appears to be correct behavior according to the spec.

Ops, my bad. Will fix that tonight!

  • It's not clear how to use the configuration

@ms140569 is working on a documentation page, maybe he can post some examples here?

@deluan if I've accurately described the state of this feature, could we get this issue re-opened until some of the problems are fixed? Apologies if I've misunderstood something, I know I'm tinkering with brand new work here but I really wanted to get it working.

Thanks for the feedback. I'll reopen until we get a doc page (or a reply from @ms140569 with some examples)

deluan commented 8 months ago
  • It's not available to any clients because the getUser endpoint returns a hardcoded "jukeboxRole": false. Clients I've tested (e.g. Ultrasonic) interpret this as jukebox mode being unavailable or not allowed for the user. This appears to be correct behavior according to the spec.

Fixed in 2cd43581

afontenot commented 8 months ago

Thanks for the very quick response @deluan!

I've been digging a little further and found some more issues.

  1. The jukebox code doesn't really seem to use the device configuration for anything important, other than as internal labels. I think it would be a good idea to have navidrome pick good default values for this and not crash if the user doesn't set them in the config file.

This means I was wrong about the configuration being the reason the jukebox didn't play anything in my tests. The real reason is a combination of two additional issues:

  1. The jukebox doesn't seem to receive a start request from Ultrasonic in a format that it likes. Sending action=start with curl seems to work. I'll have to dig into this further at some point. Once the play queue has been started manually with curl, everything seems to work OK from Ultrasonic. (Weirdly, it works even after restarting navidrome. Not sure what's happening here.)

  2. The code fails to launch mpv when you have a MPVPath set in your config. I figured something complicatedly wrong was happening here, but then I discovered this: https://github.com/navidrome/navidrome/blob/master/core/playback/mpv/mpv.go#L113

if conf.Server.MPVPath != "" {
    mpvPath = conf.Server.FFmpegPath
    mpvPath, mpvErr = exec.LookPath(mpvPath)
}

I think you want conf.Server.MPVPath there?

  1. I see a panic when changing albums with Ultrasonic. This occurs during the Skip action, and the first few lines of traceback follow:
 -> github.com/navidrome/navidrome/core/playback.(*PlaybackDevice).Skip
 ->   /home/user/navidrome/core/playback/device.go:161

    github.com/navidrome/navidrome/server/subsonic.(*Router).JukeboxControl
      /home/user/navidrome/server/subsonic/jukebox.go:85
    github.com/navidrome/navidrome/server/subsonic.(*Router).routes.func16.h.func1
      /home/user/navidrome/server/subsonic/api.go:205
    github.com/navidrome/navidrome/server/subsonic.hr.func1
      /home/user/navidrome/server/subsonic/api.go:212
    net/http.HandlerFunc.ServeHTTP
afontenot commented 8 months ago

Okay, a little addition. I think I understand the cause of the panic. It happens when you've just reset the play queue with a set action and a song id, and then you skip to the song you just added. The code looks like this:

if index != pd.PlaybackQueue.Index {
        if pd.ActiveTrack != nil {
                pd.ActiveTrack.Close()
                pd.ActiveTrack = nil
        }

        err := pd.switchActiveTrackByIndex(index)
        if err != nil {
                return pd.getStatus(), err
        }
}

err := pd.ActiveTrack.SetPosition(offset)
if err != nil {
        log.Error(ctx, "error setting position", err)
        return pd.getStatus(), err
}

The logic here is that if the request asks us to change the index, we set a new active track, and if this fails, we bail out early. If we aren't changing the index, then we ought to be able to change the playhead (offset), since a track is presumably already active.

The problem with this reasoning is that if we've rewritten the queue with set but haven't started playing yet, we don't have an active track.

I think this fix should be reasonable:

if index != pd.PlaybackQueue.Index && pd.ActiveTrack != nil {
        pd.ActiveTrack.Close()
        pd.ActiveTrack = nil
}

if pd.ActiveTrack == nil {
        err := pd.switchActiveTrackByIndex(index)
        if err != nil {
                return pd.getStatus(), err
        }
}

err := pd.ActiveTrack.SetPosition(offset)
if err != nil {
        log.Error(ctx, "error setting position", err)
        return pd.getStatus(), err
}

Happy to send in a PR if that would help.


It's worth noting that Ultrasonic seems to send a set action with no id, probably to clear to the queue, right before calling set again with id to add an album. The first request seems to fail harmlessly, causing a log entry in Navidrome. It's not obvious to me why they do this, I might want to chase this down with Ultrasonic at some point.

ms140569 commented 8 months ago

@afontenot Thanks for your valuable feedback! I was pretty busy in the last time. I'm working on bugfixes and an overhaul of the configuration. We changed to mpv playback, but this is no at all reflected in the config file syntax. Besides this, I'm seeing a couple of bugs when running on an rpi4.

To keep things short: I'm working on it, bear with me.

ms140569 commented 8 months ago

State of the union: I'm facing bugs/different behaviour in the MPV-IPC package we are using:

https://github.com/dexterlb/mpvipc

This worked without any flaws on MacOSX, but shows a quite different behaviour and timing on Linux. I've tested on x86_64 and aarch64 (rpi4).

Could someone please doublecheck on Windows?

ms140569 commented 7 months ago

Okay, a little addition. I think I understand the cause of the panic. It happens when you've just reset the play queue with a set action and a song id, and then you skip to the song you just added. The code looks like this:


if index != pd.PlaybackQueue.Index {

        if pd.ActiveTrack != nil {

                pd.ActiveTrack.Close()

                pd.ActiveTrack = nil

        }

        err := pd.switchActiveTrackByIndex(index)

        if err != nil {

                return pd.getStatus(), err

        }

}

err := pd.ActiveTrack.SetPosition(offset)

if err != nil {

        log.Error(ctx, "error setting position", err)

        return pd.getStatus(), err

}

The logic here is that if the request asks us to change the index, we set a new active track, and if this fails, we bail out early. If we aren't changing the index, then we ought to be able to change the playhead (offset), since a track is presumably already active.

The problem with this reasoning is that if we've rewritten the queue with set but haven't started playing yet, we don't have an active track.

I think this fix should be reasonable:


if index != pd.PlaybackQueue.Index && pd.ActiveTrack != nil {

        pd.ActiveTrack.Close()

        pd.ActiveTrack = nil

}

if pd.ActiveTrack == nil {

        err := pd.switchActiveTrackByIndex(index)

        if err != nil {

                return pd.getStatus(), err

        }

}

err := pd.ActiveTrack.SetPosition(offset)

if err != nil {

        log.Error(ctx, "error setting position", err)

        return pd.getStatus(), err

}

Happy to send in a PR if that would help.


It's worth noting that Ultrasonic seems to send a set action with no id, probably to clear to the queue, right before calling set again with id to add an album. The first request seems to fail harmlessly, causing a log entry in Navidrome. It's not obvious to me why they do this, I might want to chase this down with Ultrasonic at some point.

Youre patch did the trick, thanks! It will be included in my PR with other fixes and configfile cleanups.

ms140569 commented 7 months ago

https://github.com/navidrome/navidrome/pull/2554

afontenot commented 7 months ago

@ms140569 Suggestion: id should be treated as an optional parameter for the set operation. set is just clear followed by an add for each id, so not providing an id should be equivalent to clear.

I realize this may seem pointless since clear already exists, but Ultrasonic appears to actually use this instead of clear, because it handles all playlist operations in the same way (by building an internal lists of songs and then calling set). Unless you think it is (or ought to be) inconsistent with the spec to call clear without an id?

Also: I reported the issue with Ultrasonic failing to send the start command. https://gitlab.com/ultrasonic/ultrasonic/-/issues/1266

daredoes commented 6 months ago

Would it be possible to update the documentation with how to link this to snapcast via MPV? https://www.navidrome.org/docs/usage/jukebox/ https://github.com/badaix/snapcast/blob/develop/doc/player_setup.md#mpv

Also linking the meta_mopidy.py file for sending metadata to snapcast if anyone wants to take a stab at making one for Navidrome

https://github.com/badaix/snapcast/blob/develop/server/etc/plug-ins/meta_mopidy.py

MindrustUK commented 6 months ago

@daredoes I'm not sure it's trivial to get get MPV playing to Snapcast without further code mods in Navidrome. This probably needs to be it's own ticket about adding support for Snapcast as a separate feature to the Jukebox it's self.

From what I can see you'd need to;

To something like:

mpvComdTemplate "mpv --audio-display=no --pause %f --input-ipc-server=%s --audio-channels=stereo --audio-samplerate=48000 --audio-format=s16 --ao=pcm --ao-pcm-file=/var/run/snapcast/navidromefifo"

It's something I'm also interested in but I don't have the time to invest beyond investigating a proof of concept for now. It would also be nice to see Jukebox control from the main webui rather than a Subsonic client.

ms140569 commented 6 months ago

I was already musing about the idea to externalize the mpv shellout template into the configuration file. This is yet another use case. @deluan

Am So., 19. Nov. 2023 um 23:33 Uhr schrieb MindrustUK < @.***>:

@daredoes https://github.com/daredoes I'm not sure it's trivial to get get MPV playing to Snapcast without further code mods in Navidrome. This probably needs to be it's own ticket about adding support for Snapcast as a separate feature to the Jukebox it's self.

From what I can see you'd need to;

-

Add a special device class in Navidrome to handle;

To something like:

mpvComdTemplate "mpv --audio-display=no --pause %f --input-ipc-server=%s --audio-channels=stereo --audio-samplerate=48000 --audio-format=s16 --ao=pcm --ao-pcm-file=/var/run/snapcast/navidromefifo"

  • Then using the device handler mentioned above select this 'device' (Fifopipe or Socket) for playback. Looks like this happens in track.go

It's something I'm also interested in but I don't have the time to invest beyond investigating a proof of concept for now. It would also be nice to see Jukebox control from the main webui rather than a Subsonic client.

— Reply to this email directly, view it on GitHub https://github.com/navidrome/navidrome/issues/364#issuecomment-1817998938, or unsubscribe https://github.com/notifications/unsubscribe-auth/AACLJJPUH7CDHFPI5N5V3K3YFKCKLAVCNFSM4OEO6AZ2U5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TCOBRG44TSOBZGM4A . You are receiving this because you were mentioned.Message ID: @.***>

-- Matthias Schmidt Marienstraße 14

67346 Speyer

++49 6232 6842676 ++49 171 5767209

484C 1E1E 71B4 1389 900B 3C07 8901 C978 4D8F 66CF

Public Key: matthias-schmidt-gmail-com.asc https://www.dropbox.com/s/k4liuahxfzbveg7/matthias-schmidt-gmail-com.asc?dl=1

daredoes commented 6 months ago

I've found when using Mopidy with Snapcast, I get the best quality and least amount of errors when streaming the audio by having Mopidy run a TCP server, and Snapcast being the client. Could technically do this locally or over a public IP, which might be a better end product for Navidrome than modifying MPV

https://github.com/badaix/snapcast/blob/develop/doc/configuration.md#tcp-server

MindrustUK commented 6 months ago

@ms140569 +1 for moving the MPV shellout template into config. This adds some desirable functionality without having to recompile from source.

deluan commented 6 months ago

I was already musing about the idea to externalize the mpv shellout template into the configuration file. This is yet another use case. @deluan - what do you think?

I'm ok with that.

deluan commented 6 months ago

Closing this as we now have the Jukebox functionality released. Any requests for improvements should be open in Discussions and bugs should be reported as a new issue.

daredoes commented 2 months ago

Looking for buy in on the following discussion https://github.com/navidrome/navidrome/discussions/2875