Search code examples
amazon-web-servicesamazon-cloudfronthttp-live-streamingpre-signed-url

Modify .m3u8 file to sign each url with Cloudfront


I am struggling to read .m3u8 file in Javascript and modify its segments to get signed by Cloudfront for streaming hls content.

enter image description here

const s3ObjectKey = `${folderName}/${folderName}.m3u8`;
    const url = `${process.env.CLOUDFRONT_DOMAIN}/${s3ObjectKey}`;
    const privateKey = fs.readFileSync(
      new URL("../private_key.pem", import.meta.url),
      {
        encoding: "utf8",
      }
    );
    const keyPairId = process.env.CLOUDFRONT_KEY_PAIR_ID;
    const dateLessThan = new Date(new Date().getTime() + 60 * 60000);
    const m3u8Url = cloudfrontSignedUrl({
      url,
      keyPairId,
      dateLessThan,
      privateKey,
    });

After I get the signed m3u8Url I need to modify its segments to be signed.

Any help will be appreciated.


Solution

  • Answer form AWS Blog post

    import boto3
    import os
    
    
    KEY_PREFIX = os.environ.get('KEY_PREFIX')
    S3_BUCKET = os.environ.get('S3_BUCKET')
    SEGMENT_FILE_EXT = os.environ.get('SEGMENT_FILE_EXT', '.ts')
    
    required_vars = [KEY_PREFIX, S3_BUCKET]
    if not all(required_vars):
        raise KeyError(f'Missing required environment variable/s. Required vars {required_vars}.')
    
    
    s3 = boto3.client('s3')
    
    
    def lambda_handler(event, context):
        try:
            s3_key = event['pathParameters']['proxy']
            obj = s3.get_object(Bucket=S3_BUCKET, Key=s3_key)
    
            body = obj['Body'].read().decode('utf-8')
            qp = event['queryStringParameters']
    
            params = ['?']
            # reconstruct query param uri
            [(params.append(p.replace(KEY_PREFIX, '') + '=' + qp[p] + "&")) for p in qp if KEY_PREFIX in p]
            sign_params = ''.join(params).rstrip("&")
    
            # append query params to each segment
            resp_body = body.replace(SEGMENT_FILE_EXT, ''.join([SEGMENT_FILE_EXT, sign_params]))
    
            return {
                'statusCode': 200,
                'body': resp_body
            }
        except Exception as e:
            print(e)
    
        return {'statusCode': 500, 'body': ''}
    
    

    Let’s go over the key areas in the python code:

    • KEY_PREFIX environment variable is the prefix pattern that Lambda uses to identify the CloudFront query params. In my example it is -PREFIX.
    • S3_BUCKET environment variable the S3 bucket name to which Lambda will make the request to get the main manifest.

    Note: For illustration purposes I used an environment variable that is set, for instance, when the Lambda function is created. You can change this part to have a lookup logic, which is especially helpful if you have different S3 buckets that are used to store media content.

    • SEGMENT_FILE_EXT environment variable file extension of your media file. It default’s to .ts, you can override the value by setting this environment variable in Lambda function configuration.
    • s3_key variable is the path to the file in your S3 bucket which is represented as a URL that is used by the client to make the request. In my example the URL is https://myapp.com/movies/movie_1/index.m3u8 and the s3_key is movies/movie_1/index.m3u8, which matches exactly with the s3 bucket folder structure as shown earlier in Figure 5. By following this convention, when creating a S3 folder structure for the URL path, s3_key will dynamically get the correct path to the file in your S3 bucket. To learn more about Lambda proxy integration, see Set up a proxy integration with a proxy resource.
    • sign_params variable is the reconstructed CloudFront signed URL query parameters.
    • resp_body variable, is the final modified main manifest that is returned to the client. The replace function appends the CloudFront signed URL query parameters to each segment in the manifest file. The final result is assigned to the resp_body variable.

    This Lambda function is getting the manifest file in its original form from your S3 bucket and modifying it so that when the playback player makes a request to get the next segment, the request already includes the CloudFront signed URL query parameters. This allows you to restrict access to the video content based on user permissions for each video. Figure 6 illustrates manifest before and after the modification.

    enter image description here