在iOS浏览器中流式传输AWS S3 HLS视频 [英] Stream AWS S3 HLS Videos in iOS Browsers

查看:54
本文介绍了在iOS浏览器中流式传输AWS S3 HLS视频的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何在iOS Safari浏览器中流式传输HLS(.m3u8)?我的视频存储在AWS S3存储桶中,访问视频和音频 .m3u8 的唯一方法是传递签名的URL.

我正在使用 videojs 来流式传输视频. videojs.Hls.xhr.beforeRequest 在iOS浏览器上不起作用.我还读到iOS不支持MSE,是否可以使用其他方法来传递签名的URL,以便能够在iOS浏览器上流式传输我的视频?

这是我的示例代码和错误的屏幕截图:

  videojs.Hls.xhr.beforeRequest = function(options){如果(options.uri.includes('音频')){options.uri = options.uri +'?Policy ='+策略+'& Key-Pair-Id ='+ keyPairId +'& Signature ='+签名;}否则,如果(options.uri.includes('Video')){options.uri = options.uri +'?Policy ='+策略+'& Key-Pair-Id ='+ keyPairId +'& Signature ='+签名;}返回选项}var overlayNative = false;var player = videojs('video-test',{控件":是的,流体":是的,"preload":无","techOrder":["html5"],"html5":{"hls":{"withCredentials":true,OverrideNative:OverrideNative,},},nativeVideoTracks:!overrideNative,nativeAudioTracks:!overrideNative,nativeTextTracks:!overrideNative});player.src({src:网址,键入:"application/x-mpegURL",withCredentials:true});  

解决方案

除了在ReactJS中实现之外,完全相同的问题videojs vhs覆盖无效,因为它与Safari和解析(或不解析)选项有关,以查看通过寄存器m3u8的后续调用的安全性参数.

还有其他一些人在处理这个问题,例如保存并传播了发行版之后,Safari和Chrome视频播放都可以正常工作!

这是一个比我预想的要大得多的兔子洞和一个学位(或15个),但当然,一旦全部写完,一切似乎都非常合逻辑且显而易见.我希望这至少部分地可以帮助我在互联网上找到的其他人,使用S3的AWS Cloudfront infront在所有主要浏览器上提供安全的流式私有内容

How can I stream HLS(.m3u8) in iOS Safari Browsers? My videos are stored in AWS S3 Bucket and the only way to access the video and audio .m3u8 is to pass a signed URL.

I am using videojs to stream videos. videojs.Hls.xhr.beforeRequest is not working on iOS browsers. I also read that MSE is not supported in iOS, is there any alternative I can use to pass a signed URL to be able to stream my videos on iOS browsers?

Here are my sample codes and screenshot of error:

videojs.Hls.xhr.beforeRequest = function(options) {

               
                if (options.uri.includes('Audio')) {
                    options.uri = options.uri + '?Policy=' + policy + '&Key-Pair-Id=' + keyPairId + '&Signature=' + signature;

                }
                else if (options.uri.includes('Video')) {
                    options.uri = options.uri + '?Policy=' + policy + '&Key-Pair-Id=' + keyPairId + '&Signature=' + signature;
                   
                }

                return options
}

var overrideNative = false;

var player = videojs('video-test', {
  "controls": true,
        "fluid": true,
        "preload": 'none',
        "techOrder": ["html5"],
        "html5": {
            "hls": {
                "withCredentials": true,
                 overrideNative: overrideNative,
                        
                 
                }, 
        },
            nativeVideoTracks: !overrideNative,
            nativeAudioTracks: !overrideNative,
            nativeTextTracks: !overrideNative
        });

player.src(
{
   src: url, type: "application/x-mpegURL", withCredentials: true
});

解决方案

Exact same issue, except implemented in ReactJS the videojs vhs overrides do not work, as it has to do with Safari and the parsing (or not) of the options to see the security parameters for subsequent calls past the register m3u8.

There are a few other people dealing with this, such as https://github.com/awslabs/unicornflix/issues/15

i've tried everything, from amazon IVS+VideoJS attempts, to re-writing my class modules as functional to try examples I've found; and basically always end up right back at this issue

---------------UPDATE BELOW--------------- (and grab a comfy seat)

Delivering protected video from S3 via Cloudfront using secure cookies (for iOS based browsers + all Safari) and secure urls for Chrome and everything else.

website architecture:

Presumptions: equivalent setup to above cloud architecture, specifically the IAM configuration for CF to S3 bucket, and the related S3 security configurations for IAM and CORS.

TL/DR:

NON-SAFARI aka Chrome etc - use secure urls (VERY easy OOTB); the above guide worked for chrome, but not for safari.

Safari requires secure cookies for streaming hls natively, and won’t let recognize xhr.beforeRequest overloads at all. SAFARI / iOS BROWSERS BASED ON SAFARI - use secure cookies Everything below, explains this.

Setting cookies, is easy enough sounding! Its probably why there is no end to end example anywhere in AWS CloudFront, AWS Forums, or AWSDeveloper Slack channel, that its presumed to be easy because, hey its just cookies right?

Right. END TL/DR

Solution Details

The ‘AH-HA!’ moment was finally understanding that for this to work, you need to be able to set a cookie for a cloudfront server, from your own server, which is basically an enormous web security no-no. aka - ‘domains need to be the same, all the way down/up the network call’

comments here https://jwplayer-support-archive.netlify.app/questions/16356614-signed-cookies-on-cloudfront-with-hls-and-dash

and here link https://www.spacevatican.org/2015/5/1/using-cloudfront-signed-cookies/

both combined with original AWS documentation about signed cookies with a cname of a domain to apply to subdomains, all combined for me finally.

The solution is:

  1. setup a CNAME for your cloudfront instance; ie: you can’t set a cookie against 5j1h24j1j.cloudfront.net as you don’t own it, but you can CNAME something like cloudfront.<your-domain>.com in your DNS. good docs exists for this particular step at https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-to-cloudfront-distribution.html
  2. Important - you need to also setup a reference for this CNAME in your CF Distribution. (if you want SSL, you need to re-sign your domain cert with cloudfront.<your-domain>.com, then upload this cert to AWS Certificate Manager, so it will be referencable in the CF Distribution edit screen drop down list (which, you can’t save unless you select something).
  3. for your local development box, set up a hosts file overload for whatever the NodeJS listening/bound IP is. ie: if your node is bound to 0.0.0.0, then edit your /etc/hosts to have a line 0.0.0.0 dev.<your-domain>.com - when you deploy to your production host, the domain will obviously work there.
  4. Now, in your backend (aka server side) code, where you will set the cookies, you need to set the domain parameter, and you can’t directly wildcard but you can leave it as <your-domain.com>(which in a browser, if you inspect using developer tools, you will see listed as .<your-domain>.com NOTE THE LEADING DOT. This is fine, and expected behaviour for a modern browser; essentially saying ‘any subdomain of <your-domain>.com will have these cookies accessible for.

What the above does, is make sure that END TO END, your are able to send the cookie, assigned to the .<your-domain>.com from a call starting in dev.<your-domain>.com or your future production <your-domain>.com through to the same uri but on a different port for your backend, then on to CF via your CNAME which is a subdomain the cookie can see now. At this point, its up to CF to pass on the required headers to the S3 instance.

But wait, there is more to do client side first. A thing that blocked me even seeing the cookies in the first place, was the fact they don’t get set unless the requestor/initiator uses a ‘withCredentials: true’ flag in the network call that starts it. In my code, that is a ReactJS componentDidMount() based Axios network REST GET call to my backend nodeJS endpoint for the video list (which the nodeJS gets from graphQL in AWS, but thats not needed for this explanation of my fix).

componentDidMount() {
        axios.get('http://dev.<your-domain>.com:3000/api/my-data-endpoint'
        ,{
          withCredentials: true,
        })
         .then(vidData => {
          this.setState({
            ....//set stuff  for player component include to use
          });

        })
    }

When my axios call did not have ‘withCredentials: true’, the cookies were never sent back; as soon as i had that? my cookies were at least sent back to the first caller, localhost (with no domain parameter in the cookie, it defaults to calling, which i had as local host at the time), which therefore meant it would never pass it to CF, which was the 2435h23l4jjfsj.cloudfront.net name at that point.

So, updating axios to use dev.<your-domain>.com for server access, and the withCredentials flag, my cookies were set, on the call to my backend info about the videos. As AWS documentation does point out, the cookies need to be fully set BEFORE the call for secure content, so this is accomplished.

In the above described call to my api, i get back something like

{src:’https://cloudfront.<your-domain>.com/path-to-secure-register-m3u8-file’, qps:’?policy=x&signature=y&key-pair-id=z’, blah blah}

[sidebar - signed urls are all generated in the cloud by a lambda] For Chrome, the player code will append the two together, then Wherever you instantiate your video.js player, overload the videojs.Hls.xhr.beforeRequest as follows

videojs.Hls.xhr.beforeRequest = function (options) {
  options.uri = `${options.uri}${videojs.getAllPlayers()[0].options().token}`;
  return options;
 };

which puts the query string of ?policy=x&signature=y&Key-Pair-ID=z on the end of every sub-file in the stream after the register m3u8 file kicks it off.

the backend call to the api described above, also tears apart the QP’s to set the cookies before the json is sent as a response, as follows

res.cookie("CloudFront-Key-Pair-Id", keypair, {httpOnly: true, path: "/", domain: ‘<your-domain>.com'});
res.cookie("CloudFront-Signature", sig, {httpOnly: true, path: "/", domain: ‘<your-domain>.com'});
res.cookie("CloudFront-Policy", poli, {httpOnly: true, path: "/", domain: ‘<your-domain>.com'});

INTERRUPT - now we have set withCredentials to true, you probably see CORS issues; fun. in your server side code (my reactJS) i set a few headers in my nodejs router

res.header("Access-Control-Allow-Credentials", "true");
res.header("Access-Control-Allow-Origin", "http://dev.<your-domain>.com:8080"); // will be set to just <your-domain>.com for production

At this point, stuff still wasn’t working though. This is because the cloud code was putting the CF 234hgjghg.cloudfront.net domain into the policy, and not my CNAME mapping. I updated this in the cloud. So now my calls for video data, returned urls to the secure m3u8 using cloudfront.<your-domain>.com and not the cloudfront.net which is described here https://forums.aws.amazon.com/thread.jspa?messageID=610961&#610961 in the last response step 3.

At THIS point, if i used safari debug tools, I knew i was close, because my responses to attempted streaming changed from the no key or cookie xml, to

<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>

error, and in it, was a reference to my S3 bucket. This meant to me, that my CF distribution was essentially happy with the cookie based policy, key-id, and signature, and had passed me on to S3, but S3 told me to get lost.

The good thing at this point though, was that the 3 required cloudfront cookies were set from dev.<your-domain>.com all the way through to the cloudfront.<your-domain>.com calls for the m3u8 register file, and then in all the subsequent calls to a .ts or .m3u8

OK, so I spent a bit of time in the s3 config (not editing anything, just reviewing everything… which looked 100% fine to me), and then went back to CF distribution behaviours edit page, where you setup headers to forward. settings (listed below, then a screenshot of mine):

  • cache and origin request settings: use legacy cache settings
  • cache based on selected request headers - whitelist
    • add origin, access-control-request-headers, access-control-request-method. you will need explicitly type the last 2 in, they didn’t auto-complete for me nor show in the suggestion list, but add custom button worked.
    • object caching: use origin cache headers
    • forward cookies/query strings - none(improves caching) on both
    • restrict viewer access (use signed urls or cookies) - yes (this is the entire point of this headache lol)
    • trusted signer, self

After the distribution had saved and propagated, Safari and Chrome video playing both worked!

This was quite a rabbits hole and a degree (or 15) more difficult than I anticipated, but of course once its all written out, it all seems so logical and obvious. I hope this at least partially helps the others i found on the internet with secure streaming private content across all major browsers using AWS Cloudfront infront of S3

这篇关于在iOS浏览器中流式传输AWS S3 HLS视频的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆