How to sign an RTMP url for Amazon CloudFront - amazon-web-services

I'm trying to create signed urls for an RTMP distribution in Amazon's CloudFront. I have the following working:
Signed URLs for Web distribution (over http and https) - so I know I am able to sign URLs correctly.
Unsigned URLs for RTMP distribution - so I know I have CloudFront and S3 setup properly for RTMP.
Main question - now that I'm trying to get a signed url for RTMP, it never seems to be playable.
Part of the confusion is based on the format of the url (similar to this question). So I don't know if it matters which part of the url I sign - if I sign the whole thing (like my http urls), or if I only sign a portion, and if I include the mp4: prefix in the path.
There seem to be a lot of pseudo-similar questions on Stackoverflow, but they seem to be related to slightly different issues, and not about actually creating a signed url for RTMP.

Unfortunately there are many variations to how a RTMP url can be created, which
caused a large portion of the confusion. The following is the way that I was able to get this to work with Amazon CloudFront. To be clear, this was to be used in a *.SMIL file, so it might be different if you only need a single url.
The S3ObjectSummary object has a key for the file, which might be something like folder1/folder2/video.mp4.
Use the above key, and remove the .mp4 extension.
Sign the url as normal (I used CloudFrontService.signUrlCanned().
In the *.smil file that is generated, set the base reference to rtmp://<CloudFront RTMP Distribution Domain>/cfx/st
In the video element, set the height and width, and in the src attribute, prepend mp4: to the signed url portion.
Here is an example SMIL file.
<smil data-livestyle-extension="available">
<head>
<meta base="rtmp://some-cloud-front-domain.net/cfx/st"/>
</head>
<body>
<switch>
<video height="720" width="1280" src="mp4:<signed portion of video path>" />
<video height="480" width="853" src="mp4:<signed portion of video path>" />
</switch>
</body>
</smil>

With Python/boto3, I've managed to sign media files with the rsa_signer
http://boto3.readthedocs.io/en/latest/reference/services/cloudfront.html#generate-a-signed-url-for-amazon-cloudfront
by signing only the path to the media file.
Let's say you want to stream an S3 bucket media file located at 'videos/test.mp4'
Following the boto3 example from the link above, here's what you can do to serve the file with flowplayer:
signed_url = cloudfront_signer.generate_presigned_url(
'videos/test.mp4', date_less_than=expire_date)
Then in the flowplayer code (Django template syntax):
<div class="flowplayer fp-slim" data-rtmp="rtmp://{{cloudfront_domain_prefix}}.cloudfront.net:1935/cfx/st">
<video>
<source type="video/flash" src="mp4:{{signed_src}}">
</video>
</div>

Related

AWS S3 - automatically change response header

Our team is building a web app that allows users to download video files. We currently host our files on AWS S3, but since our site doesn't reside on AWS, we can't use <a href="blah"> to prompt download. If we use that html element, users simply get redirected to a video player - which is fine, but Safari on mobile doesn't allow for users to download the video file via the video player.
We found that manually setting the file's content disposition to attachment on S3 works, but we have not found a way to automate that. We tried adding a content-disposition: attachment key-value pairing in our payload, which works, but adds a "User defined" meta data in the form of x-amz-meta-content-disposition, which doesn't work as the file could not be downloaded as an attachment. It seems only "System defined" works.
Has anyone ever encountered this issue before and found a workaround?
see screenshot for what I'm referencing
You can set the content disposition when the file is created.
This is done by uploading the file via a presigned url.
See https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html for details on the presigned urls.
Alternatively you can use a presigned url to return to get the file from S3 and override the content disposition header on the GET request.

How to stream a presigned video from an AWS S3 bucket?

When generating a presigned video URL from AWS S3 bucket the video will download in mp4 format if I use the URL in the web browser , however, it will not stream if I put it in the src attribute of a video tag. Below is an example of what the presigned url looks like. How can I use this url to stream?
<video width="320" height="240" controls preload="auto">
<source src="https://s3.eu-north-1.amazonaws.com/daycare.videos/iland-guard/yamit/2020-12-09/cam_0/20201209_043123.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA6OHOPCC5DWJXXO7O%2F20210309%2Feu-north-1%2Fs3%2Faws4_request&X-Amz-Date=20210309T075948Z&X-Amz-Expires=900&X-Amz-Signature=1cfb2dc7658b90714cd5b52b157f3caf878be6504a4d5f9d1a1be76a599abae8&X-Amz-SignedHeaders=host" type="video/mp4">
</video>
To test this, I did the following:
Put an .mp4 file in an Amazon S3 bucket (no Bucket Policy, no ACL)
Generated a pre-signed url using the AWS CLI aws s3 presign command
Tested the pre-signed URL by pasting it into a browser Address Bar -- it worked
Substituted the pre-signed URL in your code (above) -- it worked
I did have a strange problem with a space in the filename -- I had to quote the path for the AWS CLI to include the raw space rather than using a + to represent the space.
The pre-signed URL that was generated looked like this:
https://bucketname.s3.amazonaws.com/A2%20File.mp4?AWSAccessKeyId=AKIAxxx&Signature=xxx&Expires=1615413373
I see that it uses a different format to the one presented in your question.
I replicated above, using the Node library. The code seems to work fine. The key thing to make sure is whether the presigned URL works when placed in your browser. The SDK generates a URL regardless of whether the file exists or not, so make sure the presigned URL is actually functioning.
The format of my presigned URL matches that of yours:
<video width="320" height="240" controls preload="auto">
<source src="https://bucket.s3.ca-central-1.amazonaws.com/Glass%20one/2e71b368-1d22-4c56-a690-b534eea3def8.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAQOTARPS7GGSVO5F7%2F20210915%2Fca-central-1%2Fs3%2Faws4_request&X-Amz-Date=20210915T223104Z&X-Amz-Expires=3600&X-Amz-Signature=f61fc82b7ebd55ea83290587786d61fe41cb6137624d243bf36c46257ef2aab6&X-Amz-SignedHeaders=host&x-id=GetObject" type="video/mp4">
</video>

Byte range requests from aws S3

guys so I have a problem consistently and reliably seeking videos with HTML5. I am getting the videos from an AWS S3 Bucket using Nodejs all videos are in mp4 format. I have tried multiple things to get the video's current time to move every time (most of the time it works but occasionally it doesn't move) but to no avail.
Heres my code:
router.get("/*", (req, res, next) => {
let params = {
Bucket: "bucketName",
Key: decodeURIComponent(req.path.substring(1))
};
s3.getObject(params, function(err, data) {
if (err) {
res.status(500).send(err);
} else {
res.contentType(data.ContentType);
res.send(data.Body);
}
});
});
}
I've been doing some reading and people are saying you can use byte-range requests and request the whole video through a byte-range request. This guy seems to do it with a local file but I am at a loss about how to do it with an s3 file. See post: can't seek html5 video or audio in chrome. The other suggestion I've heard of people doing is HLS encoding but I am not sure what is the best way or how to implement them can someone point me in the right direction?
I think the best answer is probably to implement an HLS or DASH streaming solution. Here is an example of HLS with S3 and CloudFront. And here is a more comprehensive Best Practices for Streaming Media Delivery.
Right now, your app server is simply reading the entire video file from S3 and then sending the entire video file contents directly to the client in an HTTP response. While it appears to work, it might be better to avoid proxying this content, and instead serve it directly to the client from S3 (or CloudFront). One way to do that, for private content, is to send the client an S3 pre-signed URL.
I tested a simple HTML5 video web page against an S3-hosted MPEG video file and was able to view it fine on Chrome, as well as seek back and forth at will. I tested with a relatively small MPEG (15MB).
<html>
<body>
<h1>Stack Overflow 65796272 Video Sample</h1>
<p>This video and associated poster are sourced from Amazon S3 via pre-signed URL.</p>
<div id="container">
<video id='video' controls="controls" preload='none' width="600" poster="https://poster-presigned-url-here">
<source id='mp4' src="https://video-presigned-url-here" type='video/mp4' />
<p>Your user agent does not support the HTML5 video element.</p>
</video>
</div>
</body>
</html>
I pre-created the poster and video pre-signed URLs using the awscli, but you can do this using an AWS SDK and serve them dynamically to your client (or inject them into the HTML sent to the client using any standard template engine such as Express.js). You can remove the poster, if not needed. Note that pre-signed URLs are time-limited.

og:image from Digital Ocean space/Amazon S3 could not be processed as an image because it has an invalid content type

I am building a website where a user can upload a cover image. The cover image will then be resized and upload to Digital Ocean Space. What happens after that is I will then use the resized version (1200*627) as the og:image. This is what the header looks like after Facebook scraped it.
<meta property="og:image" content="https://sgp1.digitaloceanspaces.com/hologram-develop/__sized__/staging/businesscard/cover/1/7fd5f9ef-d9fe-4fb1-a835-b4aab37532f5-crop-c0-5__0-5-1200x627-70.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=PCOBIQX2EC5XUDKEYJ2N%2F20200917%2Fsgp1%2Fs3%2Faws4_request&X-Amz-Date=20200917T031717Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=36544132bfb2d81d2a4189ed29dd9ec8aad70a5c67875372956e51ec978cb2d8" />
However, I am getting below error:
Provided og:image URL, https://sgp1.digitaloceanspaces.com/hologram-develop/__sized__/staging/businesscard/cover/1/7fd5f9ef-d9fe-4fb1-a835-b4aab37532f5-crop-c0-5__0-5-1200x627-70.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=PCOBIQX2EC5XUDKEYJ2N%2F20200917%2Fsgp1%2Fs3%2Faws4_request&X-Amz-Date=20200917T031717Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=36544132bfb2d81d2a4189ed29dd9ec8aad70a5c67875372956e51ec978cb2d8 could not be processed as an image because it has an invalid content type.
Upon closer inspection, the content type is correct when I try to access the URL, which is image/jpeg.
I had seen this issue which the person place the private link without the credential which causes the problem. However, it is not quite the same for my case as my URL include the credential to the bucket.
The problem is also not due to the image size. The size is 1200*627, which is the recommended size of Facebook. Does Facebook only take in Public image URL? Or do I need to do anything extra for it to works?

Denying direct access to AWS S3 Object

In a web application scenario, html5 <video> tag is used as follows:
<video src="https://s3-us-west-2.amazonaws.com/mybucket/avideo.mp4">
<source type="video/mp4">
HTML5 Video is required
</video>
How can I stop somebody from directly accessing the video by copy-pasting https://s3-us-west-2.amazonaws.com/mybucket/avideo.mp4 in a browser URL bar?
See the example on this page regarding restricting access to only requests that include a specific HTTP referrer.