jellyfin / jellyfin-chromecast

Chromecast Client for Jellyfin
https://jellyfin.org
GNU General Public License v2.0
131 stars 39 forks source link

Casting from Jellyfin server from behind reverse proxy #110

Open SteveDinn opened 3 years ago

SteveDinn commented 3 years ago

This isn't really a bug, but a call for advice. I logged this bug in the main Jellyfin repo and got a depressing answer. I had also logged a feature request that I linked to from a comment on that bug, that probably has a very slim chance of getting implemented.

https://github.com/jellyfin/jellyfin/issues/4917

Can anyone suggest a course of action for me that will allow me to cast locally-hosted media from Jellyfin?

TL/DR: Chromecasts have hard-coded DNS servers, and are about to deprecate playing media from non-HTTPS sources, and my ISP doesn't allow NAT loopback, so I can't play my locally-hosted media on my Chromecasts.

joshuaboniface commented 3 years ago

my ISP doesn't allow NAT loopback

This is a function of your router, not your ISP. Any properly-made and properly-configured router can do NAT reflection (NAT loopback) just fine.

That said, yes, the Chromecast is a giant pain in the ass if the Chromecast is able to actually communicate directly with the Jellyfin instance without being forced through the proxy. I can think of 3 good ways to work around this:

  1. Have the Jellyfin instance listen only on 127.0.0.1 (localhost) and have the proxy on the same machine. This will prevent the Chromecast from attempting to communicate directly with it over HTTP.
  2. Have both on different networks and block access via a firewall of port 8096 from the Chromecast network to the Jellyfin network. This is the solution I use and it works fine, but requires at least 2 networks in your setup so not a universal option.
  3. Enable HTTPS in Jellyfin itself, so that the Chromecast is able to connect to it via HTTPS. You can still have the reverse proxy handle SSL termination and connect to the backend on port 8096 if you want, or you can disable HTTP listening entirely and have the Proxy->Jellyfin connection happen over HTTPS too.
anthonylavado commented 3 years ago

I feel like this should be on the original issue, but casting from an HTTP source has been disabled/known since 2019: https://github.com/jellyfin/jellyfin/issues/791#issuecomment-459935636

The ONLY exception is if you access something as localhost (e.g. http://localhost/blah). In your other issue, you mentioned services that worked on HTTP. Can you please share any examples?

anthonylavado commented 3 years ago

I should clarify - Google Chrome is what does not allow casting from an HTTP domain. If you used software (e.g. the Jellyfin Android app), then you can use HTTP. There is no limitation there. It is strictly when trying from Google Chrome, the browser.

SteveDinn commented 3 years ago

Now that you mention it only being Chrome that enforces this, I realized that the others are all using their specific apps. I guess that's how they work around this issue.

I stand by my opinion that anything on have same network should be able to cast without HTTPS. But I guess that's up to Google.

SteveDinn commented 3 years ago

Managed to replace my ISP-supplied router with a Ubiquiti EdgeRouter. I finally have NAT loopback! I think I won't have this problem any longer, but it still sucks for those who will.

hawken93 commented 3 years ago

Hi.

So far I have noticed two ways to get around this:

(5 minutes with anyones chromecast and you can take ownership of the device.. So secure platform..)

I think bug reports should be opened with google for this to be honest. And the DNS issue as well.

SteveDinn commented 3 years ago

This is unrelated to this bug; I've been meaning to write it up, but I'm not sure what to say yet.

I've noticed some differences in the android app and the website when casting using the nightly Chromecast app. When on the app, it starts casting properly, i.e., the Jellyfin logo appears, but it crashes almost immediately when attempting to play something. The same scenario using the website works much more often. If they're both truly using the same version of the Chromecast app, then I'm guessing something's up with the way the app tells it to play things.

neopc10 commented 3 years ago

When the move was made from the Cordova app it lost functionality. An Android sender needs to be added to the Kotlin app for casting to work.

gidoBOSSftw5731 commented 3 years ago

Just as an update, using the nightly version of chromecasting (in the player settings, from an android phone) works occasionally but I end up either using my android TV or Kodi (or kodi app on android tv when I need the codec support and my raspi is being unhappy). When I tested the full nightly systems 2-4 weeks ago they worked better but were far from perfect, and the nginx trick didnt help much either. This all being said, it looks like they are building the functionality to properly support reverse proxies.

DHandspikerWade commented 3 years ago

I'm also running into the same issue @SteveDinn.

While Steve able to replace their router, Bell Canada has removed that ability with their newest routers. The new ones cannot be replaced and have no bridge mode making it so adding another router to the mix forces everything on the network to be double-NAT'd.

Could there for the client to make a local DNS request or allow the sender specify an ip address? Feel like it would be messy, but for less technical users who's first impression may be trying to use Chromecast, it's better than saying "this software doesn't support your ISP".

ginger-tek commented 2 years ago

This appears to still be an issue. I have what seems to be a successful reverse proxy going to my JF server. I can access the server inside and outside my home network, and stream flawlessly on both Android and web browser.

But, I cannot cast to my Google Home Mini's from either Android or web browser clients; they both spit out the following error:

Your Google Cast receiver is unable to contact the Jellyfin server. Please check the connection and try again

This really bums me out, so hopefully something will be fixed soon, whether that's here or at Google :/

jameskimmel commented 2 years ago

Same. Reverse proxy, SSL, but Chromcast does not show up as a destination for Jellyfin on iOS. Airplay to AppleTV works.

ginger-tek commented 2 years ago

Same. Reverse proxy, SSL, but Chromcast does not show up as a destination for Jellyfin on iOS. Airplay to AppleTV works.

Oh, your issue sounds different. My minis actually show up in the cast list, and I can connect to them; i even hear the little jingle from the mini. But when I try to play something, I get the little error pop up.

anthonylavado commented 2 years ago

@jameskimmel Our iOS clients do not support Chromecast at this time. We hope to bring it back to at least our beta Swiftfin client when possible. If you need a workaround for a local server, you can use VLC and DLNA/UPNP for now. If you have a Chromecast with Google TV (the newest one), just install the Android TV app.

ginger-tek commented 1 year ago

Apparently, if you connect using IP, Chromecast works. If you connect using reverse proxy domain, it doesn't. Definitely a DNS issue, but still not sure how to fix it :/

Codecasaurus commented 1 year ago

Apparently, if you connect using IP, Chromecast works. If you connect using reverse proxy domain, it doesn't. Definitely a DNS issue, but still not sure how to fix it :/

You'll probably have to use DNAT to redirect the Chromecast's DNS requests (which are hardcoded to resolve via Google DNS) to your local resolver. I just went through this after finally being frustrated I couldn't cast Jellyfin :) (Previously you could just block the Chromecast's egress on port 53 and it would fallback to your internal resolver, but now this just breaks the Chromecast.)

Another potential solution would be to have a public DNS record for your jellyfin server that Google DNS can read and do some sort of hairpin NAT setup.

SteveDinn commented 1 year ago

You'll probably have to use DNAT to redirect the Chromecast's DNS requests (which are hardcoded to resolve via Google DNS) to your local resolver.

I tried this a while back, and it didn't work for me. The requests were successfully intercepted, but the Chromecasts freaked out and refused to do anything. I suspect they are somehow verifying their DNS servers are actually the servers they expect them to be.

ginger-tek commented 1 year ago

How was Emby doing it before the split? I wonder if older versions of open source Emby can still do it out of the box without any configuration, so maybe we could compare what is dfferent?

jameskimmel commented 1 year ago

Another potential solution would be to have a public DNS record for your jellyfin server that Google DNS can read and do some sort of hairpin NAT setup.

I don't think that is the case. My Jellyfin instance has a public DNS on noip and it does not work at my friends home.

It also did not work at my home where I override all Port 53 to my local pihole that has a dns override setting to point to the local instead of public IP. In my local home, this could be because of a cert error. For other clients like my laptop, I add my opnsense CA, but I don't think this is possible for chromecast. I don't own a chromecast anymore, so I can't further test this.

Codecasaurus commented 1 year ago

You'll probably have to use DNAT to redirect the Chromecast's DNS requests (which are hardcoded to resolve via Google DNS) to your local resolver.

I tried this a while back, and it didn't work for me. The requests were successfully intercepted, but the Chromecasts freaked out and refused to do anything. I suspect they are somehow verifying their DNS servers are actually the servers they expect them to be.

Do you have Google's public DNS servers as your upstream resolver? If not, give that a try.

The running theory on why Chromecasts hardcode Google DNS is for geolocation for Stadia services. The Chromecast wants to know what the closest Google data center is and they only expose that to/rely on their DNS for whatever reason. The Chromecast thinks it's still reaching out to Google DNS but not getting the response back it expects, thus "freaking out."

ginger-tek commented 1 year ago

Another potential solution would be to have a public DNS record for your jellyfin server that Google DNS can read and do some sort of hairpin NAT setup.

I don't think that is the case. My Jellyfin instance has a public DNS on noip and it does not work at my friends home.

It also did not work at my home where I override all Port 53 to my local pihole that has a dns override setting to point to the local instead of public IP. In my local home, this could be because of a cert error. For other clients like my laptop, I add my opnsense CA, but I don't think this is possible for chromecast. I don't own a chromecast anymore, so I can't further test this.

This is so bizarre. I loaded up the latest Emby release, and that works with my reverse proxy no problem. I haven't yet checked an older version of Emby from when it was open source, but I'm just confused on how JF is based on the same codebase, but it somehow lost a pretty crucial feature 🤔

jameskimmel commented 1 year ago

Do you have Google's public DNS servers as your upstream resolver? If not, give that a try.

At my friends house, yes. He has a very basic setting. No pihole nothing. Just a plain ISP modem that uses the ISP DNS Server. I think that alone should be enough and even if there is a hardcoded 8.8.8.8 server, this is not blocked by his setup.

gidoBOSSftw5731 commented 1 year ago

Another potential solution would be to have a public DNS record for your jellyfin server that Google DNS can read and do some sort of hairpin NAT setup.

I don't think that is the case. My Jellyfin instance has a public DNS on noip and it does not work at my friends home.

It also did not work at my home where I override all Port 53 to my local pihole that has a dns override setting to point to the local instead of public IP. In my local home, this could be because of a cert error. For other clients like my laptop, I add my opnsense CA, but I don't think this is possible for chromecast. I don't own a chromecast anymore, so I can't further test this.

I will say, chromecasts are a bit picky about HTTPS stuff, and arguably rightly so. If you can't use a real CA/Cert, the only way chromecasts will cooperate is being on a local net over http.

That being said, I too have an issue still (can confirm on all 100% latest when I get home but it was broken as of 10.8) and I use a pretty standard nginx proxy with let's encrypt cert.) Not 100% sure where the issue lies, and I find it interesting that apparently emby had this figured out... I can usually get casting to work by: Opening devtools Watching the video I want (helps if I need it to be transcoded) copy the main.m3u8 url use catt or chromecast-cli to play it

It's sometimes a little buggy but actually usually works.

ginger-tek commented 1 year ago

I will say, chromecasts are a bit picky about HTTPS stuff, and arguably rightly so. If you can't use a real CA/Cert, the only way chromecasts will cooperate is being on a local net over http.

But then how is Emby doing it? For the most part, they're in the same boat as JF configuration wise, yet casting works with a reverse proxy using a let's encrypt cert 🤔

Also, didn't know there was a CLI for chromecast, that's pretty cool!

gidoBOSSftw5731 commented 1 year ago

I will say, chromecasts are a bit picky about HTTPS stuff, and arguably rightly so. If you can't use a real CA/Cert, the only way chromecasts will cooperate is being on a local net over http.

But then how is Emby doing it? For the most part, they're in the same boat as JF configuration wise, yet casting works with a reverse proxy using a let's encrypt cert thinking

I mean, I kind of doubt emby can cast with a self-signed cert? That's a chromecast thing, not a JF thing. Maybe it just uses http? If someone could run a pcap on that it could be enlightening.

Also, didn't know there was a CLI for chromecast, that's pretty cool!

They're all unofficial but it's great for bodging something together when the family is all waiting on you to "fix the tv" on movie night.

jameskimmel commented 1 year ago

I mean, I kind of doubt emby can cast with a self-signed cert? That's a chromecast thing, not a JF thing. Maybe it just uses http? If someone could run a pcap on that it could be enlightening.

We have to be careful to not mix different stuff together. I get that I can't use a local CA. That is not surprising and probably impossible to change.

What I don't get and what I think is the root problem is this:

Why does Chromecast not work from remote, despite me having a public DNS A and AAAA record and a valid lets encrypt cert?

SteveDinn commented 1 year ago

Could the solution be as simple as having the Jellyfin server detect if it and the Chromecast are on the same local network? This way, even if the JF server is accessed from behind a reverse proxy with an SSL cert, we could still decide to build the receiver's source address using HTTP and an internal IP address, rather than HTTPS and the DNS name.

We might want to build this behind an option with a warning because it's not cool to circumvent somebody's HTTPS without at least letting them know that's going to happen. But because this will make Chromecast playback work in situations where it was previously unable to, I think it's a valid tradeoff.

jameskimmel commented 1 year ago

Could the solution be as simple as having the Jellyfin server detect if it and the Chromecast are on the same local network? This way, even if the JF server is accessed from behind a reverse proxy with an SSL cert, we could still decide to build the receiver's source address using HTTP and an internal IP address, rather than HTTPS and the DNS name.

Easy solution for that problem in my opinion would be to use an AAAA record. That way the public and private IP are the same and you don't need complicated workarounds.

gidoBOSSftw5731 commented 1 year ago

A few things

Easy solution for that problem in my opinion would be to use an AAAA record. That way the public and private IP are the same and you don't need complicated workarounds.

I have full IPv6 setup, doesn't currently work (the setup is everything uses the same domain, it has the ipv6 that routes to the main nginx proxy, and v4 is handled by a second nginx proxy on a line with v4 and v6, proxied back to the main nginx proxy)

"detect if it's on the local network"

While this could work, I don't see why you'd want to discriminate against remote vs local chromecasts, nor why you'd need to. I don't know what fancy crap JF is trying to do, but I'd propose massively simplifying it to more-or-less the instructions I used for my bodge-job (but automated in whatever app, ofc)

And, in case it wasn't clear, my chromecasts are equally nonfuctional both locally and remotely.

SteveDinn commented 1 year ago

I don't see why you'd want to discriminate against remote vs local chromecasts

Purely because this is the reason why some people can't play media from behind a reverse proxy. If they don't have loopback NAT, and can't get their CC's DNS lookups to find their JF server properly, it seems as though their only recourse is to use HTTP from a local IP address.

ginger-tek commented 1 year ago

I don't see why you'd want to discriminate against remote vs local chromecasts

Purely because this is the reason why some people can't play media from behind a reverse proxy. If they don't have loopback NAT, and can't get their CC's DNS lookups to find their JF server properly, it seems as though their only recourse is to use HTTP from a local IP address.

Correct me if I'm wrong, but a NAT loop back is just a local DNS on your router that overrides the external lookup to point directly to a 192.168.x.x, correct?

SteveDinn commented 1 year ago

Correct me if I'm wrong, but a NAT loop back is just a local DNS on your router that overrides the external lookup to point directly to a 192.168.x.x, correct?

No, that's split DNS. NAT Loopback (or hairpin) allows the actual external IP to be accessed from within your network. Here's a discussion about one vs. the other. https://www.reddit.com/r/eero/comments/6we6er/hairpin_nat_what_is_it_good_for/

ginger-tek commented 1 year ago

Correct me if I'm wrong, but a NAT loop back is just a local DNS on your router that overrides the external lookup to point directly to a 192.168.x.x, correct?

No, that's split DNS. NAT Loopback (or hairpin) allows the actual external IP to be accessed from within your network. Here's a discussion about one vs. the other. https://www.reddit.com/r/eero/comments/6we6er/hairpin_nat_what_is_it_good_for/

Ah, so I've not tried that yet, thought I did. Thanks!

jameskimmel commented 1 year ago

I think I have tried that by using an AAAA record? But then again, it does not work from remote either, where no hairpin nor split DNS is needed :)

gidoBOSSftw5731 commented 1 year ago

My understanding is that the issue stems from the server not knowing its own external address, so it tries to send clients to 192.168.x.x or maybe even localhost, but remote clients can't necessarily access that. JF has a lot of issues regarding reverse proxies and I really don't get why, or why it wasn't just built around them in the first place (it's a pretty good default to put any web thing behind one anyway, especially when it comes to https cert management/perms.)

ginger-tek commented 1 year ago

Yeah, especially since Emby is doing it just fine, and that's also typically run behind a proxy. I feel you are correct that it's definitely something up with JF itself, and that it's just not sending the right info to the casting API, causing the error.

I may take some time to try and setup a local dev environment of the server to see what would need to be changed, but not too sure where to start (relatively new to .NET Core).

DHandspikerWade commented 1 year ago

I would worry that it's going to be a waste of your time unless you want to become the maintainer.

The only one PR to got reviewed in the last year. There hasn't been a release in almost 3 years. Depend-bot is closing it's own PRs as out of date. Anthony and the subreddit have been recommending to use the Android TV app for the newer Chromecasts.

It's pretty fair to say the Chromecast client is abandoned.

ginger-tek commented 1 year ago

Well that's just disheartening. I know not everyone uses casting, but it's a pretty crucial feature compared to the competition. Just sucks that at the very least there isn't more thorough or clear documentation on how exactly a reverse proxy should work to have both local and external support for casting without having hack together something, or have to switch between 192.168.x.x when on the couch and jellyfin.mydomain.com when on the road :|

nielsvanvelzen commented 1 year ago

It's pretty fair to say the Chromecast client is abandoned.

Please see this comment, the client is not dead but the maintainer is busy with other stuff.

There hasn't been a release in almost 3 years.

Because the master branch is not stable enough for a release, you can still use it by flipping a switch in the web client though.

Also, most of the fixes related to chromecast connection issues would never end up in this repository, those bugs need to be fixed in the clients that start the casting (jellyfin-web, jellyfin-android etc.) or the server.

DHandspikerWade commented 1 year ago

I know not everyone uses casting, but it's a pretty crucial feature compared to the competition

I completely agree.

Similar to my earlier comments, I believe it is a poor user experience especially when Chromecast is assumed to be the simplest client and used as a first impression.

Users have no reason to not believe it'll work so they are sent on a wild goose chase to fix it. By default the Android app tries to use the "stable" Chromecast client, which cannot connect to Jellyfin as of server version 10.8.0. On iOS, the cast button doesn't even find the Chromecast.

Please see this comment, the client is not dead but the maintainer is busy with other stuff.

Even in that comment, YouKnowBlom recommends the Android TV version. It's hard to argue to the project isn't abandoned when even the maintainer doesn't think it's usable and recommends to look else where. Also doing so on a PR waiting on review since January 2021 and had been earmarked for the first release the client.

neopc10 commented 1 year ago

The unstable channel, accessible through a setting on the web client, leads to the master branch of the Jellyfin-Chromecast which Blom started to bring over to post-2014 Chromecast APIs (the CAF). Blom went inactive and development died at that time.

Hawken came along months later with his Misc Fixes PR that made the CAF/unstable channel useable but could not be tested by the wider community/people who didn't pay $5 to Google to put up their own Chromecast server running the code. Hawken went inactive a few weeks after nobody with merging permissions engaged. Development died again. Several months after that, automerges of dependencies were enabled and the unstable branch became unloadable.

It's pretty dead.

edit: Maybe it can be salvaged at least a bit through https://developers.google.com/cast/docs/android_tv_receiver

PrplHaz4 commented 1 year ago

Well that's just disheartening. I know not everyone uses casting, but it's a pretty crucial feature compared to the competition. Just sucks that at the very least there isn't more thorough or clear documentation on how exactly a reverse proxy should work to have both local and external support for casting without having hack together something, or have to switch between 192.168.x.x when on the couch and jellyfin.mydomain.com when on the road :|

I believe at one point in time, PublishedServerURL was used to signal the URI value external services should reference (for situations where the server needs to identify where content should be found (like when accessing via https through a proxy from outside the docker network). Not sure on current state, but I believe that was the intention...

That one value was driving the following:

YouKnowBlom commented 1 year ago

There is fair criticism here. I have been very inactive and that's something I do regret.

Even in that comment, YouKnowBlom recommends the Android TV version. It's hard to argue to the project isn't abandoned when even the maintainer doesn't think it's usable and recommends to look else where.

That's not quite what I meant. Since I started developing the receiver my entertainment setup has been upgraded, and to match the capabilities of the rest, so has my media player. Don't get me wrong, I love what my chromecast has done for me but at the end of the day it's entry level hardware with limited support for codecs. That's why I have been using the Android TV app as my primary player. This does not take away from the fact that I daily drove the receiver during the days I was the most active. The reason I became inactive was not because I suddenly became too comfortable with the ATV app. Instead I simply could not justify spending the little time I had left over.

I hope to become more active again, and maybe getting #107 merged will be the first sign of that. Sadly only time will tell as I cannot say how busy I will be in the future. With that said it's easier for me to set aside time to review PRs than it is to sit down to write code, so anyone that can contribute, please do.

ginger-tek commented 1 year ago

I am glad to see some discussion regarding potential development/progress. Tbh, as long as someone is willing to do PR review and merges, I think that is enough for the community to step in and help do the code work. I am willing to help pitch in to get this working again if someone will be around to merge 👍

ginger-tek commented 1 year ago

So, I spent some time actually looking at the Chromecast API for Web, and I was able to mock up a simple implementation just to get a Jellyfin stream actually playing on my Google Home Mini using my public reverse proxy domain pointing to my local JF server. Well, I was able to do just that in less time that I thought. Not only does the stream URI work as intended for my proxy domain, but I also tried using my local IP and port 8096, which also worked as expected.

Now, this doesn't really say what JF is doing differently, as I think my rudimentary test and the current repo is like comparing apples and oranges atm. Nevertheless, I was able to get it working rather easily just by passing the universal stream URI for the item to the loadMedia method, and it instantly started playing on my mini.

Make note I did use the DEFAULT_MEDIA_RECEIVER_APP_ID to standup my instance of the browser API client, so idk if that makes much of a difference compared to a registered Chromecast app. If anyone knows anything about that, chime in.

Again, not sure what requirements are being met by the current repo's design, but it does surprise me a bit that just flinging the universal URI at the API worked. Is the current repo intended to be run on the casting device itself? Does all the work of sending the correct data to the API actually occur on e.g. my home mini and not in the browser?

gidoBOSSftw5731 commented 1 year ago

but it does surprise me a bit that just flinging the universal URI at the API worked.

See my previous reply: https://github.com/jellyfin/jellyfin-chromecast/issues/110#issuecomment-1337478141

Not totally sure what they're doing that makes this so broken when the simple answer just works

ginger-tek commented 1 year ago

I agree. I've taken some more time to polish my example code, so I might start looking into exactly what is going on in JF that isn't working. Hopefully by understanding how it should work will point out what isn't, and maybe work towards a refactor

ginger-tek commented 1 year ago

Forgot to respond; here's my example app showcasing functional casting directly from the JF server API regardless of hostname: https://jellycast.gingertek.repl.co

BloodyIron commented 1 year ago

Hey so pretty sure this is relevant/maybe helpful to this topic : https://github.com/jellyfin/jellyfin/issues/8457#issuecomment-1668745260

:)

jameskimmel commented 1 year ago

Hey @BloodyIron thanks for the link.

This could help for some people on local networks, but I am using Chromecast from remote, with public DNS A and AAAA record that 8.8.8.8 can resolve and a valid lets encrypt cert.

Anyway I basically have given up on Chromecast and moved on to AppleTV.