hzeller / gmrender-resurrect

Resource efficient UPnP/DLNA renderer, optimal for Raspberry Pi, CuBox or a general MediaServer. Fork of GMediaRenderer to add some features to make it usable.
GNU General Public License v2.0
841 stars 204 forks source link

Jellyfin Support #217

Closed diabl0w closed 2 years ago

diabl0w commented 3 years ago

Jellyfin is a FOSS fork of Emby/Plex Media Server... it can serve audio files over DLNA. For devices such as my samsung TV, when I send media files, it is able to fully control the TV including queuing tracks, change volume, skip tracks, seek through tracks, play/pause/stop etc. However, when I point it to one of my many raspberry pi's running gmrender-resurrect, I can only play songs, change volume, and play pause... but I cannot do anything else like seek through the tracks or skip the currently playing track to the next one in the queue.

I am not sure if this is a jellyfin or gmrender issue, but jellyfin does have an advanced config section for adding compatibility to DLNA devices via "profiles". It matches via a manufacturer id among other things which I was able to find. Can you tell me maybe what other things I can add to this advanced config to make these other features work? An example of possible options you can see in this samsung TV config: https://github.com/jellyfin/jellyfin/blob/master/Emby.Dlna/Profiles/SamsungSmartTvProfile.cs. Things like extra headers

hzeller commented 3 years ago

This sounds probably more like a question for jellyfin then ? If you want to debug a particular issue e.g. with skipping, maybe create a logfile and see what goes wrong in that case.

diabl0w commented 3 years ago

Thanks for the fast response, and I apologize for the duplicate issue. I am not sure if this is a jellyfin or gmrender issue, I really dont know much about the behind the scenes, I am just a user. I have attached logs from the jellyfin side as well as the gmrender side.

Exactly what I did while these logs were happening was: Add a playlist to the queue and start playing, skip to the next track, skip to the next track, stop playback, disconnect.

What happened in real life was: the queue was added and started playing, the track was skipped in the user interface and showed the next track playing, but in reality gmrender just kept playing the same song. Same thing happened for the next song .... and the stop and disconnect worked properly.

Side note: when skipping tracks and the same track keeps actually playing in real life, the UI shows that the new track is playing from the same time position that the last track was skipped at. Meaning if I skipped a track that was 12 seconds into progress, the user interface then showed the next track playing at 12...13...14 seconds. I am assuming that is because gmrender is still feeding back the playback progress of the previous song.

jellyfin.txt gmrender.txt

diabl0w commented 3 years ago

These logs should be better, use them instead: gmrender2.txt jellyfin2.txt

I am not really seeing anything going on with the logs, except maybe in the jellyfin logs it looks like maybe some kind of error is occuring during the skips and then jellyfin quickly disconnects and reconnects to gmrender? but I am honestly not sure

mill1000 commented 3 years ago

Based on the log from gmrender it looks like Jellyfin only changes the media URI, but does not issue a "play" command so gmrender does not play the new file.

In comparison, my control point of choice stops playback, changes the uri, and then issues a play command.

I'd have to review the UPNP spec to know for sure, but it appears that Jellyfin isn't issuing the correct sequence of commands,

I stand corrected. It may be our end. http://upnp.org/specs/av/UPnP-av-AVTransport-v1-Service.pdf

2.4.1. SetAVTransportURI

.... image

mill1000 commented 3 years ago

If you've got the time to debug, I've got a branch here: https://github.com/mill1000/gmrender-resurrect/tree/issues/217_jellyfin you could try.

I've made a minor change to load the URI into the player if a SetAVTransportURI is received will the device is already playing.

This might fix your issues with skipping tracks.

Ra72xx commented 3 years ago

Is there any progress on this? I use both Jellyfin and Emby, and always run into this problem. Makes it impossible to stream more than one song at once...

mill1000 commented 3 years ago

@Larx could you give the branch I linked a try? It that fixes the issue I can submit a PR

Ra72xx commented 3 years ago

Thanks for the quick reply. After posting my question I found another approach for my problem (streaming client for Emby/Jellyfin in order to connect to bluetooth speakers). I now have been successfull using Mopidy as the bridge between media server and speakers, which also gives a bit more control. So currently my question has been settled otherwise ;-).

hzeller commented 3 years ago

If it turns out that Jellyfin does not use the protocol correctly, we should probably upstream a fix there.

On Tue, Mar 16, 2021, 07:38 Tucker Kern @.***> wrote:

@Larx https://github.com/Larx could you give the branch I linked a try? It that fixes the issue I can submit a PR

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/hzeller/gmrender-resurrect/issues/217#issuecomment-800315126, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABCNCNVKGKHTT23SSWIEI3TD5UN5ANCNFSM4SRPSNJQ .

mijofa commented 2 years ago

I gave @mill1000's branch a try and it does solve the problem with skipping tracks.

The real problem I'm having with it is that gmediarender doesn't seem to send status updates back to Jellyfin. So when Jellyfin sends a pause signal gmediarender will actually pause, but Jellyfin thinks the media is still playing.

Seems like the same thing is causing weirdness when seeking through the media, I'll seek to say 50% in, the seek works but Jellyfin's progress continues from the beginning, but then I'll seek back to 25% in and it seeks correctly, but it seems gmediarender then sends a status update of the previous position as Jellyfin then shows progress from the 50% position of the previous seek. [UPDATE 1: It does get seek updates, but it takes ~20 seconds (far too long in my opinion) pause state does not update though] Is there a way for me to increase the upnp/dlna debugging of gmediarender? I'd like to see more info on what signals are being sent, but the only log-level arguments seem to be for gstreamer's log levels.

I'm not super confident in my C skills (Python dev normally) but this at least seems like it might be a simple fix which I'll try and take a look at once I figure out how this code is structured.

UPDATE 2: I've done some digging around using Wireshark to watch the network traffic. I think the issue with seek updates not being reflected in Jellyfin is because Jellyfin is asking for an update quicker than gmediarender has actually updated the seeked time, so it's effectively:

  1. JF: Hey gmrender, seek to 50%
  2. GM: Ok, gimme a sec
  3. JF: Hey gmrender, where are you seeked to?
  4. GM: 1%
  5. GM: Ok done

Jellyfin also checks in asking for an update every 15-30 seconds or so, just to make sure everything is where it expects. That's why I'm seeing the seek update get to Jellyfin eventually. Not sure why the pause state isn't working, I can definitely see gmediarender sending "PAUSED_PLAYBACK" to the GetTransportInfo immediately after the pause command. Jellyfin also seems to stop doing the regular "check-in" every 15-30 seconds, so that might be a bug entirely on Jellyfin's end, but I'm not so sure about that yet.

UPDATE 3: Found this in the code at the seek function in upnp_transport.c. Sounds like it's exactly what I've run into:

// TODO(hzeller): Seeking might take some time,
// pretend to already be there. Should we go into
// TRANSITION mode ?
// (gstreamer will go into PAUSE, then PLAYING)
mijofa commented 2 years ago

Hrmm, is the output_gstreamer_seek function supposed to return -1 on success, or failure?

I believe that if statement is backwards as it's currently returning '-1' on success, and '0' on failure, which is the opposite of what I normally expect, and by the looks of things the opposite of what the seek function in upnp_transport seems to expect. I think that is the main thing causing my problem, swapping them around makes things a lot smoother, not perfect, but a heck of a lot better.

Minor sidenote: Using gst_element_seek_simple would make that function a bit simpler, and easier to parse, given it's not making use of any of the extra things seek does which seek_simple doesn't anyway.

UPDATE: And for a final touch, replacing the "PAUSED_PLAYBACK" string with "PausedPlayback" lets Jellyfin properly recognise when the media is paused (also worked as "PAUSEDPLAYBACK"). So either Jellyfin or gmrender is probably breaking the DLNA spec there, not sure which, my bet's on Jellyfin there though. I'll ask around in their IRC channel on Monday

mill1000 commented 2 years ago

I agree that output_gstreamer_seek looks like it returns the incorrect value. Probably just a copy-paste bug since other output module functions typically have the form of

if (gst_element_do_something() == GST_STATE_CHANGE_FAILURE)
  return -1;

I can make the change in my branch and create a PR.

According to the spec I attached above "PAUSED_PLAYBACK" is the correct form. image

mijofa commented 2 years ago

Thanks for making the Jellyfin issue, I was happy to do a PR for all the Jellyfin fixes, wanted to get 1 last thing solved first though.

When Jellyfin tries to "Continue watching" a media file from half way through, gmrender doesn't seek to that half way mark. But if I press stop then start it again from the beginning, gmrender starts it back where it should have the first time. I haven't done much further investigation into this, I actually suspect they're 2 separate issues but haven't done enough testing to have any more evidence than a gut feeling there. First being that Jellyfin might be telling it to seek before gmrender is ready. Second being that gmrender isn't properly closing the player object when stopping the media playback, so it's still remember the previous state such as seek time despite not properly seeking to it previously.

mill1000 commented 2 years ago

Oh well you're more than welcome to. I can close the one I just opened. Just let me know what you want to do.

I wonder if Jellyfin might be sending the "seek" command first, and then the "play" command. Not sure exactly what order gstreamer needs. A review of this might be in order: https://gstreamer.freedesktop.org/documentation/application-development/advanced/queryevents.html?gi-language=c

mijofa commented 2 years ago

You're all good, stick with yours I'll do another if necessary. :)

Yeah that was my thinking, haven't done any packet sniffing yet to confirm that though as I'm away for the weekend. If that is the case, it's might be another issue for Jellyfin, although a workaround here could be to cache the seek instruction until gstreamer starts playing. That's fairly hacking though. Will investigate, thanks for the help! :)

This should be the last thing I need to get rid of my near decade old home made media system

mijofa commented 2 years ago

First issue is that the play() function is returning before gstreamer has actually started playing the stream. This is because gst_element_set_state(...) is triggering an async change and we're supposed to wait for a "GST_MESSAGE_STATE_CHANGED" message before reporting that we're in playback state. I've bodged together a hacky fix for this but it's a wait-loop rather than figuring out the async stuff. I'm attaching it here as a patch for documentation purposes, but I'm not currently happy enough with it to make a PR because it really should be doing async stuff.

I can't reproduce the 2nd half of that anymore, so I guess my suspicion of it being 2 separate issues was probably incorrect.

mijofa commented 2 years ago

I've been using that messy patched version the past couple days and it's working well. Couple general video issues I've run into that I should probably spawn off into a separate issue once I've done some extra investigation:

mijofa commented 2 years ago

After many hours of trying to parse the XMl, I determined that a) I don't know what I'm doing, and b) It won't really work anyway because Jellyfin (at least) sends all subtitle streams in that metadata, and gives no indication of what should/shouldn't be enabled.

Also ran into an entirely unrelated issue in Jellyfin's design that means this probably can't work for me the way I wanted it to anyway. So I probably won't be working on it much more