├── .github └── PULL_REQUEST_TEMPLATE.md ├── BasicConfiguration └── README.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CloudFormation ├── BasicConfiguration │ ├── EMTConfigCDNOption.json │ ├── README.md │ └── emtconfigcdnoption.py ├── CustomMetrics │ ├── EMTMetrics.json │ ├── README.md │ ├── emt_metrics.py │ └── subscription_filter.py ├── README.md └── SimpleMockADS │ ├── README.md │ └── SimpleMockADS.json ├── CustomMetrics ├── README.md ├── configuration_name.png ├── created_alarm.png ├── enabled_triggers.png ├── graph_errors.png ├── subscriptions.png └── triggers.png ├── DefaultMetrics ├── README.md └── app.py ├── LICENSE ├── Logs ├── README.md └── app.py ├── MockADS └── README.md ├── NOTICE ├── README.md ├── Resources └── README.md └── Workshops ├── Behaviors.png ├── CloudFrontConfig.png ├── Config.png ├── InsertingAdMarkers ├── README.md ├── cfn_params.png ├── cloudwatch_event_rule.png ├── insert_every_minute.png ├── lambda_config.png ├── medialive_channel_id.png └── scheduled_splice_insert.png ├── MediaTailorModule.md ├── Origins.png └── Playback.png /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 7 | -------------------------------------------------------------------------------- /BasicConfiguration/README.md: -------------------------------------------------------------------------------- 1 | If you've never created an AWS Elemental MediaTailor configuration before, it is worthwhile to follow [this tutorial](https://github.com/aws-samples/aws-media-services-simple-live-workflow/tree/master/5-MediaTailor). It walks you through setting up MediaTailor in the context of a live video streaming workflow. 2 | 3 | Creating a configuration has been automated here using this [template](../CloudFormation/BasicConfiguration), with an optional CloudFront distribution. 4 | 5 | On its own, the template is not a big time saver as compared to creating a configuration manually via the MediaTailor console. However, it can be used (with some edits) in conjunction with other templates to automate a more involved workflow. One example would be a workflow that creates an AWS Elemental MediaLive channel that pushes to AWS Elemental MediaPackage, then a MediaTailor configuration using the MediaPackage channel as a video source. -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/aws-samples/aws-elemental-mediatailor-tools/issues), or [recently closed](https://github.com/aws-samples/aws-elemental-mediatailor-tools/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws-samples/aws-elemental-mediatailor-tools/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/aws-samples/aws-elemental-mediatailor-tools/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /CloudFormation/BasicConfiguration/EMTConfigCDNOption.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Resources": { 4 | "EMTConfigLambda": { 5 | "Type": "AWS::Lambda::Function", 6 | "Properties": { 7 | "Code": { 8 | "S3Bucket": { 9 | "Fn::Join": [ 10 | "-", 11 | [ 12 | "rodeolabz", 13 | { 14 | "Ref": "AWS::Region" 15 | } 16 | ] 17 | ] 18 | }, 19 | "S3Key": "mediatailor/emtconfigcdnoption.zip" 20 | }, 21 | "Environment": { 22 | "Variables": { 23 | "ConfigName": { 24 | "Ref": "ConfigName" 25 | }, 26 | "VideoSource": { 27 | "Ref": "VideoSourceURL" 28 | }, 29 | "ADS": { 30 | "Ref": "ADS" 31 | }, 32 | "SlateAdURL": { 33 | "Ref": "SlateAdURL" 34 | }, 35 | "EnableCloudFront": { 36 | "Ref": "EnableCloudFront" 37 | } 38 | } 39 | }, 40 | "Handler": "emtconfigcdnoption.lambda_handler", 41 | "MemorySize": 1024, 42 | "Role": { 43 | "Fn::GetAtt": [ 44 | "EMTRole", 45 | "Arn" 46 | ] 47 | }, 48 | "Runtime": "python3.6", 49 | "Timeout": 300 50 | } 51 | }, 52 | "EMTRole": { 53 | "Type": "AWS::IAM::Role", 54 | "Properties": { 55 | "ManagedPolicyArns": [ 56 | "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess", 57 | "arn:aws:iam::aws:policy/AWSLambdaFullAccess" 58 | ], 59 | "AssumeRolePolicyDocument": { 60 | "Version": "2012-10-17", 61 | "Statement": [ 62 | { 63 | "Effect": "Allow", 64 | "Principal": { 65 | "Service": [ 66 | "lambda.amazonaws.com" 67 | ] 68 | }, 69 | "Action": [ 70 | "sts:AssumeRole" 71 | ] 72 | } 73 | ] 74 | }, 75 | "Path": "/", 76 | "Policies": [ 77 | { 78 | "PolicyDocument": { 79 | "Version": "2012-10-17", 80 | "Statement": [ 81 | { 82 | "Effect": "Allow", 83 | "Action": [ 84 | "mediatailor:*" 85 | ], 86 | "Resource": "*" 87 | } 88 | ] 89 | }, 90 | "PolicyName": "EMTPolicy" 91 | } 92 | ] 93 | } 94 | }, 95 | "MediaTailorConfigResource": { 96 | "Type": "AWS::CloudFormation::CustomResource", 97 | "Properties": { 98 | "ServiceToken": { 99 | "Fn::GetAtt": [ 100 | "EMTConfigLambda", 101 | "Arn" 102 | ] 103 | } 104 | } 105 | }, 106 | "CloudFront": { 107 | "Type": "AWS::CloudFront::Distribution", 108 | "Condition": "CreateCFDistribution", 109 | "Properties": { 110 | "DistributionConfig": { 111 | "Comment": "CloudFront for MediaTailor", 112 | "CacheBehaviors": [ 113 | { 114 | "TargetOriginId": "MediaTailor", 115 | "ViewerProtocolPolicy": "allow-all", 116 | "ForwardedValues": { 117 | "Cookies": { 118 | "Forward": "none" 119 | }, 120 | "QueryString": true 121 | }, 122 | "MaxTTL": 31536000, 123 | "PathPattern": "/v1/*", 124 | "SmoothStreaming": false, 125 | "DefaultTTL": 86400, 126 | "AllowedMethods": [ 127 | "HEAD", 128 | "GET" 129 | ], 130 | "CachedMethods": [ 131 | "HEAD", 132 | "GET" 133 | ], 134 | "MinTTL": 0, 135 | "Compress": false 136 | }, 137 | { 138 | "TargetOriginId": "Origin", 139 | "ViewerProtocolPolicy": "allow-all", 140 | "ForwardedValues": { 141 | "Cookies": { 142 | "Forward": "none" 143 | }, 144 | "QueryString": true 145 | }, 146 | "MaxTTL": 31536000, 147 | "PathPattern": { 148 | "Fn::Join": [ 149 | "", 150 | [ 151 | { 152 | "Fn::GetAtt": [ 153 | "MediaTailorConfigResource", 154 | "VideoSourcePath" 155 | ] 156 | }, 157 | "/*" 158 | ] 159 | ] 160 | }, 161 | "SmoothStreaming": false, 162 | "DefaultTTL": 86400, 163 | "AllowedMethods": [ 164 | "HEAD", 165 | "GET" 166 | ], 167 | "MinTTL": 0, 168 | "Compress": false 169 | } 170 | ], 171 | "WebACLId": "", 172 | "Origins": [ 173 | { 174 | "OriginPath": "", 175 | "CustomOriginConfig": { 176 | "OriginProtocolPolicy": "https-only", 177 | "OriginReadTimeout": 30, 178 | "HTTPPort": 80, 179 | "HTTPSPort": 443, 180 | "OriginKeepaliveTimeout": 5 181 | }, 182 | "Id": "Origin", 183 | "DomainName": { 184 | "Fn::GetAtt": [ 185 | "MediaTailorConfigResource", 186 | "OriginDomainName" 187 | ] 188 | } 189 | }, 190 | { 191 | "OriginPath": "", 192 | "CustomOriginConfig": { 193 | "OriginProtocolPolicy": "https-only", 194 | "OriginReadTimeout": 30, 195 | "HTTPPort": 80, 196 | "HTTPSPort": 443, 197 | "OriginKeepaliveTimeout": 5 198 | }, 199 | "Id": "AdvertisementService", 200 | "DomainName": { 201 | "Fn::Join": [ 202 | ".", 203 | [ 204 | "ads", 205 | "mediatailor", 206 | { 207 | "Ref": "AWS::Region" 208 | }, 209 | "amazonaws.com" 210 | ] 211 | ] 212 | } 213 | }, 214 | { 215 | "OriginPath": "", 216 | "CustomOriginConfig": { 217 | "OriginProtocolPolicy": "https-only", 218 | "OriginReadTimeout": 30, 219 | "HTTPPort": 80, 220 | "HTTPSPort": 443, 221 | "OriginKeepaliveTimeout": 5 222 | }, 223 | "Id": "MediaTailor", 224 | "DomainName": { 225 | "Fn::GetAtt": [ 226 | "MediaTailorConfigResource", 227 | "MediaTailorDomainName" 228 | ] 229 | } 230 | } 231 | ], 232 | "DefaultRootObject": "", 233 | "PriceClass": "PriceClass_100", 234 | "Enabled": true, 235 | "DefaultCacheBehavior": { 236 | "TargetOriginId": "AdvertisementService", 237 | "ViewerProtocolPolicy": "allow-all", 238 | "ForwardedValues": { 239 | "Cookies": { 240 | "Forward": "none" 241 | }, 242 | "QueryString": true 243 | }, 244 | "MaxTTL": 31536000, 245 | "SmoothStreaming": false, 246 | "DefaultTTL": 86400, 247 | "AllowedMethods": [ 248 | "HEAD", 249 | "GET" 250 | ], 251 | "CachedMethods": [ 252 | "HEAD", 253 | "GET" 254 | ], 255 | "MinTTL": 0, 256 | "Compress": false 257 | }, 258 | "ViewerCertificate": { 259 | "CloudFrontDefaultCertificate": true 260 | }, 261 | "HttpVersion": "http2" 262 | } 263 | } 264 | }, 265 | "MediaTailorUpdateResource": { 266 | "Type": "AWS::CloudFormation::CustomResource", 267 | "Condition": "CreateCFDistribution", 268 | "Properties": { 269 | "ServiceToken": { 270 | "Fn::GetAtt": [ 271 | "EMTConfigLambda", 272 | "Arn" 273 | ] 274 | }, 275 | "AdSegmentUrlPrefix": { 276 | "Fn::Join": [ 277 | "", 278 | [ 279 | "https://", 280 | { 281 | "Fn::GetAtt": [ 282 | "CloudFront", 283 | "DomainName" 284 | ] 285 | } 286 | ] 287 | ] 288 | } 289 | } 290 | } 291 | }, 292 | "Conditions": { 293 | "CreateCFDistribution": { 294 | "Fn::Equals": [ 295 | { 296 | "Ref": "EnableCloudFront" 297 | }, 298 | "True" 299 | ] 300 | } 301 | }, 302 | "Parameters": { 303 | "ConfigName": { 304 | "Description": "MediaTailor Configuration Name", 305 | "Type": "String", 306 | "Default": "MyEMTConfig" 307 | }, 308 | "VideoSourceURL": { 309 | "Description": "The URL for the master playlist for the HLS source stream, minus the asset ID. It's assumed that this video has ad markers.", 310 | "Type": "String", 311 | "Default": "https://cf98fa7b2ee4452e.mediapackage.us-east-1.amazonaws.com/out/v1/6477e4bc4bd84cbb895808281b1942b2" 312 | }, 313 | "ADS": { 314 | "Description": "The URL for the ad decision server (ADS)", 315 | "Type": "String", 316 | "Default": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear&correlator=[avail.random]" 317 | }, 318 | "SlateAdURL": { 319 | "Description": " The URL for a high-quality video asset (MP4) to transcode and use to fill in time that's not used by ads", 320 | "Type": "String" 321 | }, 322 | "EnableCloudFront": { 323 | "Description": "If enabled, a new CloudFront distribution is created for MediaTailor.", 324 | "Type": "String", 325 | "Default": "False", 326 | "AllowedValues": [ 327 | "True", 328 | "False" 329 | ] 330 | } 331 | }, 332 | "Outputs": { 333 | "HLSPlaybackPrefix": { 334 | "Value": { 335 | "Fn::GetAtt": [ 336 | "MediaTailorConfigResource", 337 | "HLSPlaybackPrefix" 338 | ] 339 | }, 340 | "Description": "The URL that is used to initiate a playback session for devices that support Apple HLS. The session uses server-side reporting." 341 | }, 342 | "CloudFrontPlaybackPrefix": { 343 | "Value": { 344 | "Fn::Join": [ 345 | "", 346 | [ 347 | "https://", 348 | { 349 | "Fn::GetAtt": [ 350 | "CloudFront", 351 | "DomainName" 352 | ] 353 | }, 354 | { 355 | "Fn::GetAtt": [ 356 | "MediaTailorUpdateResource", 357 | "HLSPlaybackPath" 358 | ] 359 | } 360 | ] 361 | ] 362 | }, 363 | "Condition" : "CreateCFDistribution", 364 | "Description": "The URL with CloudFront, that is used to initiate a playback session for devices that support Apple HLS. The session uses server-side reporting." 365 | } 366 | } 367 | } -------------------------------------------------------------------------------- /CloudFormation/BasicConfiguration/README.md: -------------------------------------------------------------------------------- 1 | This [template](EMTConfigCDNOption.json) automates the creation of a MediaTailor configuration. It takes the following parameters: 2 | 3 | 1. ConfigName - (required) MediaTailor configuration name 4 | 1. VideoSourceURL - (required) The URL prefix for the master playlist for the HLS source stream, minus the asset ID 5 | 1. ADS - (required) The URL for the ad decision server (ADS) 6 | 1. SlateAdURL - The URL for a high-quality video asset to transcode and use to fill in time that's not used by ads 7 | 1. EnableCloudFront - (False by default) If set to true, will create a CloudFront distribution for the MediaTailor configuration 8 | -------------------------------------------------------------------------------- /CloudFormation/BasicConfiguration/emtconfigcdnoption.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import json 3 | import os 4 | from botocore.vendored import requests 5 | import string 6 | from urllib.parse import urlparse 7 | 8 | 9 | def lambda_handler(event, context): 10 | emt = boto3.client("mediatailor") 11 | print("mediatailor event: " + json.dumps(event)) 12 | session = boto3.session.Session() 13 | region = session.region_name 14 | 15 | # get environment variables 16 | configName = os.environ["ConfigName"] 17 | videoSource = os.environ["VideoSource"] 18 | adServer = os.environ["ADS"] 19 | adSlate = os.environ["SlateAdURL"] 20 | enableCF = os.environ["EnableCloudFront"] 21 | cdnPrefix = "" 22 | cdnSegmentPrefix = "" 23 | 24 | # all the info we need to set up a proper distribution 25 | parsedSourceURL = urlparse(videoSource) 26 | VideoSourceDomainName = parsedSourceURL.netloc 27 | VideoSourcePath = parsedSourceURL.path 28 | 29 | if "AdSegmentUrlPrefix" in event["ResourceProperties"]: 30 | cdnPrefix = event["ResourceProperties"]["AdSegmentUrlPrefix"] 31 | cdnSegmentPrefix = cdnPrefix + VideoSourcePath 32 | 33 | response = {} 34 | result = { 35 | "Status": "SUCCESS", 36 | "Data": response, 37 | "ResourceId": configName 38 | } 39 | if event["RequestType"] == "Create" or event["RequestType"] == "Update": 40 | print("configName: " + configName) 41 | print("videoSource: " + videoSource) 42 | print("ADS: " + adServer) 43 | print("Enable CloudFront: " + enableCF) 44 | try: 45 | if cdnPrefix != "": 46 | EMTData = emt.put_playback_configuration( 47 | AdDecisionServerUrl=adServer, 48 | Name=configName, 49 | VideoContentSourceUrl=videoSource, 50 | SlateAdUrl=adSlate, # ok even if this is empty 51 | CdnConfiguration={ 52 | "AdSegmentUrlPrefix": cdnPrefix, 53 | "ContentSegmentUrlPrefix": cdnSegmentPrefix 54 | } 55 | ) 56 | else: 57 | EMTData = emt.put_playback_configuration( 58 | AdDecisionServerUrl=adServer, 59 | Name=configName, 60 | VideoContentSourceUrl=videoSource, 61 | SlateAdUrl=adSlate # ok even if this is empty 62 | ) 63 | print("emt data: " + json.dumps(EMTData)) 64 | parsedEMTPlaybackURL = urlparse(EMTData["HlsConfiguration"]["ManifestEndpointPrefix"]) 65 | MediaTailorDomainName = parsedEMTPlaybackURL.netloc 66 | HLSPlaybackPath = parsedEMTPlaybackURL.path 67 | MediaTailorAdsDomainName = "ads.mediatailor." + region + ".amazonaws.com" 68 | 69 | # add the DASH playback prefix to response when you update this with latest EMT model through Layers 70 | result["Data"] = { 71 | "OriginDomainName": VideoSourceDomainName, 72 | "VideoSourcePath": VideoSourcePath, 73 | "MediaTailorDomainName": MediaTailorDomainName, 74 | "HLSPlaybackPrefix": EMTData["HlsConfiguration"]["ManifestEndpointPrefix"], 75 | "HLSPlaybackPath": HLSPlaybackPath 76 | } 77 | except Exception as exp: 78 | print("Exception: %s" % exp) 79 | result["Status"] = "FAILED" 80 | result["Data"] = {"Exception": str(exp)} 81 | 82 | elif event["RequestType"] == "Delete": 83 | # iterate over the list of log groups and then remove subscription filter 84 | try: 85 | print("Delete requested") 86 | result["Data"] = emt.delete_playback_configuration(Name=configName) 87 | except Exception as exp: 88 | print("Exception: %s" % exp) 89 | result["Status"] = "FAILED", 90 | result["Data"] = {"Exception": str(exp)} 91 | send(event, context, result["Status"], 92 | result["Data"], result["ResourceId"]) 93 | return 94 | 95 | 96 | def send(event, context, responseStatus, responseData, physicalResourceId): 97 | responseUrl = event["ResponseURL"] 98 | 99 | responseBody = { 100 | "Status": responseStatus, 101 | "Reason": "See the details in CloudWatch Log Stream: " + context.log_stream_name, 102 | "PhysicalResourceId": physicalResourceId or context.log_stream_name, 103 | "StackId": event["StackId"], 104 | "RequestId": event["RequestId"], 105 | "LogicalResourceId": event["LogicalResourceId"], 106 | "Data": responseData 107 | } 108 | 109 | json_responseBody = json.dumps(responseBody) 110 | 111 | print("Response body:\n" + json_responseBody) 112 | 113 | headers = { 114 | "content-type": "", 115 | "content-length": str(len(json_responseBody)) 116 | } 117 | 118 | try: 119 | response = requests.put(responseUrl, 120 | data=json_responseBody, 121 | headers=headers) 122 | print("Status code: " + response.reason) 123 | 124 | except Exception as e: 125 | print("send(..) failed executing requests.put(..): " + str(e)) 126 | 127 | return 128 | -------------------------------------------------------------------------------- /CloudFormation/CustomMetrics/EMTMetrics.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Resources": { 4 | "EMTErrorsLambda": { 5 | "Type": "AWS::Lambda::Function", 6 | "Properties": { 7 | "Code": { 8 | "S3Bucket": { 9 | "Fn::Join": [ 10 | "-", 11 | [ 12 | "rodeolabz", 13 | { 14 | "Ref": "AWS::Region" 15 | } 16 | ] 17 | ] 18 | }, 19 | "S3Key": "mediatailor/emtmetrics.zip" 20 | }, 21 | "Environment": { 22 | "Variables": { 23 | "MetricName": { 24 | "Ref": "MetricName" 25 | }, 26 | "Namespace": { 27 | "Ref": "Namespace" 28 | }, 29 | "CreateAlarm": { 30 | "Ref": "CreateAlarm" 31 | } 32 | } 33 | }, 34 | "Handler": "emt_metrics.lambda_handler", 35 | "MemorySize": 512, 36 | "Role": { 37 | "Fn::GetAtt": [ 38 | "EMTErrorsLambdaRole", 39 | "Arn" 40 | ] 41 | }, 42 | "Runtime": "python3.6", 43 | "Timeout": 300 44 | } 45 | }, 46 | "EMTErrorsLambdaRole": { 47 | "Type": "AWS::IAM::Role", 48 | "Properties": { 49 | "ManagedPolicyArns": [ 50 | "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess", 51 | "arn:aws:iam::aws:policy/AWSLambdaFullAccess" 52 | ], 53 | "AssumeRolePolicyDocument": { 54 | "Version": "2012-10-17", 55 | "Statement": [ 56 | { 57 | "Effect": "Allow", 58 | "Principal": { 59 | "Service": [ 60 | "lambda.amazonaws.com" 61 | ] 62 | }, 63 | "Action": [ 64 | "sts:AssumeRole" 65 | ] 66 | } 67 | ] 68 | }, 69 | "Path": "/" 70 | } 71 | }, 72 | "SubscriptionLambdaPermission": { 73 | "Type": "AWS::Lambda::Permission", 74 | "Properties": { 75 | "Action": "lambda:InvokeFunction", 76 | "FunctionName": { 77 | "Ref": "EMTErrorsLambda" 78 | }, 79 | "Principal": { 80 | "Fn::Join": [ 81 | ".", 82 | [ 83 | "logs", 84 | { 85 | "Ref": "AWS::Region" 86 | }, 87 | "amazonaws.com" 88 | ] 89 | ] 90 | } 91 | } 92 | }, 93 | "EMTSubscriptionFilter": { 94 | "Type": "AWS::Lambda::Function", 95 | "Properties": { 96 | "Code": { 97 | "S3Bucket": { 98 | "Fn::Join": [ 99 | "-", 100 | [ 101 | "rodeolabz", 102 | { 103 | "Ref": "AWS::Region" 104 | } 105 | ] 106 | ] 107 | }, 108 | "S3Key": "mediatailor/emtsubscriptionfilter.zip" 109 | }, 110 | "Environment": { 111 | "Variables": { 112 | "FilterPattern": { 113 | "Ref": "LogFilterPattern" 114 | }, 115 | "DestinationARN": { 116 | "Fn::GetAtt": [ 117 | "EMTErrorsLambda", 118 | "Arn" 119 | ] 120 | } 121 | } 122 | }, 123 | "Handler": "subscription_filter.lambda_handler", 124 | "MemorySize": 512, 125 | "Role": { 126 | "Fn::GetAtt": [ 127 | "EMTErrorsLambdaRole", 128 | "Arn" 129 | ] 130 | }, 131 | "Runtime": "python3.6", 132 | "Timeout": 300 133 | } 134 | }, 135 | "SubscriptionFilterTrigger": { 136 | "Type": "AWS::CloudFormation::CustomResource", 137 | "Properties": { 138 | "ServiceToken": { 139 | "Fn::GetAtt": [ 140 | "EMTSubscriptionFilter", 141 | "Arn" 142 | ] 143 | }, 144 | "LogGroupNames": { 145 | "Ref": "LogGroupNames" 146 | } 147 | } 148 | } 149 | }, 150 | "Parameters": { 151 | "LogFilterPattern": { 152 | "Description": "Log filter in CloudWatch filter and pattern syntax", 153 | "Type": "String", 154 | "Default": "{$.eventType = *ERROR*}" 155 | }, 156 | "LogGroupNames": { 157 | "Description": "Comma delimited list of Log Groups to subscribe for metric generation", 158 | "Type": "CommaDelimitedList", 159 | "Default": "MediaTailor/AdDecisionServerInteractions, MediaTailor/ManifestService" 160 | }, 161 | "MetricName": { 162 | "Description": "Name of the Metric to be created", 163 | "Type": "String", 164 | "Default": "EMT Errors" 165 | }, 166 | "Namespace": { 167 | "Description": "Namespace to put the Metric", 168 | "Type": "String", 169 | "Default": "Custom MediaTailor" 170 | }, 171 | "CreateAlarm": { 172 | "Description": "True of False, whether to create an alarm for each Metric dimension. If true, Alarm is created and will activate when 3 or more errors are detected in the last 15 minutes.", 173 | "Type": "String", 174 | "Default": "True" 175 | } 176 | } 177 | } -------------------------------------------------------------------------------- /CloudFormation/CustomMetrics/README.md: -------------------------------------------------------------------------------- 1 | The [template](EMTMetrics.json) will create the Lambda, IAM role needed by Lambda, subscription to Lambda, and the CloudWatch alarm. The template will **not** create the IAM role that give AWS Elemental MediaTailor access to CloudWatch (the first section in this tutorial). It assumes this step has been done previously. It takes the following parameters: 2 | 3 | 1. LogFilterPattern - (required) Log filter in CloudWatch filter and pattern syntax 4 | 1. LogGroupNames - (required) Comma delimited list of Log Groups to subscribe for metric generation 5 | 1. MetricName - (requied) Name of metric to be created 6 | 1. Namespace - (required) Namespace to put the metric in 7 | 1. CreateAlarm - True of False, whether to create an alarm for each Metric dimension. If true, Alarm is created and will activate when 3 or more errors are detected in the last 15 minutes (the threshold and period are currently hardcoded and not being passed in as parameters) 8 | 9 | This template will fail if: 10 | 1. One or both of the AWS Elemental MediaTailor log groups does not exist yet. You must generate logs in order for the log groups to exist. 11 | 1. Some service (like a Lambda) is already subscribed to one or both AWS Elemental MediaTailor log groups. -------------------------------------------------------------------------------- /CloudFormation/CustomMetrics/emt_metrics.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import base64 3 | import gzip 4 | import json 5 | from urllib.parse import urlparse 6 | import os 7 | 8 | def lambda_handler(event, context): 9 | cw_client = boto3.client('cloudwatch') 10 | print ("event: " + json.dumps(event)) 11 | create_alarm = os.environ['CreateAlarm'] 12 | metric_name = os.environ['MetricName'] 13 | namespace = os.environ['Namespace'] 14 | 15 | d = gzip.decompress(base64.b64decode(event['awslogs']['data'])) 16 | dj = json.loads(d.decode()) 17 | for logevent in dj['logEvents']: 18 | ads_log = json.loads(logevent['message']) 19 | event_type = ads_log['eventType'] 20 | config_name = ads_log['originId'] 21 | print ("event_type " + event_type) 22 | print ("config_name " + config_name) 23 | emit_metric(cw_client, metric_name, namespace, config_name, create_alarm) 24 | return 25 | 26 | def emit_metric(cw_client, metric_name, namespace, config_name, create_alarm): 27 | print ("adding count to metric %s in namespace %s" % (metric_name, namespace)) 28 | dimension_name = "Configuration Name" 29 | cw_client.put_metric_data( 30 | Namespace = namespace, 31 | MetricData = [ 32 | { 33 | 'MetricName': metric_name, 34 | 'Dimensions': [ 35 | { 36 | 'Name' : dimension_name, 37 | 'Value' : config_name 38 | }, 39 | ], 40 | "Value": 1, 41 | "Unit": "Count" 42 | } 43 | ] 44 | ) 45 | if create_alarm == "True": 46 | add_alarm(cw_client, metric_name, config_name, namespace, dimension_name) 47 | 48 | def add_alarm(cw_client, metric_name, config_name, namespace, dimension_name): 49 | print ("adding alarm to metric %s in namespace %s" % (metric_name, namespace)) 50 | cw_client.put_metric_alarm( 51 | AlarmName= config_name + "_Errors_Alarm", 52 | ActionsEnabled=False, 53 | MetricName=metric_name, 54 | Namespace=namespace, 55 | Statistic='Sum', 56 | Dimensions=[ 57 | { 58 | 'Name': dimension_name, 59 | 'Value': config_name 60 | }, 61 | ], 62 | Period=900, 63 | Unit='Count', 64 | EvaluationPeriods=1, 65 | Threshold=3, 66 | ComparisonOperator='GreaterThanOrEqualToThreshold' 67 | ) -------------------------------------------------------------------------------- /CloudFormation/CustomMetrics/subscription_filter.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import json 3 | import os 4 | from botocore.vendored import requests 5 | import string 6 | 7 | def lambda_handler(event, context): 8 | client = boto3.client('logs') 9 | 10 | print ("subscription filter event: " + json.dumps(event)) 11 | 12 | # get environment variables 13 | filterName = "ADSErrors" 14 | destinationArn = os.environ['DestinationARN'] 15 | filterPattern = os.environ['FilterPattern'] 16 | logGroupNameList = event["ResourceProperties"]["LogGroupNames"] 17 | response = {} 18 | 19 | result = { 20 | 'Status': 'SUCCESS', 21 | 'Data': response, 22 | 'ResourceId': event["LogicalResourceId"] 23 | } 24 | 25 | if event["RequestType"] == "Create" or event["RequestType"] == "Update": 26 | print ("destinationARN: " + destinationArn) 27 | print ("filterPattern: " + filterPattern) 28 | #iterate over the list of log groups and then put the subscription filter 29 | try: 30 | for logGroup in logGroupNameList: 31 | print ("LogGroupName: " + logGroup) 32 | response = client.put_subscription_filter( 33 | logGroupName=logGroup, 34 | filterName=filterName, 35 | filterPattern=filterPattern, 36 | destinationArn=destinationArn 37 | ) 38 | except Exception as exp: 39 | print("Exception: %s" % exp) 40 | response = {"Exception": str(exp)} 41 | result = { 42 | 'Status': 'FAILED', 43 | 'Data': response, 44 | 'ResourceId': event["LogicalResourceId"] 45 | } 46 | elif event["RequestType"] == "Delete": 47 | #iterate over the list of log groups and then remove subscription filter 48 | try: 49 | print("Delete requested") 50 | for logGroup in logGroupNameList: 51 | response = client.delete_subscription_filter( 52 | logGroupName=logGroup, 53 | filterName=filterName 54 | ) 55 | except Exception as exp: 56 | print("Exception: %s" % exp) 57 | response = {"Exception": str(exp)} 58 | result = { 59 | 'Status': 'FAILED', 60 | 'Data': response, 61 | 'ResourceId': event["LogicalResourceId"] 62 | } 63 | send(event, context, result['Status'], 64 | result['Data'], result['ResourceId']) 65 | return 66 | 67 | 68 | def send(event, context, responseStatus, responseData, physicalResourceId): 69 | responseUrl = event['ResponseURL'] 70 | 71 | responseBody = { 72 | 'Status': responseStatus, 73 | 'Reason': 'See the details in CloudWatch Log Stream: ' + context.log_stream_name, 74 | 'PhysicalResourceId': physicalResourceId or context.log_stream_name, 75 | 'StackId': event['StackId'], 76 | 'RequestId': event['RequestId'], 77 | 'LogicalResourceId': event['LogicalResourceId'], 78 | 'Data': responseData 79 | } 80 | 81 | json_responseBody = json.dumps(responseBody) 82 | 83 | print("Response body:\n" + json_responseBody) 84 | 85 | headers = { 86 | 'content-type': '', 87 | 'content-length': str(len(json_responseBody)) 88 | } 89 | 90 | try: 91 | response = requests.put(responseUrl, 92 | data=json_responseBody, 93 | headers=headers) 94 | print("Status code: " + response.reason) 95 | 96 | except Exception as e: 97 | print("send(..) failed executing requests.put(..): " + str(e)) 98 | 99 | return 100 | -------------------------------------------------------------------------------- /CloudFormation/README.md: -------------------------------------------------------------------------------- 1 | ## CloudFormation Templates 2 | 3 | ### 1. [Basic Configuration](BasicConfiguration) 4 | Creates an AWS Elemental MediaTailor configuration. 5 | 6 | ### 2. [Custom Metrics](CustomMetrics) 7 | Automates the [Custom Metrics tutorial](../CustomMetrics). 8 | 9 | ### 3. [Simple Mock ADS](SimpleMockADS) 10 | Sets up a simple ADS. -------------------------------------------------------------------------------- /CloudFormation/SimpleMockADS/README.md: -------------------------------------------------------------------------------- 1 | The [template](SimpleMockADS.json) deploys an API Gateway and Lambda that acts as a simple ADS. A CloudFront distribution is deployed in front of API Gateway. To personalize the ads, the gateway returns a different VAST XML response depending on the device used. The Lambda function keys off of the User-Agent header passed on by MediaTailor to determine which response to give back. A device running Linux will get a different set of ads than one running MacOS, for example. 2 | 3 | The template requires no input parameters. 4 | 5 | It outputs the CloudFront URL for API Gateway, which can then be used as the URL for the ad decision server (ADS) when creating a MediaTailor configuration. 6 | -------------------------------------------------------------------------------- /CloudFormation/SimpleMockADS/SimpleMockADS.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "Creates an API Gateway with CloudFront that acts as a simple Ad Decision Server. Returns a different VAST XML response depending on User-Agent header.", 4 | "Resources": { 5 | "AdServer": { 6 | "Type": "AWS::Lambda::Function", 7 | "Properties": { 8 | "Code": { 9 | "ZipFile": { "Fn::Join": ["\n", [ 10 | "import json", 11 | "import urllib2", 12 | "def handler(event, context):", 13 | " userAgent = event['headers']['User-Agent']", 14 | " print('user agent: ' + userAgent)", 15 | " if \"Linux\" in userAgent:", 16 | " response = urllib2.urlopen('http://d2qohgpffhaffh.cloudfront.net/MediaTailor/VASTDemo1.xml')", 17 | " elif \"Mac OS\" in userAgent:", 18 | " response = urllib2.urlopen('http://d2qohgpffhaffh.cloudfront.net/MediaTailor/VASTDemo2.xml')", 19 | " else:", 20 | " response = urllib2.urlopen('http://d2qohgpffhaffh.cloudfront.net/MediaTailor/VASTDemo3.xml')", 21 | " content = response.read()", 22 | " return {", 23 | " 'statusCode': 200,", 24 | " 'headers': { 'Content-Type': 'application/xml' },", 25 | " 'body': content", 26 | " }" 27 | ] 28 | ]} 29 | }, 30 | "Environment": {}, 31 | "Handler": "index.handler", 32 | "MemorySize": 3008, 33 | "Role": { 34 | "Fn::GetAtt": [ 35 | "LambdaRole", 36 | "Arn" 37 | ] 38 | }, 39 | "Runtime": "python2.7", 40 | "Timeout": 300 41 | } 42 | }, 43 | "LambdaRole": { 44 | "Type": "AWS::IAM::Role", 45 | "Properties": { 46 | "ManagedPolicyArns": [ 47 | "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 48 | ], 49 | "AssumeRolePolicyDocument": { 50 | "Version": "2012-10-17", 51 | "Statement": [ 52 | { 53 | "Effect": "Allow", 54 | "Principal": { 55 | "Service": [ 56 | "lambda.amazonaws.com" 57 | ] 58 | }, 59 | "Action": [ 60 | "sts:AssumeRole" 61 | ] 62 | } 63 | ] 64 | }, 65 | "Path": "/" 66 | } 67 | }, 68 | "ADSGateway": { 69 | "Type": "AWS::ApiGateway::RestApi", 70 | "Properties": { 71 | "Name": "Mock ADS API", 72 | "Description": "API used for making ADS request", 73 | "FailOnWarnings": true 74 | } 75 | }, 76 | "LambdaPermission": { 77 | "Type": "AWS::Lambda::Permission", 78 | "Properties": { 79 | "Action": "lambda:invokeFunction", 80 | "FunctionName": { 81 | "Fn::GetAtt": [ 82 | "AdServer", 83 | "Arn" 84 | ] 85 | }, 86 | "Principal": "apigateway.amazonaws.com", 87 | "SourceArn": { 88 | "Fn::Join": [ 89 | "", 90 | [ 91 | "arn:aws:execute-api:", 92 | { 93 | "Ref": "AWS::Region" 94 | }, 95 | ":", 96 | { 97 | "Ref": "AWS::AccountId" 98 | }, 99 | ":", 100 | { 101 | "Ref": "ADSGateway" 102 | }, 103 | "/*" 104 | ] 105 | ] 106 | } 107 | } 108 | }, 109 | "ADSGatewayResource": { 110 | "Type": "AWS::ApiGateway::Resource", 111 | "Properties": { 112 | "RestApiId": { 113 | "Ref": "ADSGateway" 114 | }, 115 | "ParentId": { 116 | "Fn::GetAtt": [ 117 | "ADSGateway", 118 | "RootResourceId" 119 | ] 120 | }, 121 | "PathPart": "MyADS" 122 | } 123 | }, 124 | "ADSGatewayMethod": { 125 | "Type": "AWS::ApiGateway::Method", 126 | "Properties": { 127 | "ResourceId": { 128 | "Ref": "ADSGatewayResource" 129 | }, 130 | "RestApiId": { 131 | "Ref": "ADSGateway" 132 | }, 133 | "HttpMethod": "GET", 134 | "AuthorizationType": "NONE", 135 | "Integration": { 136 | "Type": "AWS_PROXY", 137 | "IntegrationHttpMethod": "POST", 138 | "Uri": { 139 | "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${AdServer.Arn}/invocations" 140 | } 141 | } 142 | }, 143 | "DependsOn": [ 144 | "LambdaPermission" 145 | ] 146 | }, 147 | "ApiDeployment": { 148 | "Type": "AWS::ApiGateway::Deployment", 149 | "Properties": { 150 | "RestApiId": { 151 | "Ref": "ADSGateway" 152 | }, 153 | "StageName": "DeployStage" 154 | }, 155 | "DependsOn": [ 156 | "ADSGatewayMethod" 157 | ] 158 | }, 159 | "APIGatewayAcct": { 160 | "Type": "AWS::ApiGateway::Account", 161 | "Properties": { 162 | "CloudWatchRoleArn": { 163 | "Fn::GetAtt": [ 164 | "APIGatewayCloudWatchLogsRole", 165 | "Arn" 166 | ] 167 | } 168 | } 169 | }, 170 | "APIGatewayCloudWatchLogsRole": { 171 | "Type": "AWS::IAM::Role", 172 | "Properties": { 173 | "AssumeRolePolicyDocument": { 174 | "Version": "2012-10-17", 175 | "Statement": [ 176 | { 177 | "Effect": "Allow", 178 | "Principal": { 179 | "Service": [ 180 | "apigateway.amazonaws.com" 181 | ] 182 | }, 183 | "Action": [ 184 | "sts:AssumeRole" 185 | ] 186 | } 187 | ] 188 | }, 189 | "Policies": [ 190 | { 191 | "PolicyName": "ApiGatewayLogsPolicy", 192 | "PolicyDocument": { 193 | "Version": "2012-10-17", 194 | "Statement": [ 195 | { 196 | "Effect": "Allow", 197 | "Action": [ 198 | "logs:CreateLogGroup", 199 | "logs:CreateLogStream", 200 | "logs:DescribeLogGroups", 201 | "logs:DescribeLogStreams", 202 | "logs:PutLogEvents", 203 | "logs:GetLogEvents", 204 | "logs:FilterLogEvents" 205 | ], 206 | "Resource": "*" 207 | } 208 | ] 209 | } 210 | } 211 | ] 212 | } 213 | }, 214 | "ADSAPIStage": { 215 | "Type": "AWS::ApiGateway::Stage", 216 | "Properties": { 217 | "DeploymentId": { 218 | "Ref": "ApiDeployment" 219 | }, 220 | "MethodSettings": [ 221 | { 222 | "DataTraceEnabled": true, 223 | "HttpMethod": "*", 224 | "LoggingLevel": "INFO", 225 | "ResourcePath": "/*" 226 | } 227 | ], 228 | "RestApiId": { 229 | "Ref": "ADSGateway" 230 | }, 231 | "StageName": "Dev" 232 | }, 233 | "DependsOn": [ 234 | "APIGatewayAcct" 235 | ] 236 | }, 237 | "CloudFront": { 238 | "Type": "AWS::CloudFront::Distribution", 239 | "Properties": { 240 | "DistributionConfig": { 241 | "Comment": "Mock ADS", 242 | "Origins": [ 243 | { 244 | "Id": "API-GW", 245 | "DomainName": { 246 | "Fn::Join": [ 247 | "", 248 | [ 249 | { 250 | "Ref": "ADSGateway" 251 | }, 252 | ".execute-api.", 253 | { 254 | "Ref": "AWS::Region" 255 | }, 256 | ".amazonaws.com" 257 | ] 258 | ] 259 | }, 260 | "CustomOriginConfig": { 261 | "OriginProtocolPolicy": "https-only" 262 | } 263 | } 264 | ], 265 | "Enabled": "true", 266 | "DefaultCacheBehavior": { 267 | "TargetOriginId": "API-GW", 268 | "ForwardedValues": { 269 | "QueryString": "false", 270 | "Cookies": { 271 | "Forward": "none" 272 | } 273 | }, 274 | "ViewerProtocolPolicy": "allow-all" 275 | } 276 | } 277 | } 278 | } 279 | }, 280 | "Parameters": {}, 281 | "Outputs": { 282 | "AdDecisionServerURL": { 283 | "Value": { 284 | "Fn::Join": [ 285 | "/", 286 | [ 287 | {"Fn::Sub": "https://${CloudFront.DomainName}"}, 288 | { 289 | "Ref": "ADSAPIStage" 290 | }, 291 | "MyADS" 292 | ] 293 | ] 294 | }, 295 | "Description": "Ad Decision Server URL (API Gateway with CloudFront)" 296 | } 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /CustomMetrics/README.md: -------------------------------------------------------------------------------- 1 | # Creating a Custom AWS Elemental MediaTailor Metric and CloudWatch Alarm 2 | 3 | This tutorial will take you through creating a custom metric using two of the AWS Elemental MediaTailor log groups that have been made available in CloudWatch. One that keeps track of Manifest related errors (ManifestService), and another that records Ad Server and AWS Elemental MediaTailor activities(AdDecisionServerInteractions). In this exercise, we create a new Amazon CloudWatch metric that tallies a count each time an error is recorded, regardless of which log group the error is written to. We will then create an Amazon CloudWatch alarm that gets triggered whenever a threshold of errors is reached in a given amount of time. This entire tutorial will be done in the **us-east-1** region. 4 | 5 | Here are the high level steps we will walk through to accomplish our task above: 6 | 1. [Create an IAM role that gives AWS Elemental MediaTailor access Amazon CloudWatch](#1-create-an-iam-role-that-gives-aws-elemental-mediatailor-access-amazon-cloudwatch) 7 | 1. [Generate AWS Elemental MediaTailor logs](#2-generate-aws-elemental-mediatailor-logs) 8 | 1. [Create an IAM role that the AWS Lambda will need to execute](#3-create-an-iam-role-that-the-aws-lambda-will-need-to-execute) 9 | 1. [Using an AWS Lambda, create/update a custom metric that will add a count every time a MediaTailor related error occurs](#4-create-update-a-custom-metric-using-aws-lambda) 10 | 1. [Subscribe the AWS Lambda to the AWS Elemental MediaTailor log groups](#5-subscribe-the-aws-lambda-to-the-aws-elemental-mediatailor-log-groups) 11 | 1. [Test the custom metric](#6-test-the-custom-metric) 12 | 1. [Create an Amazon CloudWatch alarm based on the metric created](#7-create-an-amazon-cloudwatch-alarm-based-on-the-metric-created) 13 | 14 | # Requirements/Assumptions 15 | 1. You have an AWS account. 16 | 1. You have some familiarity with AWS IAM, Lambda and CloudWatch. 17 | 1. The entire tutorial will be done in **us-east-1** region. 18 | 19 | # Implementation Instructions 20 | 21 | ## 1. Create an IAM role that gives AWS Elemental MediaTailor access Amazon CloudWatch 22 | You will need to explicitly give AWS Elemental MediaTailor access to write logs to Amazon CloudWatch. Detailed instructions on how to do that can be found in [this Amazon IAM documentation](https://docs.aws.amazon.com/mediatailor/latest/ug/monitoring-permissions.html). 23 | Without doing this step, AWS Elemental MediaTailor won't be able to create and write logs. 24 | 25 | ## 2. Generate AWS Elemental MediaTailor logs 26 | Let's verify that logs are indeed being generated and written to Amazon CloudWatch. To do this, we will create a new AWS Elemental MediaTailor configuration. First, we will provide it with a bogus origin server to generate logs in the MediaTailor/ManifestService log group. Then we will correct that, but provide the configuration with an Ad Decision Server that is unreachable. This will generate errors in the MediaTailor/AdDecisionServerInteractions log group. 27 | 28 | ### Generate a manifest service related error 29 | 1. Navigate to the AWS Elemental MediaTailor console. 30 | 1. Create a new configuration. 31 | 1. Enter **TestConfigBogusAds** for **Configuration Name**. 32 | 1. Enter **https://cf98fa7b2ee4450e.mediapackage.us-east-1.amazonaws.com/out/v1/6477e4bc4bd84cbb895808281b1942c0** for **Video content source**. Note that this is an incorrect origin server URL. 33 | 1. Enter **https://bogusads.net** for **Ad Decision Server**. 34 | 1. On a browser, navigate to the [VideoJS open source HLS player](https://videojs.github.io/videojs-contrib-hls/). 35 | 1. In the **Video URL** text box, enter the **HLS playback prefix** generated by AWS Elemental MediaTailor, concatenated with **index.m3u8** (this is the stream name of the pre-generated origin endpoint). You'll end up with complete URL like so: **https://7a87b292c71a4fb4b0218f6d270338c9.mediatailor.us-east-1.amazonaws.com/v1/master/cf6421621b389b384c1fd22e51603ee95db76ae0/TestConfigBogusAds/index.m3u8** 36 | 1. Hit the Load button. This should fail to load since the origin server is unreachable. 37 | 1. Navigate over to the AWS CloudWatch console. 38 | 1. On the left hand side navigation panel, select **Logs**. 39 | 1. In the **Filter** search box, enter **MediaTailor** and hit enter. You should see MediaTailor/ManifestService. Click on that log group. 40 | 1. Inside the log group, you should see a Log Stream with a prefix matching your AWS Elemental MediaTailor configuration name (**TestConfigBogusAds**). Click on that log stream. You should see the logged error message indicating the failure to reach the origin server. 41 | 42 | ### Generate an ad server related error 43 | 1. Navigate back to the AWS Elemental MediaTailor console. 44 | 1. Edit the configuration you created. 45 | 1. Enter **https://cf98fa7b2ee4450e.mediapackage.us-east-1.amazonaws.com/out/v1/6477e4bc4bd84cbb895808281b1942b2** for **Video content source**. This is now the correct origin server URL. 46 | 1. Go back to the VideoJS open source HLS player and play back the AWS Elemental MediaTailor endpoint you used earlier. 47 | 1. Let it run long enough until it hits an ad avail. AWS Elemental MediaTailor should now succeed fetching video segments from the origin server but will fail to reach the ADS. 48 | 1. Navigate back to the AWS CloudWatch console. 49 | 1. On the left hand side navigation panel, select **Logs**. 50 | 1. In the **Filter** search box, enter **MediaTailor** and hit enter. You should see MediaTailor/AdDecisionServerInteractions. Click on that log group. 51 | 1. Inside the log group, you should see a Log Stream with a prefix matching your AWS Elemental MediaTailor configuration name (**TestConfigBogusAds**). Click on that log stream. You should see the logged error message indicating the failure to reach the ADS. 52 | 53 | ## 3. Create an IAM role that the AWS Lambda will need to execute 54 | We will now create a role that the Lambda we're going to write can assume, in order to write metric data into AWS CloudWatch. 55 | 56 | 1. Navigate to the AWS IAM console. 57 | 1. Select **Roles**. 58 | 1. Click on **Create Role**. 59 | 1. Click on **Lambda**. 60 | 1. Click **Next**. 61 | 1. In the **Search text box**, type **AWSLambdaFullAccess**. Check the box next to the Policy with the same name to select it. 62 | 1. Click **Next: Review**. 63 | 1. Type **MetricLambdaRole** in the Role name. 64 | 1. Click on **Create role**. 65 | 66 | ## 4. Create/update a custom metric using AWS Lambda 67 | This Lambda will create the metric we're going to call **MediaTailor Errors**. It will be created in a custom namespace (AWS Elemental MediaTailor normally writes in the AWS/MediaTailor namespace) we'll call **Custom MediaTailor**. The metric is created for every AWS Elemental MediaTailor configuration name. Each time an error is logged in either of the AWS Elemental MediaTailor log groups, this lambda will get triggered and add to the count of the custom metric. 68 | 69 | 1. Navigate to the AWS Lambda console. 70 | 1. Click on **Create Function**. 71 | 1. Enter **MyEMTMetricLambda** for the function name. 72 | 1. Select **Python 3.6** for Runtime. 73 | 1. Under **Existing Role**, look for and select the IAM Role you created in the previous section. 74 | 1. Click on **Create Function**. 75 | 1. Scroll down to the function code panel and replace the code sample in `lambda_function.py` with code below and hit the **Save** button: 76 | 77 | ``` 78 | import boto3 79 | import base64 80 | import gzip 81 | import json 82 | import os 83 | 84 | def lambda_handler(event, context): 85 | cw_client = boto3.client('cloudwatch') 86 | metric_name = "MediaTailor Errors" 87 | namespace = "Custom MediaTailor" 88 | 89 | data = gzip.decompress(base64.b64decode(event['awslogs']['data'])) 90 | decoded_data = json.loads(data.decode()) 91 | for logevent in decoded_data['logEvents']: 92 | ads_log = json.loads(logevent['message']) 93 | event_type = ads_log['eventType'] 94 | config_name = ads_log['originId'] 95 | print ("Event type: %s Config Name: %s" % (event_type, config_name)) 96 | emit_metric(cw_client, metric_name, namespace, config_name) 97 | return 98 | 99 | def emit_metric(cw_client, metric_name, namespace, config_name): 100 | print ("adding count to metric %s in namespace %s" % (metric_name, namespace)) 101 | dimension_name = "Configuration Name" 102 | cw_client.put_metric_data( 103 | Namespace = namespace, 104 | MetricData = [ 105 | { 106 | 'MetricName': metric_name, 107 | 'Dimensions': [ 108 | { 109 | 'Name' : dimension_name, 110 | 'Value' : config_name 111 | }, 112 | ], 113 | "Value": 1, 114 | "Unit": "Count" 115 | } 116 | ] 117 | ) 118 | 119 | ``` 120 | 121 | ## 5. Subscribe the AWS Lambda to the AWS Elemental MediaTailor log groups 122 | In order for the Lambda we previously created to be triggered, we will need to subscribe it to the two AWS Elemental MediaTailor log groups. However, we only want it to get triggered whenever errors are logged, and not any other time. We accomplish this by adding a specific Filter Pattern matching error event types to the subscription. 123 | 124 | 1. Still on the AWS Lambda console, scroll back to the top and add a trigger to the Lambda we just created. Click on **CloudWatch Logs**. 125 | 1. Scroll down to the **Configure Triggers** section. 126 | 1. Under the **Log Group** dropdown, select **MediaTailor/AdDecisionServerInteractions**. 127 | 1. Enter **MetricLambdaTrigger1** for **Filter Name**. 128 | 1. To limit the trigger to only occur when errors appear in this particular Log Group, enter **{$.eventType = \*ERROR\*}** for the **Filter Pattern**. 129 | 1. Hit the Add button. 130 | 1. Hit the Save button. 131 | 1. Scroll back to the top of the Lambda console and add another trigger to the Lambda. Click on **CloudWatch Logs**. 132 | 1. Scroll down to the **Configure Triggers** section. 133 | 1. Under the **Log Group** dropdown, select **MediaTailor/ManifestService**. 134 | 1. Enter **MetricLambdaTrigger2** for **Filter Name**. 135 | 1. To limit the trigger to only occur when errors appear in this particular Log Group, enter **{$.eventType = \*ERROR\*}** for the **Filter Pattern**. 136 | ![alt text](triggers.png) 137 | 1. Hit the **Add** button. 138 | 1. Hit the **Save** button. 139 | ![alt text](enabled_triggers.png) 140 | 1. Verify that the Lambda is subscribed to the two Log Groups by navigating over the the AWS CloudWatch console, then click on **Logs**. 141 | 1. In the Filter box, type **MediaTailor** and hit enter. This should bring up the two log groups and show the Lambda you created under **Subscriptions**. 142 | ![alt text](subscriptions.png) 143 | 144 | ## 6. Test the custom metric 145 | To ensure that the custom metric actually gets created and updated by our Lambda, we will once again play back the AWS Elemental MediaTailor's generated HLS endpoint we tested in the 2nd section of this tutorial. During playback, an error will once again be generated in the MediaTailor/AdDecisionServerInteractions log group since the ADS we provided is unreachable. This will in turn, trigger the Lambda we created, which will then create/update the metric in the **Custom MediaTailor** namespace in AWS CloudWatch. 146 | 147 | 1. On a browser, navigate to the [VideoJS open source HLS player](https://videojs.github.io/videojs-contrib-hls/). 148 | 1. In the **Video URL** text box, enter the **HLS playback prefix** generated by AWS Elemental MediaTailor, concatenated with **index.m3u8** (this is the stream name of the pre-generated origin endpoint). You'll end up with complete URL like so: **https://7a87b292c71a4fb4b0218f6d270338c9.mediatailor.us-east-1.amazonaws.com/v1/master/cf6421621b389b384c1fd22e51603ee95db76ae0/TestConfigBogusAds/index.m3u8** 149 | 1. Hit the Play button to start playback. Let it run long enough until it hits an ad slate. AWS Elemental MediaTailor will attempt to talk to the Ad Decision Server at this point and fail. 150 | 1. Navigate over to the AWS CloudWatch console. 151 | 1. Click on **Metrics**. 152 | 1. Click on **Custom MediaTailor**. You should see the AWS Elemental MediaTailor configuration (**TestConfigBogusAds**) we previously created under Configuration Name. Metric Name should show **MediaTailor Errors** which is the metric name we gave it in the Lambda. 153 | 1. Select the Configuration Name and click on the **Graphed Metrics** tab. 154 | 1. Change the **Statistic** to **Sum** and update the **Period** to 15 minutes. 155 | 1. At the top of the graph, change the graph type dropdown from **Line** to **Number**. You should see the number of times an error has been recoded in this particular metric. 156 | 1. Let your playback run long enough to generate at least 3 errors in the metric. 157 | ![alt text](graph_errors.png) 158 | 159 | 160 | ## 7. Create an Amazon CloudWatch alarm based on the metric created 161 | The Amazon CloudWatch alarm we're going to create here will get triggered whenever 3 or more errors get recorded by our custom metric, in a 15 minute period. The threshold is arbitrary and can be adjusted to reflect a specific business need. 162 | 163 | 1. Still on the AWS CloudWatch console, cick on **Alarms** on the left side navigation. 164 | 1. Click on the **Create Alarm** button. 165 | 1. In the **Search Metrics** search box, type in our AWS Elemental MediaTailor Configuration name **TestConfigBogusAds**. 166 | 1. Under **Custom MediaTailor > Configuration Name**, select our configuration name. 167 | ![alt text](configuration_name.png) 168 | 1. Click **Next**. 169 | 1. Enter **MediaTailorErrorsAlarm** for the **Name**. 170 | 1. Enter **Alarm will trigger when 3 or more errors seen in the last 15 minutes** for the **Description**. 171 | 1. Set **Whenever MediaTailor Errors is >=** to 3. 172 | 1. On the right hand side, under **Alarm Preview**, set **Period** to **15 minutes**. 173 | 1. Statistics should be set to **Standard** and **Sum**. 174 | 1. Under **Actions**, delete the default Notification. We will not be setting notifications for this exercise. 175 | 1. Click the **Create Alarm** button. 176 | 1. Back at the CloudWatch Alarms page, type **MediaTailorErrorsAlarm** in the **Search Alarm** search box and hit Enter. This should return the Alarm we just created in an Alarm state. 177 | ![alt text](created_alarm.png) 178 | 179 | # AWS CloudFormation 180 | This tutorial has been automated. An AWS CloudFormation template has been provided that will create the Lambda, IAM role needed by Lambda, subscription to Lambda, and the CloudWatch alarm. The template will **not** create the IAM role that give AWS Elemental MediaTailor access to CloudWatch (the first section in this tutorial). It assumes this step has previously been done. 181 | 182 | The template is available in the **us-east-1** region only. Copy of template and python codes can be found [here](../CloudFormation/CustomMetrics). 183 | 184 | Launch stack by clicking [here](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=MediaTailorCustomMetrics&templateURL=https://s3.amazonaws.com/rodeolabz-us-east-1/mediatailor/EMTMetrics.json). -------------------------------------------------------------------------------- /CustomMetrics/configuration_name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-elemental-mediatailor-tools/e0461cfdb1f3738ae48a51a81e854c64a468f553/CustomMetrics/configuration_name.png -------------------------------------------------------------------------------- /CustomMetrics/created_alarm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-elemental-mediatailor-tools/e0461cfdb1f3738ae48a51a81e854c64a468f553/CustomMetrics/created_alarm.png -------------------------------------------------------------------------------- /CustomMetrics/enabled_triggers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-elemental-mediatailor-tools/e0461cfdb1f3738ae48a51a81e854c64a468f553/CustomMetrics/enabled_triggers.png -------------------------------------------------------------------------------- /CustomMetrics/graph_errors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-elemental-mediatailor-tools/e0461cfdb1f3738ae48a51a81e854c64a468f553/CustomMetrics/graph_errors.png -------------------------------------------------------------------------------- /CustomMetrics/subscriptions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-elemental-mediatailor-tools/e0461cfdb1f3738ae48a51a81e854c64a468f553/CustomMetrics/subscriptions.png -------------------------------------------------------------------------------- /CustomMetrics/triggers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-elemental-mediatailor-tools/e0461cfdb1f3738ae48a51a81e854c64a468f553/CustomMetrics/triggers.png -------------------------------------------------------------------------------- /DefaultMetrics/README.md: -------------------------------------------------------------------------------- 1 | ## AWS Elemental Mediatailor Default Metrics 2 | 3 | The code sample included here (app.py) was created using the [AWS Chalice](https://github.com/aws/chalice) tool. 4 | The code accepts one parameter, OriginId, which is the AWS Elemental MediaTailor configuration name of interest. This parameter would be passed through the API Gateway that has been created automatically by Chalice. 5 | 6 | The AWS Elemental MediaTailor default metrics are queried for very specific metric names including: 'AdDecisionServer.FillRate', 'Avail.FillRate', 'AdDecisionServer.Ads', 'Avail.FilledDuration', 'AdDecisionServer.Duration', and 'Avail.Duration'. The query gets the average of these metrics in the last 60 minutes. 7 | 8 | The returned average values can be passed along to another function, like a script for UI display. -------------------------------------------------------------------------------- /DefaultMetrics/app.py: -------------------------------------------------------------------------------- 1 | from chalice import Chalice 2 | import boto3 3 | import json 4 | import datetime 5 | 6 | app = Chalice(app_name='cw-metrics') 7 | 8 | 9 | @app.route('/', methods=['GET'], cors=True) 10 | def index(): 11 | #for debug 12 | print (json.dumps(app.current_request.to_dict())) 13 | try: 14 | # Create CloudWatchLogs client 15 | cwclient = boto3.client('cloudwatch') 16 | #filter parameters 17 | originId = app.current_request.query_params['originId'] 18 | 19 | #60 min ago represented in timedelta 20 | delta = datetime.timedelta(minutes=60) 21 | #endtime is now 22 | end = datetime.datetime.now() 23 | #starttime is 60 min ago 24 | start = end - delta 25 | 26 | # HTTP response 27 | response = {} 28 | metricNames = ['AdDecisionServer.FillRate', 'Avail.FillRate', 'AdDecisionServer.Ads', 'Avail.FilledDuration', 'AdDecisionServer.Duration', 'Avail.Duration'] 29 | #Value is for the specific MediaTailor configuration we want to get 30 | for metric in metricNames: 31 | label = metric 32 | try: 33 | result = cwclient.get_metric_statistics( 34 | Namespace = 'AWS/MediaTailor', 35 | MetricName = metric, 36 | Dimensions = [ 37 | { 38 | 'Name': 'ConfigurationName', 39 | 'Value': originId 40 | } 41 | ], 42 | StartTime = start, 43 | EndTime = end, 44 | Period = 3600, 45 | Statistics = ['Average'] 46 | ) 47 | print (result) 48 | if 'Duration' in metric: 49 | label = label + " (Milliseconds)" 50 | else: 51 | label = label + " (Count)" 52 | response[label] = round(result['Datapoints'][0]['Average'], 2) 53 | except Exception as ex: 54 | print ("failed with metric " + metric) 55 | pass 56 | print (response) 57 | except Exception as ex: 58 | response = ex 59 | print (ex) 60 | 61 | return response 62 | 63 | 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Logs/README.md: -------------------------------------------------------------------------------- 1 | ## AWS Elemental Mediatailor Logs 2 | 3 | The code sample included here (app.py) was created using the [AWS Chalice](https://github.com/aws/chalice) tool. 4 | The code accepts one parameter, OriginId, which is the AWS Elemental MediaTailor configuration name of interest. This parameter would be passed through the API Gateway that has been created automatically by Chalice. 5 | 6 | There are currently two AWS Elemental MediaTailor log groups available via Amazon CloudWatch: 'MediaTailor/AdDecisionServerInteractions' and 'MediaTailor/ManifestService'. This application queries the 'MediaTailor/AdDecisionServerInteractions' specifically. It first gets all the logStreamNames in this log group, then uses all those logStreamNames to extract the actual logs in the last 5 minutes. In addition, it only gets the log entries where the OriginId provided appears. 7 | 8 | The returned log entries can be passed along for further processing or simply displayed. -------------------------------------------------------------------------------- /Logs/app.py: -------------------------------------------------------------------------------- 1 | import json 2 | import boto3 3 | import datetime 4 | from chalice import Chalice 5 | 6 | app = Chalice(app_name='emt-cw-logs') 7 | 8 | 9 | @app.route('/', methods=['GET'], cors=True) 10 | def index(): 11 | #for debug 12 | print (json.dumps(app.current_request.to_dict())) 13 | 14 | originId = event['query_params']['originId'] 15 | #filter parameters 16 | logGroupName = 'MediaTailor/AdDecisionServerInteractions' 17 | 18 | try: 19 | response = {} 20 | # HTTP response 21 | response = get_logs(5, originId, logGroupName) # get logs from last 5 minutes 22 | 23 | if(len(response['events']) == 0): #empty list 24 | print ("no logs from last 5 minutes") 25 | except Exception as ex: 26 | response = ex 27 | print (ex) 28 | print (response) 29 | return response 30 | 31 | #period in minutes 32 | def get_logs(period, originId, logGroupName): 33 | logStreamNamesList = [] 34 | # Create CloudWatchLogs client 35 | cloudwatch_logs = boto3.client('logs') 36 | 37 | #get all the appropriate log streams 38 | log_streams = cloudwatch_logs.describe_log_streams( 39 | logGroupName=logGroupName, 40 | logStreamNamePrefix=originId) 41 | print(log_streams) 42 | if (len(log_streams['logStreams'])>0): 43 | for stream in log_streams['logStreams']: 44 | logStreamNamesList.append(stream['logStreamName']) 45 | print (logStreamNamesList) 46 | 47 | #delta min ago represented in timedelta 48 | delta = datetime.timedelta(minutes=period) 49 | #endtime is now 50 | end = datetime.datetime.now() 51 | #starttime is 60 min ago 52 | start = end - delta 53 | #in ms since epoch 54 | start = int(start.strftime("%s")) * 1000 55 | end = int(end.strftime("%s")) * 1000 56 | 57 | #only filter on originId, grab all event types 58 | loginfo = cloudwatch_logs.filter_log_events( 59 | logGroupName=logGroupName, 60 | logStreamNames=logStreamNamesList, 61 | filterPattern='{($.originId = ' + originId + ')}', 62 | startTime=start, 63 | endTime=end, 64 | limit=100 65 | ) 66 | return loginfo -------------------------------------------------------------------------------- /MockADS/README.md: -------------------------------------------------------------------------------- 1 | ## Mock Ad Decision Server (ADS) 2 | 3 | In order to configure MediaTailor, an ad decision server must be provided. During testing you may use a simple VAST response XML as a static response to MediaTailor's request for an ad. A tutorial is available on [how to build simple VAST response XML](https://aws.amazon.com/blogs/media/build-your-own-vast-3-0-response-xml-to-test-with-aws-elemental-mediatailor/). 4 | 5 | [Here](../CloudFormation/SimpleMockADS), we provide a template that deploys an API Gateway and Lambda with CloudFront, that acts as a simple ADS. The gateway returns a different VAST XML response depending on the device used for playback. 6 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | AWS Elemental Mediatailor Tools 2 | Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## AWS Elemental Mediatailor Tools 2 | 3 | [AWS Elemental MediaTailor](https://aws.amazon.com/mediatailor/) is a content personalization and monetization service that allows customers to implement stitched server-side ad insertion for streaming video while maintaining high quality of service. 4 | 5 | This repository will serve up code samples, code snippets, fully functional apps and tools, as well as links to other related resources that should make using and interacting with AWS Elemental MediaTailor simple and straighforward. 6 | 7 | ## License 8 | 9 | This library is licensed under the Apache 2.0 License. 10 | -------------------------------------------------------------------------------- /Resources/README.md: -------------------------------------------------------------------------------- 1 | ## Resources 2 | 3 | Below is a list of resources that will help you get started using AWS Elemental MediaTailor. 4 | 5 | * High level overview of the various [ad serving protocols that MediaTailor supports](https://aws.amazon.com/blogs/media/aws-elemental-mediatailor-supported-video-ad-serving-protocols/) 6 | * Tutorial on [how to build simple VAST response XML](https://aws.amazon.com/blogs/media/build-your-own-vast-3-0-response-xml-to-test-with-aws-elemental-mediatailor/) which you can use in lieu of a production Ad Decision Server (ADS) for testing purposes 7 | * Step-by-step instructions on [how to use AWS Elemental MediaTailor in a live workflow](https://github.com/aws-samples/aws-media-services-simple-live-workflow/tree/master/5-MediaTailor) 8 | * Tutorial on [how to use AWS Elemental MediaTailor in a Video-On-Demand (VOD) workflow](https://github.com/aws-samples/aws-media-services-simple-vod-workflow/tree/master/11-VODMediaTailor) 9 | * [How to insert ad markers to VOD assets](https://github.com/aws-samples/aws-media-services-simple-vod-workflow/tree/master/12-AdMarkerInsertion) using AWS Elemental MediaConvert 10 | -------------------------------------------------------------------------------- /Workshops/Behaviors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-elemental-mediatailor-tools/e0461cfdb1f3738ae48a51a81e854c64a468f553/Workshops/Behaviors.png -------------------------------------------------------------------------------- /Workshops/CloudFrontConfig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-elemental-mediatailor-tools/e0461cfdb1f3738ae48a51a81e854c64a468f553/Workshops/CloudFrontConfig.png -------------------------------------------------------------------------------- /Workshops/Config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-elemental-mediatailor-tools/e0461cfdb1f3738ae48a51a81e854c64a468f553/Workshops/Config.png -------------------------------------------------------------------------------- /Workshops/InsertingAdMarkers/README.md: -------------------------------------------------------------------------------- 1 | # Inserting Ad Markers with AWS Elemental MediaLive 2 | 3 | This module will show you how to insert ad markers into a MediaLive stream. This stream will be pushed to the AWS Elemental MediaPackage origin for HLS packaging. We will then in turn use MediaPackage's HLS endpoint as a video source to MediaTailor for monetization. 4 | 5 | ## Implementation Instructions 6 | 7 | ### 1. Deploy the simple live workshop CloudFormation template 8 | This step will create the necessary MediaLive and MediaPackage channels. 9 | 10 | **Step-by-step instructions** 11 | 12 | 1. From your browser, navigate to the [aws-media-services-simple-live-workflow page](https://github.com/aws-samples/aws-media-services-simple-live-workflow/tree/master/CloudFormation). 13 | 14 | 1. Copy the [CloudFormation link](https://s3-us-west-2.amazonaws.com/rodeolabz-us-west-2/cloudformation/LiveStreamingWorkshopResources.json) to launch the entire workflow. 15 | 16 | 1. From the AWS console, go to CloudFormation. Click on **Create stack** button. 17 | 18 | 1. Under **Amazon S3 URL**, paste the CloudFormation link into the textbox. Click **Next**. 19 | 20 | 1. Provide a stack name like `simple-live`. 21 | 22 | 1. The default input currently has ad markers. We want to use a source that doesn't have ad markers yet. Replace **HLSPrimarySource** and **SecondaryPrimarySource** with: 23 | `http://d2qohgpffhaffh.cloudfront.net/HLS/vanlife/sdr_uncage_vanlife.m3u8` 24 | 25 | ![alt](cfn_params.png) 26 | 27 | 1. Click **Next** twice. 28 | 29 | 1. Under **Capabilities**, check both boxes to acknowledge the required resources. Then click on the **Create Stack** button. 30 | 31 | 1. Once stack is fully deployed, click on the `simple-live-MediaLiveModuleStack-[random-string]` nested stack. Under **Outputs**, note the **MediaLiveChannelId** value. You will need this later. 32 | 33 | ![alt](medialive_channel_id.png) 34 | 35 | ### 2. Create and test the Lambda that will insert ad markers to MediaLive 36 | 37 | **Step-by-step instructions** 38 | 39 | 1. From the AWS console, go to Lambda. 40 | 41 | 1. Click on **Create function** button. 42 | 43 | 1. Provide a function name like `InsertAdMarker`. 44 | 45 | 1. Choose `Python 3.7` for your runtime. 46 | 47 | 1. For the execution role, choose `Use an existing role`. In the existing role dropdown, choose the role that the CloudFormation created for you named `simple-live-MediaLiveModuleS-MediaLiveResourceRole-[random-string]`. 48 | 49 | If you've created your channels and IAM roles manually by following other tutorials, choose the IAM role you've created that gave Lambda rights to use MediaLive. 50 | 51 | ![alt](lambda_config.png) 52 | 53 | 1. Replace the code in lambda_function.py with the following: 54 | ``` 55 | import datetime 56 | import boto3 57 | import json 58 | 59 | def lambda_handler(event, context): 60 | print (json.dumps(event)) 61 | offset = 20 #in seconds 62 | channel = "8523202" #replace with your MediaLive channel ID 63 | event_id = 1001 #id of your choice 64 | duration = 900000 #duration of ad (10 sec* 90000 Hz ticks) 65 | medialive = boto3.client('medialive') 66 | 67 | now = datetime.datetime.utcnow() 68 | #print("Current UTS Time:", now) 69 | action = splice_insert(now, int(offset), int(event_id), int(duration)) 70 | try: 71 | response = medialive.batch_update_schedule(ChannelId=channel, Creates={'ScheduleActions':[action]}) 72 | print("medialive schedule response: ") 73 | print(json.dumps(response)) 74 | except Exception as e: 75 | print("Error creating Schedule Action") 76 | print(e) 77 | return response 78 | 79 | # start_time = when to insert our ad 80 | # offset = added to start_time to determine actual time of ad marker insertion; 81 | # if offset is too soon, actual time to insert marker may have already passed 82 | # by the time MediaLive receives the scheduled action and will fail 83 | # event_id = splice event ID as defined in SCTE-35 84 | # duration = length of ad 85 | def splice_insert(start_time, offset, event_id, duration): 86 | actual_start_time = start_time + datetime.timedelta(seconds=offset) 87 | actual_start_time_str = actual_start_time.strftime('%Y-%m-%dT%H:%M:%SZ') 88 | action={ 89 | 'ActionName': 'splice_insert.{}'.format(actual_start_time_str), #actionName must be unique so we append the time string 90 | 'ScheduleActionSettings': { 91 | 'Scte35SpliceInsertSettings': { 92 | 'SpliceEventId': event_id, 93 | 'Duration': duration 94 | } 95 | }, 96 | 'ScheduleActionStartSettings': { 97 | 'FixedModeScheduleActionStartSettings': { 98 | 'Time': actual_start_time_str 99 | } 100 | } 101 | } 102 | return action 103 | ``` 104 | 1. Make sure to replace the **channel** value with your own MediaLive channel ID. Save the Lambda. 105 | 1. Let's test this Lambda by clicking on the **Test** button. 106 | 1. Give the test a name like `SimpleTest` and hit the **Save** button. 107 | 1. Select the test you just created and saved and click the **Test** button. 108 | 1. If there were no errors, then the ad marker we scheduled for insertion should have made it to MediaLive. 109 | 1. On the AWS console, navigate to MediaLive. Select the channel that was created by your stack earlier. Under schedule, you should see an item with type `Scte35 splice insert`. Click on the **View Settings** link to inspect the details of the scheduled action. It should reflect all the settings that was defined in the Lambda that you created earlier. 110 | 111 | ![alt](scheduled_splice_insert.png) 112 | 113 | ### 3. Trigger Lambda via CloudWatch scheduled events 114 | Now that the Lambda has been defined, let's schedule it to get triggered at a regular interval, say every 60 seconds so that we can easily test this with MediaTailor later. 115 | 116 | 1. From the AWS console, navigate to CloudWatch. 117 | 118 | 1. Under **Events**, click on **Rules**. Click on **Create Rule** button. 119 | 120 | 1. Under **Event Source**, choose **Schedule**. 121 | 122 | 1. In **Fixed rate of**, edit to say `1 minute`. 123 | 124 | 1. Under **Targets**, click on **Add Target**. 125 | 126 | 1. In the **Function** dropdown, select your `InsertAdMarker` function. 127 | 128 | ![alt](cloudwatch_event_rule.png) 129 | 130 | 1. Click on **Configure details** button. 131 | 132 | 1. Provide a name like `InsertAdMarkerEveryMinute`. Make sure to leave the **State** to `Enabled`. Otherwise, this rule will not run. Click on **Create Rule** button. 133 | 134 | 1. With the event enabled, you should see a scheduled `Scte35 splice insert` in the MediaLive channel's Schedule every minute. 135 | 136 | ![alt](insert_every_minute.png) 137 | 138 | ### 4. Monetize your workflow 139 | Now that ad markers are making their way into our stream, it's time to add MediaTailor to the workflow. 140 | 141 | 1. Follow the [instructions here](https://github.com/aws-samples/aws-media-services-simple-live-workflow/tree/master/5-MediaTailor) on setting up your MediaTailor configuration. 142 | 143 | 1. In section 1, step 5, use the VAST tag from DFP for your ADS: 144 | `https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear&correlator=[avail.random]` 145 | 146 | 1. For testing purposes only, you may skip step 3 (Integrating with Amazon CloudFront). During playback testing, you should see an ad for 10 seconds every minute. 147 | 148 | 149 | ## Completion 150 | 151 | Congratulations! You've successfully created a MediaLive stream with ad markers being inserted at a regular interval. In addition, you have monetized your stream using MediaTailor. 152 | 153 | 154 | ## Cloud Resource Clean Up 155 | 156 | ### CloudFormation 157 | Select and delete the Live Streaming and SPEKE CloudFormation templates deployed as part of the prerequisites. This will clean up all the resources created by the two templates. 158 | 159 | ### AWS Elemental MediaTailor 160 | Select the configuration you created and hit the **Delete** button to remove the configuration. 161 | -------------------------------------------------------------------------------- /Workshops/InsertingAdMarkers/cfn_params.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-elemental-mediatailor-tools/e0461cfdb1f3738ae48a51a81e854c64a468f553/Workshops/InsertingAdMarkers/cfn_params.png -------------------------------------------------------------------------------- /Workshops/InsertingAdMarkers/cloudwatch_event_rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-elemental-mediatailor-tools/e0461cfdb1f3738ae48a51a81e854c64a468f553/Workshops/InsertingAdMarkers/cloudwatch_event_rule.png -------------------------------------------------------------------------------- /Workshops/InsertingAdMarkers/insert_every_minute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-elemental-mediatailor-tools/e0461cfdb1f3738ae48a51a81e854c64a468f553/Workshops/InsertingAdMarkers/insert_every_minute.png -------------------------------------------------------------------------------- /Workshops/InsertingAdMarkers/lambda_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-elemental-mediatailor-tools/e0461cfdb1f3738ae48a51a81e854c64a468f553/Workshops/InsertingAdMarkers/lambda_config.png -------------------------------------------------------------------------------- /Workshops/InsertingAdMarkers/medialive_channel_id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-elemental-mediatailor-tools/e0461cfdb1f3738ae48a51a81e854c64a468f553/Workshops/InsertingAdMarkers/medialive_channel_id.png -------------------------------------------------------------------------------- /Workshops/InsertingAdMarkers/scheduled_splice_insert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-elemental-mediatailor-tools/e0461cfdb1f3738ae48a51a81e854c64a468f553/Workshops/InsertingAdMarkers/scheduled_splice_insert.png -------------------------------------------------------------------------------- /Workshops/MediaTailorModule.md: -------------------------------------------------------------------------------- 1 | # Monetizing an Encrypted Live Stream with AWS Elemental MediaTailor 2 | This module will take you through creating a Mediatailor configuration using previously created AWS Elemental MediaLive and AWS Elemental MediaPackage channels. The MediaPackage channel will have been encrypted using a SPEKE reference server. 3 | 4 | 5 | ## Prerequisites 6 | This module relies on the completion of the following previous modules: 7 | * Launch and deploy the [Live Streaming on AWS Solution CloudFormation template](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=LiveStreaming&templateURL=https:%2F%2Fs3.amazonaws.com%2Fsolutions-reference%2Flive-streaming-on-aws%2Flatest%2Flive-streaming-on-aws.template) - Sets up MediaLive and MediaPackage and CloudFront. Make sure to **start your MediaLive channel** once the template has been fully deployed. 8 | * Launch and deploy the [SPEKE reference server](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=reinvent-live-speke&templateURL=https://s3.amazonaws.com/rodeolabz-us-east-1/speke/speke_reference.json) CloudFormation template, then follow the instructions on [encrypting your MediaPackage HLS channel](https://github.com/awslabs/speke-reference-server/blob/master/workflow/drm-live.md). 9 | 10 | You will also need an Ad decision server (ADS). For this workshop, we will use a previously deployed [simple, serverless mock ADS](../MockADS). 11 | 12 | ## Implementation Instructions 13 | 14 | ### 1. Create an AWS Elemental MediaTailor Configuration 15 | 16 | **Step-by-step instructions** 17 | 18 | 1. From the AWS Management Console, choose **Services** then select **AWS Elemental MediaTailor**. 19 | 20 | 1. Click on **Create configuration**. 21 | 22 | 1. Enter `MyTestConfig` for the **Configuration Name**. 23 | 24 | 1. For the **Video content source**, enter the encrypted MediaPackage HLS Endpoint URL, but **_without the manifest filename_** . 25 | 26 | If your MediaPackage endpoint is: 27 | 28 | https://547f72e6652371c3.mediapackage.us-west-2.amazonaws.com/out/v1/85ae554d000d48cab55aab1ae3df9071/index.m3u8 29 | 30 | Then your content source is: 31 | 32 | https://547f72e6652371c3.mediapackage.us-west-2.amazonaws.com/out/v1/85ae554d000d48cab55aab1ae3df9071 33 | 34 | 1. Enter `https://d2v7jbplksd24.cloudfront.net/Dev/MyADS` for the **Ad decision server**. 35 | 36 | 1. Expand the **Additional configuration** section. Enter `https://d2qohgpffhaffh.cloudfront.net/MediaTailor/AWSE_Logo_15sec.mp4` for the **Slate ad**. 37 | 38 | ![alt](Config.png) 39 | 40 | 1. Click **Create Configuration**. 41 | 42 | 1. Click on **MyTestConfig**. Under **Playback endpoints**, note down the **HLS playback prefix** as you'll need it in the next section. 43 | 44 | ### 2. Test MediaTailor Playback 45 | 46 | 47 | 1. Construct the playback URL that you will need to play back your video. This will consist of the **HLS playback prefix** (eg. _https://f445cfa805184f3e8d86dc2ac1137efa.mediatailor.us-west-2.amazonaws.com/v1/master/cf6421621b389b384c1fd22e51603ee95db76ae0/MyTestConfig/_) 48 | concatenated with the **manifest filename of your MediaPackage playback endpoint** (eg. _index.m3u8_) 49 | 50 | Sample full playback URL: _https://f445cfa805184f3e8d86dc2ac1137efa.mediatailor.us-west-2.amazonaws.com/v1/master/cf6421621b389b384c1fd22e51603ee95db76ae0/MyTestConfig/index.m3u8_ 51 | 52 | 1. On a browser, navigate to [VideoJS's HLS demo player page](https://videojs.github.io/videojs-contrib-hls/). 53 | 54 | 1. In the **Video URL** testbox, enter the playback URL constructed above and hit Load. If your player doesn't start automatically, hit the Play button. You should see the main content of your video play for the first 60 seconds, followed by ads for 45 seconds, and then back to video content. **Note:** You may not see ads the first time you play back if ads are not ready and are still being transcoded. 55 | 56 | ![alt](Playback.png) 57 | 58 | `CHECKPOINT!` Make sure that playback is working before proceeding to the next section. Integrating with CloudFront will only work if initial playback is successful. 59 | 60 | ### 3. Integrate with Amazon CloudFront 61 | 62 | **Step-by-step instructions** 63 | 64 | #### 3a. Add Origins to the CloudFront Distribution 65 | 66 | 1. From the AWS Management Console, choose **Services** then select **CloudFront**. 67 | 68 | 1. Select the distribution created by the template ran at the beginning of the workshop. 69 | 70 | 1. Click on the **Origins** tab, and click on the **Create Origin** button. 71 | 72 | 1. Enter MediaTailor's hostname for the **Origin Domain Name**. This will come from the **HLS playback prefix** of MediaTailor (e.g. _f445cfa805184f3e8d86dc2ac1137efa.mediatailor.us-west-2.amazonaws.com_) 73 | 74 | 1. Leave the **Origin Path** blank. 75 | 76 | 1. Update the **Origin ID** to `MediaTailor`. 77 | 78 | 1. Update the **Origin Protocol Policy** to **HTTPS**. 79 | 80 | 1. Click **Create**. 81 | 82 | 1. Go back to the **Origins** tab, and click on the **Create Origin** button. 83 | 84 | 1. Enter MediaTailor's ad server hostname for the **Origin Domain Name**. Since we're in us-west-2, this will be: 85 | `ads.mediatailor.us-west-2.amazonaws.com` 86 | 87 | 1. Leave the **Origin Path** blank. 88 | 89 | 1. Update the Origin ID to `MediaTailor-Ads`. 90 | 91 | 1. Update the **Origin Protocol Policy** to **HTTPS**. 92 | 93 | 1. Click on **Create**. 94 | 95 | ![alt](Origins.png) 96 | 97 | 98 | #### 3b. Add Cache Behaviors to Your Distribution 99 | 100 | 1. Click on the **Behaviors** tab and click on the **Create Behavior** button. 101 | 102 | 1. Select the **Default** behavior, and click on the **Edit** button. 103 | 104 | 1. Update the **Origin** and select the `MediaTailor-Ads` origin. 105 | 106 | 1. Set **Cache Based on Selected Request Headers** to `None (Improves caching)`. 107 | 108 | 1. Click on **Yes, Edit**. 109 | 110 | 1. Click the **Create** button to add another cache behavior. 111 | 112 | 1. Enter `/v1/*` for the **Path Pattern**. 113 | 114 | 1. Under **Origin**, select the `MediaTailor` origin. 115 | 116 | 1. For **Query String Forwarding and Caching**, select **Forward all, cache based on all**. 117 | 118 | 1. Click on **Create**. 119 | 120 | 1. Click the **Create** button to add another cache behavior. 121 | 122 | 1. Enter `/out/v1/*` for the **Path Pattern**. 123 | 124 | 1. Under **Origin**, select the `mediapackage` origin. 125 | 126 | 1. For **Query String Forwarding and Caching**, select **Forward all, cache based on all**. 127 | 128 | 1. Click on **Create**. 129 | 130 | 1. Under the **Behaviors** tab, double-check the precedence of the caching behavior as this matters. Your primary precedence should be the MediaTailor origin (precedence 0), followed by the MediaPackage origin (precedence 1), and lastly by the ads origin (which is at Precedence 2 and is the Default). If this is not the precedence reflected, select one of the Behaviors and **Change Precedence** by clicking on either the **Move Up** or **Move Down** button, to make the adjustment. 131 | 132 | **NOTE:** When you make changes to the CloudFront distribution, it will go into *In Progress* state. Once it's back to the *Deployed state*, your changes will have taken effect. 133 | 134 | ![alt](Behaviors.png) 135 | 136 | 137 | ## Update MediaTailor Configuration with CloudFront Details 138 | 139 | 1. From the AWS Management Console, choose **Services** then select **AWS Elemental MediaTailor**. 140 | 141 | 1. Click on the Configuration (`MyTestConfig`) you created in section 1 and hit the **Edit** button. 142 | 143 | 1. For the **CDN content segment prefix**, construct your URL by putting together the protocol(https), the CloudFront **Domain Name**, and the path of the MediaPackage endpoint URL. For example: _https://**d32ftvdskdjq4u.cloudfront.net**/out/v1/85ae554d000d48cab55aab1ae3df9071_ 144 | 145 | 1. For the **CDN ad segment prefix**, construct your URL by putting together the protocol, and the CloudFront **Domain Name**. For example: _https://d32ftvdskdjq4u.cloudfront.net_ 146 | 147 | 1. Click **Save**. 148 | 149 | ![alt](CloudFrontConfig.png) 150 | 151 | ## Test MediaTailor Playback with CloudFront 152 | 153 | 1. Once your CloudFront distribution is in the **Deployed** status, and **Enabled** state, try playing back your stream using the same demo player. Take your playback URL from Section 2, Step 2 and replace the MediaTailor hostname with the CloudFront **Domain Name** that was assigned to your distribution. 154 | 155 | For example, if your MediaTailor playback URL is: _https://**f445cfa805184f3e8d86dc2ac1137efa.mediatailor.us-west-2.amazonaws.com**/v1/master/cf6421621b389b384c1fd22e51603ee95db76ae0/MyTestConfig/index.m3u8_ 156 | 157 | 158 | then your CloudFront playback URL is: 159 | _https://**d32ftvdskdjq4u.cloudfront.net**/v1/master/cf6421621b389b384c1fd22e51603ee95db76ae0/MyTestConfig/index.m3u8_ 160 | 161 | 1. Video should play back as before the CloudFront integration. You should see the main content of your video play for the first 60 seconds, followed by ads for 45 seconds, and then back to video content. 162 | 163 | 164 | ## Completion 165 | 166 | Congratulations! You've successfully integrated your streaming video with AWS Elemental MediaTailor. In addition, you have successfully integrated MediaTailor with Amazon CloudFront. 167 | 168 | ## Cloud Resource Clean Up 169 | 170 | ### CloudFormation 171 | Select and delete the Live Streaming and SPEKE CloudFormation templates deployed as part of the prerequisites. This will clean up all the resources created by the two templates. 172 | 173 | ### AWS Elemental MediaTailor 174 | Select the configuration you created and hit the **Delete** button to remove the configuration. 175 | -------------------------------------------------------------------------------- /Workshops/Origins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-elemental-mediatailor-tools/e0461cfdb1f3738ae48a51a81e854c64a468f553/Workshops/Origins.png -------------------------------------------------------------------------------- /Workshops/Playback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-elemental-mediatailor-tools/e0461cfdb1f3738ae48a51a81e854c64a468f553/Workshops/Playback.png --------------------------------------------------------------------------------