6

Called proxy with URL http://192.168.xx.xx:8080/3hyw7hwoajn21/HLSPlaylist.m3u8 Called proxy with URL http://192.168.xx.xx:8080/3hyw7hwoajn21/HLS_540_v4.m3u8 Called proxy with URL http://192.168.xx.xx:8080/3hyw7hwoajn21/HLS_AUDIO_160_K_v4.m3u8 Called proxy with URL http://192.168.xx.xx:8080/3hyw7hwoajn21/HLS_224_v4.m3u8

Here's an example Reddit video: https://www.reddit.com/r/me_irl/comments/b3vrs4/me_irl

Looking through the JSON, it has a few options for video sources:

"reddit_video": {
    "dash_url": "https://v.redd.it/3hyw7hwoajn21/DASHPlaylist.mpd",
    "duration": 76,
    "fallback_url": "https://v.redd.it/3hyw7hwoajn21/DASH_720?source=fallback",
    "height": 720,
    "hls_url": "https://v.redd.it/3hyw7hwoajn21/HLSPlaylist.m3u8",
    "is_gif": false,
    "scrubber_media_url": "https://v.redd.it/3hyw7hwoajn21/DASH_240",
    "transcoding_status": "completed",
    "width": 1280
}

While I seemingly can get other HLS/m3u8 videos to work with the Chromecast SDK (for example Google's own example HLS video), I cannot seem to get any of these sources to work.

I've tried https://v.redd.it/3hyw7hwoajn21/HLSPlaylist.m3u8 with the stream type set to both "live" or "buffered", I've tried the content type as "application/x-mpegURL", and I've tried the same for the dash URL https://v.redd.it/3hyw7hwoajn21/DASHPlaylist.mpd with content type "application/dash+xml" also to no avail. I found this question that seems to indicate some possibility?

I've also noticed with the DASH file there's a separate video and audio stream (https://v.redd.it/3hyw7hwoajn21/DASH_720 and https://v.redd.it/3hyw7hwoajn21/audio) worst case scenario is there a way to play the video stream with the separate audio stream playing too on the Chromecast?

Is it not possible for the Chromecast to play these video types?

UPDATE

Jesse and aergistal suggested that it has to do with the lack of CORS headers. I built a custom receiver app to be able to get better debugging logs, and this was indeed (the first) issue; Chromecast complains about CORS.

Using nginx on I built a local reverse proxy that adds all the CORS headers, then I give Chromecast that proxy URL instead and this CORS error went away.

However, using the HLS/m3u8 link it still wouldn't stream. Now it complains of the following:

[cast.player.hls.PackedAudioParser] Neither ID3 nor ADTS header was found at 0

and

[cast.player.api.Host] error: cast.player.api.ErrorCode.NETWORK/315

and

[cast.receiver.MediaManager] Load metadata error: Error

Full log:

enter image description here

Which causes it to still not play. Any ideas?

Adding the CORS issue allows the DASHPlaylist.mpd variant to load (it wouldn't before), which is great, but not so great at the same time because the reverse proxy requires you to download the entire response first, and where the DASH URL is just an entire MP4 (whereas the HLS is byte ranges) it means the reverse proxy has to download the entire DASH video first before showing it, which takes ages compared to the HLS.

So it'd still be optimal to get the HLS working due to speed, but is it just doomed not to work due to a playback issue on the Chromecast?

Doug Smith
  • 29,668
  • 57
  • 204
  • 388
  • Maybe the DASH would be worth a try. https://developers.google.com/cast/docs/mpl/streaming_protocols. –  Mar 23 '19 at 18:45
  • @Jesse What do you mean exactly? I tried "application/dash+xml" as the content type when using the DASH stream URL as noted above, should I be doing something different? – Doug Smith Mar 23 '19 at 20:59
  • Meh, looks like you would need to build a receiver for the dash. That seems like a lot of work for Thanos, lol. –  Mar 24 '19 at 01:27
  • @Jesse I kind of want to build a video app for Reddit so it's basically for the entire Reddit video platform not just this one Thanos video. What do you mean by a receiver app? How would that change things? I don't think you can have two active media streams going even with a receiver app can you? – Doug Smith Mar 24 '19 at 02:47
  • Well, is actually what it says, or that’s what it was changed to for the screenshot? It should be a real web address. –  Mar 27 '19 at 20:23

2 Answers2

7

Solution for HLS with separate audio tracks


Based on the information from the latest log there is a mismatch between the chosen segment format and actual format used in the stream. The stream uses AAC in MPEG-TS whereas the Cast SDK tries to parse it as packed audio.

A reply on the Cast issue tracker shows that the HlsSegmentFormat defaults to MPEG2_TS if the stream is multiplexed and MPEG_AUDIO_ES otherwise.

The suggested solution for the CAF receiver is to intercept the load requests and override the segment format with loadRequestData.media.hlsSegmentFormat = cast.framework.messages.HlsSegmentFormat.TS. A slightly modified example:

<html>
<head>
</head>
<body>
  <cast-media-player id="player"></cast-media-player>
  <script type="text/javascript" src="//www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js">
  </script>
  <script>
    const context = cast.framework.CastReceiverContext.getInstance();
    const playerManager = context.getPlayerManager();
    // intercept the LOAD request
    playerManager.setMessageInterceptor(
        cast.framework.messages.MessageType.LOAD, loadRequestData => {
            loadRequestData.media.hlsSegmentFormat = cast.framework.messages.HlsSegmentFormat.TS;
            return loadRequestData;
        });
    context.start();
  </script>
</body>
</html>

Original source

Yet another example


Solution for CORS


The Google Cast reference gives you the solution:

If you're having problems playing streams on a Cast device, it may be an issue with CORS. Use a publicly available CORS proxy server to test your streams

The problem with the publicly available proxies is that they enforce a size limit due to bandwidth concerns, so make your own or use an open-source one. If the app runs on a mobile device you can also make it a local server.

The current streams are not protected by DRM. This will get more complicated or outright impossible later if they add CDN authentication or protect the streams with DRM.


Regarding the CORS headers you must make sure preflight requests are supported: the client might send an OPTIONS first to check CORS support (including allowed methods and headers).

HTTP range requests must also be supported for your streams meaning the appropriate headers must be authorized and exposed.

Example preflight request configuration from https://enable-cors.org:

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,POST,OPTIONS
Access-Control-Allow-Headers: DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range
Access-Control-Expose-Headers: Content-Length,Content-Range


You will need to allow at least: GET, OPTIONS, the Content-Type and Range headers, and expose Content-Length,Content-Range. Remove duplicate headers if provided by the remote server.

aergistal
  • 29,947
  • 5
  • 70
  • 92
  • Proxy would work, but if you were to make this available to everyone on Reddit, wouldn’t you would be killing someone’s server and Reddit’s server with traffic? It seems like you would potentially have 2x the chance of getting blacklisted at some point. If Reddit has capacity to support Chromecast, couldn’t they just add the headers themselves to their servers? –  Mar 25 '19 at 20:13
  • @aergistal Could you run a CORS proxy locally rather than requiring a hosted-server one (it is on a mobile device indeed)? It seems tricky because those m3u8 files just link out to a bunch of different files so you'd have to proxy those as well. Any idea how? I'd have no idea how to even start a proxy on a mobile device – Doug Smith Mar 25 '19 at 21:07
  • @Jesse the proxy becomes the "client", the CDN sees the same amount of traffic. There are other reasons why they wouldn't like it and it's probably covered in the legal agreements. – aergistal Mar 25 '19 at 21:14
  • @Doug-Smith you need an embedded web server for your required platform. The playlists/manifests must be rewritten to point to your local server. – aergistal Mar 25 '19 at 21:23
  • @aergistal Okay, so I added an embedded web server. Tried with the working Google URL in the original question, it streamed to the Chromecast properly, and I confirmed with Charles that all the requests were going over the local server rather than the Google server. But as soon as I tried that with the Reddit URL, it still didn't work, and I again confirmed all traffic was going over the local server. I even can see in the response header the `Access-Control-Allow-Origin: *` I added. Is there another header I need to add? – Doug Smith Mar 26 '19 at 00:55
  • @DougSmith Can you play the proxied stream on another device on the same subnet? For HLS do you see the main playlist, variant playlists and the segment HTTP range requests passing through? It would help if you could update your question and add a sample request/response trace showing the headers, for example for one of the segments (`.ts`). – aergistal Mar 26 '19 at 07:54
  • @DougSmith and yes there are other headers that might be needed, for example `Access-Control-Allow-Headers: Range` and `Access-Control-Expose-Headers: Content-Length,Content-Range` since it needs to do those range requests. Inspect the request headers and methods and check out https://enable-cors.org/. Before the GET it might do an OPTIONS request to so make sure you forward that one too. – aergistal Mar 26 '19 at 08:09
  • @DougSmith just did a quick test using Nginx as a proxy and was able to stream the HLS to Chrome/JWPlayer on Windows which was not working previously by adding all the CORS headers and removing any origin duplicates. – aergistal Mar 26 '19 at 08:41
  • It requires an origin header as well I believe, that's a CORS thing. However, I'll still stick with my original ethical answer. Possible, but not recommended. https://corsproxy.github.io/ –  Mar 26 '19 at 13:42
  • @Jesse `Origin` is a request header and in this case it doesn't matter at all since the proxy is the one returning `Access-Control-Allow-Origin` headers, and it will always respond with *any* (`*`). – aergistal Mar 26 '19 at 14:15
  • @aergistal Okay I will update the original question with that information. What do you mean by play the proxied stream on another device on the same subnet? The proxy server is on my iPhone, and if I navigate the the proxy URL on my laptop I can play back the stream perfectly in Safari. But if I send that same URL to the Chromecast it fails. Is that what you mean? All the HTTP segments go through via the laptop, but only a few on the phone. Will include this info too. Give me like 30 minutes and it'll be up there. – Doug Smith Mar 26 '19 at 17:35
  • @DougSmith see my other replies and updated answer, you most likely just need to add the rest of the headers. – aergistal Mar 26 '19 at 17:51
  • I added some info to the question, it seems the Chromecast is asking URLs outside of what I provided? I provide all the headers you indicated (I think) – Doug Smith Mar 26 '19 at 17:57
  • @DougSmith Yes, it's normal, it's how HLS works. You gave it a master playlist with multiple renditions and the player will switch between them during playback based on the available bandwidth. You should also see the segment requests (`*.ts`), if you don't see them you have a problem with the range requests. Did you add the rest of the headers (allow methods, headers, expose headers?) – aergistal Mar 26 '19 at 18:07
  • @aergistal I added my code above for the proxy server that shows me doing this. – Doug Smith Mar 26 '19 at 18:07
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/190716/discussion-between-aergistal-and-doug-smith). – aergistal Mar 26 '19 at 18:09
  • @DougSmith found an official answer, please try with the code in the updated answer – aergistal Mar 27 '19 at 21:54
  • 2
    Wow you got everything – Doug Smith Mar 28 '19 at 01:43
  • 1
    I'm glad it works. It turns out @Jesse was right, the official fix *is* hacky indeed. Hopefully they'll improve HLS support in a future CAF release. – aergistal Mar 28 '19 at 07:43
  • Nice work. The good news is a complimentary iOS app might only take 10 minutes using that json, an object serializer, and if the video did not play directly in the app, just use the jpg images in the app for the icons with image links embedded in those to the m3u8. –  Mar 28 '19 at 09:46
6

Conclusion

The most ethical answer is to work with Reddit to ensure they set the proper CORS headers. CORS headers are required in the Google documentation.

Simulating your issue

Using this tester:

https://developer.jwplayer.com/tools/stream-tester/

It simulates some of the same experience you were having in your code with the Chromecast SDK. The Google video played without the Playready DRM setting, but the reddit videos did not (in most browsers).

MS EDGE and jwplayer

If you select Playready and put anything for Playready url, even leaving it blank, it works for M3U8.

Internet Explorer and jwplayer

Error, 232011 A manifest request was made without proper crossdomain credentials. Cannot load M3U8: Crossdomain access denied. This video cannot be played because of a technical error.

This indicates that perhaps CORS is not enabled on the reddit servers. More on that below.

Firefox and jwplayer

Nothing there seems to work with jwplayer.

Chrome and jwplayer

Doesn't work with jwplayer.

Safari and jwplayer player

You indicated it worked without needing to set any of the DRM settings.

iPhone/Apple TV

I tried it and the m3u8 videos are able to use AirPlay directly to cast from my phone to the Apple TV (4K).

Simulation Summary

All the M3U8 videos can already stream from iPhone to AppleTV just fine with Airplay. It seems to work it Edge, and also in Safari, so maybe it only works because Reddit has accepted Apple streaming via airplay as a service, but not Chromecast. Not quite sure there, but how else could it be explained? More clarification from someone would be great.

Root Cause Analysis

Notice the google link you shared includes this header:

Access-Control-Allow-Origin

and it is set to * (aka. all), meaning the server will share the requested resources with any domain on the Internet.

https://tools.geekflare.com/report/cors-test/https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/hls/DesigningForGoogleCast.m3u8

The reddit links don’t have that header, meaning CORS is not enabled to allow resource sharing, meaning it is not designed to work.

Description of the CORS headers https://www.codecademy.com/articles/what-is-cors

The Access-Control-Allow-Origin header allows servers to specify how their resources are shared with external domains. When a GET request is made to access a resource on Server A, Server A will respond with a value for the Access-Control-Allow-Origin header. Many times, this value will be *, meaning that Server A will share the requested resources with any domain on the Internet. Other times, the value of this header may be set to a particular domain (or list of domains), meaning that Server A will share its resources with that specific domain (or list of domains). The Access-Control-Allow-Origin header is critical to resource security.

There are several resources indicating that CORS must be enabled from the server side:

https://stackoverflow.com/a/28360045/9105725

https://help.ooyala.com/video-platform/concepts/cors_enablement.html

Even Google says these headers need to be set: https://developers.google.com/cast/docs/chrome_sender/advanced

CORS requirements For adaptive media streaming, Google Cast requires the presence of CORS headers, but even simple mp4 media streams require CORS if they include Tracks. If you want to enable Tracks for any media, you must enable CORS for both your track streams and your media streams. So, if you do not have CORS headers available for your simple mp4 media on your server, and you then add a simple subtitle track, you will not be able to stream your media unless you update your server to include the appropriate CORS header. In addition, you need to allow at least the following headers: Content-Type, Accept-Encoding, and Range. Note that the last two headers are additional headers that you may not have needed previously.

  • I'm confused, using that site if I put in the m3u8 above, set DRM to None and hit Test, it loads fine. – Doug Smith Mar 24 '19 at 03:51
  • Strange. It didn't work for me the first time without it. Maybe it is somehow already registered a license when I tried it to that site. –  Mar 24 '19 at 04:11
  • Here, try this one. I did not "fix" it yet... https://v.redd.it/g99ef3uv4yk21/HLSPlaylist.m3u8 try that one without setting the DRM in the jwplayer. Try others as well. –  Mar 24 '19 at 04:19
  • I tried that one, still no DRM I can see. I don't see anything on Reddit's side indicating there would be either. But as far as code to share would go, a great open source implementation of the Google Cast SDK is "pychromecast" on GitHub, with a very simple setup process if you're familiar with Python at all. The official SDK from Google is also available and pretty easy to set up in a few different languages, is there a language you're most comfortable with? – Doug Smith Mar 24 '19 at 19:21
  • C# and Java are my most familiar languages. But, it’s mostly the same feel for all. I’ll give it a look when I get time. P.S. I used MS Edge when I did the test for that jwplayer. So, maybe it’s a MS thing. –  Mar 24 '19 at 19:33
  • I don't have Edge as I'm on a Mac, but it loads fine in Safari, but Firefox not at all. Very interesting. It won't let me select Playready at all though, it's grayed out – Doug Smith Mar 24 '19 at 22:57
  • Well, the mpu8 is an Apple proprietary format I believe. Playready is a Microsoft thing, so only works in MS browsers. Everyone wants their own video format. –  Mar 24 '19 at 23:00
  • Oh ok, but then why does nothing work on Chromecast? – Doug Smith Mar 24 '19 at 23:12
  • Content type of video/mp4 or video/* might be worth a shot for the Dash_720. The type shown in the Web element is mp4. –  Mar 24 '19 at 23:45
  • The Dash_720 is indeed an mp4, there's just no audio element – Doug Smith Mar 25 '19 at 00:34
  • Whoever did the -1, could you explain what’s wrong with the answer? –  Mar 25 '19 at 16:04
  • 1
    Updated the original question, it's unfortunately not (just) the CORS headers – Doug Smith Mar 27 '19 at 16:42
  • Try not redirecting to https. The stream works with just http. You don't use the same port for http and https. Not sure how NGINX work, but is the url rewrite enough by itself to redirect the port from 8080 to 80, or is something else required. In any case, the proxy will be listening both on 80 and 8080. If you wanted to use https, up to 4 ports would be involved. –  Mar 27 '19 at 17:16
  • 1
    I don't see what that has to do with anything? It's just getting https from Reddit than presenting it over http. If you're concerned about the warning in the screenshot it's just a warning because when you convert your app to be "published", you can't link to non-https things if it's from an https source URL. But I don't see how that would affect development. – Doug Smith Mar 27 '19 at 18:42