├── .gitignore ├── AUDIO_ONLY_EMP ├── .gitignore ├── .npmignore ├── README.md ├── bin │ └── index.ts ├── cdk.json ├── images │ └── code-sample-arch.png ├── jest.config.js ├── lib │ ├── config.ts │ ├── encoder-settings │ │ ├── audio.ts │ │ ├── base.ts │ │ ├── output-group-settings.ts │ │ ├── output.ts │ │ └── video.ts │ ├── ms │ │ ├── media-connect.ts │ │ ├── media-live.ts │ │ └── media-package.ts │ └── stack │ │ └── encoding-stack.ts ├── package-lock.json ├── package.json └── tsconfig.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── EMX_SECRET_SHARING ├── README.md ├── TEST.md ├── architecture │ └── architecture.png ├── emx-entitlement-consumer │ ├── .eslintignore │ ├── .eslintrc │ ├── .gitignore │ ├── .npmignore │ ├── .prettierrc │ ├── README.md │ ├── bin │ │ └── index.ts │ ├── cdk.json │ ├── jest.config.js │ ├── lambda │ │ └── index.js │ ├── lib │ │ ├── constants │ │ │ └── granted-config.ts │ │ ├── consumer-role-construct.ts │ │ ├── emx-construct.ts │ │ ├── encoder-settings │ │ │ ├── audio.ts │ │ │ ├── base.ts │ │ │ ├── output-group-settings.ts │ │ │ ├── output.ts │ │ │ └── video.ts │ │ ├── helpers │ │ │ ├── algorithm.ts │ │ │ └── arn-parsers.ts │ │ ├── index.ts │ │ └── mediaservices │ │ │ ├── media-live.ts │ │ │ └── media-package.ts │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json ├── emx-entitlement-grant │ ├── .eslintignore │ ├── .eslintrc │ ├── .gitignore │ ├── .npmignore │ ├── .prettierrc │ ├── README.md │ ├── bin │ │ └── index.ts │ ├── cdk.json │ ├── jest.config.js │ ├── lib │ │ ├── encoder-settings │ │ │ ├── audio.ts │ │ │ ├── base.ts │ │ │ ├── output-group-settings.ts │ │ │ ├── output.ts │ │ │ └── video.ts │ │ ├── granter-construct.ts │ │ ├── helpers │ │ │ └── algorithm.ts │ │ ├── index.ts │ │ └── media-services │ │ │ └── medialive.ts │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json └── screenshots │ ├── emx-alerts.png │ ├── emx-monitoring.png │ └── secret-manager.png ├── LEF ├── .gitignore ├── .npmignore ├── README.md ├── RELEASES.md ├── bin │ └── lef.ts ├── cdk.json ├── config │ ├── default │ │ ├── eventConfiguration.ts │ │ ├── eventGroupConfiguration.ts │ │ └── foundationConfiguration.ts │ └── low-latency │ │ ├── eventConfiguration.ts │ │ ├── eventGroupConfiguration.ts │ │ └── foundationConfiguration.ts ├── encoding-profiles │ ├── hd-avc-50fps-sample │ │ ├── medialive-cmaf-ingest-v1.json │ │ ├── medialive-hls-ts-v1.json │ │ ├── mediatailor-dash-cmaf-v1.json │ │ ├── mediatailor-dash-v1.json │ │ └── mediatailor-hls-cmaf-v1.json │ └── low-latency-hd-avc-50fps-sample │ │ ├── medialive-cmaf-ingest-v1.json │ │ ├── medialive-hls-ts-v1.json │ │ ├── mediatailor-dash-v1.json │ │ └── mediatailor-hls-cmaf-v1.json ├── jest.config.js ├── lambda │ ├── check_mediatailor_logger_role │ │ └── index.py │ ├── check_valid_sns_subscription_exists │ │ └── index.py │ └── daily_medialive_notification │ │ └── index.py ├── lib │ ├── event │ │ ├── eventConfigInterface.ts │ │ ├── lef_event_stack.ts │ │ ├── medialive.ts │ │ └── mediapackagev2.ts │ ├── event_group │ │ ├── cloudfront.ts │ │ ├── eventGroupConfigInterface.ts │ │ ├── lef_event_group_stack.ts │ │ └── mediatailor.ts │ └── foundation │ │ ├── foundation.ts │ │ ├── foundationConfigInterface.ts │ │ └── lef_foundation_stack.ts ├── package-lock.json ├── package.json ├── resources │ ├── ArchitectureDiagram-ElementalLive.png │ ├── ArchitectureDiagram-MediaLive.png │ └── LiveEventFramework.drawio ├── test │ └── lef.test.ts ├── tools │ ├── custom-transcode-profiles │ │ ├── README.md │ │ └── load_custom_transcode_profiles.py │ ├── elemental-live │ │ ├── README.md │ │ └── create_elemental_live_user.py │ ├── encoding-profile-generator │ │ ├── README.md │ │ ├── generate_encoding_profile_set.py │ │ ├── requirements.txt │ │ └── sample-configs │ │ │ ├── hd-avc-50fps-sample.yaml │ │ │ ├── hd-hevc-50fps-sample.yaml │ │ │ ├── low-latency-hd-avc-50fps-sample.yaml │ │ │ └── low-latency-hd-hevc-50fps-sample.yaml │ ├── generate_uris.sh │ ├── mediatailor-logger-role │ │ ├── README.md │ │ └── check_mediatailor_logger_role.py │ ├── requirements.txt │ ├── start_channel.sh │ └── stop_channel.sh └── tsconfig.json ├── LICENSE ├── LILO ├── CDK-README.md ├── LICENSE.txt ├── README.md ├── app.py ├── cdk.json ├── images │ └── LILO.png ├── lilo │ ├── __init__.py │ ├── iam_nested_stack.py │ ├── lambda │ │ └── medialive_channel_start_function.py │ ├── lilo_automation_nested_stack.py │ ├── lilo_stack.py │ └── medialive_nested_stack.py ├── requirements.txt ├── run.sh ├── setup.py └── source.bat ├── LIVE2VOD ├── .gitignore ├── .npmignore ├── README.md ├── cdk.json ├── images │ ├── CodeSample.png │ └── FullDiagram.png ├── jest.config.js ├── package-lock.json ├── package.json ├── src │ ├── @infrastructure │ │ ├── api │ │ │ └── create-harvest-api.ts │ │ ├── config.ts │ │ ├── encoder-settings │ │ │ ├── audio.ts │ │ │ ├── base.ts │ │ │ ├── output-group-settings.ts │ │ │ ├── output.ts │ │ │ └── video.ts │ │ ├── index.ts │ │ ├── lambdas │ │ │ ├── harvest-clip-action-fn.ts │ │ │ └── harvest-clip-completed-fn.ts │ │ ├── mediaservices │ │ │ ├── media-connect.ts │ │ │ ├── media-live.ts │ │ │ └── media-package.ts │ │ └── stacks │ │ │ ├── harvest-api.ts │ │ │ ├── harvest-complete.ts │ │ │ ├── media-services.ts │ │ │ └── playout.ts │ ├── harvest-complete.ts │ └── start-harvest.ts └── tsconfig.json ├── LIVE_CW_MONITOR ├── .gitignore ├── .npmignore ├── README.md ├── bin │ └── index.ts ├── cdk.json ├── images │ └── sample-architecture.png ├── jest.config.js ├── lib │ ├── config.ts │ ├── encoder-settings │ │ ├── audio.ts │ │ ├── base.ts │ │ ├── output-group-settings.ts │ │ ├── output.ts │ │ └── video.ts │ ├── mediaservices │ │ ├── media-live.ts │ │ └── media-package.ts │ └── stacks │ │ ├── media-services.ts │ │ └── monitoring-stack.ts ├── package-lock.json ├── package.json └── tsconfig.json ├── LIVE_THUMBNAIL_MONITORING ├── .gitignore ├── .npmignore ├── README.md ├── architecture │ └── architecture.png ├── cdk.json ├── jest.config.js ├── package-lock.json ├── package.json ├── screenshots │ ├── example.png │ ├── frame-capture-settings.png │ ├── output-setup.png │ ├── setup.png │ └── thumbnail-configuration.png ├── src │ ├── @infrastructure │ │ ├── api │ │ │ └── create-thumbnail-api.ts │ │ ├── index.ts │ │ ├── lambdas │ │ │ ├── file-delete-fn.ts │ │ │ └── thumbnail-fn.ts │ │ └── stacks │ │ │ └── thumbnail-api.ts │ ├── delete-thumbnails-contents.ts │ └── fetch-thumbnails.ts └── tsconfig.json ├── OTT ├── .gitignore ├── .npmignore ├── README.md ├── architecture_AEML-AEMP.png ├── bin │ └── medialive-mediapackage-cloudfront.ts ├── cdk.json ├── config │ ├── configuration.json │ └── encoding-profiles │ │ ├── hd-1080p-30fps.json │ │ ├── hd-720p-25fps.json │ │ └── sd-540p-30fps.json ├── jest.config.js ├── lib │ ├── cloudfront.ts │ ├── custom_ressources │ │ ├── medialive-autostart.ts │ │ └── mediapackage-cmaf-output.ts │ ├── lambda │ │ ├── medialive_channel_start_function │ │ │ └── index.py │ │ └── mediapackage_cmaf_geturl_function │ │ │ └── index.py │ ├── medialive-mediapackage-cloudfront-stack.ts │ ├── medialive.ts │ ├── mediapackage.ts │ └── mediapackage_secrets.ts ├── package-lock.json ├── package.json └── tsconfig.json ├── OTT_LOW_LATENCY ├── .gitignore ├── .npmignore ├── README.md ├── architecture_AEML-AEMP.png ├── bin │ └── medialive-mediapackage-cloudfront.ts ├── cdk.json ├── config │ ├── configuration.json │ └── encoding-profiles │ │ ├── hd-1080p-30fps.json │ │ ├── hd-720p-25fps.json │ │ └── sd-540p-30fps.json ├── jest.config.js ├── lib │ ├── cloudfront.ts │ ├── custom_ressources │ │ ├── medialive-autostart.ts │ │ └── mediapackage-extra-attributes.ts │ ├── lambda │ │ ├── medialive_channel_start_function │ │ │ └── index.py │ │ └── mediapackage_extra_attrib_function │ │ │ └── index.py │ ├── medialive-mediapackage-cloudfront-stack.ts │ ├── medialive.ts │ └── mediapackagev2.ts ├── package-lock.json ├── package.json └── tsconfig.json ├── PRIVATE_LIVE ├── .gitignore ├── .npmignore ├── README.md ├── bin │ └── index.ts ├── cdk.json ├── images │ ├── CodeDemo.png │ └── HighLevelArchitecture.png ├── jest.config.js ├── lib │ ├── config.ts │ ├── encoder-settings │ │ ├── audio.ts │ │ ├── base.ts │ │ ├── output-group-settings.ts │ │ ├── output.ts │ │ └── video.ts │ ├── mediaservices │ │ ├── media-connect.ts │ │ └── media-live.ts │ ├── stacks │ │ ├── media-services.ts │ │ └── networking.ts │ └── vpc.ts ├── package-lock.json ├── package.json └── tsconfig.json ├── README.md ├── SSAI ├── .gitignore ├── .npmignore ├── README.md ├── architecture_AEML-AEMP-AEMT.png ├── bin │ └── medialive-mediapackage-mediatailor-cloudfront.ts ├── cdk.json ├── config │ ├── configuration.json │ └── encoding-profiles │ │ ├── hd-1080p-30fps.json │ │ ├── hd-720p-25fps.json │ │ └── sd-540p-30fps.json ├── jest.config.js ├── lib │ ├── cloudfront.ts │ ├── custom_ressources │ │ ├── medialive-autostart.ts │ │ ├── mediapackage-cmaf-output.ts │ │ └── s3-update-config-file.ts │ ├── lambda │ │ ├── medialive_channel_start_function │ │ │ └── index.py │ │ └── mediapackage_cmaf_geturl_function │ │ │ └── index.py │ ├── medialive-mediapackage-mediatailor-cloudfront-stack.ts │ ├── medialive.ts │ ├── mediapackage.ts │ ├── mediapackage_secrets.ts │ └── mediatailor.ts ├── package.json ├── resources │ └── demo_website │ │ ├── config.json │ │ ├── css │ │ └── app.css │ │ ├── img │ │ ├── favicon.ico │ │ └── smile.png │ │ ├── index.html │ │ └── js │ │ └── main.js └── tsconfig.json ├── SSAI_CMAF_AND_DASH ├── .gitignore ├── .npmignore ├── README.md ├── architecture_AEML-AEMP-AEMT.png ├── bin │ └── medialive-mediapackage-mediatailor-cloudfront.ts ├── cdk.json ├── config │ ├── configuration.json │ └── encoding-profiles │ │ ├── hd-1080p-30fps.json │ │ ├── hd-720p-25fps.json │ │ └── sd-540p-30fps.json ├── images │ └── SSAI_CMAF_DASH.png ├── jest.config.js ├── lib │ ├── cloudfront.ts │ ├── custom_ressources │ │ ├── medialive-autostart.ts │ │ ├── mediapackage-cmaf-output.ts │ │ ├── mediatailor-update-playback-config.ts │ │ └── s3-update-config-file.ts │ ├── lambda │ │ ├── medialive_channel_start_function │ │ │ └── index.py │ │ ├── mediapackage_cmaf_geturl_function │ │ │ └── index.py │ │ └── mediatailor_update_playback_config_function │ │ │ └── index.py │ ├── medialive-mediapackage-mediatailor-cloudfront-stack.ts │ ├── medialive.ts │ ├── mediapackage.ts │ ├── mediapackage_secrets.ts │ └── mediatailor.ts ├── package.json ├── resources │ └── demo_website │ │ ├── config.json │ │ ├── css │ │ └── app.css │ │ ├── img │ │ ├── favicon.ico │ │ └── smile.png │ │ ├── index.html │ │ └── js │ │ └── main.js └── tsconfig.json └── SUSTAINABILITY_LIVE ├── .gitignore ├── .npmignore ├── README.md ├── bin └── index.ts ├── cdk.json ├── images └── channels.drawio.png ├── jest.config.js ├── lib ├── config.ts ├── encoder-settings │ ├── audio.ts │ ├── base.ts │ ├── output-group-settings.ts │ ├── output.ts │ └── video.ts ├── mediaservices │ ├── media-connect.ts │ ├── media-live.ts │ └── media-package.ts └── stacks │ └── media-services.ts ├── package-lock.json ├── package.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /AUDIO_ONLY_EMP/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | cdk-exports.json 6 | 7 | # CDK asset staging directory 8 | .cdk.staging 9 | cdk.out 10 | 11 | cdk.context.json -------------------------------------------------------------------------------- /AUDIO_ONLY_EMP/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /AUDIO_ONLY_EMP/bin/index.ts: -------------------------------------------------------------------------------- 1 | import { App, Aspects } from "aws-cdk-lib"; 2 | import { AudioOnlyEndcodingStack } from "./stack/encoding-stack"; 3 | import { AwsSolutionsChecks } from "cdk-nag"; 4 | 5 | export const STACK_PREFIX_NAME = "MediaServicesRefArch"; 6 | 7 | const app = new App(); 8 | new AudioOnlyEndcodingStack(app); 9 | Aspects.of(app).add(new AwsSolutionsChecks()); 10 | -------------------------------------------------------------------------------- /AUDIO_ONLY_EMP/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/index.ts", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 21 | "@aws-cdk/core:stackRelativeExports": true, 22 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 23 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 24 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 25 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 28 | "@aws-cdk/core:checkSecretUsage": true, 29 | "@aws-cdk/aws-iam:minimizePolicies": true, 30 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, 31 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 32 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 33 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, 34 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, 35 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, 36 | "@aws-cdk/core:enablePartitionLiterals": true, 37 | "@aws-cdk/core:target-partitions": [ 38 | "aws", 39 | "aws-cn" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /AUDIO_ONLY_EMP/images/code-sample-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/AUDIO_ONLY_EMP/images/code-sample-arch.png -------------------------------------------------------------------------------- /AUDIO_ONLY_EMP/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /AUDIO_ONLY_EMP/lib/config.ts: -------------------------------------------------------------------------------- 1 | // Edit to use your desired configuration 2 | 3 | /** 4 | * MediaConnect Whitelist CIDR - this is to narrow down traffic to ensure that you are only receiving traffic from your upstream system. 5 | */ 6 | export const DEST_MEDIA_CONNECT_WHITELIST_CIDR = "0.0.0.0/0"; 7 | -------------------------------------------------------------------------------- /AUDIO_ONLY_EMP/lib/encoder-settings/audio.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { Base, OPTIONS } from "./base"; 3 | 4 | export class AudioAAC extends Base { 5 | constructor(id: string, private aacProps?: CfnChannel.AacSettingsProperty) { 6 | super(id, OPTIONS.AUDIO); 7 | } 8 | 9 | private baseAacSettings: CfnChannel.AacSettingsProperty = {}; 10 | 11 | public getAudioProfile(): CfnChannel.AudioDescriptionProperty { 12 | return { 13 | codecSettings: { 14 | aacSettings: { 15 | ...this.baseAacSettings, 16 | ...this.aacProps, 17 | }, 18 | }, 19 | name: this.getUniqueId(), 20 | }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AUDIO_ONLY_EMP/lib/encoder-settings/base.ts: -------------------------------------------------------------------------------- 1 | import { createHash } from "crypto"; 2 | 3 | export enum OPTIONS { 4 | AUDIO = "audio", 5 | VIDEO = "video", 6 | NONE = "none", 7 | } 8 | 9 | export class Base { 10 | constructor(private parentId: string, private option: OPTIONS) {} 11 | 12 | private generatedId: string = createHash("md5").update(this.parentId).digest("hex").toString().substring(0, 5); 13 | protected uniqueId = this.generalUniqueName(this.generatedId, this.option); 14 | 15 | public getUniqueId() { 16 | return this.uniqueId; 17 | } 18 | 19 | private generalUniqueName(id: string, prefixOptions: OPTIONS) { 20 | return `${prefixOptions != OPTIONS.NONE ? `${prefixOptions}_` : ""}${id}`; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AUDIO_ONLY_EMP/lib/encoder-settings/output-group-settings.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { CfnChannel as MpChannel } from "aws-cdk-lib/aws-mediapackage"; 3 | 4 | export class MPOutputGroupSettings { 5 | constructor(private mp: MpChannel) {} 6 | 7 | private baseProps: CfnChannel.OutputGroupSettingsProperty = { 8 | mediaPackageGroupSettings: { 9 | destination: { 10 | destinationRefId: this.mp.ref, 11 | }, 12 | }, 13 | }; 14 | 15 | public getOutputGroupSettings(): CfnChannel.OutputGroupSettingsProperty { 16 | return { 17 | ...this.baseProps, 18 | }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /AUDIO_ONLY_EMP/lib/encoder-settings/output.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { Base, OPTIONS } from "./base"; 3 | 4 | interface Inputs { 5 | videoSettings?: string; 6 | audioSettings?: string[]; 7 | } 8 | export class MpOutput extends Base { 9 | constructor(id: string, private props: Inputs) { 10 | super(id, OPTIONS.NONE); 11 | } 12 | 13 | private baseProps: CfnChannel.OutputProperty = { 14 | outputSettings: { 15 | mediaPackageOutputSettings: {}, 16 | }, 17 | outputName: this.getUniqueId(), 18 | videoDescriptionName: this.props.videoSettings, 19 | audioDescriptionNames: this.props.audioSettings, 20 | captionDescriptionNames: [], 21 | }; 22 | 23 | public getOutputSettings(): CfnChannel.OutputProperty { 24 | return this.baseProps; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /AUDIO_ONLY_EMP/lib/encoder-settings/video.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { Base, OPTIONS } from "./base"; 3 | 4 | interface IVideo { 5 | height: number; 6 | width: number; 7 | } 8 | 9 | type H264BaseSettings = Omit; 10 | interface H264Settings extends H264BaseSettings { 11 | bitrate: number; 12 | } 13 | 14 | export class VideoH264 extends Base { 15 | constructor(private id: string, private resolutionProps: IVideo, private h264Props: H264Settings) { 16 | super(id, OPTIONS.VIDEO); 17 | } 18 | 19 | private baseH264Props: H264BaseSettings = { 20 | framerateControl: "SPECIFIED", 21 | framerateNumerator: 25, 22 | framerateDenominator: 1, 23 | gopSize: 2, 24 | gopSizeUnits: "SECONDS", 25 | parControl: "SPECIFIED", 26 | parNumerator: 1, 27 | parDenominator: 1, 28 | }; 29 | 30 | public getVideoProfile(): CfnChannel.VideoDescriptionProperty { 31 | return { 32 | codecSettings: { 33 | h264Settings: { 34 | ...this.baseH264Props, 35 | ...this.h264Props, 36 | }, 37 | }, 38 | height: this.resolutionProps.height, 39 | name: this.getUniqueId(), 40 | width: this.resolutionProps.width, 41 | }; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /AUDIO_ONLY_EMP/lib/ms/media-connect.ts: -------------------------------------------------------------------------------- 1 | import { CfnOutput, Stack } from "aws-cdk-lib"; 2 | import { CfnFlow } from "aws-cdk-lib/aws-mediaconnect"; 3 | import { STACK_PREFIX_NAME } from "../../bin"; 4 | import { DEST_MEDIA_CONNECT_WHITELIST_CIDR } from "../config"; 5 | 6 | /** 7 | * Create input flow 8 | */ 9 | export function createMediaConnectFlow(stack: Stack, region: string): CfnFlow { 10 | const flow = new CfnFlow(stack, `flow-${region}`, { 11 | name: `${STACK_PREFIX_NAME}-ao-flow-${region}`, 12 | source: { 13 | name: "source-to-emx", 14 | protocol: "zixi-push", 15 | whitelistCidr: DEST_MEDIA_CONNECT_WHITELIST_CIDR, 16 | }, 17 | availabilityZone: region, 18 | }); 19 | 20 | new CfnOutput(stack, "emx-input-flow", { 21 | exportName: `${stack.stackName}-emx-input-flow`, 22 | value: flow.ref, 23 | }); 24 | 25 | return flow; 26 | } 27 | -------------------------------------------------------------------------------- /AUDIO_ONLY_EMP/lib/ms/media-package.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { CfnChannel, CfnOriginEndpoint } from "aws-cdk-lib/aws-mediapackage"; 3 | import { STACK_PREFIX_NAME } from "../../bin"; 4 | 5 | export function createMediaPackage(stack: Stack): CfnChannel { 6 | const mp = new CfnChannel(stack, "emp-channel", { 7 | id: `${STACK_PREFIX_NAME}-ao-emp-channel`, 8 | }); 9 | 10 | const endpoint = new CfnOriginEndpoint(stack, "mp-endpoint", { 11 | channelId: mp.id, 12 | id: `${STACK_PREFIX_NAME}-ao-emp-channel-hls-output`, 13 | hlsPackage: { 14 | segmentDurationSeconds: 10, 15 | }, 16 | }); 17 | 18 | endpoint.node.addDependency(mp); 19 | 20 | return mp; 21 | } 22 | -------------------------------------------------------------------------------- /AUDIO_ONLY_EMP/lib/stack/encoding-stack.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { createMediaLive, createMediaLiveInput } from "../ms/media-live"; 3 | import { createMediaPackage } from "../ms/media-package"; 4 | import { createMediaConnectFlow } from "../ms/media-connect"; 5 | import { STACK_PREFIX_NAME } from "../../bin"; 6 | import { Construct } from "constructs"; 7 | 8 | export class AudioOnlyEndcodingStack extends Stack { 9 | constructor(app: Construct) { 10 | super(app, `${STACK_PREFIX_NAME}-ao-encoding-video-stack`, { 11 | env: { 12 | region: process.env.CDK_DEFAULT_REGION, 13 | account: process.env.CDK_DEFAULT_ACCOUNT, 14 | }, 15 | description: 16 | "AWS CDK MediaServices Reference Architectures: Audio Only workflow with EMP Origin. Stack contains MediaLive encoding pushing to EMP in Origin stack (with small video output).", 17 | }); 18 | } 19 | 20 | protected mediaConnectFlow1A = createMediaConnectFlow(this, `${this.region}a`); 21 | protected input = createMediaLiveInput(this, this.mediaConnectFlow1A.attrFlowArn); 22 | protected mp = createMediaPackage(this); 23 | 24 | protected eml = createMediaLive(this, { 25 | input: this.input, 26 | mp: this.mp, 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /AUDIO_ONLY_EMP/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "audio-only-using-emp", 3 | "version": "0.1.0", 4 | "engines": { 5 | "node": ">=14.0.0 <=18.17.1" 6 | }, 7 | "scripts": { 8 | "watch": "tsc -w", 9 | "test": "jest", 10 | "cdk": "cdk" 11 | }, 12 | "devDependencies": { 13 | "@types/jest": "^27.5.2", 14 | "@types/node": "10.17.27", 15 | "@types/prettier": "2.6.0", 16 | "@typescript-eslint/eslint-plugin": "^5.40.1", 17 | "@typescript-eslint/parser": "^5.40.1", 18 | "aws-cdk": "2.87.0", 19 | "cdk-nag": "2.21.50", 20 | "jest": "^27.5.1", 21 | "ts-jest": "^27.1.4", 22 | "ts-node": "^10.9.1", 23 | "typescript": "~3.9.7" 24 | }, 25 | "dependencies": { 26 | "aws-cdk-lib": "2.87.0", 27 | "constructs": "^10.0.0", 28 | "source-map-support": "^0.5.21" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /AUDIO_ONLY_EMP/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2018" 7 | ], 8 | "declaration": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": false, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "experimentalDecorators": true, 21 | "strictPropertyInitialization": false, 22 | "typeRoots": [ 23 | "./node_modules/@types" 24 | ], 25 | "rootDirs": ["bin", "lib"], 26 | }, 27 | "exclude": [ 28 | "node_modules", 29 | "cdk.out" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/TEST.md: -------------------------------------------------------------------------------- 1 | # Testing the code 2 | 3 | ## Overview 4 | 5 | In this section we will guide you through how to test this architecture and trigger a fetch of the secret from granting account. 6 | 7 | ## Opening the live stream 8 | 9 | Ensure both Elemental MediaLive and Elemental MediaConnect is running in each accounts. 10 | Navigate to "outputs" on the CloudFormation stack in the consuming account where you can fetch the manifest URL to open in a player (such as HLS.js) to view the stream. 11 | 12 | ## Automatic detection and remediation 13 | 14 | In the granting account, you can find the secret within Secrets Manager, named similar to `EmxEntitlementGrant/-`. 15 | Like mentioned in the main [README.md](./README.md), this is not a key rotation solution - but you can test for when a secret is altered by mistake. 16 | 17 | In the console press the "Edit" button and change a couple of the characters in the Secret value field. It is important to keep the same length due to the encryption string length. 18 | 19 | ![secret-manager-view](./screenshots/secret-manager.png) 20 | 21 | Simultaneously, watch the stream you previously loaded in to your test player - after a period of time you'll see the the stream go to black frames with no audio - this is because AWS Elemental MediaConnect can no longer decrypt the stream. 22 | 23 | If you are in the console, navigate to Elemental MediaConnect in the consuming account - you will be able to see alerts under the MediaConnect Flow created via this deployment. 24 | 25 | ![emx-alerts-decrypting-errors](./screenshots/emx-alerts.png) 26 | 27 | You can also see something happened in the stream within the "Source health metrics". 28 | 29 | ![emx-alerts-errors](./screenshots/emx-monitoring.png) 30 | 31 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/architecture/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/EMX_SECRET_SHARING/architecture/architecture.png -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-consumer/.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules/* 2 | **/package-lock.json 3 | 4 | **/build/* 5 | **/bundles/* 6 | **/dist/* 7 | 8 | **/cdk.out 9 | **/.cdk.staging -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-consumer/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": [ 4 | "@typescript-eslint", 5 | "no-loops", 6 | ], 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 12 | "prettier", 13 | ], 14 | "parserOptions": { 15 | "project": ["./EMX_SECRET_SHARING/emx-entitlement-consumer/tsconfig.json"], 16 | "sourceType": "module", 17 | }, 18 | "rules": { 19 | "no-console": 2, 20 | "no-loops/no-loops": 2, 21 | }, 22 | } -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-consumer/.gitignore: -------------------------------------------------------------------------------- 1 | !jest.config.js 2 | *.d.ts 3 | node_modules 4 | cdk-exports.json 5 | 6 | # CDK asset staging directory 7 | .cdk.staging 8 | cdk.out 9 | 10 | cdk.context.json 11 | 12 | .DS_Store 13 | dist -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-consumer/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-consumer/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": false, 5 | "printWidth": 160 6 | } -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-consumer/README.md: -------------------------------------------------------------------------------- 1 | Refer to main [README.md](../README.md). -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-consumer/bin/index.ts: -------------------------------------------------------------------------------- 1 | import { App, Aspects } from "aws-cdk-lib"; 2 | import { ConsumeEmxEntitlementStack } from "../lib/"; 3 | import { AwsSolutionsChecks } from 'cdk-nag'; 4 | 5 | const app = new App(); 6 | 7 | /** 8 | * Determines whether context variable exists to just deploy the role construct. 9 | * This is important for Part 1 of the deployment (refer to README.md for full instructions). 10 | * 11 | * @returns boolean 12 | */ 13 | function deploymentRoleStep(): boolean{ 14 | const stage = app.node.tryGetContext("stage") as string; 15 | if(!stage || stage != "role") return false; 16 | return true; 17 | } 18 | 19 | const consumeEmxEntitlement = new ConsumeEmxEntitlementStack(app); 20 | const consumerRole = consumeEmxEntitlement.createConsumeRole(); 21 | 22 | if(!deploymentRoleStep()){ 23 | const consumerFlow = consumeEmxEntitlement.createEntitlementConsumer(consumerRole.role); 24 | consumeEmxEntitlement.createStream(consumerFlow.emx) 25 | } 26 | 27 | Aspects.of(app).add(new AwsSolutionsChecks()); 28 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-consumer/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/index.ts", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 21 | "@aws-cdk/core:checkSecretUsage": true, 22 | "@aws-cdk/core:target-partitions": [ 23 | "aws", 24 | "aws-cn" 25 | ], 26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 28 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, 29 | "@aws-cdk/aws-iam:minimizePolicies": true, 30 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 31 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 32 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, 33 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, 34 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, 35 | "@aws-cdk/core:enablePartitionLiterals": true, 36 | "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, 37 | "@aws-cdk/aws-iam:standardizedServicePrincipals": true, 38 | "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, 39 | "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, 40 | "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, 41 | "@aws-cdk/aws-route53-patters:useCertificate": true, 42 | "@aws-cdk/customresources:installLatestAwsSdkDefault": false 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-consumer/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-consumer/lambda/index.js: -------------------------------------------------------------------------------- 1 | const sm = require("@aws-sdk/client-secrets-manager"); 2 | 3 | exports.handler = async function (event, context) { 4 | 5 | try { 6 | 7 | const smclient = new sm.SecretsManagerClient({}); 8 | const smresponse = await smclient.send( 9 | new sm.GetSecretValueCommand({ 10 | SecretId: process.env.GRANTED_SECRET, 11 | }), 12 | ); 13 | 14 | await smclient.send( 15 | new sm.UpdateSecretCommand({ 16 | SecretId: process.env.CONSUMER_SECRET, 17 | SecretString: smresponse.SecretString, 18 | }), 19 | ); 20 | 21 | } catch (err) { 22 | console.error(err); 23 | } 24 | 25 | return "OK"; 26 | }; -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-consumer/lib/constants/granted-config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 ARN's below need to be the values in the "granter" account 3 | * 4 | * Can be found in the outputs of the stack created in the other account 5 | */ 6 | 7 | /** 8 | * EMX Entitlement ARN from Granted account 9 | * 10 | * Can be found in the "Resources" tab on the CloudFormation stack 11 | */ 12 | export const ENTITLEMENT_ARN = "arn:aws:mediaconnect:...."; 13 | 14 | /** 15 | * ARN of the secret in the Granted account 16 | */ 17 | export const SECRET_ARN = "arn:aws:secretsmanager:...."; 18 | 19 | /** 20 | * If you haven't been provided an SNS Topic ARN - set this to `undefined` 21 | */ 22 | export const SNS_ARN = "arn:aws:sns:...."; 23 | 24 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-consumer/lib/consumer-role-construct.ts: -------------------------------------------------------------------------------- 1 | import { CfnOutput } from "aws-cdk-lib"; 2 | import { Role, ServicePrincipal, ManagedPolicy } from "aws-cdk-lib/aws-iam"; 3 | import { NagSuppressions } from "cdk-nag"; 4 | import { Construct } from "constructs"; 5 | 6 | /** 7 | * This construct needs to be deployed first but a "consumer" 8 | * It provides mainly a role that will need to be provided back to the "supplier" so that it is able to assume a role in their account. 9 | * 10 | * Without this Construct, the consumable role in the "granting" account will be open to the consumer whole account which is not least privilage. 11 | */ 12 | export class EmxEntitlementConsumerRole extends Construct { 13 | constructor(scope: Construct) { 14 | super(scope, "entitlement"); 15 | 16 | NagSuppressions.addResourceSuppressions(this.role, [ 17 | { id: 'AwsSolutions-IAM4', reason: 'Lambda execution role to get stats on execution.' }, 18 | ]); 19 | } 20 | 21 | public role = new Role(this, "fn-role", { 22 | assumedBy: new ServicePrincipal("lambda.amazonaws.com"), 23 | managedPolicies: [ManagedPolicy.fromManagedPolicyArn(this, "write", "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole")], 24 | }); 25 | 26 | public outputs = [ 27 | new CfnOutput(this, "consumer-role-output", { 28 | value: this.role.roleArn, 29 | exportName: "consumer-role-arn-output", 30 | description: "Send this Role ARN to the supplier of your Elemental MediaConnect Output Flow.", 31 | }), 32 | ]; 33 | } 34 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-consumer/lib/encoder-settings/audio.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { Base, OPTIONS } from "./base"; 3 | 4 | export class AudioAAC extends Base { 5 | constructor(id: string, private aacProps?: CfnChannel.AacSettingsProperty) { 6 | super(id, OPTIONS.AUDIO); 7 | } 8 | 9 | private baseAacSettings: CfnChannel.AacSettingsProperty = {}; 10 | 11 | public getAudioProfile(): CfnChannel.AudioDescriptionProperty { 12 | return { 13 | codecSettings: { 14 | aacSettings: { 15 | ...this.baseAacSettings, 16 | ...this.aacProps, 17 | }, 18 | }, 19 | name: this.getUniqueId(), 20 | }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-consumer/lib/encoder-settings/base.ts: -------------------------------------------------------------------------------- 1 | import { createHash } from "crypto"; 2 | 3 | export enum OPTIONS { 4 | AUDIO = "audio", 5 | VIDEO = "video", 6 | NONE = "none", 7 | } 8 | 9 | export class Base { 10 | constructor(private parentId: string, private option: OPTIONS) {} 11 | 12 | private generatedId: string = createHash("md5").update(this.parentId).digest("hex").toString().substring(0, 5); 13 | protected uniqueId = this.generalUniqueName(this.generatedId, this.option); 14 | 15 | public getUniqueId(): string { 16 | return this.uniqueId; 17 | } 18 | 19 | private generalUniqueName(id: string, prefixOptions: OPTIONS): string { 20 | return `${prefixOptions != OPTIONS.NONE ? `${prefixOptions}_` : ""}${id}`; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-consumer/lib/encoder-settings/output-group-settings.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | 3 | export class MPOutputGroupSettings { 4 | constructor(private output: string) {} 5 | 6 | private baseProps: CfnChannel.OutputGroupSettingsProperty = { 7 | cmafIngestGroupSettings: { 8 | destination: { 9 | destinationRefId: this.output 10 | }, 11 | } 12 | }; 13 | 14 | public getOutputGroupSettings(): CfnChannel.OutputGroupSettingsProperty { 15 | return { 16 | ...this.baseProps, 17 | }; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-consumer/lib/encoder-settings/output.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { Base, OPTIONS } from "./base"; 3 | 4 | export class MpOutput extends Base { 5 | constructor(id: string, private nameModifier: string, private props: { videoSettings: string } | {audioSettings: string}) { 6 | super(id, OPTIONS.NONE); 7 | } 8 | 9 | private baseProps: CfnChannel.OutputProperty = { 10 | outputSettings: { 11 | cmafIngestOutputSettings: { 12 | nameModifier: this.nameModifier, 13 | }, 14 | }, 15 | outputName: this.getUniqueId(), 16 | videoDescriptionName: "videoSettings" in this.props ? this.props.videoSettings : undefined, 17 | audioDescriptionNames: "audioSettings" in this.props ? [this.props.audioSettings] : undefined, 18 | }; 19 | 20 | public getOutputSettings(): CfnChannel.OutputProperty { 21 | return this.baseProps; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-consumer/lib/encoder-settings/video.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { Base, OPTIONS } from "./base"; 3 | 4 | interface IVideo { 5 | height: number; 6 | width: number; 7 | } 8 | 9 | type H264BaseSettings = Omit; 10 | interface H264Settings extends H264BaseSettings { 11 | bitrate: number; 12 | } 13 | 14 | export class VideoH264 extends Base { 15 | constructor(private id: string, private resolutionProps: IVideo, private h264Props: H264Settings) { 16 | super(id, OPTIONS.VIDEO); 17 | } 18 | 19 | private baseH264Props: H264BaseSettings = { 20 | framerateControl: "SPECIFIED", 21 | framerateNumerator: 25, 22 | framerateDenominator: 1, 23 | gopSize: 2, 24 | gopSizeUnits: "SECONDS", 25 | parControl: "SPECIFIED", 26 | parNumerator: 1, 27 | parDenominator: 1, 28 | }; 29 | 30 | public getVideoProfile(): CfnChannel.VideoDescriptionProperty { 31 | return { 32 | codecSettings: { 33 | h264Settings: { 34 | ...this.baseH264Props, 35 | ...this.h264Props, 36 | }, 37 | }, 38 | height: this.resolutionProps.height, 39 | name: this.getUniqueId(), 40 | width: this.resolutionProps.width, 41 | }; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-consumer/lib/helpers/algorithm.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Encryption Algorithm 3 | */ 4 | export enum ALGORITHM { 5 | AES128 = "AES128", 6 | AES192 = "AES192", 7 | AES256 = "AES256", 8 | } -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-consumer/lib/helpers/arn-parsers.ts: -------------------------------------------------------------------------------- 1 | 2 | export function parseEntitlementArn(arn: string){ 3 | const splitArn = arn.split(":"); 4 | return splitArn[splitArn.length-1]; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-consumer/lib/mediaservices/media-package.ts: -------------------------------------------------------------------------------- 1 | import { Aws, Stack } from "aws-cdk-lib"; 2 | import { CfnChannel, CfnOriginEndpoint, CfnChannelGroup, CfnOriginEndpointPolicy } from "aws-cdk-lib/aws-mediapackagev2"; 3 | import { STACK_PREFIX_NAME } from ".."; 4 | import { Effect, PolicyDocument, PolicyStatement, ServicePrincipal } from "aws-cdk-lib/aws-iam"; 5 | import { Distribution } from "aws-cdk-lib/aws-cloudfront"; 6 | 7 | export function createMediaPackageChannelGroup(stack: Stack): CfnChannelGroup { 8 | return new CfnChannelGroup(stack, "mp-group", { 9 | channelGroupName: `${STACK_PREFIX_NAME}-entitled-channel-group`, 10 | }); 11 | } 12 | 13 | export function createMediaPackage(stack: Stack, channelGroup: CfnChannelGroup, distribution: Distribution): { 14 | channel: CfnChannel, 15 | endpoint: CfnOriginEndpoint, 16 | } { 17 | const channel = new CfnChannel(stack, "emp-channel", { 18 | channelName: `${STACK_PREFIX_NAME}-entitled-channel`, 19 | channelGroupName: channelGroup.channelGroupName, 20 | inputType: "CMAF" 21 | }); 22 | 23 | const endpoint = new CfnOriginEndpoint(stack, "emp-endpoint", { 24 | channelGroupName: channelGroup.channelGroupName, 25 | channelName: channel.channelName, 26 | originEndpointName: `${STACK_PREFIX_NAME}-entitled-hls-output`, 27 | containerType: "TS", 28 | hlsManifests: [{ 29 | manifestName: "index", 30 | }], 31 | startoverWindowSeconds: 10800, 32 | }); 33 | 34 | const policy = new CfnOriginEndpointPolicy(stack, "origin-endpoint-policy", { 35 | channelGroupName: channelGroup.channelGroupName, 36 | channelName: channel.channelName, 37 | originEndpointName: endpoint.originEndpointName, 38 | policy: new PolicyDocument({ 39 | statements: [ 40 | new PolicyStatement({ 41 | sid: "AllowRequestsFromCloudFront", 42 | effect: Effect.ALLOW, 43 | actions: [ 44 | "mediapackagev2:GetObject", 45 | "mediapackagev2:GetHeadObject", 46 | ], 47 | principals: [ 48 | new ServicePrincipal("cloudfront.amazonaws.com"), 49 | ], 50 | resources: [endpoint.attrArn], 51 | conditions: { 52 | StringEquals: { 53 | "aws:SourceArn": [`arn:aws:cloudfront::${Aws.ACCOUNT_ID}:distribution/${distribution.distributionId}` ], 54 | }, 55 | }, 56 | }), 57 | ] 58 | }) 59 | }); 60 | 61 | endpoint.addDependency(channel); 62 | channel.addDependency(channelGroup); 63 | policy.addDependency(endpoint); 64 | 65 | return { 66 | channel, 67 | endpoint, 68 | }; 69 | } 70 | 71 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-consumer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "emx-entitlement-consumer", 3 | "version": "0.1.0", 4 | "engines": { 5 | "node": ">=18.0.0" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "lint": "eslint . --ext .ts", 11 | "prettier": "prettier --check --config .prettierrc 'lib/**/*.ts' 'bin/**/*.ts'", 12 | "format": "prettier --write --config .prettierrc 'lib/**/*.ts' 'bin/**/*.ts'", 13 | "test": "jest", 14 | "cdk": "cdk" 15 | }, 16 | "devDependencies": { 17 | "@types/jest": "^27.5.2", 18 | "@types/node": "10.17.27", 19 | "@typescript-eslint/eslint-plugin": "^5.40.1", 20 | "@typescript-eslint/parser": "^5.40.1", 21 | "aws-cdk": "2.177.0", 22 | "cdk-nag": "2.21.50", 23 | "eslint": "^8.31.0", 24 | "eslint-config-prettier": "^8.5.0", 25 | "eslint-plugin-no-loops": "^0.3.0", 26 | "jest": "^27.5.1", 27 | "prettier": "^2.8.1", 28 | "ts-jest": "^27.1.4", 29 | "ts-node": "^10.9.1", 30 | "typescript": "~3.9.7" 31 | }, 32 | "dependencies": { 33 | "aws-cdk-lib": "2.177.0", 34 | "aws-sdk": "^2.1354.0", 35 | "constructs": "^10.0.0", 36 | "source-map-support": "^0.5.21" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-consumer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2020" 7 | ], 8 | "declaration": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": false, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "experimentalDecorators": true, 21 | "strictPropertyInitialization": false, 22 | "typeRoots": [ 23 | "./node_modules/@types" 24 | ], 25 | "types": ["node"], 26 | "rootDirs": ["lib", "bin", "lambda"], 27 | }, 28 | "exclude": [ 29 | "node_modules", 30 | "cdk.out" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-grant/.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules/* 2 | **/package-lock.json 3 | 4 | **/build/* 5 | **/bundles/* 6 | **/dist/* 7 | 8 | **/cdk.out 9 | **/.cdk.staging -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-grant/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": [ 4 | "@typescript-eslint", 5 | "no-loops", 6 | ], 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 12 | "prettier", 13 | ], 14 | "parserOptions": { 15 | "project": ["./EMX_SECRET_SHARING/emx-entitlement-grant/tsconfig.json"], 16 | "sourceType": "module", 17 | }, 18 | "rules": { 19 | "no-console": 2, 20 | "no-loops/no-loops": 2, 21 | }, 22 | } -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-grant/.gitignore: -------------------------------------------------------------------------------- 1 | !jest.config.js 2 | *.d.ts 3 | node_modules 4 | cdk-exports.json 5 | 6 | # CDK asset staging directory 7 | .cdk.staging 8 | cdk.out 9 | 10 | cdk.context.json 11 | 12 | .DS_Store 13 | dist -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-grant/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-grant/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": false, 5 | "printWidth": 160 6 | } -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-grant/README.md: -------------------------------------------------------------------------------- 1 | Refer to main [README.md](../README.md). -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-grant/bin/index.ts: -------------------------------------------------------------------------------- 1 | import { App, Aspects } from "aws-cdk-lib"; 2 | import { EmxEntitlementGrantStack } from "../lib/"; 3 | import { AwsSolutionsChecks } from 'cdk-nag'; 4 | 5 | const app = new App(); 6 | new EmxEntitlementGrantStack(app); 7 | 8 | Aspects.of(app).add(new AwsSolutionsChecks()); 9 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-grant/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/index.ts", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 21 | "@aws-cdk/core:checkSecretUsage": true, 22 | "@aws-cdk/core:target-partitions": [ 23 | "aws", 24 | "aws-cn" 25 | ], 26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 28 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, 29 | "@aws-cdk/aws-iam:minimizePolicies": true, 30 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 31 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 32 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, 33 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, 34 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, 35 | "@aws-cdk/core:enablePartitionLiterals": true, 36 | "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, 37 | "@aws-cdk/aws-iam:standardizedServicePrincipals": true, 38 | "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, 39 | "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, 40 | "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, 41 | "@aws-cdk/aws-route53-patters:useCertificate": true, 42 | "@aws-cdk/customresources:installLatestAwsSdkDefault": false 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-grant/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-grant/lib/encoder-settings/audio.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { Base, OPTIONS } from "./base"; 3 | 4 | export class AudioAAC extends Base { 5 | constructor(id: string, private aacProps?: CfnChannel.AacSettingsProperty) { 6 | super(id, OPTIONS.AUDIO); 7 | } 8 | 9 | private baseAacSettings: CfnChannel.AacSettingsProperty = {}; 10 | 11 | public getAudioProfile(): CfnChannel.AudioDescriptionProperty { 12 | return { 13 | codecSettings: { 14 | aacSettings: { 15 | ...this.baseAacSettings, 16 | ...this.aacProps, 17 | }, 18 | }, 19 | name: this.getUniqueId(), 20 | }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-grant/lib/encoder-settings/base.ts: -------------------------------------------------------------------------------- 1 | import { createHash } from "crypto"; 2 | 3 | export enum OPTIONS { 4 | AUDIO = "audio", 5 | VIDEO = "video", 6 | NONE = "none", 7 | } 8 | 9 | export class Base { 10 | constructor(private parentId: string, private option: OPTIONS) {} 11 | 12 | private generatedId: string = createHash("md5").update(this.parentId).digest("hex").toString().substring(0, 5); 13 | protected uniqueId = this.generalUniqueName(this.generatedId, this.option); 14 | 15 | public getUniqueId(): string { 16 | return this.uniqueId; 17 | } 18 | 19 | private generalUniqueName(id: string, prefixOptions: OPTIONS): string { 20 | return `${prefixOptions != OPTIONS.NONE ? `${prefixOptions}_` : ""}${id}`; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-grant/lib/encoder-settings/output-group-settings.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | 3 | export class UdpOutputGroupSettings { 4 | 5 | private baseProps: CfnChannel.OutputGroupSettingsProperty = { 6 | udpGroupSettings: { 7 | inputLossAction: "EMIT_PROGRAM", 8 | }, 9 | }; 10 | 11 | public getOutputGroupSettings(): CfnChannel.OutputGroupSettingsProperty { 12 | return { 13 | ...this.baseProps, 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-grant/lib/encoder-settings/output.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { Base, OPTIONS } from "./base"; 3 | 4 | export class UdpOutput extends Base { 5 | constructor(id: string, private videoSettings: string, private audioSettings: string) { 6 | super(id, OPTIONS.NONE); 7 | } 8 | 9 | private baseProps: CfnChannel.OutputProperty = { 10 | outputSettings: { 11 | udpOutputSettings: { 12 | bufferMsec: 1000, 13 | containerSettings: { 14 | m2TsSettings: {} 15 | }, 16 | destination: { 17 | destinationRefId: this.getUniqueId(), 18 | } 19 | }, 20 | }, 21 | outputName: this.getUniqueId(), 22 | videoDescriptionName: this.videoSettings, 23 | audioDescriptionNames: [this.audioSettings], 24 | captionDescriptionNames: [], 25 | }; 26 | 27 | public getOutputSettings(): CfnChannel.OutputProperty { 28 | return this.baseProps; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-grant/lib/encoder-settings/video.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { Base, OPTIONS } from "./base"; 3 | 4 | interface IVideo { 5 | height: number; 6 | width: number; 7 | } 8 | 9 | type H264BaseSettings = Omit; 10 | interface H264Settings extends H264BaseSettings { 11 | bitrate: number; 12 | } 13 | 14 | export class VideoH264 extends Base { 15 | constructor(private id: string, private resolutionProps: IVideo, private h264Props: H264Settings) { 16 | super(id, OPTIONS.VIDEO); 17 | } 18 | 19 | private baseH264Props: H264BaseSettings = { 20 | framerateControl: "SPECIFIED", 21 | framerateNumerator: 25, 22 | framerateDenominator: 1, 23 | gopSize: 2, 24 | gopSizeUnits: "SECONDS", 25 | parControl: "SPECIFIED", 26 | parNumerator: 1, 27 | parDenominator: 1, 28 | }; 29 | 30 | public getVideoProfile(): CfnChannel.VideoDescriptionProperty { 31 | return { 32 | codecSettings: { 33 | h264Settings: { 34 | ...this.baseH264Props, 35 | ...this.h264Props, 36 | }, 37 | }, 38 | height: this.resolutionProps.height, 39 | name: this.getUniqueId(), 40 | width: this.resolutionProps.width, 41 | }; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-grant/lib/helpers/algorithm.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Encryption Algorithm 3 | */ 4 | export enum ALGORITHM { 5 | AES128 = "AES128", 6 | AES192 = "AES192", 7 | AES256 = "AES256", 8 | } 9 | 10 | 11 | /** 12 | * Key Length needed for each ALGORITHM 13 | */ 14 | export const ALGO: { [key in ALGORITHM]: number } = { 15 | [ALGORITHM.AES128]: 32, 16 | [ALGORITHM.AES192]: 47, 17 | [ALGORITHM.AES256]: 64, 18 | }; 19 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-grant/lib/index.ts: -------------------------------------------------------------------------------- 1 | import { App, Stack } from "aws-cdk-lib"; 2 | import { CfnFlow } from "aws-cdk-lib/aws-mediaconnect"; 3 | import { EmxEntitlementGrant } from "./granter-construct"; 4 | import { ALGORITHM } from "./helpers/algorithm"; 5 | import { createMediaLive, createMediaLiveInput } from "./media-services/medialive"; 6 | 7 | export class EmxEntitlementGrantStack extends Stack { 8 | constructor(app: App) { 9 | super(app, "EmxEntitlementGrantStack", {}); 10 | } 11 | 12 | /** 13 | * 1/ Create an EMX flow 14 | * 15 | * This will be for the source content 16 | */ 17 | protected emx = new CfnFlow(this, "b2b-output-flow", { 18 | name: "EMXSecretSharePoC-b2b-output", 19 | source: { 20 | name: "EMXSecretSharePoC-MediaLive-to-MediaConnect", 21 | description: "distribution from MediaLive to entitled accounts", 22 | protocol: "rtp-fec", 23 | whitelistCidr: "0.0.0.0/0", // Ensure you lockdown to the output of MediaLive after initial deployment 24 | }, 25 | }); 26 | 27 | /** 28 | * Setup supporting infrastructure to push to MediaConnect Flow (and to entitled account downstream) 29 | */ 30 | protected input = createMediaLiveInput(this, "s3://...."); 31 | protected medialive = createMediaLive(this, { 32 | input: this.input, 33 | output: this.emx, 34 | }); 35 | 36 | /** 37 | * 2/ Construct to build out entitlement flows 38 | * 39 | * This is per a customer/entitlement - so you can ensure secrets aren't shared across multiple entitlements. 40 | */ 41 | protected entitlements = new EmxEntitlementGrant(this, { 42 | streamIdentifier: "c1", 43 | accountId: "....", 44 | consumerRoleArn: "arn:aws:iam::....", 45 | algorithm: ALGORITHM.AES256, 46 | emxFlow: this.emx, 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-grant/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "emx-entitlement-grant", 3 | "version": "0.1.0", 4 | "engines": { 5 | "node": ">=18.0.0" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "lint": "eslint . --ext .ts", 11 | "prettier": "prettier --check --config .prettierrc 'lib/**/*.ts' 'bin/**/*.ts'", 12 | "format": "prettier --write --config .prettierrc 'lib/**/*.ts' 'bin/**/*.ts'", 13 | "test": "jest", 14 | "cdk": "cdk" 15 | }, 16 | "devDependencies": { 17 | "@types/jest": "^27.5.2", 18 | "@types/node": "10.17.27", 19 | "@typescript-eslint/eslint-plugin": "^5.40.1", 20 | "@typescript-eslint/parser": "^5.40.1", 21 | "aws-cdk": "2.177.0", 22 | "cdk-nag": "2.21.50", 23 | "eslint": "^8.31.0", 24 | "eslint-config-prettier": "^8.5.0", 25 | "eslint-plugin-no-loops": "^0.3.0", 26 | "jest": "^27.5.1", 27 | "prettier": "^2.8.1", 28 | "ts-jest": "^27.1.4", 29 | "ts-node": "^10.9.1", 30 | "typescript": "~3.9.7" 31 | }, 32 | "dependencies": { 33 | "aws-cdk-lib": "2.177.0", 34 | "aws-sdk": "^2.1354.0", 35 | "constructs": "^10.0.0", 36 | "source-map-support": "^0.5.21" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/emx-entitlement-grant/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2020" 7 | ], 8 | "declaration": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": false, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "experimentalDecorators": true, 21 | "strictPropertyInitialization": false, 22 | "typeRoots": [ 23 | "./node_modules/@types" 24 | ], 25 | "types": ["node"], 26 | "rootDirs": ["lib", "bin", "lambda"], 27 | }, 28 | "exclude": [ 29 | "node_modules", 30 | "cdk.out" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/screenshots/emx-alerts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/EMX_SECRET_SHARING/screenshots/emx-alerts.png -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/screenshots/emx-monitoring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/EMX_SECRET_SHARING/screenshots/emx-monitoring.png -------------------------------------------------------------------------------- /EMX_SECRET_SHARING/screenshots/secret-manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/EMX_SECRET_SHARING/screenshots/secret-manager.png -------------------------------------------------------------------------------- /LEF/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | .DS_Store 6 | cdk-exports-*.json 7 | cdk.context.json 8 | .venv 9 | 10 | # CDK asset staging directory 11 | .cdk.staging 12 | cdk.out 13 | 14 | prompts 15 | -------------------------------------------------------------------------------- /LEF/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /LEF/RELEASES.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | 3 | ## v1.0.2 (2024-11-22) 4 | 5 | ### New Features 6 | - Support MediaPackage V2 CMAF Ingest 7 | - Support for utcTiming configuration on MediaPackage DASH Manifests 8 | - Enable Frame Capture rendition on MediaLive sample encoding profiles 9 | - Include option to configure CloudFront Key Group ID to be applied to all behaviours in CloudFront Distribution and implement viewer restrictions. 10 | 11 | ### Improvements 12 | - Improve security by setting resourcePolicyType to CUSTOM on the MediaPackage Origin Endpoint. This change configures a policy on the MediaPackage endpoint with statements to only allow access from the MediaTailor and CloudFront resources deployed by the Live Event Framework. 13 | - Moved default configuration files from 'config' folder to 'config/default' folder. This change was required to make room for additional pre-canned sets of configuration files for different use cases (e.g. low latency HLS). 14 | - Sample encoding profile updates 15 | - Remove ac3 and eac3 audio tracks from sample profiles to reduce cost of running workflow (This can still be configured by creating your own custom profiles using tools/encoding-profile-generator) 16 | - Enhanced tools/encoding-profile-generator to generate MediaLive encoding profile using the CMAF Ingest output group. This is in addition to the existing MediaLive encoding profile using a HLS output group. 17 | - Include support for generation of profiles with audio in multiple languages 18 | - Updated README.md with CloudShell deployment instructions 19 | - Changed default MediaTailor settings to: 20 | - Disable bumpers 21 | - Disable setting of Personalization Threshold 22 | - Update Packages 23 | - Upgrade AWS Lambda Function run times to PYTHON_3_13 24 | - Set CloudFront Error TTL to a fixed value of 1 second regardless of segment size. Previously, this TTL may have been set to half a segment length for segment sizes > 2 25 | 26 | ### Bug Fixes 27 | - CloudFront Behaviour Optimizations: 28 | - Update /v1/segments/* and /v1/dashsegments/* behaviours to cache requests. Requests are cached to prevent duplicate server side tracking beacons being sent by MediaTailor when customer rewatches an ad period. 29 | - Introduced specific CloudFront Cache and Origin Request Policies for Ad Captions segments 30 | - Disable caching on /v1/tracking/* behaviour 31 | - Ordered behaviours to group behaviours for similar functions across DASH and HLS Streams 32 | - generate_encoding_profile_set.py updated to enforce the use of 3 letter language codes. Previously, script was using 2 letter codes. 33 | 34 | ### Breaking Changes 35 | - None 36 | -------------------------------------------------------------------------------- /LEF/config/default/eventGroupConfiguration.ts: -------------------------------------------------------------------------------- 1 | import { IEventGroupConfig } from '../../lib/event_group/eventGroupConfigInterface'; 2 | 3 | export const EVENT_GROUP_CONFIG: IEventGroupConfig = { 4 | cloudFront: { 5 | nominalSegmentLength: 4, 6 | s3LoggingEnabled: true, 7 | enableIpv6: true, 8 | tokenizationFunctionArn: '', 9 | }, 10 | mediaTailor: { 11 | adDecisionServerUrl: 'https://n8ljfs0h09.execute-api.us-west-2.amazonaws.com/v1/ads?duration=[session.avail_duration_secs]', 12 | contentSegmentUrl: '[player_params.content_segment_prefix]', 13 | adSegmentUrl: '[player_params.ad_segment_prefix]', 14 | adMarkerPassthrough: false, 15 | slateAdUrl: 'https://d1zxfw9n55b23r.cloudfront.net/interstitials/SlateAd.mov', 16 | // Bumpers can be enabled by uncommenting the 'bumper' section below. 17 | // bumper: { 18 | // startUrl: 'https://d1zxfw9n55b23r.cloudfront.net/interstitials/Bumper.mov', 19 | // endUrl: 'https://d1zxfw9n55b23r.cloudfront.net/interstitials/Bumper.mov', 20 | // }, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /LEF/config/default/foundationConfiguration.ts: -------------------------------------------------------------------------------- 1 | import { IFoundationConfig } from '../../lib/foundation/foundationConfigInterface'; 2 | 3 | export const FOUNDATION_CONFIG: IFoundationConfig = { 4 | cloudFront: { 5 | logging: { 6 | logRetentionPeriod: 30 // days 7 | }, 8 | allowedMediaPackageManifestQueryStrings: [ 9 | 'aws.manifestfilter', 10 | 'start', 11 | 'end', 12 | 'time_delay', 13 | // #########Action Require to enable Low Latency HLS support ########## 14 | // The _HLS_msn, _HLS_part and _HLS_skip all need to be enabled to support low latency hls 15 | // playback. They have been disabled by default to eliminate the need for users to raise a 16 | // support ticket and request an increase in the CloudFront cache key limit above the default 17 | // of 10. In one of the CloudFront behaviours all the allowedMediaPackageManifestQueryStrings 18 | // and allowedMediaTailorManifestQueryStrings values need to be included in the origin request 19 | // policy. Therefore the combined number of query string parameters across 20 | // allowedMediaTailorManifestQueryStrings and allowedMediaTailorManifestQueryStrings cannot 21 | // exceed 10 without requesting a limit increase. 22 | // '_HLS_msn', 23 | // '_HLS_part', 24 | // '_HLS_skip', 25 | // ####################################################################### 26 | ], 27 | allowedMediaTailorManifestQueryStrings: [ 28 | // ######### Reporting Mode - Optional Manifest Query String ############################## 29 | // Optionally include if reporting mode needs to be set to a non-default value for sessions 30 | // using implicit session initialization. 31 | // ######################################################################################## 32 | // 'reportingMode', 33 | 'aws.sessionId', 34 | 'aws.streamId', 35 | 'aws.logMode', 36 | 'playerParams.transcode_profile', 37 | 'playerParams.content_segment_prefix', 38 | 'playerParams.ad_segment_prefix' 39 | ] 40 | } 41 | }; -------------------------------------------------------------------------------- /LEF/config/low-latency/eventGroupConfiguration.ts: -------------------------------------------------------------------------------- 1 | import { IEventGroupConfig } from '../../lib/event_group/eventGroupConfigInterface'; 2 | 3 | export const EVENT_GROUP_CONFIG: IEventGroupConfig = { 4 | cloudFront: { 5 | nominalSegmentLength: 1, 6 | s3LoggingEnabled: true, 7 | enableIpv6: true, 8 | tokenizationFunctionArn: '', 9 | }, 10 | mediaTailor: { 11 | adDecisionServerUrl: 'https://n8ljfs0h09.execute-api.us-west-2.amazonaws.com/v1/ads?duration=[session.avail_duration_secs]', 12 | contentSegmentUrl: '[player_params.content_segment_prefix]', 13 | adSegmentUrl: '[player_params.ad_segment_prefix]', 14 | adMarkerPassthrough: false, 15 | slateAdUrl: 'https://d1zxfw9n55b23r.cloudfront.net/interstitials/SlateAd.mov', 16 | // Bumpers can be enabled by uncommenting the 'bumper' section below. 17 | // bumper: { 18 | // startUrl: 'https://d1zxfw9n55b23r.cloudfront.net/interstitials/Bumper.mov', 19 | // endUrl: 'https://d1zxfw9n55b23r.cloudfront.net/interstitials/Bumper.mov', 20 | // }, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /LEF/config/low-latency/foundationConfiguration.ts: -------------------------------------------------------------------------------- 1 | import { IFoundationConfig } from '../../lib/foundation/foundationConfigInterface'; 2 | 3 | export const FOUNDATION_CONFIG: IFoundationConfig = { 4 | cloudFront: { 5 | logging: { 6 | logRetentionPeriod: 30 // days 7 | }, 8 | allowedMediaPackageManifestQueryStrings: [ 9 | 'aws.manifestfilter', 10 | // 'start', 11 | // 'end', 12 | // 'time_delay', 13 | // #########Action Require to enable Low Latency HLS support ########## 14 | // The _HLS_msn, _HLS_part and _HLS_skip all need to be enabled to support low latency hls 15 | // playback. They have been disabled by default to eliminate the need for users to raise a 16 | // support ticket and request an increase in the CloudFront cache key limit above the default 17 | // of 10. In one of the CloudFront behaviours all the allowedMediaPackageManifestQueryStrings 18 | // and allowedMediaTailorManifestQueryStrings values need to be included in the origin request 19 | // policy. Therefore the combined number of query string parameters across 20 | // allowedMediaTailorManifestQueryStrings and allowedMediaTailorManifestQueryStrings cannot 21 | // exceed 10 without requesting a limit increase. 22 | '_HLS_msn', 23 | '_HLS_part', 24 | '_HLS_skip', 25 | // ####################################################################### 26 | ], 27 | allowedMediaTailorManifestQueryStrings: [ 28 | // ######### Reporting Mode - Optional Manifest Query String ############################## 29 | // Optionally include if reporting mode needs to be set to a non-default value for sessions 30 | // using implicit session initialization. 31 | // ######################################################################################## 32 | // 'reportingMode', 33 | 'aws.sessionId', 34 | 'aws.streamId', 35 | 'aws.logMode', 36 | // 'playerParams.transcode_profile', 37 | 'playerParams.content_segment_prefix', 38 | 'playerParams.ad_segment_prefix' 39 | ] 40 | } 41 | }; -------------------------------------------------------------------------------- /LEF/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /LEF/lib/foundation/foundationConfigInterface.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance 5 | * with the License. A copy of the License is located at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions 11 | * and limitations under the License. 12 | */ 13 | 14 | export interface IFoundationConfig { 15 | cloudFront: ICloudFrontConfig; 16 | }; 17 | 18 | export interface ICloudFrontConfig { 19 | logging: { 20 | logRetentionPeriod: number; 21 | }; 22 | allowedMediaPackageManifestQueryStrings: string[]; 23 | allowedMediaTailorManifestQueryStrings: string[]; 24 | }; -------------------------------------------------------------------------------- /LEF/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lef", 3 | "version": "1.0.2", 4 | "bin": { 5 | "lef": "bin/lef.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@types/jest": "29.5.14", 15 | "@types/js-yaml": "4.0.9", 16 | "@types/node": "22.13.1", 17 | "aws-cdk": "2.177.0", 18 | "jest": "29.7.0", 19 | "js-yaml": "4.1.0", 20 | "ts-jest": "29.2.5", 21 | "ts-node": "10.9.2", 22 | "typescript": "5.7.3" 23 | }, 24 | "dependencies": { 25 | "aws-cdk-lib": "2.177.0", 26 | "cdk-nag": "2.35.9", 27 | "constructs": "10.4.2", 28 | "glob": "11.0.1", 29 | "source-map-support": "0.5.21" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LEF/resources/ArchitectureDiagram-ElementalLive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/LEF/resources/ArchitectureDiagram-ElementalLive.png -------------------------------------------------------------------------------- /LEF/resources/ArchitectureDiagram-MediaLive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/LEF/resources/ArchitectureDiagram-MediaLive.png -------------------------------------------------------------------------------- /LEF/test/lef.test.ts: -------------------------------------------------------------------------------- 1 | // import * as cdk from 'aws-cdk-lib'; 2 | // import { Template } from 'aws-cdk-lib/assertions'; 3 | // import * as Lef from '../lib/lef-stack'; 4 | 5 | // example test. To run these tests, uncomment this file along with the 6 | // example resource in lib/lef-stack.ts 7 | test("SQS Queue Created", () => { 8 | // const app = new cdk.App(); 9 | // // WHEN 10 | // const stack = new Lef.LefStack(app, 'MyTestStack'); 11 | // // THEN 12 | // const template = Template.fromStack(stack); 13 | // template.hasResourceProperties('AWS::SQS::Queue', { 14 | // VisibilityTimeout: 300 15 | // }); 16 | }); 17 | -------------------------------------------------------------------------------- /LEF/tools/encoding-profile-generator/README.md: -------------------------------------------------------------------------------- 1 | # Encoding Profile Generator 2 | 3 | The **_tools/encoding-profile-generator/generate_encoding_profile_set.py_** script reads a yaml configuration containing the adaptive bitrate 4 | ladder settings and generates the following output encoding profiles: 5 | 6 | - MediaLive HLS/TS 7 | - MediaLive CMAF Ingest 8 | - MediaTailor HLS/CMAF Custom Transcode Profile 9 | - MediaTailor DASH/CMAF Custom Transcode Profile 10 | 11 | This script endeavours to produce custom transcode profiles to minimise issues as players transition in and out of ad breaks. The script only supports a limited number of output codecs and is not intended to support the full range of MediaLive / MediaConvert profiles settings. 12 | 13 | Below is an example command to generate a sample set of profiles based on the configuration in **encoding-profiles/profile-generator-configs/hd-avc-50fps-sample.yaml**. This is the command which was used to generate the profiles in **encoding-profiles/sample-profiles**. 14 | 15 | ```bash 16 | # Create Python virtual environment to run scripts (if local Python is not being used) 17 | python3 -m venv .venv 18 | source .venv/bin/activate 19 | pip install -r tools/requirements.txt 20 | 21 | tools/encoding-profile-generator/generate_encoding_profile_set.py --config tools/encoding-profile-generator/sample-configs/hd-avc-50fps-sample.yaml \ 22 | --version 1 \ 23 | --output-path encoding-profiles 24 | 25 | ``` 26 | -------------------------------------------------------------------------------- /LEF/tools/encoding-profile-generator/requirements.txt: -------------------------------------------------------------------------------- 1 | boto3 2 | pyyaml 3 | langcodes -------------------------------------------------------------------------------- /LEF/tools/mediatailor-logger-role/README.md: -------------------------------------------------------------------------------- 1 | # Setting up MediaTailor permissions for Amazon CloudWatch 2 | 3 | For any MediaTailor workflow it is extremely important for at least some CloudWatch logging to be enabled. These logs provide vital insight into the operation of the workflow. 4 | 5 | MediaTailor is granted permissions to log to CloudWatch by creating a role called 'MediaTailorLogger' and attaching the CloudWatchLogsFullAccess and CloudWatchFullAccess managed policies. 6 | 7 | When the Live Event Framework Foundation stack is deployed a check is performed to verify the MediaTailorLogger role exists and has appropriate permissions. If the role does not exist or does not have appropriate permissions the CDK deployment will fail. 8 | 9 | The **_tools/mediatailor-logger-role/check_mediatailor_logger_role.py_** script simplifies the creation of the MediaTailorLogger role in your account. For detailed instructions on creating the role manually see [Setting up permissions for Amazon CloudWatch](https://docs.aws.amazon.com/mediatailor/latest/ug/monitoring-permissions.html). 10 | 11 | The MediaTailorLogger role only needs to be created in the account once. After the role is created the level of logging for each MediaTailor Configuration is determine by the log percentage set on the individual MediaTailor Configurations. 12 | 13 | **Note:** If the MediaTailorLogger role is not created with the required permissions MediaTailor will be unable to any events to CloudWatch. 14 | 15 | During development and integration where the volume of requests is relatively small it makes sense to log 100% of requests ensure logs are available for any failing test cases. As the workload moves to production the volume (and corresponding cost) of MediaTailor logging will increase significantly. For a production workload a log sampling level of 10% or less is recommended to minimise costs while retaining a reasonable level of observability on the workload. 16 | 17 | Below is an example command to run the **_tools/mediatailor-logger-role/check_mediatailor_logger_role.py_** script. This script will check if the MediaTailorLogger role exists. If the role does not exist the user will be prompted with an option for the script to create the role. 18 | 19 | ```bash 20 | # Create Python virtual environment to run scripts (if local Python is not being used) 21 | python3 -m venv .venv 22 | source .venv/bin/activate 23 | pip install -r tools/requirements.txt 24 | 25 | tools/mediatailor-logger-role/check_mediatailor_logger_role.py [--profile AWS_PROFILE] [--region AWS_REGION] 26 | ``` 27 | -------------------------------------------------------------------------------- /LEF/tools/requirements.txt: -------------------------------------------------------------------------------- 1 | boto3 2 | pyyaml 3 | langcodes -------------------------------------------------------------------------------- /LEF/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2020", 7 | "dom" 8 | ], 9 | "declaration": true, 10 | "strict": true, 11 | "noImplicitAny": true, 12 | "strictNullChecks": true, 13 | "noImplicitThis": true, 14 | "alwaysStrict": true, 15 | "noUnusedLocals": false, 16 | "noUnusedParameters": false, 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": false, 19 | "inlineSourceMap": true, 20 | "inlineSources": true, 21 | "experimentalDecorators": true, 22 | "strictPropertyInitialization": false, 23 | "typeRoots": [ 24 | "./node_modules/@types" 25 | ] 26 | }, 27 | "exclude": [ 28 | "node_modules", 29 | "cdk.out" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /LILO/CDK-README.md: -------------------------------------------------------------------------------- 1 | 2 | # Welcome to your CDK Python project! 3 | 4 | This is a blank project for Python development with CDK. 5 | 6 | The `cdk.json` file tells the CDK Toolkit how to execute your app. 7 | 8 | This project is set up like a standard Python project. The initialization 9 | process also creates a virtualenv within this project, stored under the `.venv` 10 | directory. To create the virtualenv it assumes that there is a `python3` 11 | (or `python` for Windows) executable in your path with access to the `venv` 12 | package. If for any reason the automatic creation of the virtualenv fails, 13 | you can create the virtualenv manually. 14 | 15 | To manually create a virtualenv on MacOS and Linux: 16 | 17 | ``` 18 | $ python3 -m venv .venv 19 | ``` 20 | 21 | After the init process completes and the virtualenv is created, you can use the following 22 | step to activate your virtualenv. 23 | 24 | ``` 25 | $ source .venv/bin/activate 26 | ``` 27 | 28 | If you are a Windows platform, you would activate the virtualenv like this: 29 | 30 | ``` 31 | % .venv\Scripts\activate.bat 32 | ``` 33 | 34 | Once the virtualenv is activated, you can install the required dependencies. 35 | 36 | ``` 37 | $ pip install -r requirements.txt 38 | ``` 39 | 40 | At this point you can now synthesize the CloudFormation template for this code. 41 | 42 | ``` 43 | $ cdk synth 44 | ``` 45 | 46 | To add additional dependencies, for example other CDK libraries, just add 47 | them to your `setup.py` file and rerun the `pip install -r requirements.txt` 48 | command. 49 | 50 | ## Useful commands 51 | 52 | * `cdk ls` list all stacks in the app 53 | * `cdk synth` emits the synthesized CloudFormation template 54 | * `cdk deploy` deploy this stack to your default AWS account/region 55 | * `cdk diff` compare deployed stack with current state 56 | * `cdk docs` open CDK documentation 57 | 58 | Enjoy! 59 | -------------------------------------------------------------------------------- /LILO/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 10 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 11 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 12 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 13 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 14 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /LILO/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # author: Emmanuel Etheve 4 | # email: ethevee@amazon.com 5 | # description: This is the core file of the LILO application 6 | # This file contain the logic and code to build the LILO application 7 | # created: 17/05/2021 (dd/mm/yyyy) 8 | # modified: 04/04/2022 (dd/mm/yyyy) 9 | # filename: app.py 10 | 11 | # from aws_cdk import core as cdk 12 | from aws_cdk import App, Stack 13 | from lilo.lilo_stack import LiloStack 14 | from lilo.iam_nested_stack import iam_nested_stack 15 | from lilo.medialive_nested_stack import medialive_nested_stack 16 | from lilo.lilo_automation_nested_stack import lilo_automation_nested_stack 17 | 18 | # Automation 19 | auto_start = True # automatically start flows and channel 20 | 21 | # stack configuration 22 | my_region = 'eu-west-1' # aws region to be used 23 | my_customer_name = 'Client' # customer, department or company name of who is the owner of the stack 24 | my_stack_name = "Dev" # name to be used for the stack also know as workflow name 25 | my_project_name = "LILO" # project name to be used for resources in the stack 26 | 27 | # MediaLive parameters 28 | # file S3 path and name to be used for the loop 29 | source = "s3ssl://refarch-public/Sizzle2021.mp4" 30 | # destination IP 31 | destination = "127.0.0.1" 32 | # destination port number 33 | port = 5000 34 | 35 | lilo = App() 36 | core = LiloStack( 37 | lilo, 38 | my_stack_name + my_customer_name, 39 | env={'region': my_region} 40 | ) 41 | 42 | permission = iam_nested_stack( 43 | core, 44 | f"{my_customer_name}_Iam", 45 | stack=my_stack_name, 46 | ) 47 | 48 | automation = lilo_automation_nested_stack( 49 | core, 50 | f"{my_customer_name}_Auto", 51 | auto_start=auto_start, 52 | stack_name=my_stack_name, 53 | role=permission.aems_automation_role, 54 | ) 55 | 56 | channel = medialive_nested_stack( 57 | core, 58 | f"{my_customer_name}_Eml", 59 | source=source, 60 | destination=destination, 61 | port=port, 62 | role=permission.LiloMediaLiveAccessRole, 63 | stack_name=my_stack_name 64 | ) 65 | channel.add_dependency(permission) 66 | channel.add_dependency(automation) 67 | 68 | lilo.synth() 69 | -------------------------------------------------------------------------------- /LILO/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "python3 app.py", 3 | "context": { 4 | "@aws-cdk/core:stackRelativeExports": "true" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /LILO/images/LILO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/LILO/images/LILO.png -------------------------------------------------------------------------------- /LILO/lilo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/LILO/lilo/__init__.py -------------------------------------------------------------------------------- /LILO/lilo/lambda/medialive_channel_start_function.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | # author: Emmanuel Etheve 5 | # email: ethevee@amazon.com 6 | # description: This code contain the python script to automatically start a channel upon successful creation 7 | # created: 02/07/2021 (dd/mm/yyyy) 8 | # modified: 30/08/2021 (dd/mm/yyyy) 9 | # filename: medialive_channel_start_function.py 10 | 11 | import boto3 as aws 12 | import json 13 | import time 14 | 15 | def medialive_channel_start_function(event, context): 16 | 17 | time.sleep(50) 18 | print("---------------------------\n", 19 | "event\n", 20 | json.loads(json.dumps(event, indent=2))) 21 | # print(json.dumps(context, indent=2)) 22 | 23 | print("state", json.loads(json.dumps(event, indent=2))["detail"]["state"]) 24 | print("channel id", json.loads(json.dumps(event, indent=2))["resources"][0].split(":")[-1]) 25 | 26 | _channel_id = json.loads(json.dumps(event, indent=2))["resources"][0].split(":")[-1] 27 | 28 | if "CREATED" in json.loads(json.dumps(event, indent=2))["detail"]["state"]: 29 | my_eml = aws.client('medialive') 30 | eml_response = my_eml.start_channel( 31 | ChannelId=_channel_id 32 | ) 33 | 34 | print("---------------------------\n", 35 | "eml response start channel\n", 36 | json.loads(json.dumps(eml_response, indent=2))) 37 | 38 | if "ERROR" in eml_response: 39 | print("eml start channel " + _channel_id + "failed") -------------------------------------------------------------------------------- /LILO/lilo/lilo_automation_nested_stack.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | # author: Emmanuel Etheve 5 | # email: ethevee@amazon.com 6 | # description: This is file hold the code to deploy eventbridge triggers as well as lambda functions used for the 7 | # automation of the LILO application 8 | # created: 31/05/2021 (dd/mm/yyyy) 9 | # modified: 04/04/2022 (dd/mm/yyyy) 10 | # filename: lilo_automation_nested_stack.py 11 | 12 | from constructs import Construct 13 | from aws_cdk import ( 14 | NestedStack, 15 | Duration, 16 | aws_iam as iam, 17 | aws_lambda as _lambda, 18 | aws_events as events, 19 | aws_events_targets as targets, 20 | ) 21 | 22 | class lilo_automation_nested_stack(NestedStack): 23 | 24 | def __init__( 25 | self, 26 | scope: Construct, 27 | construct_id: str, 28 | role: iam.Role, 29 | auto_start: bool, 30 | stack_name: str, 31 | **kwargs) -> None: 32 | super().__init__(scope, construct_id, **kwargs) 33 | 34 | if auto_start: 35 | # Lambda configuration definitions 36 | self.medialive_channel_start_function = _lambda.Function( 37 | self, f"{stack_name}_medialive_channel_start_function", 38 | runtime=_lambda.Runtime.PYTHON_3_8, 39 | # code=_lambda.Code.asset('lilo/lambda'), 40 | code=_lambda.Code.from_asset('lilo/lambda'), 41 | handler='medialive_channel_start_function.medialive_channel_start_function', 42 | role=role, 43 | function_name=f"{stack_name}_medialive_channel_start_function", 44 | timeout=Duration.seconds(125), 45 | ) 46 | 47 | # EventBridge rule definition 48 | self.event_medialive_channel_start = events.Rule( 49 | self, 50 | id=f"{stack_name}_event_medialive_channel_start", 51 | rule_name=f"{stack_name}_event_medialive_channel_start", 52 | enabled=True, 53 | event_pattern=events.EventPattern( 54 | source=[ 55 | "aws.medialive" 56 | ], 57 | detail_type=[ 58 | "MediaLive Channel State Change" 59 | ] 60 | ), 61 | ) 62 | self.event_medialive_channel_start.add_target(targets.LambdaFunction(self.medialive_channel_start_function)) 63 | -------------------------------------------------------------------------------- /LILO/lilo/lilo_stack.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | # author: Emmanuel Etheve 5 | # email: ethevee@amazon.com 6 | # description: This is file hold the code of the core stack to deploy shared ressources between the stack that 7 | # can also be used by other services 8 | # created: 17/05/2021 (dd/mm/yyyy) 9 | # modified: 04/04/2022 (dd/mm/yyyy) 10 | # filename: lilo.py 11 | 12 | from constructs import Construct 13 | from aws_cdk import ( 14 | Stack, 15 | NestedStack, 16 | aws_medialive as eml, 17 | aws_iam as iam, 18 | ) 19 | from lilo.medialive_nested_stack import medialive_nested_stack 20 | class LiloStack(Stack): 21 | 22 | def __init__( 23 | self, 24 | scope: Construct, 25 | construct_id: str, 26 | **kwargs) -> None: 27 | super().__init__(scope, construct_id, **kwargs) 28 | 29 | def set_output( 30 | self, 31 | medialive: medialive_nested_stack, 32 | ): 33 | self.channel_id = cdk.CfnOutput( 34 | self, 35 | 'ChannnelId', 36 | value=medialive.my_eml_tx_channel.ref, 37 | ) 38 | -------------------------------------------------------------------------------- /LILO/requirements.txt: -------------------------------------------------------------------------------- 1 | -e . 2 | -------------------------------------------------------------------------------- /LILO/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # author: Emmanuel Etheve 4 | # email: ethevee@amazon.com 5 | # description: This code contain the bash deployment helper script for the LILO application 6 | # created: 21/07/2021 (dd/mm/yyyy) 7 | # modified: 30/08/2021 (dd/mm/yyyy) 8 | # filename: run.sh 9 | 10 | helpFunction() 11 | { 12 | echo "" 13 | echo "Usage: $0 -a [deploy | destroy]" # -p " 14 | echo -e "\ta: choose between deploy and destroy" 15 | # echo -e "\tp: aws config profile to be used for deployment" 16 | exit 1 # Exit script after printing help 17 | } 18 | 19 | configFunction() 20 | { 21 | source .venv/bin/activate 22 | pip install -r requirements.txt 23 | cdk bootstrap 24 | } 25 | 26 | deployFunction() 27 | { 28 | export LILOPATH=$(pwd) 29 | mkdir $LILOPATH/app 30 | cd $LILOPATH/app || exit 31 | cdk init app --language=python 32 | shopt -s dotglob nullglob 33 | mv $LILOPATH/LILO/* $LILOPATH/app/ 34 | rm -Rf $LILOPATH/LILO 35 | source .venv/bin/activate 36 | pip install -r requirements.txt 37 | cdk bootstrap 38 | cdk deploy --require-approval never 39 | } 40 | 41 | destroyFunction() 42 | { 43 | export LILOPATH=$(pwd) 44 | cd "$LILOPATH/app/" || exit 45 | source .venv/bin/activate 46 | cdk destroy -f 47 | } 48 | 49 | while getopts "a:p:" opt 50 | do 51 | case "$opt" in 52 | a ) action="$OPTARG";; 53 | # p ) profile="$OPTARG";; 54 | ? ) helpFunction ;; # Print helpFunction in case parameter is non-existent 55 | esac 56 | done 57 | 58 | echo "$0" 59 | 60 | # Print helpFunction in case parameters are empty 61 | if [ -z "$action" ] #|| [ -z "$profile" ] 62 | then 63 | echo "Some or all of the parameters are empty"; 64 | helpFunction 65 | fi 66 | 67 | # Begin script in case all parameters are correct 68 | case "$action" in 69 | configure ) configFunction ;; 70 | deploy ) deployFunction ;; 71 | destroy ) destroyFunction ;; 72 | esac 73 | -------------------------------------------------------------------------------- /LILO/setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | 4 | with open("README.md") as fp: 5 | long_description = fp.read() 6 | 7 | 8 | setuptools.setup( 9 | name="lilo", 10 | version="0.0.1", 11 | 12 | description="Live encoding loop application using AWS S3 and AWS Elemental MediaLive", 13 | long_description=long_description, 14 | long_description_content_type="text/markdown", 15 | 16 | author="Emmanuel Etheve", 17 | 18 | package_dir={"": "lilo"}, 19 | packages=setuptools.find_packages(where="lilo"), 20 | 21 | install_requires=[ 22 | "aws-cdk-lib>=2.0.0", 23 | "constructs>=10.0.0", 24 | "boto3", 25 | ], 26 | 27 | python_requires=">=3.6", 28 | 29 | classifiers=[ 30 | "Development Status :: 4 - Beta", 31 | 32 | "Intended Audience :: Developers", 33 | 34 | "Programming Language :: JavaScript", 35 | "Programming Language :: Python :: 3 :: Only", 36 | "Programming Language :: Python :: 3.6", 37 | "Programming Language :: Python :: 3.7", 38 | "Programming Language :: Python :: 3.8", 39 | 40 | "Topic :: Software Development :: Code Generators", 41 | "Topic :: Utilities", 42 | 43 | "Typing :: Typed", 44 | ], 45 | ) 46 | -------------------------------------------------------------------------------- /LILO/source.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem The sole purpose of this script is to make the command 4 | rem 5 | rem source .venv/bin/activate 6 | rem 7 | rem (which activates a Python virtualenv on Linux or Mac OS X) work on Windows. 8 | rem On Windows, this command just runs this batch file (the argument is ignored). 9 | rem 10 | rem Now we don't need to document a Windows command for activating a virtualenv. 11 | 12 | echo Executing .venv\Scripts\activate.bat for you 13 | .venv\Scripts\activate.bat 14 | -------------------------------------------------------------------------------- /LIVE2VOD/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | .DS_Store 6 | 7 | # CDK asset staging directory 8 | .cdk.staging 9 | cdk.out 10 | 11 | dist 12 | yarn-error.log -------------------------------------------------------------------------------- /LIVE2VOD/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /LIVE2VOD/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts dist/@infrastructure/index.js", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 21 | "@aws-cdk/core:stackRelativeExports": true, 22 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 23 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 24 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 25 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 28 | "@aws-cdk/core:checkSecretUsage": true, 29 | "@aws-cdk/aws-iam:minimizePolicies": true, 30 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, 31 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 32 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 33 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, 34 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, 35 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, 36 | "@aws-cdk/core:enablePartitionLiterals": true, 37 | "@aws-cdk/core:target-partitions": [ 38 | "aws", 39 | "aws-cn" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LIVE2VOD/images/CodeSample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/LIVE2VOD/images/CodeSample.png -------------------------------------------------------------------------------- /LIVE2VOD/images/FullDiagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/LIVE2VOD/images/FullDiagram.png -------------------------------------------------------------------------------- /LIVE2VOD/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /LIVE2VOD/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "live2vod-workflow", 3 | "version": "0.1.0", 4 | "engines": { 5 | "node": ">=14.0.0 <=18.12.0" 6 | }, 7 | "scripts": { 8 | "prebuild": "rimraf dist", 9 | "build": "tsc && shx cp package.json dist && cd dist && npm install --production", 10 | "watch": "tsc -w", 11 | "test": "jest", 12 | "cdk": "cdk" 13 | }, 14 | "devDependencies": { 15 | "@types/aws-lambda": "^8.10.76", 16 | "@types/prettier": "2.6.0", 17 | "@typescript-eslint/eslint-plugin": "^5.40.1", 18 | "@typescript-eslint/parser": "^5.40.1", 19 | "aws-cdk": "2.98.0", 20 | "aws-cdk-lib": "2.98.0", 21 | "cdk-nag": "2.21.50", 22 | "constructs": "^10.0.0", 23 | "jest": "^27.5.1", 24 | "rimraf": "^4.4.0", 25 | "shx": "^0.3.4", 26 | "ts-jest": "^27.1.4", 27 | "ts-node": "10.4.0", 28 | "typescript": "4.4.4" 29 | }, 30 | "dependencies": { 31 | "aws-lambda": "1.0.6", 32 | "aws-sdk": "^2.1354.0", 33 | "source-map-support": "^0.5.21" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LIVE2VOD/src/@infrastructure/config.ts: -------------------------------------------------------------------------------- 1 | // Edit to use your desired configuration 2 | 3 | /** 4 | * MediaConnect Whitelist CIDR - this is to narrow down traffic to ensure that you are only receiving traffic from your upstream system. 5 | */ 6 | export const DEST_MEDIA_CONNECT_WHITELIST_CIDR = "0.0.0.0/0"; 7 | -------------------------------------------------------------------------------- /LIVE2VOD/src/@infrastructure/encoder-settings/audio.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { Base, OPTIONS } from "./base"; 3 | 4 | export class AudioAAC extends Base { 5 | constructor(id: string, private aacProps?: CfnChannel.AacSettingsProperty) { 6 | super(id, OPTIONS.AUDIO); 7 | } 8 | 9 | private baseAacSettings: CfnChannel.AacSettingsProperty = {}; 10 | 11 | public getAudioProfile(): CfnChannel.AudioDescriptionProperty { 12 | return { 13 | codecSettings: { 14 | aacSettings: { 15 | ...this.baseAacSettings, 16 | ...this.aacProps, 17 | }, 18 | }, 19 | name: this.getUniqueId(), 20 | }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LIVE2VOD/src/@infrastructure/encoder-settings/base.ts: -------------------------------------------------------------------------------- 1 | import { createHash } from "crypto"; 2 | 3 | export enum OPTIONS { 4 | AUDIO = "audio", 5 | VIDEO = "video", 6 | NONE = "none", 7 | } 8 | 9 | export class Base { 10 | constructor(private parentId: string, private option: OPTIONS) {} 11 | 12 | private generatedId: string = createHash("md5").update(this.parentId).digest("hex").toString().substring(0, 5); 13 | protected uniqueId = this.generalUniqueName(this.generatedId, this.option); 14 | 15 | public getUniqueId(): string { 16 | return this.uniqueId; 17 | } 18 | 19 | private generalUniqueName(id: string, prefixOptions: OPTIONS): string { 20 | return `${prefixOptions != OPTIONS.NONE ? `${prefixOptions}_` : ""}${id}`; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LIVE2VOD/src/@infrastructure/encoder-settings/output-group-settings.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { CfnChannel as MpChannel } from "aws-cdk-lib/aws-mediapackage"; 3 | 4 | export class MPOutputGroupSettings { 5 | constructor(private mp: MpChannel) {} 6 | 7 | private baseProps: CfnChannel.OutputGroupSettingsProperty = { 8 | mediaPackageGroupSettings: { 9 | destination: { 10 | destinationRefId: this.mp.ref, 11 | }, 12 | }, 13 | }; 14 | 15 | public getOutputGroupSettings(): CfnChannel.OutputGroupSettingsProperty { 16 | return { 17 | ...this.baseProps, 18 | }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /LIVE2VOD/src/@infrastructure/encoder-settings/output.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { Base, OPTIONS } from "./base"; 3 | 4 | export class MpOutput extends Base { 5 | constructor(id: string, private videoSettings: string, private audioSettings: string) { 6 | super(id, OPTIONS.NONE); 7 | } 8 | 9 | private baseProps: CfnChannel.OutputProperty = { 10 | outputSettings: { 11 | mediaPackageOutputSettings: {}, 12 | }, 13 | outputName: this.getUniqueId(), 14 | videoDescriptionName: this.videoSettings, 15 | audioDescriptionNames: [this.audioSettings], 16 | captionDescriptionNames: [], 17 | }; 18 | 19 | public getOutputSettings(): CfnChannel.OutputProperty { 20 | return this.baseProps; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LIVE2VOD/src/@infrastructure/encoder-settings/video.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { Base, OPTIONS } from "./base"; 3 | 4 | interface IVideo { 5 | height: number; 6 | width: number; 7 | } 8 | 9 | type H264BaseSettings = Omit; 10 | interface H264Settings extends H264BaseSettings { 11 | bitrate: number; 12 | } 13 | 14 | export class VideoH264 extends Base { 15 | constructor(private id: string, private resolutionProps: IVideo, private h264Props: H264Settings) { 16 | super(id, OPTIONS.VIDEO); 17 | } 18 | 19 | private baseH264Props: H264BaseSettings = { 20 | framerateControl: "SPECIFIED", 21 | framerateNumerator: 25, 22 | framerateDenominator: 1, 23 | gopSize: 2, 24 | gopSizeUnits: "SECONDS", 25 | parControl: "SPECIFIED", 26 | parNumerator: 1, 27 | parDenominator: 1, 28 | }; 29 | 30 | public getVideoProfile(): CfnChannel.VideoDescriptionProperty { 31 | return { 32 | codecSettings: { 33 | h264Settings: { 34 | ...this.baseH264Props, 35 | ...this.h264Props, 36 | }, 37 | }, 38 | height: this.resolutionProps.height, 39 | name: this.getUniqueId(), 40 | width: this.resolutionProps.width, 41 | }; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LIVE2VOD/src/@infrastructure/index.ts: -------------------------------------------------------------------------------- 1 | import { App, Aspects } from "aws-cdk-lib"; 2 | import { createMediaLiveStack } from "./stacks/media-services"; 3 | import { createPlayoutStack } from "./stacks/playout"; 4 | import { createHarvestApiStack } from "./stacks/harvest-api"; 5 | import { getHarvestCompleteStack } from "./stacks/harvest-complete"; 6 | import { AwsSolutionsChecks } from "cdk-nag"; 7 | 8 | export const STACK_PREFIX_NAME = "MediaServicesRefArch"; 9 | 10 | const app = new App(); 11 | const playoutOutputs = createPlayoutStack(app); 12 | const mediaSvcsOutputs = createMediaLiveStack(app); 13 | const harvestApiOutputs = createHarvestApiStack(app, playoutOutputs); 14 | getHarvestCompleteStack(app, mediaSvcsOutputs, harvestApiOutputs.harvestIamMpRoleArn); 15 | Aspects.of(app).add(new AwsSolutionsChecks()); 16 | -------------------------------------------------------------------------------- /LIVE2VOD/src/@infrastructure/lambdas/harvest-clip-action-fn.ts: -------------------------------------------------------------------------------- 1 | import { Role } from "aws-cdk-lib/aws-iam"; 2 | import { Code, Function as Fn, Runtime } from "aws-cdk-lib/aws-lambda"; 3 | import { Duration, Stack } from "aws-cdk-lib"; 4 | 5 | export function createHarvestClipLambda(scope: Stack, playoutBucketName: string, role: Role, mpRoleArn: string): Fn { 6 | return new Fn(scope, "start-harvest-fn", { 7 | code: Code.fromAsset("./dist"), 8 | handler: "start-harvest.handler", 9 | runtime: Runtime.NODEJS_18_X, 10 | role, 11 | timeout: Duration.seconds(30), 12 | environment: { 13 | DESTINATION_BUCKET: playoutBucketName, 14 | HARVEST_ROLE_ARN: mpRoleArn, 15 | }, 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /LIVE2VOD/src/@infrastructure/lambdas/harvest-clip-completed-fn.ts: -------------------------------------------------------------------------------- 1 | import { Role } from "aws-cdk-lib/aws-iam"; 2 | import { Code, Function as Fn, Runtime } from "aws-cdk-lib/aws-lambda"; 3 | import { Duration, Stack } from "aws-cdk-lib"; 4 | 5 | interface IHarvestCompletedInput { 6 | role: Role; 7 | mpVodPackagingGroupId: string; 8 | } 9 | 10 | export function createHarvestCompleteFn(stack: Stack, props: IHarvestCompletedInput): Fn { 11 | const { role, mpVodPackagingGroupId } = props; 12 | 13 | return new Fn(stack, "harvested-complete-fn", { 14 | code: Code.fromAsset("./dist"), 15 | handler: "harvest-complete.handler", 16 | runtime: Runtime.NODEJS_18_X, 17 | timeout: Duration.seconds(30), 18 | role, 19 | environment: { 20 | MP_VOD_PACKAGING_GROUP: mpVodPackagingGroupId, 21 | }, 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /LIVE2VOD/src/@infrastructure/mediaservices/media-connect.ts: -------------------------------------------------------------------------------- 1 | import { CfnOutput, Stack } from "aws-cdk-lib"; 2 | import { CfnFlow } from "aws-cdk-lib/aws-mediaconnect"; 3 | import { STACK_PREFIX_NAME } from ".."; 4 | import { DEST_MEDIA_CONNECT_WHITELIST_CIDR } from "../config"; 5 | 6 | /** 7 | * Create input flow 8 | */ 9 | export function createMediaConnectFlow(stack: Stack, region: string): CfnFlow { 10 | const flow = new CfnFlow(stack, `flow-${region}`, { 11 | name: `${STACK_PREFIX_NAME}-L2V-flow-${region}`, 12 | source: { 13 | name: "source-to-emx", 14 | protocol: "zixi-push", 15 | whitelistCidr: DEST_MEDIA_CONNECT_WHITELIST_CIDR, 16 | }, 17 | availabilityZone: region, 18 | }); 19 | 20 | new CfnOutput(stack, "emx-input-flow", { 21 | exportName: `${stack.stackName}-emx-input-flow`, 22 | value: flow.ref, 23 | }); 24 | 25 | return flow; 26 | } 27 | -------------------------------------------------------------------------------- /LIVE2VOD/src/@infrastructure/mediaservices/media-package.ts: -------------------------------------------------------------------------------- 1 | import { CfnOutput, Stack } from "aws-cdk-lib"; 2 | import { CfnChannel, CfnOriginEndpoint, CfnPackagingConfiguration, CfnPackagingGroup } from "aws-cdk-lib/aws-mediapackage"; 3 | import { STACK_PREFIX_NAME } from ".."; 4 | 5 | export function createMediaPackage(stack: Stack): CfnChannel { 6 | const mp = new CfnChannel(stack, "mp-channel", { 7 | id: `${STACK_PREFIX_NAME}-L2V-EMP-channel`, 8 | }); 9 | 10 | const endpoint = new CfnOriginEndpoint(stack, "mp-endpoint", { 11 | channelId: mp.id, 12 | id: `${STACK_PREFIX_NAME}-L2V-EMP-hls-output`, 13 | hlsPackage: {}, 14 | startoverWindowSeconds: 10800, 15 | }); 16 | 17 | endpoint.node.addDependency(mp); 18 | 19 | new CfnOutput(stack, "emp-channel-output", { 20 | value: mp.ref, 21 | exportName: `${stack.stackName}-emp-channel-output`, 22 | }); 23 | 24 | new CfnOutput(stack, "emp-channel-hls-output", { 25 | value: endpoint.ref, 26 | exportName: `${stack.stackName}-emp-channel-hls-output`, 27 | }); 28 | 29 | return mp; 30 | } 31 | 32 | export function createMediaPackageVodGroup(stack: Stack): CfnPackagingGroup { 33 | const mp = new CfnPackagingGroup(stack, "mp-packaging-group", { 34 | id: `${STACK_PREFIX_NAME}-L2V-EMP-packaging-group`, 35 | }); 36 | 37 | const mpVod = new CfnPackagingConfiguration(stack, "mp-packaging-configuration", { 38 | id: `${STACK_PREFIX_NAME}-L2V-EMP-hls-packaging-config`, 39 | packagingGroupId: mp.id, 40 | hlsPackage: { 41 | hlsManifests: [ 42 | { 43 | manifestName: "index", 44 | }, 45 | ], 46 | }, 47 | }); 48 | 49 | mpVod.addDependsOn(mp); 50 | return mp; 51 | } 52 | -------------------------------------------------------------------------------- /LIVE2VOD/src/@infrastructure/stacks/media-services.ts: -------------------------------------------------------------------------------- 1 | import { App, Stack } from "aws-cdk-lib"; 2 | import { createMediaLive, createMediaLiveInput } from "../mediaservices/media-live"; 3 | import { createMediaConnectFlow } from "../mediaservices/media-connect"; 4 | import { createMediaPackage, createMediaPackageVodGroup } from "../mediaservices/media-package"; 5 | import { STACK_PREFIX_NAME } from ".."; 6 | 7 | export interface IMediaServicesOutput { 8 | stack: Stack; 9 | mediapackageArn: string; 10 | mpVodPackagingGroup: string; 11 | } 12 | 13 | export function createMediaLiveStack(app: App): IMediaServicesOutput { 14 | const stack = new Stack(app, `${STACK_PREFIX_NAME}-L2V-media-stack`, { 15 | env: { 16 | region: process.env.CDK_DEFAULT_REGION, 17 | account: process.env.CDK_DEFAULT_ACCOUNT, 18 | }, 19 | }); 20 | 21 | // Create MediaConnect Flow 22 | const mediaConnectFlow1A = createMediaConnectFlow(stack, `${stack.region}a`); 23 | 24 | // Setup an input from Elemental Live Encoder to MediaLive (through MediaConnect) 25 | const input = createMediaLiveInput(stack, mediaConnectFlow1A.attrFlowArn); 26 | 27 | const mp = createMediaPackage(stack); 28 | 29 | const mpVodPackagingGroup = createMediaPackageVodGroup(stack); 30 | const medialive = createMediaLive(stack, { 31 | input, 32 | mp, 33 | }); 34 | 35 | medialive.node.addDependency(mp); 36 | 37 | return { 38 | stack, 39 | mediapackageArn: mp.attrArn, 40 | mpVodPackagingGroup: mpVodPackagingGroup.id, 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /LIVE2VOD/src/@infrastructure/stacks/playout.ts: -------------------------------------------------------------------------------- 1 | import { App, RemovalPolicy, Stack } from "aws-cdk-lib"; 2 | import { BlockPublicAccess, Bucket, BucketEncryption } from "aws-cdk-lib/aws-s3"; 3 | import { NagSuppressions } from "cdk-nag"; 4 | import { STACK_PREFIX_NAME } from ".."; 5 | 6 | export interface IPlayoutOutputs { 7 | bucketName: string; 8 | bucketArn: string; 9 | } 10 | 11 | /** 12 | * Create VOD Playout stack - the destination of the harvested clips. 13 | */ 14 | export function createPlayoutStack(app: App): IPlayoutOutputs { 15 | const stack = new Stack(app, `${STACK_PREFIX_NAME}-L2V-vodassets-stack`, { 16 | env: { 17 | region: process.env.CDK_DEFAULT_REGION, 18 | account: process.env.CDK_DEFAULT_ACCOUNT, 19 | }, 20 | }); 21 | 22 | const bucket = new Bucket(stack, "clips-origin", { 23 | removalPolicy: RemovalPolicy.DESTROY, // Destroy when you delete the stack 24 | blockPublicAccess: BlockPublicAccess.BLOCK_ALL, 25 | encryption: BucketEncryption.S3_MANAGED, 26 | }); 27 | 28 | NagSuppressions.addResourceSuppressions(bucket, [ 29 | { 30 | id: "AwsSolutions-S1", 31 | reason: "No server access logs enabled for S3 bucket created.", 32 | }, 33 | { 34 | id: "AwsSolutions-S10", 35 | reason: "Bucket disable SSL for MediaPackage to read from S3.", 36 | }, 37 | ]); 38 | 39 | return { 40 | bucketName: bucket.bucketName, 41 | bucketArn: bucket.bucketArn, 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /LIVE2VOD/src/harvest-complete.ts: -------------------------------------------------------------------------------- 1 | import { APIGatewayEventRequestContext, Callback } from "aws-lambda"; 2 | import * as AWS from "aws-sdk"; 3 | 4 | const mp = new AWS.MediaPackageVod(); 5 | 6 | enum HarvestJob { 7 | SUCCEEDED = "SUCCEEDED", 8 | FAILED = "FAILED", 9 | } 10 | 11 | interface IMediaPackageEvent { 12 | detail: { 13 | harvest_job: { 14 | id: string; 15 | status: string; 16 | s3_destination: { 17 | bucket_name: string; 18 | manifest_key: string; 19 | role_arn: string; 20 | }; 21 | }; 22 | }; 23 | } 24 | 25 | export async function handler(event: IMediaPackageEvent, _context: APIGatewayEventRequestContext, callback: Callback): Promise { 26 | const mpVodPackagingGroup = process.env.MP_VOD_PACKAGING_GROUP; 27 | if (!mpVodPackagingGroup) { 28 | return callback(null, { 29 | statusCode: 400, 30 | headers: { 31 | "Access-Control-Allow-Headers": "*", 32 | "Access-Control-Allow-Origin": "*", 33 | "Access-Control-Allow-Methods": "OPTIONS,POST,GET", 34 | }, 35 | body: "Missing MP Packaging Group", 36 | }); 37 | } 38 | 39 | const harvestJob = event.detail.harvest_job; 40 | const id: string = harvestJob.id; 41 | const status: string = harvestJob.status; 42 | const bucketName: string = harvestJob.s3_destination.bucket_name; 43 | const manifestKey: string = harvestJob.s3_destination.manifest_key; 44 | const roleArn: string = harvestJob.s3_destination.role_arn; 45 | 46 | if (status == HarvestJob.FAILED) { 47 | return callback(null, { 48 | statusCode: 400, 49 | headers: { 50 | "Access-Control-Allow-Headers": "*", 51 | "Access-Control-Allow-Origin": "*", 52 | "Access-Control-Allow-Methods": "OPTIONS,POST,GET", 53 | }, 54 | body: "Harvest Failed", 55 | }); 56 | } 57 | 58 | const mpAsset = await mp 59 | .createAsset({ 60 | Id: id, 61 | PackagingGroupId: mpVodPackagingGroup, 62 | SourceArn: `arn:aws:s3:::${bucketName}/${manifestKey}`, 63 | SourceRoleArn: roleArn, 64 | }) 65 | .promise(); 66 | 67 | if (!mpAsset || !mpAsset.EgressEndpoints) { 68 | return callback(null, { 69 | statusCode: 400, 70 | headers: { 71 | "Access-Control-Allow-Headers": "*", 72 | "Access-Control-Allow-Origin": "*", 73 | "Access-Control-Allow-Methods": "OPTIONS,POST,GET", 74 | }, 75 | body: "Something went wrong", 76 | }); 77 | } 78 | 79 | return callback(null, { 80 | statusCode: 200, 81 | headers: { 82 | "Access-Control-Allow-Headers": "*", 83 | "Access-Control-Allow-Origin": "*", 84 | "Access-Control-Allow-Methods": "OPTIONS,POST,GET", 85 | }, 86 | body: "ok", 87 | }); 88 | } 89 | -------------------------------------------------------------------------------- /LIVE2VOD/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": ["es2018"], 6 | "declaration": true, 7 | "strict": true, 8 | "noImplicitAny": true, 9 | "strictNullChecks": true, 10 | "noImplicitThis": true, 11 | "alwaysStrict": true, 12 | "noUnusedLocals": false, 13 | "noUnusedParameters": false, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": false, 16 | "inlineSourceMap": true, 17 | "inlineSources": true, 18 | "experimentalDecorators": true, 19 | "strictPropertyInitialization": false, 20 | "typeRoots": ["./node_modules/@types"], 21 | "outDir": "./dist", 22 | "rootDir": "./src", 23 | }, 24 | "exclude": ["cdk.out"] 25 | } 26 | -------------------------------------------------------------------------------- /LIVE_CW_MONITOR/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | .DS_Store 6 | 7 | # CDK asset staging directory 8 | .cdk.staging 9 | cdk.out 10 | 11 | dist 12 | yarn-error.log -------------------------------------------------------------------------------- /LIVE_CW_MONITOR/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /LIVE_CW_MONITOR/bin/index.ts: -------------------------------------------------------------------------------- 1 | import { App, Aspects } from "aws-cdk-lib"; 2 | import { AwsSolutionsChecks } from "cdk-nag"; 3 | import { MediaStack } from "../lib/stacks/media-services"; 4 | import { MonitoringStack } from "../lib/stacks/monitoring-stack"; 5 | 6 | const app = new App(); 7 | const mediaStack = new MediaStack(app); 8 | new MonitoringStack(app, mediaStack.ml.ref, mediaStack.mp.ref); 9 | Aspects.of(app).add(new AwsSolutionsChecks()); 10 | -------------------------------------------------------------------------------- /LIVE_CW_MONITOR/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/index.ts", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 21 | "@aws-cdk/core:stackRelativeExports": true, 22 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 23 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 24 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 25 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 28 | "@aws-cdk/core:checkSecretUsage": true, 29 | "@aws-cdk/aws-iam:minimizePolicies": true, 30 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, 31 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 32 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 33 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, 34 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, 35 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, 36 | "@aws-cdk/core:enablePartitionLiterals": true, 37 | "@aws-cdk/core:target-partitions": [ 38 | "aws", 39 | "aws-cn" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LIVE_CW_MONITOR/images/sample-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/LIVE_CW_MONITOR/images/sample-architecture.png -------------------------------------------------------------------------------- /LIVE_CW_MONITOR/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /LIVE_CW_MONITOR/lib/config.ts: -------------------------------------------------------------------------------- 1 | // Edit to use your desired configuration 2 | 3 | /** 4 | * MediaLive input bucket name 5 | */ 6 | export const INPUT_BUCKET_NAME = "mybucketname"; 7 | 8 | /** 9 | * MediaLive input file name (in the bucket) 10 | */ 11 | export const INPUT_BUCKET_MP4_FILE = "myfile.mp4"; 12 | -------------------------------------------------------------------------------- /LIVE_CW_MONITOR/lib/encoder-settings/audio.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { Base, OPTIONS } from "./base"; 3 | 4 | export class AudioAAC extends Base { 5 | constructor(id: string, private aacProps?: CfnChannel.AacSettingsProperty) { 6 | super(id, OPTIONS.AUDIO); 7 | } 8 | 9 | private baseAacSettings: CfnChannel.AacSettingsProperty = {}; 10 | 11 | public getAudioProfile(): CfnChannel.AudioDescriptionProperty { 12 | return { 13 | codecSettings: { 14 | aacSettings: { 15 | ...this.baseAacSettings, 16 | ...this.aacProps, 17 | }, 18 | }, 19 | name: this.getUniqueId(), 20 | }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LIVE_CW_MONITOR/lib/encoder-settings/base.ts: -------------------------------------------------------------------------------- 1 | import { createHash } from "crypto"; 2 | 3 | export enum OPTIONS { 4 | AUDIO = "audio", 5 | VIDEO = "video", 6 | NONE = "none", 7 | } 8 | 9 | export class Base { 10 | constructor(private parentId: string, private option: OPTIONS) {} 11 | 12 | private generatedId: string = createHash("md5").update(this.parentId).digest("hex").toString().substring(0, 5); 13 | protected uniqueId = this.generalUniqueName(this.generatedId, this.option); 14 | 15 | public getUniqueId() { 16 | return this.uniqueId; 17 | } 18 | 19 | private generalUniqueName(id: string, prefixOptions: OPTIONS) { 20 | return `${prefixOptions != OPTIONS.NONE ? `${prefixOptions}_` : ""}${id}`; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LIVE_CW_MONITOR/lib/encoder-settings/output-group-settings.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { CfnChannel as MpChannel } from "aws-cdk-lib/aws-mediapackage"; 3 | 4 | export class MPOutputGroupSettings { 5 | constructor(private mp: MpChannel) {} 6 | 7 | private baseProps: CfnChannel.OutputGroupSettingsProperty = { 8 | mediaPackageGroupSettings: { 9 | destination: { 10 | destinationRefId: this.mp.ref, 11 | }, 12 | }, 13 | }; 14 | 15 | public getOutputGroupSettings(): CfnChannel.OutputGroupSettingsProperty { 16 | return { 17 | ...this.baseProps, 18 | }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /LIVE_CW_MONITOR/lib/encoder-settings/output.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { Base, OPTIONS } from "./base"; 3 | 4 | export class MpOutput extends Base { 5 | constructor(id: string, private videoSettings: string, private audioSettings: string) { 6 | super(id, OPTIONS.NONE); 7 | } 8 | 9 | private baseProps: CfnChannel.OutputProperty = { 10 | outputSettings: { 11 | mediaPackageOutputSettings: {}, 12 | }, 13 | outputName: this.getUniqueId(), 14 | videoDescriptionName: this.videoSettings, 15 | audioDescriptionNames: [this.audioSettings], 16 | captionDescriptionNames: [], 17 | }; 18 | 19 | public getOutputSettings(): CfnChannel.OutputProperty { 20 | return this.baseProps; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LIVE_CW_MONITOR/lib/encoder-settings/video.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { Base, OPTIONS } from "./base"; 3 | 4 | interface IVideo { 5 | height: number; 6 | width: number; 7 | } 8 | 9 | type H264BaseSettings = Omit; 10 | interface H264Settings extends H264BaseSettings { 11 | bitrate: number; 12 | } 13 | 14 | export class VideoH264 extends Base { 15 | constructor(private id: string, private resolutionProps: IVideo, private h264Props: H264Settings) { 16 | super(id, OPTIONS.VIDEO); 17 | } 18 | 19 | private baseH264Props: H264BaseSettings = { 20 | framerateControl: "SPECIFIED", 21 | framerateNumerator: 25, 22 | framerateDenominator: 1, 23 | gopSize: 2, 24 | gopSizeUnits: "SECONDS", 25 | parControl: "SPECIFIED", 26 | parNumerator: 1, 27 | parDenominator: 1, 28 | }; 29 | 30 | public getVideoProfile(): CfnChannel.VideoDescriptionProperty { 31 | return { 32 | codecSettings: { 33 | h264Settings: { 34 | ...this.baseH264Props, 35 | ...this.h264Props, 36 | }, 37 | }, 38 | height: this.resolutionProps.height, 39 | name: this.getUniqueId(), 40 | width: this.resolutionProps.width, 41 | }; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LIVE_CW_MONITOR/lib/mediaservices/media-package.ts: -------------------------------------------------------------------------------- 1 | import { CfnOutput, Stack } from "aws-cdk-lib"; 2 | import { CfnChannel, CfnOriginEndpoint } from "aws-cdk-lib/aws-mediapackage"; 3 | 4 | export function createMediaPackage(stack: Stack) { 5 | const mp = new CfnChannel(stack, "sustainable-emp-channel", { 6 | id: `${stack.stackName}-EMP-channel`, 7 | }); 8 | const ep = new CfnOriginEndpoint(stack, "emp-endpoint", { 9 | channelId: mp.id, 10 | id: `${stack.stackName}-EMP-hls-output`, 11 | hlsPackage: {}, 12 | }); 13 | 14 | ep.addDependsOn(mp); 15 | 16 | new CfnOutput(stack, "emp-channel-output", { 17 | value: mp.ref, 18 | exportName: "emp-channel-output", 19 | }); 20 | 21 | new CfnOutput(stack, "emp-channel-hls-output", { 22 | value: ep.ref, 23 | exportName: "emp-channel-hls-output", 24 | }); 25 | 26 | return mp; 27 | } 28 | -------------------------------------------------------------------------------- /LIVE_CW_MONITOR/lib/stacks/media-services.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { createMediaPackage } from "../mediaservices/media-package"; 3 | import { createMediaLive, createMediaLiveInput } from "../mediaservices/media-live"; 4 | import { INPUT_BUCKET_MP4_FILE, INPUT_BUCKET_NAME } from "../config"; 5 | import { Construct } from "constructs"; 6 | 7 | export const STACK_PREFIX_NAME = "MediaServicesRefArch"; 8 | 9 | export interface IMediaServicesOutput { 10 | emlId: string; 11 | empId: string; 12 | } 13 | 14 | export class MediaStack extends Stack { 15 | constructor(app: Construct) { 16 | super(app, `${STACK_PREFIX_NAME}-live-cw-monitoring-encoding-stack`, { 17 | env: { 18 | region: process.env.CDK_DEFAULT_REGION, 19 | account: process.env.CDK_DEFAULT_ACCOUNT, 20 | }, 21 | }); 22 | } 23 | 24 | protected input = createMediaLiveInput(this, INPUT_BUCKET_NAME, INPUT_BUCKET_MP4_FILE); 25 | public mp = createMediaPackage(this); 26 | public ml = createMediaLive(this, { 27 | input: this.input, 28 | mp: this.mp, 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /LIVE_CW_MONITOR/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "live-streaming-monitoring", 3 | "version": "0.1.0", 4 | "engines": { 5 | "node": ">=14.0.0 <=18.12.0" 6 | }, 7 | "scripts": { 8 | "build": "rm -rf dist && tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@types/jest": "^27.5.2", 15 | "@types/node": "10.17.27", 16 | "@types/prettier": "2.6.0", 17 | "@typescript-eslint/eslint-plugin": "^5.40.1", 18 | "@typescript-eslint/parser": "^5.40.1", 19 | "aws-cdk": "2.87.0", 20 | "cdk-nag": "2.21.50", 21 | "jest": "^27.5.1", 22 | "ts-jest": "^27.1.4", 23 | "ts-node": "10.4.0", 24 | "typescript": "4.4.4" 25 | }, 26 | "dependencies": { 27 | "aws-cdk-lib": "2.87.0", 28 | "constructs": "^10.0.0", 29 | "source-map-support": "^0.5.21" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LIVE_CW_MONITOR/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2018" 7 | ], 8 | "declaration": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": false, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "experimentalDecorators": true, 21 | "strictPropertyInitialization": false, 22 | "typeRoots": [ 23 | "./node_modules/@types" 24 | ], 25 | "rootDirs": ["lib", "bin"], 26 | }, 27 | "exclude": [ 28 | "node_modules", 29 | "cdk.out" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /LIVE_THUMBNAIL_MONITORING/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | .DS_Store 6 | 7 | # CDK asset staging directory 8 | .cdk.staging 9 | cdk.out 10 | 11 | dist 12 | yarn-error.log -------------------------------------------------------------------------------- /LIVE_THUMBNAIL_MONITORING/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /LIVE_THUMBNAIL_MONITORING/architecture/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/LIVE_THUMBNAIL_MONITORING/architecture/architecture.png -------------------------------------------------------------------------------- /LIVE_THUMBNAIL_MONITORING/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts dist/@infrastructure/index.js", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 21 | "@aws-cdk/core:stackRelativeExports": true, 22 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 23 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 24 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 25 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 28 | "@aws-cdk/core:checkSecretUsage": true, 29 | "@aws-cdk/aws-iam:minimizePolicies": true, 30 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, 31 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 32 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 33 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, 34 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, 35 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, 36 | "@aws-cdk/core:enablePartitionLiterals": true, 37 | "@aws-cdk/core:target-partitions": [ 38 | "aws", 39 | "aws-cn" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LIVE_THUMBNAIL_MONITORING/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /LIVE_THUMBNAIL_MONITORING/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "live-thumbnail-monitoring-workflow", 3 | "version": "0.1.0", 4 | "engines": { 5 | "node": ">=14.0.0 <=18.17.1" 6 | }, 7 | "scripts": { 8 | "prebuild": "rimraf dist", 9 | "build": "tsc && shx cp package.json dist && cd dist && npm install --production", 10 | "watch": "tsc -w", 11 | "test": "jest", 12 | "cdk": "cdk" 13 | }, 14 | "devDependencies": { 15 | "@types/pino": "^7.0.5", 16 | "@types/aws-lambda": "^8.10.76", 17 | "@types/prettier": "2.6.0", 18 | "@typescript-eslint/eslint-plugin": "^5.40.1", 19 | "@typescript-eslint/parser": "^5.40.1", 20 | "aws-cdk": "2.87.0", 21 | "aws-cdk-lib": "2.87.0", 22 | "cdk-nag": "2.21.50", 23 | "constructs": "^10.0.0", 24 | "jest": "^27.5.1", 25 | "rimraf": "^4.4.0", 26 | "shx": "^0.3.4", 27 | "ts-jest": "^27.1.4", 28 | "ts-node": "10.4.0", 29 | "typescript": "4.4.4" 30 | }, 31 | "dependencies": { 32 | "aws-lambda": "1.0.6", 33 | "aws-sdk": "2.1414.0", 34 | "axios": "^1.7.4", 35 | "pino": "^8.14.1", 36 | "source-map-support": "^0.5.21", 37 | "ts-is-present": "^1.2.2" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LIVE_THUMBNAIL_MONITORING/screenshots/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/LIVE_THUMBNAIL_MONITORING/screenshots/example.png -------------------------------------------------------------------------------- /LIVE_THUMBNAIL_MONITORING/screenshots/frame-capture-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/LIVE_THUMBNAIL_MONITORING/screenshots/frame-capture-settings.png -------------------------------------------------------------------------------- /LIVE_THUMBNAIL_MONITORING/screenshots/output-setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/LIVE_THUMBNAIL_MONITORING/screenshots/output-setup.png -------------------------------------------------------------------------------- /LIVE_THUMBNAIL_MONITORING/screenshots/setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/LIVE_THUMBNAIL_MONITORING/screenshots/setup.png -------------------------------------------------------------------------------- /LIVE_THUMBNAIL_MONITORING/screenshots/thumbnail-configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/LIVE_THUMBNAIL_MONITORING/screenshots/thumbnail-configuration.png -------------------------------------------------------------------------------- /LIVE_THUMBNAIL_MONITORING/src/@infrastructure/index.ts: -------------------------------------------------------------------------------- 1 | import { App, Aspects } from "aws-cdk-lib"; 2 | import { createthumbnailApiStack } from "./stacks/thumbnail-api"; 3 | import { AwsSolutionsChecks } from "cdk-nag"; 4 | 5 | export const STACK_PREFIX_NAME = "MediaServicesRefArch"; 6 | 7 | const app = new App(); 8 | createthumbnailApiStack(app); 9 | Aspects.of(app).add(new AwsSolutionsChecks()); 10 | -------------------------------------------------------------------------------- /LIVE_THUMBNAIL_MONITORING/src/@infrastructure/lambdas/file-delete-fn.ts: -------------------------------------------------------------------------------- 1 | import { Role } from "aws-cdk-lib/aws-iam"; 2 | import { Code, Function as Fn, Runtime } from "aws-cdk-lib/aws-lambda"; 3 | import { Duration, Stack } from "aws-cdk-lib"; 4 | 5 | export function createFileDeleteLambda(scope: Stack, role: Role, bucketName: string): Fn { 6 | return new Fn(scope, "deletethumbnailfn", { 7 | code: Code.fromAsset("./dist"), 8 | handler: "delete-thumbnails-contents.handler", 9 | runtime: Runtime.NODEJS_18_X, 10 | role, 11 | timeout: Duration.seconds(30), 12 | environment: { 13 | THUMBNAIL_BUCKET: bucketName, 14 | }, 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /LIVE_THUMBNAIL_MONITORING/src/@infrastructure/lambdas/thumbnail-fn.ts: -------------------------------------------------------------------------------- 1 | import { Role } from "aws-cdk-lib/aws-iam"; 2 | import { Code, Function as Fn, Runtime } from "aws-cdk-lib/aws-lambda"; 3 | import { Duration, Stack } from "aws-cdk-lib"; 4 | 5 | export function createThumbnailLambda(scope: Stack, role: Role, bucketName: string): Fn { 6 | return new Fn(scope, "thumbnailsfn", { 7 | code: Code.fromAsset("./dist"), 8 | handler: "fetch-thumbnails.handler", 9 | runtime: Runtime.NODEJS_18_X, 10 | role, 11 | timeout: Duration.seconds(30), 12 | environment: { 13 | THUMBNAIL_BUCKET: bucketName, 14 | }, 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /LIVE_THUMBNAIL_MONITORING/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2019", 4 | "module": "commonjs", 5 | "lib": ["ES2019"], 6 | "declaration": true, 7 | "strict": true, 8 | "noImplicitAny": true, 9 | "strictNullChecks": true, 10 | "noImplicitThis": true, 11 | "alwaysStrict": true, 12 | "noUnusedLocals": false, 13 | "noUnusedParameters": false, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": false, 16 | "inlineSourceMap": true, 17 | "inlineSources": true, 18 | "experimentalDecorators": true, 19 | "strictPropertyInitialization": false, 20 | "typeRoots": ["./node_modules/@types"], 21 | "outDir": "./dist", 22 | "rootDir": "./src", 23 | }, 24 | "exclude": ["cdk.out"] 25 | } 26 | -------------------------------------------------------------------------------- /OTT/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | cdk-exports.json 6 | 7 | # CDK asset staging directory 8 | .cdk.staging 9 | cdk.out 10 | -------------------------------------------------------------------------------- /OTT/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /OTT/architecture_AEML-AEMP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/OTT/architecture_AEML-AEMP.png -------------------------------------------------------------------------------- /OTT/bin/medialive-mediapackage-cloudfront.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import 'source-map-support/register'; 3 | import * as cdk from 'aws-cdk-lib'; 4 | import { Aws} from "aws-cdk-lib"; 5 | import { MedialiveMediapackageCloudfrontStack } from '../lib/medialive-mediapackage-cloudfront-stack'; 6 | import { AwsSolutionsChecks } from 'cdk-nag'; 7 | import { Aspects } from 'aws-cdk-lib'; 8 | 9 | const app = new cdk.App(); 10 | const stackName=app.node.tryGetContext('stackName') 11 | const description=app.node.tryGetContext('stackDescription') 12 | 13 | //Aspects.of(app).add(new AwsSolutionsChecks()); 14 | new MedialiveMediapackageCloudfrontStack(app, 'MedialiveMediapackageCloudfrontStack', { 15 | stackName: stackName, 16 | env: { 17 | region: `${Aws.REGION}`, 18 | account: `${Aws.ACCOUNT_ID}`, 19 | }, 20 | description 21 | }); -------------------------------------------------------------------------------- /OTT/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/medialive-mediapackage-cloudfront.ts", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 21 | "@aws-cdk/core:stackRelativeExports": true, 22 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 23 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 24 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 25 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 28 | "@aws-cdk/core:checkSecretUsage": true, 29 | "@aws-cdk/aws-iam:minimizePolicies": true, 30 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, 31 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 32 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 33 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, 34 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, 35 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, 36 | "@aws-cdk/core:enablePartitionLiterals": true, 37 | "@aws-cdk/core:target-partitions": [ 38 | "aws", 39 | "aws-cn" 40 | ], 41 | "stackName": "MediaServicesRefArch-OTT", 42 | "stackDescription": "AWS CDK MediaServices Reference Architectures: Live OTT workflow. CDK demo using AWS MediaLive, MediaPackage and Amazon CloudFront." 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /OTT/config/configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "mediaLive": { 3 | "autoStart": true, 4 | "streamName": "live", 5 | "channelClass": "STANDARD", 6 | "inputType": "URL_PULL", 7 | "sourceEndBehavior": "LOOP", 8 | "codec": "AVC", 9 | "encodingProfile": "HD-720p", 10 | "priLink": "", 11 | "secLink": "", 12 | "inputCidr": "0.0.0.0/0" , 13 | "priUrl": "https://d15an60oaeed9r.cloudfront.net/live_stream_v2/sports_reel_with_markers.m3u8", 14 | "secUrl": "https://d15an60oaeed9r.cloudfront.net/live_stream_v2/sports_reel_with_markers.m3u8", 15 | "priFlow": "", 16 | "secFlow": "" 17 | }, 18 | "mediaPackage":{ 19 | "ad_markers":"PASSTHROUGH", 20 | "hls_segment_duration_seconds": 4, 21 | "hls_playlist_window_seconds": 60, 22 | "hls_max_video_bits_per_second": 2147483647, 23 | "hls_min_video_bits_per_second": 0, 24 | "hls_stream_order": "ORIGINAL", 25 | "hls_include_I_frame":false, 26 | "hls_audio_rendition_group": true, 27 | "hls_program_date_interval":60, 28 | "dash_period_triggers": "ADS", 29 | "dash_profile": "NONE", 30 | "dash_segment_duration_seconds": 2, 31 | "dash_segment_template": "TIME_WITH_TIMELINE", 32 | "dash_manifest_window_seconds": 60, 33 | "dash_max_video_bits_per_second": 2147483647, 34 | "dash_min_video_bits_per_second": 0, 35 | "dash_stream_order": "ORIGINAL", 36 | "cmaf_segment_duration_seconds": 4, 37 | "cmaf_include_I_frame":false, 38 | "cmaf_program_date_interval":60, 39 | "cmaf_max_video_bits_per_second": 2147483647, 40 | "cmaf_min_video_bits_per_second": 0, 41 | "cmaf_stream_order": "ORIGINAL", 42 | "cmaf_playlist_window_seconds": 60, 43 | "mss_segment_duration_seconds": 2, 44 | "mss_manifest_window_seconds": 60, 45 | "mss_max_video_bits_per_second": 2147483647, 46 | "mss_min_video_bits_per_second": 0, 47 | "mss_stream_order": "ORIGINAL" 48 | } 49 | } -------------------------------------------------------------------------------- /OTT/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /OTT/lib/lambda/medialive_channel_start_function/index.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | 5 | import boto3 as aws 6 | import json 7 | 8 | def lambda_handler(event, context): 9 | 10 | print("channel id", event["mediaLiveChannelId"]) 11 | 12 | _channel_id = event["mediaLiveChannelId"] 13 | my_eml = aws.client('medialive') 14 | try: 15 | eml_response = my_eml.start_channel(ChannelId=_channel_id) 16 | print("---------------------------\n", 17 | "eml response start channel\n", 18 | json.loads(json.dumps(eml_response, indent=2))) 19 | 20 | except Exception as e: 21 | print("Error Command - eml start channel " + _channel_id + "failed") 22 | print(e) 23 | 24 | def get_channel_status(channel,medialive): 25 | " 'State': 'CREATING'|'CREATE_FAILED'|'IDLE'|'STARTING'|'RUNNING'|'RECOVERING'|'STOPPING'|'DELETING'|'DELETED'|'UPDATING'|'UPDATE_FAILED', " 26 | info_channel = medialive.describe_channel( 27 | ChannelId=channel 28 | ) 29 | return info_channel["State"] -------------------------------------------------------------------------------- /OTT/lib/lambda/mediapackage_cmaf_geturl_function/index.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | import boto3 4 | import logging 5 | emp_client = boto3.client('mediapackage') 6 | ssm_client = boto3.client('ssm') 7 | def lambda_handler(event, context): 8 | logger = logging.getLogger() 9 | logger.setLevel(logging.INFO) 10 | logger.info (f"Input parameters from cloud formation: {event}") 11 | endpointId = event["mediaPackageEndpointId"] 12 | ssmName = event["ssmName"] 13 | print(endpointId) 14 | try: 15 | response = emp_client.describe_origin_endpoint(Id=endpointId) 16 | logger.info(response) 17 | responseValue = str(response['CmafPackage']['HlsManifests'][0]['Url']) 18 | logger.info(responseValue) 19 | ssm_response = ssm_client.put_parameter( 20 | Name=ssmName, 21 | Description='Cmaf output Url', 22 | Value=responseValue, 23 | Type='String', 24 | Overwrite=True 25 | ) 26 | logger.info(ssm_response) 27 | return responseValue 28 | except Exception as e: 29 | print("Error Command - emp origin endpoint ID " + endpointId + "failed") 30 | print(e) 31 | logger.info(e) 32 | -------------------------------------------------------------------------------- /OTT/lib/mediapackage_secrets.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance 5 | * with the License. A copy of the License is located at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions 11 | * and limitations under the License. 12 | */ 13 | 14 | import { 15 | Aws, 16 | CfnOutput, 17 | aws_secretsmanager as secretsmanager, 18 | } from "aws-cdk-lib"; 19 | import { Construct } from "constructs"; 20 | import { NagSuppressions } from 'cdk-nag'; 21 | 22 | 23 | export class Secrets extends Construct { 24 | public readonly cdnSecret: secretsmanager.ISecret; 25 | 26 | constructor(scope: Construct, id: string) { 27 | super(scope, id); 28 | 29 | const cdnSecret = new secretsmanager.Secret(this, "CdnSecret", { 30 | secretName: "MediaPackage/"+Aws.STACK_NAME, 31 | description: "Secret for Secure Resilient Live Streaming Delivery", 32 | generateSecretString: { 33 | secretStringTemplate: JSON.stringify({ MediaPackageCDNIdentifier: "" }), 34 | generateStringKey: "MediaPackageCDNIdentifier", //MUST keep this StringKey to use with EMP 35 | }, 36 | }); 37 | this.cdnSecret = cdnSecret; 38 | 39 | new CfnOutput(this, "cdnSecret", { 40 | value: cdnSecret.secretName, 41 | exportName: Aws.STACK_NAME + "cdnSecret", 42 | description: "The name of the cdnSecret", 43 | }); 44 | 45 | NagSuppressions.addResourceSuppressions(cdnSecret, [ 46 | { 47 | id: 'AwsSolutions-SMG4', 48 | reason: 'Remediated through property override.', 49 | }, 50 | ]); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /OTT/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "medialive-mediapackage-cloudfront", 3 | "version": "0.1.0", 4 | "bin": { 5 | "medialive-mediapackage-cloudfront": "bin/medialive-mediapackage-cloudfront.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@types/jest": "^27.5.2", 15 | "@types/node": "10.17.27", 16 | "@types/prettier": "2.6.0", 17 | "aws-cdk": "2.45.0", 18 | "jest": "^27.5.1", 19 | "ts-jest": "^27.1.4", 20 | "ts-node": "^10.9.1", 21 | "typescript": "~3.9.7" 22 | }, 23 | "dependencies": { 24 | "aws-cdk-lib": "2.80.0", 25 | "cdk-nag": "^2.21.53", 26 | "constructs": "^10.0.0", 27 | "source-map-support": "^0.5.21" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /OTT/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2018" 7 | ], 8 | "declaration": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": false, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "experimentalDecorators": true, 21 | "strictPropertyInitialization": false, 22 | "typeRoots": [ 23 | "./node_modules/@types" 24 | ] 25 | }, 26 | "exclude": [ 27 | "node_modules", 28 | "cdk.out" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /OTT_LOW_LATENCY/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | cdk-exports.json 6 | 7 | # CDK asset staging directory 8 | .cdk.staging 9 | cdk.out 10 | -------------------------------------------------------------------------------- /OTT_LOW_LATENCY/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /OTT_LOW_LATENCY/architecture_AEML-AEMP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/OTT_LOW_LATENCY/architecture_AEML-AEMP.png -------------------------------------------------------------------------------- /OTT_LOW_LATENCY/bin/medialive-mediapackage-cloudfront.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import "source-map-support/register"; 3 | import * as cdk from "aws-cdk-lib"; 4 | import { Aws } from "aws-cdk-lib"; 5 | import { MedialiveMediapackageCloudfrontStack } from "../lib/medialive-mediapackage-cloudfront-stack"; 6 | import { AwsSolutionsChecks } from "cdk-nag"; 7 | import { Aspects } from "aws-cdk-lib"; 8 | 9 | const app = new cdk.App(); 10 | const stackName = app.node.tryGetContext("stackName"); 11 | const description = app.node.tryGetContext("stackDescription"); 12 | 13 | Aspects.of(app).add(new AwsSolutionsChecks()); 14 | new MedialiveMediapackageCloudfrontStack( 15 | app, 16 | "MedialiveMediapackageCloudfrontStack", 17 | { 18 | stackName: stackName, 19 | env: { 20 | region: `${Aws.REGION}`, 21 | account: `${Aws.ACCOUNT_ID}`, 22 | }, 23 | description, 24 | }, 25 | ); 26 | -------------------------------------------------------------------------------- /OTT_LOW_LATENCY/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/medialive-mediapackage-cloudfront.ts", 3 | "watch": { 4 | "include": ["**"], 5 | "exclude": [ 6 | "README.md", 7 | "cdk*.json", 8 | "**/*.d.ts", 9 | "**/*.js", 10 | "tsconfig.json", 11 | "package*.json", 12 | "yarn.lock", 13 | "node_modules", 14 | "test" 15 | ] 16 | }, 17 | "context": { 18 | "@aws-cdk/customresources:installLatestAwsSdkDefault": false, 19 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 20 | "@aws-cdk/core:stackRelativeExports": true, 21 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 22 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 23 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 24 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 25 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 26 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 27 | "@aws-cdk/core:checkSecretUsage": true, 28 | "@aws-cdk/aws-iam:minimizePolicies": true, 29 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, 30 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 31 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 32 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, 33 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, 34 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, 35 | "@aws-cdk/core:enablePartitionLiterals": true, 36 | "@aws-cdk/core:target-partitions": ["aws", "aws-cn"], 37 | "stackName": "MediaServicesRefArch-OTT-LL", 38 | "stackDescription": "AWS CDK MediaServices Reference Architectures: Live Low Latency OTT workflow. CDK demo using AWS MediaLive, MediaPackage V2 and Amazon CloudFront." 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /OTT_LOW_LATENCY/config/configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "mediaLive": { 3 | "autoStart": true, 4 | "streamName": "live", 5 | "channelClass": "STANDARD", 6 | "inputType": "URL_PULL", 7 | "sourceEndBehavior": "LOOP", 8 | "codec": "AVC", 9 | "encodingProfile": "HD-720p", 10 | "priLink": "", 11 | "secLink": "", 12 | "inputCidr": "0.0.0.0/0", 13 | "priUrl": "https://d15an60oaeed9r.cloudfront.net/live_stream_v2/sports_reel_with_markers.m3u8", 14 | "secUrl": "https://d15an60oaeed9r.cloudfront.net/live_stream_v2/sports_reel_with_markers.m3u8", 15 | "priFlow": "", 16 | "secFlow": "" 17 | }, 18 | "mediaPackage": { 19 | "ad_markers": "DATERANGE", 20 | "hls_segment_duration_seconds": 4, 21 | "hls_playlist_window_seconds": 60, 22 | "hls_include_I_frame": true, 23 | "hls_audio_rendition_group": true, 24 | "hls_program_date_interval": 60, 25 | "hls_startover_window_seconds": 1209600, 26 | "cmaf_segment_duration_seconds": 4, 27 | "cmaf_include_I_frame": true, 28 | "cmaf_program_date_interval": 60, 29 | "cmaf_playlist_window_seconds": 60, 30 | "cmaf_startover_window_seconds": 1209600 31 | }, 32 | "cloudFront": { 33 | "s3LoggingEnabled": false 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /OTT_LOW_LATENCY/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: "node", 3 | roots: ["/test"], 4 | testMatch: ["**/*.test.ts"], 5 | transform: { 6 | "^.+\\.tsx?$": "ts-jest", 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /OTT_LOW_LATENCY/lib/lambda/medialive_channel_start_function/index.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | 5 | import boto3 as aws 6 | import json 7 | 8 | def lambda_handler(event, context): 9 | 10 | print("channel id", event["mediaLiveChannelId"]) 11 | 12 | _channel_id = event["mediaLiveChannelId"] 13 | my_eml = aws.client('medialive') 14 | try: 15 | eml_response = my_eml.start_channel(ChannelId=_channel_id) 16 | print("---------------------------\n", 17 | "eml response start channel\n", 18 | json.loads(json.dumps(eml_response, indent=2))) 19 | 20 | except Exception as e: 21 | print("Error Command - eml start channel " + _channel_id + "failed") 22 | print(e) 23 | 24 | def get_channel_status(channel,medialive): 25 | " 'State': 'CREATING'|'CREATE_FAILED'|'IDLE'|'STARTING'|'RUNNING'|'RECOVERING'|'STOPPING'|'DELETING'|'DELETED'|'UPDATING'|'UPDATE_FAILED', " 26 | info_channel = medialive.describe_channel( 27 | ChannelId=channel 28 | ) 29 | return info_channel["State"] -------------------------------------------------------------------------------- /OTT_LOW_LATENCY/lib/lambda/mediapackage_extra_attrib_function/index.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | import boto3 4 | import logging 5 | emp_client = boto3.client('mediapackagev2') 6 | ssm_client = boto3.client('ssm') 7 | def lambda_handler(event, context): 8 | logger = logging.getLogger() 9 | logger.setLevel(logging.INFO) 10 | logger.info (f"Input parameters from cloud formation: {event}") 11 | channelGroupName = event["mediaPackageChannelGroupName"] 12 | channelName = event["mediaPackageChannelName"] 13 | ssmNamePrefix = event["ssmNamePrefix"] 14 | logger.info("channelGroupName: %s" % channelGroupName) 15 | logger.info("channelName: %s" % channelName) 16 | logger.info("ssmNamePrefix: %s" % ssmNamePrefix) 17 | try: 18 | response = emp_client.get_channel(ChannelGroupName=channelGroupName, ChannelName=channelName) 19 | logger.info("Raw get_channel response:") 20 | logger.info(response) 21 | for ingestEndpoint in response['IngestEndpoints']: 22 | paramName = "%s-%s%s" % (ssmNamePrefix, "IngestEndpoint", ingestEndpoint["Id"]) 23 | ssm_response = ssm_client.put_parameter( 24 | Name=paramName, 25 | Description='MediaPackage Channel %s/%s ingest point %s parameter' % (channelName, channelGroupName, ingestEndpoint["Id"]), 26 | Value=ingestEndpoint['Url'], 27 | Type='String', 28 | Overwrite=True 29 | ) 30 | logger.info(ssm_response) 31 | return response 32 | except Exception as e: 33 | logger.error("Error Command - emp get channel " + channelName + " in channel group " + channelGroupName + "failed") 34 | print(e) 35 | logger.info(e) 36 | -------------------------------------------------------------------------------- /OTT_LOW_LATENCY/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "medialive-mediapackage-cloudfront", 3 | "version": "0.1.0", 4 | "bin": { 5 | "medialive-mediapackage-cloudfront": "bin/medialive-mediapackage-cloudfront.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@types/jest": "^27.5.2", 15 | "@types/node": "10.17.27", 16 | "@types/prettier": "2.6.0", 17 | "aws-cdk": "2.108.1", 18 | "jest": "^27.5.1", 19 | "prettier": "3.0.3", 20 | "ts-jest": "^27.1.4", 21 | "ts-node": "^10.9.1", 22 | "typescript": "~3.9.7" 23 | }, 24 | "dependencies": { 25 | "aws-cdk-lib": "2.108.1", 26 | "cdk-nag": "^2.21.53", 27 | "constructs": "^10.0.0", 28 | "source-map-support": "^0.5.21" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /OTT_LOW_LATENCY/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": ["es2018"], 6 | "declaration": true, 7 | "strict": true, 8 | "noImplicitAny": true, 9 | "strictNullChecks": true, 10 | "noImplicitThis": true, 11 | "alwaysStrict": true, 12 | "noUnusedLocals": false, 13 | "noUnusedParameters": false, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": false, 16 | "inlineSourceMap": true, 17 | "inlineSources": true, 18 | "experimentalDecorators": true, 19 | "strictPropertyInitialization": false, 20 | "typeRoots": ["./node_modules/@types"] 21 | }, 22 | "exclude": ["node_modules", "cdk.out"] 23 | } 24 | -------------------------------------------------------------------------------- /PRIVATE_LIVE/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | cdk-exports.json 6 | 7 | # CDK asset staging directory 8 | .cdk.staging 9 | cdk.out 10 | 11 | cdk.context.json -------------------------------------------------------------------------------- /PRIVATE_LIVE/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /PRIVATE_LIVE/bin/index.ts: -------------------------------------------------------------------------------- 1 | import { App, Aspects } from "aws-cdk-lib"; 2 | import { AwsSolutionsChecks } from "cdk-nag"; 3 | import { NetworkStack } from "./stacks/networking"; 4 | import { MediaLiveStack } from "./stacks/media-services"; 5 | 6 | export const STACK_PREFIX_NAME = "MediaServicesRefArch"; 7 | 8 | const app = new App(); 9 | const networkStack = new NetworkStack(app); 10 | const mediaStack = new MediaLiveStack(app, networkStack.configuredTags); 11 | 12 | mediaStack.addDependency(networkStack); 13 | 14 | // cdk-nag checks 15 | Aspects.of(app).add(new AwsSolutionsChecks()); 16 | -------------------------------------------------------------------------------- /PRIVATE_LIVE/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/index.ts", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 21 | "@aws-cdk/core:stackRelativeExports": true, 22 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 23 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 24 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 25 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 28 | "@aws-cdk/core:checkSecretUsage": true, 29 | "@aws-cdk/aws-iam:minimizePolicies": true, 30 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, 31 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 32 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 33 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, 34 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, 35 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, 36 | "@aws-cdk/core:enablePartitionLiterals": true, 37 | "@aws-cdk/core:target-partitions": [ 38 | "aws", 39 | "aws-cn" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /PRIVATE_LIVE/images/CodeDemo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/PRIVATE_LIVE/images/CodeDemo.png -------------------------------------------------------------------------------- /PRIVATE_LIVE/images/HighLevelArchitecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/PRIVATE_LIVE/images/HighLevelArchitecture.png -------------------------------------------------------------------------------- /PRIVATE_LIVE/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /PRIVATE_LIVE/lib/config.ts: -------------------------------------------------------------------------------- 1 | // Edit to use your desired configuration 2 | 3 | /** 4 | * MediaConnect Whitelist CIDR - this is to narrow down traffic to ensure that you are only receiving traffic from your upstream system. 5 | */ 6 | export const MEDIA_CONNECT_INPUT_WHITELIST_CIDR = "0.0.0.0/0"; // Change to lockdown your source IP address 7 | 8 | /** 9 | * Output URL for MSS Output Group 10 | * This IP Address is a example destination in the private subnet created in the network stack. 11 | * 12 | * https://docs.aws.amazon.com/medialive/latest/ug/origin-server-mss.html 13 | */ 14 | export const MEDIA_LIVE_OUTPUT_TARGET = "http:///channel1/channel1"; 15 | 16 | /** 17 | * Used in VPC creation and network lookups 18 | */ 19 | export const AVAILABILITY_ZONES = ["eu-west-1a", "eu-west-1b"]; 20 | 21 | /** 22 | * CIDR Address for VPC creation and configuration 23 | */ 24 | export const VPC_CIDR = "10.0.0.0/16"; 25 | -------------------------------------------------------------------------------- /PRIVATE_LIVE/lib/encoder-settings/audio.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { Base, OPTIONS } from "./base"; 3 | 4 | export class AudioAAC extends Base { 5 | constructor(id: string, private aacProps?: CfnChannel.AacSettingsProperty) { 6 | super(id, OPTIONS.AUDIO); 7 | } 8 | 9 | private baseAacSettings: CfnChannel.AacSettingsProperty = {}; 10 | 11 | public getAudioProfile(): CfnChannel.AudioDescriptionProperty { 12 | return { 13 | codecSettings: { 14 | aacSettings: { 15 | ...this.baseAacSettings, 16 | ...this.aacProps, 17 | }, 18 | }, 19 | name: this.getUniqueId(), 20 | }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /PRIVATE_LIVE/lib/encoder-settings/base.ts: -------------------------------------------------------------------------------- 1 | import { createHash } from "crypto"; 2 | 3 | export enum OPTIONS { 4 | AUDIO = "audio", 5 | VIDEO = "video", 6 | NONE = "none", 7 | } 8 | 9 | export class Base { 10 | constructor(private parentId: string, private option: OPTIONS) {} 11 | 12 | private generatedId: string = createHash("md5").update(this.parentId).digest("hex").toString().substring(0, 5); 13 | protected uniqueId = this.generalUniqueName(this.generatedId, this.option); 14 | 15 | public getUniqueId() { 16 | return this.uniqueId; 17 | } 18 | 19 | private generalUniqueName(id: string, prefixOptions: OPTIONS) { 20 | return `${prefixOptions != OPTIONS.NONE ? `${prefixOptions}_` : ""}${id}`; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /PRIVATE_LIVE/lib/encoder-settings/output-group-settings.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | 3 | export class MSSOutputGroupSettings { 4 | constructor(private outputName: string) {} 5 | private baseProps: CfnChannel.OutputGroupSettingsProperty = { 6 | msSmoothGroupSettings: { 7 | destination: { 8 | destinationRefId: this.outputName, 9 | }, 10 | }, 11 | }; 12 | 13 | public getOutputGroupSettings(): CfnChannel.OutputGroupSettingsProperty { 14 | return { 15 | ...this.baseProps, 16 | }; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /PRIVATE_LIVE/lib/encoder-settings/output.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { Base, OPTIONS } from "./base"; 3 | 4 | export class OutputGroups extends Base { 5 | constructor(id: string, private videoSettings: string, private audioSettings: string) { 6 | super(id, OPTIONS.NONE); 7 | } 8 | 9 | private baseProps: CfnChannel.OutputProperty = { 10 | outputSettings: { 11 | msSmoothOutputSettings: {}, 12 | }, 13 | outputName: this.getUniqueId(), 14 | videoDescriptionName: this.videoSettings, 15 | audioDescriptionNames: [this.audioSettings], 16 | captionDescriptionNames: [], 17 | }; 18 | 19 | public getOutputSettings(): CfnChannel.OutputProperty { 20 | return this.baseProps; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /PRIVATE_LIVE/lib/encoder-settings/video.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { Base, OPTIONS } from "./base"; 3 | 4 | interface IVideo { 5 | height: number; 6 | width: number; 7 | } 8 | 9 | type H264BaseSettings = Omit; 10 | interface H264Settings extends H264BaseSettings { 11 | bitrate: number; 12 | } 13 | 14 | export class VideoH264 extends Base { 15 | constructor(private id: string, private resolutionProps: IVideo, private h264Props: H264Settings) { 16 | super(id, OPTIONS.VIDEO); 17 | } 18 | 19 | private baseH264Props: H264BaseSettings = { 20 | framerateControl: "SPECIFIED", 21 | framerateNumerator: 25, 22 | framerateDenominator: 1, 23 | gopSize: 2, 24 | gopSizeUnits: "SECONDS", 25 | parControl: "SPECIFIED", 26 | parNumerator: 1, 27 | parDenominator: 1, 28 | }; 29 | 30 | public getVideoProfile(): CfnChannel.VideoDescriptionProperty { 31 | return { 32 | codecSettings: { 33 | h264Settings: { 34 | ...this.baseH264Props, 35 | ...this.h264Props, 36 | }, 37 | }, 38 | height: this.resolutionProps.height, 39 | name: this.getUniqueId(), 40 | width: this.resolutionProps.width, 41 | }; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /PRIVATE_LIVE/lib/mediaservices/media-connect.ts: -------------------------------------------------------------------------------- 1 | import { CfnOutput, Stack } from "aws-cdk-lib"; 2 | import { CfnFlow } from "aws-cdk-lib/aws-mediaconnect"; 3 | import { STACK_PREFIX_NAME } from "../../bin"; 4 | import { MEDIA_CONNECT_INPUT_WHITELIST_CIDR } from "../config"; 5 | 6 | /** 7 | * Create flow from Elemental Live Encoder to MediaConnect in the Cloud 8 | */ 9 | export function createMediaConnectFlow(stack: Stack, region: string): CfnFlow { 10 | const flow = new CfnFlow(stack, `private-media-emx-flow-${region}`, { 11 | name: `${STACK_PREFIX_NAME}-EMX-flow-${region}`, 12 | source: { 13 | name: "source-from-elemental-live-encoder", 14 | description: "MediaConnect flow for Private Networking sample.", 15 | protocol: "zixi-push", 16 | whitelistCidr: MEDIA_CONNECT_INPUT_WHITELIST_CIDR, 17 | }, 18 | availabilityZone: region, 19 | }); 20 | 21 | new CfnOutput(stack, "emx-input-flow", { 22 | exportName: `${stack.stackName}-emx-input-flow`, 23 | value: flow.ref, 24 | }); 25 | 26 | return flow; 27 | } 28 | -------------------------------------------------------------------------------- /PRIVATE_LIVE/lib/stacks/media-services.ts: -------------------------------------------------------------------------------- 1 | import { App, Stack } from "aws-cdk-lib"; 2 | import { createMediaLive, createMediaLiveInput } from "../mediaservices/media-live"; 3 | import { createMediaConnectFlow } from "../mediaservices/media-connect"; 4 | import { IVpcTags } from "./networking"; 5 | import { STACK_PREFIX_NAME } from "../../bin"; 6 | 7 | export class MediaLiveStack extends Stack { 8 | constructor(app: App, private vpcTags: IVpcTags) { 9 | super(app, `${STACK_PREFIX_NAME}-private-media-stack`, { 10 | env: { 11 | region: process.env.CDK_DEFAULT_REGION, 12 | account: process.env.CDK_DEFAULT_ACCOUNT, 13 | }, 14 | description: "AWS CDK MediaServices Reference Architectures: Private Network video workflow. Stack contains MediaConnect and MediaLive.", 15 | }); 16 | } 17 | 18 | // Create MediaConnect Flow 19 | protected mediaConnectFlow1A = createMediaConnectFlow(this, "eu-west-1a"); 20 | 21 | // Setup an input from Elemental Live Encoder to MediaLive (through MediaConnect) 22 | protected input1A = createMediaLiveInput(this, this.mediaConnectFlow1A.attrFlowArn); 23 | 24 | protected eml = createMediaLive(this, { 25 | input: this.input1A, 26 | tags: this.vpcTags, 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /PRIVATE_LIVE/lib/stacks/networking.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { STACK_PREFIX_NAME } from "../../bin"; 3 | import { AVAILABILITY_ZONES } from "../config"; 4 | import { createVpc } from "../vpc"; 5 | import { Construct } from "constructs"; 6 | 7 | export interface IVpcTags { 8 | [key: string]: string; 9 | } 10 | 11 | export interface INetworkStackOutputs { 12 | stack: Stack; 13 | tags: IVpcTags; 14 | } 15 | 16 | /** 17 | * Workaround for https://github.com/aws/aws-cdk/issues/21690#issuecomment-1266201638 18 | * 19 | * When this is fixed, you can revert to using `Stack` and remove the hardcoding of the availability zones at a `Stack` level. 20 | */ 21 | export class OverriddenStack extends Stack { 22 | get availabilityZones() { 23 | return AVAILABILITY_ZONES; 24 | } 25 | } 26 | 27 | export class NetworkStack extends OverriddenStack { 28 | constructor(app: Construct) { 29 | super(app, `${STACK_PREFIX_NAME}-private-network-stack`, { 30 | env: { 31 | region: process.env.CDK_DEFAULT_REGION, 32 | account: process.env.CDK_DEFAULT_ACCOUNT, 33 | }, 34 | description: "AWS CDK MediaServices Reference Architectures: Private Network video workflow. Stack contains VPC networking.", 35 | }); 36 | } 37 | 38 | protected vpc = createVpc(this); 39 | 40 | public configuredTags = { 41 | "private-networking-stack-name": `${STACK_PREFIX_NAME}-private-network-stack`, 42 | "used-in-private-networking-media-stack": "true", 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /PRIVATE_LIVE/lib/vpc.ts: -------------------------------------------------------------------------------- 1 | import { Aspects, CfnOutput, RemovalPolicy, Stack, Tag } from "aws-cdk-lib"; 2 | import { FlowLogDestination, IpAddresses, SubnetType, Vpc } from "aws-cdk-lib/aws-ec2"; 3 | import { Role, ServicePrincipal } from "aws-cdk-lib/aws-iam"; 4 | import { LogGroup } from "aws-cdk-lib/aws-logs"; 5 | import { STACK_PREFIX_NAME } from "../bin/"; 6 | import { AVAILABILITY_ZONES, VPC_CIDR } from "./config"; 7 | 8 | export function createVpc(stack: Stack) { 9 | const logGroup = new LogGroup(stack, "vpc-loggroup", { 10 | removalPolicy: RemovalPolicy.DESTROY, 11 | logGroupName: `${STACK_PREFIX_NAME}-private-networking-vpc-loggroup`, 12 | }); 13 | const role = new Role(stack, "vpc-flowlogs-role", { 14 | assumedBy: new ServicePrincipal("vpc-flow-logs.amazonaws.com"), 15 | roleName: `${STACK_PREFIX_NAME}-private-networking-vpc-flowlogs-role`, 16 | }); 17 | 18 | const vpc = new Vpc(stack, "vpc", { 19 | vpcName: `${STACK_PREFIX_NAME}-private-networking-vpc`, 20 | availabilityZones: AVAILABILITY_ZONES, 21 | ipAddresses: IpAddresses.cidr(VPC_CIDR), 22 | natGateways: 1, 23 | subnetConfiguration: [ 24 | { 25 | name: "workflow", 26 | subnetType: SubnetType.PRIVATE_WITH_EGRESS, 27 | cidrMask: 28, 28 | }, 29 | { 30 | name: "public", 31 | subnetType: SubnetType.PUBLIC, 32 | cidrMask: 28, 33 | }, 34 | ], 35 | }); 36 | 37 | vpc.addFlowLog("vpc-cloudwatch", { 38 | destination: FlowLogDestination.toCloudWatchLogs(logGroup, role), 39 | }); 40 | 41 | Aspects.of(vpc).add(new Tag("private-networking-stack-name", `${STACK_PREFIX_NAME}-private-network-stack`)); 42 | Aspects.of(vpc).add(new Tag("used-in-private-networking-media-stack", "true")); 43 | 44 | new CfnOutput(stack, "vpc-id-output", { 45 | value: vpc.vpcId, 46 | exportName: `${stack.stackName}-vpc-id-output`, 47 | }); 48 | new CfnOutput(stack, "vpc-subnets-pri-output", { 49 | value: vpc.selectSubnets({ subnetType: SubnetType.PRIVATE_WITH_EGRESS }).subnetIds.toString(), 50 | exportName: `${stack.stackName}-vpc-pri-subnet-output`, 51 | }); 52 | new CfnOutput(stack, "vpc-subnets-pub-output", { 53 | value: vpc.selectSubnets({ subnetType: SubnetType.PUBLIC }).subnetIds.toString(), 54 | exportName: `${stack.stackName}-vpc-pub-subnet-output`, 55 | }); 56 | 57 | return vpc; 58 | } 59 | -------------------------------------------------------------------------------- /PRIVATE_LIVE/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eml-private-networking", 3 | "version": "0.1.0", 4 | "engines": { 5 | "node": ">=14.0.0 <=18.17.1" 6 | }, 7 | "scripts": { 8 | "build": "rm -rf dist && tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@types/jest": "^27.5.2", 15 | "@types/node": "10.17.27", 16 | "@types/prettier": "2.6.0", 17 | "@typescript-eslint/eslint-plugin": "^5.40.1", 18 | "@typescript-eslint/parser": "^5.40.1", 19 | "aws-cdk": "2.87.0", 20 | "cdk-nag": "2.21.50", 21 | "jest": "^27.5.1", 22 | "ts-jest": "^27.1.4", 23 | "ts-node": "^10.9.1", 24 | "typescript": "~3.9.7" 25 | }, 26 | "dependencies": { 27 | "aws-cdk-lib": "2.87.0", 28 | "constructs": "^10.0.0", 29 | "source-map-support": "^0.5.21" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /PRIVATE_LIVE/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2018" 7 | ], 8 | "declaration": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": false, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "experimentalDecorators": true, 21 | "strictPropertyInitialization": false, 22 | "typeRoots": [ 23 | "./node_modules/@types" 24 | ], 25 | "rootDirs": ["lib", "bin"], 26 | }, 27 | "exclude": [ 28 | "node_modules", 29 | "cdk.out" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /SSAI/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | !resources/demo_website/js/* 4 | *.d.ts 5 | node_modules 6 | cdk-exports.json 7 | package-lock.json 8 | 9 | # CDK asset staging directory 10 | .cdk.staging 11 | cdk.out 12 | 13 | -------------------------------------------------------------------------------- /SSAI/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /SSAI/architecture_AEML-AEMP-AEMT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/SSAI/architecture_AEML-AEMP-AEMT.png -------------------------------------------------------------------------------- /SSAI/bin/medialive-mediapackage-mediatailor-cloudfront.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import 'source-map-support/register'; 3 | import * as cdk from 'aws-cdk-lib'; 4 | import { Aws} from "aws-cdk-lib"; 5 | import { MedialiveMediapackageMediaTailorCloudfrontStack } from '../lib/medialive-mediapackage-mediatailor-cloudfront-stack'; 6 | 7 | import { App, Aspects } from 'aws-cdk-lib'; 8 | import { AwsSolutionsChecks } from 'cdk-nag'; 9 | 10 | const app = new cdk.App(); 11 | const stackName=app.node.tryGetContext('stackName') 12 | const description=app.node.tryGetContext('stackDescription') 13 | 14 | //Aspects.of(app).add(new AwsSolutionsChecks()); 15 | new MedialiveMediapackageMediaTailorCloudfrontStack(app, 'MedialiveMediapackageMediaTailorCloudfrontStack', { 16 | stackName: stackName, 17 | env: { 18 | region: `${Aws.REGION}`, 19 | account: `${Aws.ACCOUNT_ID}`, 20 | }, 21 | description 22 | }); -------------------------------------------------------------------------------- /SSAI/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/medialive-mediapackage-mediatailor-cloudfront.ts", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 21 | "@aws-cdk/core:stackRelativeExports": true, 22 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 23 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 24 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 25 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 28 | "@aws-cdk/core:checkSecretUsage": true, 29 | "@aws-cdk/aws-iam:minimizePolicies": true, 30 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, 31 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 32 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 33 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, 34 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, 35 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, 36 | "@aws-cdk/core:enablePartitionLiterals": true, 37 | "@aws-cdk/core:target-partitions": [ 38 | "aws", 39 | "aws-cn" 40 | ], 41 | "stackName": "MediaServicesRefArch-SSAI", 42 | "stackDescription": "AWS CDK MediaServices Reference Architectures: Ad insertion workflow - CDK demo using AWS MediaLive, MediaPackage, MediaTailor and Amazon CloudFront." 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /SSAI/config/configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "mediaLive": { 3 | "autoStart": true, 4 | "streamName": "live", 5 | "channelClass" : "STANDARD", 6 | "inputType" : "URL_PULL", 7 | "sourceEndBehavior" : "LOOP", 8 | "codec": "AVC", 9 | "encodingProfile" : "HD-720p", 10 | "priLink": "", 11 | "secLink" : "", 12 | "inputCidr": "0.0.0.0/0" , 13 | "priUrl": "https://d15an60oaeed9r.cloudfront.net/live_stream_v2/sports_reel_with_markers.m3u8", 14 | "secUrl" : "https://d15an60oaeed9r.cloudfront.net/live_stream_v2/sports_reel_with_markers.m3u8", 15 | "priFlow" : "", 16 | "secFlow" : "" 17 | }, 18 | "mediaPackage":{ 19 | "ad_markers":"PASSTHROUGH", 20 | "hls_segment_duration_seconds": 4, 21 | "hls_playlist_window_seconds": 60, 22 | "hls_max_video_bits_per_second": 2147483647, 23 | "hls_min_video_bits_per_second": 0, 24 | "hls_stream_order": "ORIGINAL", 25 | "hls_include_I_frame":false, 26 | "hls_audio_rendition_group": true, 27 | "hls_program_date_interval":60, 28 | "dash_period_triggers": "ADS", 29 | "dash_profile": "NONE", 30 | "dash_segment_duration_seconds": 2, 31 | "dash_segment_template" : "TIME_WITH_TIMELINE", 32 | "dash_manifest_window_seconds": 60, 33 | "dash_max_video_bits_per_second": 2147483647, 34 | "dash_min_video_bits_per_second": 0, 35 | "dash_stream_order": "ORIGINAL", 36 | "cmaf_segment_duration_seconds": 4, 37 | "cmaf_include_I_frame":false, 38 | "cmaf_program_date_interval":60, 39 | "cmaf_max_video_bits_per_second": 2147483647, 40 | "cmaf_min_video_bits_per_second": 0, 41 | "cmaf_stream_order": "ORIGINAL", 42 | "cmaf_playlist_window_seconds": 60, 43 | "mss_segment_duration_seconds": 2, 44 | "mss_manifest_window_seconds": 60, 45 | "mss_max_video_bits_per_second": 2147483647, 46 | "mss_min_video_bits_per_second": 0, 47 | "mss_stream_order": "ORIGINAL" 48 | }, 49 | "mediaTailor":{ 50 | "adDecisionServerUrl": "https://n8ljfs0h09.execute-api.us-west-2.amazonaws.com/v1/ads?duration=[session.avail_duration_secs]", 51 | "bumperStartUrl": "", 52 | "bumberEndUrl": "", 53 | "contentSegmentUrl": "../../../../../../..", 54 | "adSegmentUrl": "../../../../../../..", 55 | "slateAdUrl":"", 56 | "preRolladDecisionServerUrl": "", 57 | "preRollDuration": 1, 58 | "adMarkerPassthrough": false, 59 | "personalizationThreshold":1 60 | } 61 | } -------------------------------------------------------------------------------- /SSAI/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /SSAI/lib/custom_ressources/s3-update-config-file.ts: -------------------------------------------------------------------------------- 1 | import { 2 | custom_resources, 3 | aws_iam as iam, 4 | aws_s3 as s3, 5 | Aws 6 | } from "aws-cdk-lib"; 7 | import { Construct } from 'constructs'; 8 | import { NagSuppressions } from 'cdk-nag'; 9 | 10 | 11 | 12 | export interface MyCustomResourceProps { 13 | sessionURLHls: string; 14 | sessionURLDash: string; 15 | s3Bucket: s3.Bucket; 16 | } 17 | 18 | export class UpdateDemoWebsite extends Construct { 19 | public readonly response: string; 20 | 21 | constructor(scope: Construct, id: string, props: MyCustomResourceProps) { 22 | super(scope, id); 23 | 24 | /* 25 | * Updating the config file for the DemoWebsite 👇 26 | */ 27 | const jsonconfig = { 28 | "sessionURLHls":`${props.sessionURLHls}`, 29 | "sessionURLDash":`${props.sessionURLDash}` 30 | }; 31 | const updateConfigFile = new custom_resources.AwsCustomResource( 32 | this, 33 | "WriteS3ConfigFile", 34 | { 35 | onUpdate: { 36 | service: "S3", 37 | action: "putObject", 38 | parameters: { 39 | Body: JSON.stringify(jsonconfig), 40 | Bucket: `${props.s3Bucket.bucketName}`, 41 | Key: 'config.json', 42 | }, 43 | region: Aws.REGION, 44 | physicalResourceId: custom_resources.PhysicalResourceId.of(Date.now().toString()) 45 | }, 46 | policy: custom_resources.AwsCustomResourcePolicy.fromStatements([ 47 | new iam.PolicyStatement({ 48 | effect: iam.Effect.ALLOW, 49 | actions: ["s3:PutObject"], 50 | resources: [`${props.s3Bucket.bucketArn}/config.json`], 51 | }), 52 | ]), 53 | } 54 | ); 55 | } 56 | } -------------------------------------------------------------------------------- /SSAI/lib/lambda/medialive_channel_start_function/index.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | 5 | import boto3 as aws 6 | import json 7 | 8 | def lambda_handler(event, context): 9 | 10 | print("channel id", event["mediaLiveChannelId"]) 11 | 12 | _channel_id = event["mediaLiveChannelId"] 13 | my_eml = aws.client('medialive') 14 | try: 15 | eml_response = my_eml.start_channel(ChannelId=_channel_id) 16 | print("---------------------------\n", 17 | "eml response start channel\n", 18 | json.loads(json.dumps(eml_response, indent=2))) 19 | 20 | except Exception as e: 21 | print("Error Command - eml start channel " + _channel_id + "failed") 22 | print(e) 23 | 24 | def get_channel_status(channel,medialive): 25 | " 'State': 'CREATING'|'CREATE_FAILED'|'IDLE'|'STARTING'|'RUNNING'|'RECOVERING'|'STOPPING'|'DELETING'|'DELETED'|'UPDATING'|'UPDATE_FAILED', " 26 | info_channel = medialive.describe_channel( 27 | ChannelId=channel 28 | ) 29 | return info_channel["State"] -------------------------------------------------------------------------------- /SSAI/lib/lambda/mediapackage_cmaf_geturl_function/index.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | import boto3 4 | import logging 5 | emp_client = boto3.client('mediapackage') 6 | ssm_client = boto3.client('ssm') 7 | def lambda_handler(event, context): 8 | logger = logging.getLogger() 9 | logger.setLevel(logging.INFO) 10 | logger.info (f"Input parameters from cloud formation: {event}") 11 | endpointId = event["mediaPackageEndpointId"] 12 | ssmName = event["ssmName"] 13 | print(endpointId) 14 | try: 15 | response = emp_client.describe_origin_endpoint(Id=endpointId) 16 | logger.info(response) 17 | responseValue = str(response['CmafPackage']['HlsManifests'][0]['Url']) 18 | logger.info(responseValue) 19 | ssm_response = ssm_client.put_parameter( 20 | Name=ssmName, 21 | Description='Cmaf output Url', 22 | Value=responseValue, 23 | Type='String', 24 | Overwrite=True 25 | ) 26 | logger.info(ssm_response) 27 | return responseValue 28 | except Exception as e: 29 | print("Error Command - emp origin endpoint ID " + endpointId + "failed") 30 | print(e) 31 | logger.info(e) 32 | -------------------------------------------------------------------------------- /SSAI/lib/mediapackage_secrets.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance 5 | * with the License. A copy of the License is located at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions 11 | * and limitations under the License. 12 | */ 13 | 14 | import { 15 | Aws, 16 | CfnOutput, 17 | aws_secretsmanager as secretsmanager, 18 | } from "aws-cdk-lib"; 19 | import { Construct } from "constructs"; 20 | import { NagSuppressions } from 'cdk-nag'; 21 | 22 | 23 | export class Secrets extends Construct { 24 | public readonly cdnSecret: secretsmanager.ISecret; 25 | 26 | constructor(scope: Construct, id: string) { 27 | super(scope, id); 28 | 29 | const cdnSecret = new secretsmanager.Secret(this, "CdnSecret", { 30 | secretName: "MediaPackage/"+Aws.STACK_NAME, 31 | description: "Secret for Secure Resilient Live Streaming Delivery", 32 | generateSecretString: { 33 | secretStringTemplate: JSON.stringify({ MediaPackageCDNIdentifier: "" }), 34 | generateStringKey: "MediaPackageCDNIdentifier", //MUST keep this StringKey to use with EMP 35 | }, 36 | }); 37 | this.cdnSecret = cdnSecret; 38 | NagSuppressions.addResourceSuppressions(cdnSecret, [ 39 | { 40 | id: 'AwsSolutions-SMG4', 41 | reason: 'Remediated through property override.', 42 | }, 43 | ]); 44 | 45 | 46 | 47 | new CfnOutput(this, "cdnSecret", { 48 | value: cdnSecret.secretName, 49 | exportName: Aws.STACK_NAME + "cdnSecret", 50 | description: "The name of the cdnSecret", 51 | }); 52 | 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /SSAI/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "medialive-mediapackage-mediatailor-cloudfront", 3 | "version": "0.1.0", 4 | "bin": { 5 | "medialive-mediapackage-cloudfront": "bin/medialive-mediapackage-mediatailor-cloudfront.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@types/jest": "^27.5.2", 15 | "@types/node": "10.17.27", 16 | "@types/prettier": "2.6.0", 17 | "aws-cdk": "2.45.0", 18 | "jest": "^27.5.1", 19 | "ts-jest": "^27.1.4", 20 | "ts-node": "^10.9.1", 21 | "typescript": "~3.9.7" 22 | }, 23 | "dependencies": { 24 | "aws-cdk-lib": "2.80.0", 25 | "cdk-nag": "^2.21.53", 26 | "constructs": "^10.0.0", 27 | "source-map-support": "^0.5.21" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /SSAI/resources/demo_website/config.json: -------------------------------------------------------------------------------- 1 | {"sessionURLHls":"","sessionURLDash":""} -------------------------------------------------------------------------------- /SSAI/resources/demo_website/css/app.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --jumbotron-padding-y: 3rem; 3 | } 4 | .bg-dark { 5 | background-color: #202c3d!important; 6 | } 7 | .jumbotron { 8 | padding-top: var(--jumbotron-padding-y); 9 | padding-bottom: var(--jumbotron-padding-y); 10 | margin-bottom: 0; 11 | background-color: #fff; 12 | } 13 | @media (min-width: 768px) { 14 | .jumbotron { 15 | padding-top: calc(var(--jumbotron-padding-y) * 2); 16 | padding-bottom: calc(var(--jumbotron-padding-y) * 2); 17 | } 18 | } 19 | 20 | .jumbotron p:last-child { 21 | margin-bottom: 0; 22 | } 23 | 24 | .jumbotron .container { 25 | max-width: 40rem; 26 | } 27 | 28 | footer { 29 | padding-top: 3rem; 30 | padding-bottom: 3rem; 31 | } 32 | 33 | footer p { 34 | margin-bottom: .25rem; 35 | } 36 | 37 | .box-shadow { box-shadow: 0 .25rem .75rem rgba(208, 52, 52, 0.05); } 38 | 39 | 40 | .form-signin { 41 | width: 100%; 42 | max-width: 330px; 43 | padding: 15px; 44 | margin: 0 auto; 45 | } 46 | .form-signin .checkbox { 47 | font-weight: 400; 48 | } 49 | .form-signin .form-control { 50 | position: relative; 51 | box-sizing: border-box; 52 | height: auto; 53 | padding: 10px; 54 | font-size: 16px; 55 | } 56 | .form-signin .form-control:focus { 57 | z-index: 2; 58 | } 59 | .form-signin input[type="username"] { 60 | margin-bottom: -1px; 61 | border-bottom-right-radius: 0; 62 | border-bottom-left-radius: 0; 63 | } 64 | .form-signin input[type="password"] { 65 | margin-bottom: 10px; 66 | border-top-left-radius: 0; 67 | border-top-right-radius: 0; 68 | } 69 | 70 | pre { 71 | background-color: rgb(255, 255, 255); 72 | border: 1px solid rgb(230, 230, 230); 73 | padding: 10px 20px; 74 | margin: 20px; 75 | } 76 | .json-key { 77 | color: brown; 78 | } 79 | .json-value { 80 | color: navy; 81 | } 82 | .json-string { 83 | color: olive; 84 | } 85 | 86 | 87 | 88 | .btn:active, 89 | .active { 90 | color: black !important; 91 | background-color: #13d622 !important; 92 | border-color: #13d622 !important; 93 | } 94 | 95 | .btn .i{ 96 | color: white !important; 97 | border-color: white !important; 98 | } 99 | 100 | #hls { 101 | width: 200px; 102 | } 103 | 104 | #dash { 105 | width: 200px; 106 | } 107 | 108 | .my-btn { 109 | width: 200px; 110 | } 111 | -------------------------------------------------------------------------------- /SSAI/resources/demo_website/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/SSAI/resources/demo_website/img/favicon.ico -------------------------------------------------------------------------------- /SSAI/resources/demo_website/img/smile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/SSAI/resources/demo_website/img/smile.png -------------------------------------------------------------------------------- /SSAI/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2018" 7 | ], 8 | "declaration": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": false, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "experimentalDecorators": true, 21 | "strictPropertyInitialization": false, 22 | "typeRoots": [ 23 | "./node_modules/@types" 24 | ] 25 | }, 26 | "exclude": [ 27 | "node_modules", 28 | "cdk.out" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /SSAI_CMAF_AND_DASH/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | !resources/demo_website/js/* 4 | *.d.ts 5 | node_modules 6 | cdk-exports.json 7 | package-lock.json 8 | 9 | # CDK asset staging directory 10 | .cdk.staging 11 | cdk.out 12 | 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /SSAI_CMAF_AND_DASH/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /SSAI_CMAF_AND_DASH/architecture_AEML-AEMP-AEMT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/SSAI_CMAF_AND_DASH/architecture_AEML-AEMP-AEMT.png -------------------------------------------------------------------------------- /SSAI_CMAF_AND_DASH/bin/medialive-mediapackage-mediatailor-cloudfront.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import 'source-map-support/register'; 3 | import * as cdk from 'aws-cdk-lib'; 4 | import { Aws} from "aws-cdk-lib"; 5 | import { MedialiveMediapackageMediaTailorCloudfrontStack } from '../lib/medialive-mediapackage-mediatailor-cloudfront-stack'; 6 | 7 | import { App, Aspects } from 'aws-cdk-lib'; 8 | import { AwsSolutionsChecks } from 'cdk-nag'; 9 | 10 | const app = new cdk.App(); 11 | const stackName=app.node.tryGetContext('stackName') 12 | const description=app.node.tryGetContext('stackDescription') 13 | 14 | //Aspects.of(app).add(new AwsSolutionsChecks()); 15 | new MedialiveMediapackageMediaTailorCloudfrontStack(app, 'MedialiveMediapackageMediaTailorCloudfrontStack', { 16 | stackName: stackName, 17 | env: { 18 | region: `${Aws.REGION}`, 19 | account: `${Aws.ACCOUNT_ID}`, 20 | }, 21 | description 22 | }); -------------------------------------------------------------------------------- /SSAI_CMAF_AND_DASH/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/medialive-mediapackage-mediatailor-cloudfront.ts", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 21 | "@aws-cdk/core:stackRelativeExports": true, 22 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 23 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 24 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 25 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 28 | "@aws-cdk/core:checkSecretUsage": true, 29 | "@aws-cdk/aws-iam:minimizePolicies": true, 30 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, 31 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 32 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 33 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, 34 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, 35 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, 36 | "@aws-cdk/core:enablePartitionLiterals": true, 37 | "@aws-cdk/core:target-partitions": [ 38 | "aws", 39 | "aws-cn" 40 | ], 41 | "stackName": "MediaServicesRefArch-SSAI_CMAF_AND_DASH", 42 | "stackDescription": "AWS CDK MediaServices Reference Architectures: Ad insertion workflow using CMAF- CDK demo using AWS MediaLive, MediaPackage, MediaTailor and Amazon CloudFront." 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /SSAI_CMAF_AND_DASH/config/configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "mediaLive": { 3 | "autoStart": true, 4 | "streamName": "live", 5 | "channelClass" : "STANDARD", 6 | "inputType" : "URL_PULL", 7 | "sourceEndBehavior" : "LOOP", 8 | "codec": "AVC", 9 | "encodingProfile" : "HD-1080p", 10 | "priLink": "", 11 | "secLink" : "", 12 | "inputCidr": "0.0.0.0/0" , 13 | "priUrl": "https://d15an60oaeed9r.cloudfront.net/live_stream_v2/sports_reel_with_markers.m3u8", 14 | "secUrl" : "https://d15an60oaeed9r.cloudfront.net/live_stream_v2/sports_reel_with_markers.m3u8", 15 | "priFlow" : "", 16 | "secFlow" : "" 17 | }, 18 | "mediaPackage":{ 19 | "ad_markers":"PASSTHROUGH", 20 | "hls_segment_duration_seconds": 4, 21 | "hls_playlist_window_seconds": 300, 22 | "hls_max_video_bits_per_second": 2147483647, 23 | "hls_min_video_bits_per_second": 0, 24 | "hls_stream_order": "ORIGINAL", 25 | "hls_include_I_frame":false, 26 | "hls_audio_rendition_group": true, 27 | "hls_program_date_interval":60, 28 | "dash_period_triggers": "ADS", 29 | "dash_profile": "NONE", 30 | "dash_segment_duration_seconds": 2, 31 | "dash_segment_template" : "TIME_WITH_TIMELINE", 32 | "dash_manifest_window_seconds": 300, 33 | "dash_max_video_bits_per_second": 2147483647, 34 | "dash_min_video_bits_per_second": 0, 35 | "dash_stream_order": "ORIGINAL", 36 | "cmaf_segment_duration_seconds": 4, 37 | "cmaf_include_I_frame":false, 38 | "cmaf_program_date_interval":60, 39 | "cmaf_max_video_bits_per_second": 2147483647, 40 | "cmaf_min_video_bits_per_second": 0, 41 | "cmaf_stream_order": "ORIGINAL", 42 | "cmaf_playlist_window_seconds": 300 43 | }, 44 | "mediaTailor":{ 45 | "adDecisionServerUrl": "https://n8ljfs0h09.execute-api.us-west-2.amazonaws.com/v1/ads?duration=[session.avail_duration_secs]", 46 | "bumperStartUrl": "", 47 | "bumberEndUrl": "", 48 | "contentSegmentUrl": "../../../../../../..", 49 | "adSegmentUrl": "../../../../../../..", 50 | "slateAdUrl":"", 51 | "preRolladDecisionServerUrl": "", 52 | "preRollDuration": 1, 53 | "adMarkerPassthrough": false, 54 | "personalizationThreshold":1, 55 | "cmafCustomTranscodeProfileName": "", 56 | "dashCustomTranscodeProfileName": "" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /SSAI_CMAF_AND_DASH/images/SSAI_CMAF_DASH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/SSAI_CMAF_AND_DASH/images/SSAI_CMAF_DASH.png -------------------------------------------------------------------------------- /SSAI_CMAF_AND_DASH/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /SSAI_CMAF_AND_DASH/lib/custom_ressources/s3-update-config-file.ts: -------------------------------------------------------------------------------- 1 | import { 2 | custom_resources, 3 | aws_iam as iam, 4 | aws_s3 as s3, 5 | Aws 6 | } from "aws-cdk-lib"; 7 | import { Construct } from 'constructs'; 8 | import { NagSuppressions } from 'cdk-nag'; 9 | 10 | 11 | 12 | export interface MyCustomResourceProps { 13 | sessionURLHls: string; 14 | sessionURLDash: string; 15 | s3Bucket: s3.Bucket; 16 | } 17 | 18 | export class UpdateDemoWebsite extends Construct { 19 | public readonly response: string; 20 | 21 | constructor(scope: Construct, id: string, props: MyCustomResourceProps) { 22 | super(scope, id); 23 | 24 | /* 25 | * Updating the config file for the DemoWebsite 👇 26 | */ 27 | const jsonconfig = { 28 | "sessionURLHls":`${props.sessionURLHls}`, 29 | "sessionURLDash":`${props.sessionURLDash}` 30 | }; 31 | const updateConfigFile = new custom_resources.AwsCustomResource( 32 | this, 33 | "WriteS3ConfigFile", 34 | { 35 | onUpdate: { 36 | service: "S3", 37 | action: "putObject", 38 | parameters: { 39 | Body: JSON.stringify(jsonconfig), 40 | Bucket: `${props.s3Bucket.bucketName}`, 41 | Key: 'config.json', 42 | }, 43 | region: Aws.REGION, 44 | physicalResourceId: custom_resources.PhysicalResourceId.of(Date.now().toString()) 45 | }, 46 | policy: custom_resources.AwsCustomResourcePolicy.fromStatements([ 47 | new iam.PolicyStatement({ 48 | effect: iam.Effect.ALLOW, 49 | actions: ["s3:PutObject"], 50 | resources: [`${props.s3Bucket.bucketArn}/config.json`], 51 | }), 52 | ]), 53 | } 54 | ); 55 | } 56 | } -------------------------------------------------------------------------------- /SSAI_CMAF_AND_DASH/lib/lambda/medialive_channel_start_function/index.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | 5 | import boto3 as aws 6 | import json 7 | 8 | def lambda_handler(event, context): 9 | 10 | print("channel id", event["mediaLiveChannelId"]) 11 | 12 | _channel_id = event["mediaLiveChannelId"] 13 | my_eml = aws.client('medialive') 14 | try: 15 | eml_response = my_eml.start_channel(ChannelId=_channel_id) 16 | print("---------------------------\n", 17 | "eml response start channel\n", 18 | json.loads(json.dumps(eml_response, indent=2))) 19 | 20 | except Exception as e: 21 | print("Error Command - eml start channel " + _channel_id + "failed") 22 | print(e) 23 | 24 | def get_channel_status(channel,medialive): 25 | " 'State': 'CREATING'|'CREATE_FAILED'|'IDLE'|'STARTING'|'RUNNING'|'RECOVERING'|'STOPPING'|'DELETING'|'DELETED'|'UPDATING'|'UPDATE_FAILED', " 26 | info_channel = medialive.describe_channel( 27 | ChannelId=channel 28 | ) 29 | return info_channel["State"] -------------------------------------------------------------------------------- /SSAI_CMAF_AND_DASH/lib/lambda/mediapackage_cmaf_geturl_function/index.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | import boto3 4 | import logging 5 | emp_client = boto3.client('mediapackage') 6 | ssm_client = boto3.client('ssm') 7 | def lambda_handler(event, context): 8 | logger = logging.getLogger() 9 | logger.setLevel(logging.INFO) 10 | logger.info (f"Input parameters from cloud formation: {event}") 11 | endpointId = event["mediaPackageEndpointId"] 12 | ssmName = event["ssmName"] 13 | print(endpointId) 14 | try: 15 | response = emp_client.describe_origin_endpoint(Id=endpointId) 16 | logger.info(response) 17 | responseValue = str(response['CmafPackage']['HlsManifests'][0]['Url']) 18 | logger.info(responseValue) 19 | ssm_response = ssm_client.put_parameter( 20 | Name=ssmName, 21 | Description='Cmaf output Url', 22 | Value=responseValue, 23 | Type='String', 24 | Overwrite=True 25 | ) 26 | logger.info(ssm_response) 27 | return responseValue 28 | except Exception as e: 29 | print("Error Command - emp origin endpoint ID " + endpointId + "failed") 30 | print(e) 31 | logger.info(e) 32 | -------------------------------------------------------------------------------- /SSAI_CMAF_AND_DASH/lib/mediapackage_secrets.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance 5 | * with the License. A copy of the License is located at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions 11 | * and limitations under the License. 12 | */ 13 | 14 | import { 15 | Aws, 16 | CfnOutput, 17 | aws_secretsmanager as secretsmanager, 18 | } from "aws-cdk-lib"; 19 | import { Construct } from "constructs"; 20 | import { NagSuppressions } from 'cdk-nag'; 21 | 22 | 23 | export class Secrets extends Construct { 24 | public readonly cdnSecret: secretsmanager.ISecret; 25 | 26 | constructor(scope: Construct, id: string) { 27 | super(scope, id); 28 | 29 | const cdnSecret = new secretsmanager.Secret(this, "CdnSecret", { 30 | secretName: "MediaPackage/"+Aws.STACK_NAME, 31 | description: "Secret for Secure Resilient Live Streaming Delivery", 32 | generateSecretString: { 33 | secretStringTemplate: JSON.stringify({ MediaPackageCDNIdentifier: "" }), 34 | generateStringKey: "MediaPackageCDNIdentifier", //MUST keep this StringKey to use with EMP 35 | }, 36 | }); 37 | this.cdnSecret = cdnSecret; 38 | NagSuppressions.addResourceSuppressions(cdnSecret, [ 39 | { 40 | id: 'AwsSolutions-SMG4', 41 | reason: 'Remediated through property override.', 42 | }, 43 | ]); 44 | 45 | 46 | 47 | new CfnOutput(this, "cdnSecret", { 48 | value: cdnSecret.secretName, 49 | exportName: Aws.STACK_NAME + "cdnSecret", 50 | description: "The name of the cdnSecret", 51 | }); 52 | 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /SSAI_CMAF_AND_DASH/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "medialive-mediapackage-mediatailor-cloudfront", 3 | "version": "0.1.0", 4 | "bin": { 5 | "medialive-mediapackage-cloudfront": "bin/medialive-mediapackage-mediatailor-cloudfront.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@types/jest": "^27.5.2", 15 | "@types/node": "10.17.27", 16 | "@types/prettier": "2.6.0", 17 | "aws-cdk": "^2.87.0", 18 | "jest": "^27.5.1", 19 | "ts-jest": "^27.1.4", 20 | "ts-node": "^10.9.1", 21 | "typescript": "~3.9.7" 22 | }, 23 | "dependencies": { 24 | "aws-cdk-lib": "^2.87.0", 25 | "cdk-nag": "^2.20.10", 26 | "constructs": "^10.0.0", 27 | "source-map-support": "^0.5.21" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /SSAI_CMAF_AND_DASH/resources/demo_website/config.json: -------------------------------------------------------------------------------- 1 | {"sessionURLHls":"","sessionURLDash":""} -------------------------------------------------------------------------------- /SSAI_CMAF_AND_DASH/resources/demo_website/css/app.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --jumbotron-padding-y: 3rem; 3 | } 4 | .bg-dark { 5 | background-color: #202c3d!important; 6 | } 7 | .jumbotron { 8 | padding-top: var(--jumbotron-padding-y); 9 | padding-bottom: var(--jumbotron-padding-y); 10 | margin-bottom: 0; 11 | background-color: #fff; 12 | } 13 | @media (min-width: 768px) { 14 | .jumbotron { 15 | padding-top: calc(var(--jumbotron-padding-y) * 2); 16 | padding-bottom: calc(var(--jumbotron-padding-y) * 2); 17 | } 18 | } 19 | 20 | .jumbotron p:last-child { 21 | margin-bottom: 0; 22 | } 23 | 24 | .jumbotron .container { 25 | max-width: 40rem; 26 | } 27 | 28 | footer { 29 | padding-top: 3rem; 30 | padding-bottom: 3rem; 31 | } 32 | 33 | footer p { 34 | margin-bottom: .25rem; 35 | } 36 | 37 | .box-shadow { box-shadow: 0 .25rem .75rem rgba(208, 52, 52, 0.05); } 38 | 39 | 40 | .form-signin { 41 | width: 100%; 42 | max-width: 330px; 43 | padding: 15px; 44 | margin: 0 auto; 45 | } 46 | .form-signin .checkbox { 47 | font-weight: 400; 48 | } 49 | .form-signin .form-control { 50 | position: relative; 51 | box-sizing: border-box; 52 | height: auto; 53 | padding: 10px; 54 | font-size: 16px; 55 | } 56 | .form-signin .form-control:focus { 57 | z-index: 2; 58 | } 59 | .form-signin input[type="username"] { 60 | margin-bottom: -1px; 61 | border-bottom-right-radius: 0; 62 | border-bottom-left-radius: 0; 63 | } 64 | .form-signin input[type="password"] { 65 | margin-bottom: 10px; 66 | border-top-left-radius: 0; 67 | border-top-right-radius: 0; 68 | } 69 | 70 | pre { 71 | background-color: rgb(255, 255, 255); 72 | border: 1px solid rgb(230, 230, 230); 73 | padding: 10px 20px; 74 | margin: 20px; 75 | } 76 | .json-key { 77 | color: brown; 78 | } 79 | .json-value { 80 | color: navy; 81 | } 82 | .json-string { 83 | color: olive; 84 | } 85 | 86 | 87 | 88 | .btn:active, 89 | .active { 90 | color: black !important; 91 | background-color: #13d622 !important; 92 | border-color: #13d622 !important; 93 | } 94 | 95 | .btn .i{ 96 | color: white !important; 97 | border-color: white !important; 98 | } 99 | 100 | #hls { 101 | width: 200px; 102 | } 103 | 104 | #dash { 105 | width: 200px; 106 | } 107 | 108 | .my-btn { 109 | width: 200px; 110 | } 111 | -------------------------------------------------------------------------------- /SSAI_CMAF_AND_DASH/resources/demo_website/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/SSAI_CMAF_AND_DASH/resources/demo_website/img/favicon.ico -------------------------------------------------------------------------------- /SSAI_CMAF_AND_DASH/resources/demo_website/img/smile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/SSAI_CMAF_AND_DASH/resources/demo_website/img/smile.png -------------------------------------------------------------------------------- /SSAI_CMAF_AND_DASH/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2018" 7 | ], 8 | "declaration": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": false, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "experimentalDecorators": true, 21 | "strictPropertyInitialization": false, 22 | "typeRoots": [ 23 | "./node_modules/@types" 24 | ] 25 | }, 26 | "exclude": [ 27 | "node_modules", 28 | "cdk.out" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /SUSTAINABILITY_LIVE/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | !resources/demo_website/js/* 4 | *.d.ts 5 | node_modules 6 | cdk-exports.json 7 | 8 | # CDK asset staging directory 9 | .cdk.staging 10 | cdk.out 11 | 12 | cdk.context.json -------------------------------------------------------------------------------- /SUSTAINABILITY_LIVE/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /SUSTAINABILITY_LIVE/bin/index.ts: -------------------------------------------------------------------------------- 1 | import { App, Aspects } from "aws-cdk-lib"; 2 | import { AwsSolutionsChecks } from "cdk-nag"; 3 | import { MediaLiveStack } from "../lib/stacks/media-services"; 4 | 5 | const app = new App(); 6 | new MediaLiveStack(app); 7 | Aspects.of(app).add(new AwsSolutionsChecks()); 8 | -------------------------------------------------------------------------------- /SUSTAINABILITY_LIVE/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/index.ts", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 21 | "@aws-cdk/core:stackRelativeExports": true, 22 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 23 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 24 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 25 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 28 | "@aws-cdk/core:checkSecretUsage": true, 29 | "@aws-cdk/aws-iam:minimizePolicies": true, 30 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, 31 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 32 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 33 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, 34 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, 35 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, 36 | "@aws-cdk/core:enablePartitionLiterals": true, 37 | "@aws-cdk/core:target-partitions": [ 38 | "aws", 39 | "aws-cn" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /SUSTAINABILITY_LIVE/images/channels.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-mediaservices-refarch/6e606388270dd576a6d8e475a5660ad48da4d8c9/SUSTAINABILITY_LIVE/images/channels.drawio.png -------------------------------------------------------------------------------- /SUSTAINABILITY_LIVE/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /SUSTAINABILITY_LIVE/lib/config.ts: -------------------------------------------------------------------------------- 1 | // Edit to use your desired configuration 2 | 3 | /** 4 | * MediaConnect Whitelist CIDR - this is to narrow down traffic to ensure that you are only receiving traffic from your upstream system. 5 | */ 6 | export const DEST_MEDIA_CONNECT_WHITELIST_CIDR = "0.0.0.0/0"; 7 | 8 | /** 9 | * MediaLive input bucket name 10 | */ 11 | export const INPUT_BUCKET_NAME = "mybucketname"; 12 | 13 | /** 14 | * MediaLive input file name (in the bucket) 15 | */ 16 | export const INPUT_BUCKET_MP4_FILE = "myfile.mp4"; 17 | -------------------------------------------------------------------------------- /SUSTAINABILITY_LIVE/lib/encoder-settings/audio.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { Base, OPTIONS } from "./base"; 3 | 4 | export class AudioAAC extends Base { 5 | constructor(id: string, private aacProps?: CfnChannel.AacSettingsProperty) { 6 | super(id, OPTIONS.AUDIO); 7 | } 8 | 9 | private baseAacSettings: CfnChannel.AacSettingsProperty = {}; 10 | 11 | public getAudioProfile(): CfnChannel.AudioDescriptionProperty { 12 | return { 13 | codecSettings: { 14 | aacSettings: { 15 | ...this.baseAacSettings, 16 | ...this.aacProps, 17 | }, 18 | }, 19 | name: this.getUniqueId(), 20 | }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SUSTAINABILITY_LIVE/lib/encoder-settings/base.ts: -------------------------------------------------------------------------------- 1 | import { createHash } from "crypto"; 2 | 3 | export enum OPTIONS { 4 | AUDIO = "audio", 5 | VIDEO = "video", 6 | NONE = "none", 7 | } 8 | 9 | export class Base { 10 | constructor(private parentId: string, private option: OPTIONS) {} 11 | 12 | private generatedId: string = createHash("md5").update(this.parentId).digest("hex").toString().substring(0, 5); 13 | protected uniqueId = this.generalUniqueName(this.generatedId, this.option); 14 | 15 | public getUniqueId() { 16 | return this.uniqueId; 17 | } 18 | 19 | private generalUniqueName(id: string, prefixOptions: OPTIONS) { 20 | return `${prefixOptions != OPTIONS.NONE ? `${prefixOptions}_` : ""}${id}`; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SUSTAINABILITY_LIVE/lib/encoder-settings/output-group-settings.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { CfnChannel as MpChannel } from "aws-cdk-lib/aws-mediapackage"; 3 | 4 | export class MPOutputGroupSettings { 5 | constructor(private mp: MpChannel) {} 6 | 7 | private baseProps: CfnChannel.OutputGroupSettingsProperty = { 8 | mediaPackageGroupSettings: { 9 | destination: { 10 | destinationRefId: this.mp.ref, 11 | }, 12 | }, 13 | }; 14 | 15 | public getOutputGroupSettings(): CfnChannel.OutputGroupSettingsProperty { 16 | return { 17 | ...this.baseProps, 18 | }; 19 | } 20 | } 21 | 22 | export class UDPOutputGroupSettings { 23 | private baseProps: CfnChannel.OutputGroupSettingsProperty = { 24 | udpGroupSettings: {}, 25 | }; 26 | 27 | public getOutputGroupSettings(): CfnChannel.OutputGroupSettingsProperty { 28 | return { 29 | ...this.baseProps, 30 | }; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SUSTAINABILITY_LIVE/lib/encoder-settings/output.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { Base, OPTIONS } from "./base"; 3 | 4 | export class MpOutput extends Base { 5 | constructor(id: string, private videoSettings: string, private audioSettings: string) { 6 | super(id, OPTIONS.NONE); 7 | } 8 | 9 | private baseProps: CfnChannel.OutputProperty = { 10 | outputSettings: { 11 | mediaPackageOutputSettings: {}, 12 | }, 13 | outputName: this.getUniqueId(), 14 | videoDescriptionName: this.videoSettings, 15 | audioDescriptionNames: [this.audioSettings], 16 | captionDescriptionNames: [], 17 | }; 18 | 19 | public getOutputSettings(): CfnChannel.OutputProperty { 20 | return this.baseProps; 21 | } 22 | } 23 | 24 | export class UdpOutput extends Base { 25 | constructor(id: string, private videoSettings: string, private audioSettings: string) { 26 | super(id, OPTIONS.NONE); 27 | } 28 | 29 | private baseProps: CfnChannel.OutputProperty = { 30 | outputSettings: { 31 | udpOutputSettings: { 32 | containerSettings: { 33 | m2TsSettings: {}, 34 | }, 35 | destination: { 36 | destinationRefId: this.getUniqueId(), 37 | }, 38 | }, 39 | }, 40 | outputName: this.getUniqueId(), 41 | videoDescriptionName: this.videoSettings, 42 | audioDescriptionNames: [this.audioSettings], 43 | captionDescriptionNames: [], 44 | }; 45 | 46 | public getOutputSettings(): CfnChannel.OutputProperty { 47 | return this.baseProps; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /SUSTAINABILITY_LIVE/lib/encoder-settings/video.ts: -------------------------------------------------------------------------------- 1 | import { CfnChannel } from "aws-cdk-lib/aws-medialive"; 2 | import { Base, OPTIONS } from "./base"; 3 | 4 | interface IVideo { 5 | height: number; 6 | width: number; 7 | } 8 | 9 | type H264BaseSettings = Omit; 10 | interface H264Settings extends H264BaseSettings { 11 | bitrate: number; 12 | } 13 | 14 | export class VideoH264 extends Base { 15 | constructor(private id: string, private resolutionProps: IVideo, private h264Props: H264Settings) { 16 | super(id, OPTIONS.VIDEO); 17 | } 18 | 19 | private baseH264Props: H264BaseSettings = { 20 | framerateControl: "SPECIFIED", 21 | framerateNumerator: 25, 22 | framerateDenominator: 1, 23 | gopSize: 2, 24 | gopSizeUnits: "SECONDS", 25 | parControl: "SPECIFIED", 26 | parNumerator: 1, 27 | parDenominator: 1, 28 | }; 29 | 30 | public getVideoProfile(): CfnChannel.VideoDescriptionProperty { 31 | return { 32 | codecSettings: { 33 | h264Settings: { 34 | ...this.baseH264Props, 35 | ...this.h264Props, 36 | }, 37 | }, 38 | height: this.resolutionProps.height, 39 | name: this.getUniqueId(), 40 | width: this.resolutionProps.width, 41 | }; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /SUSTAINABILITY_LIVE/lib/mediaservices/media-connect.ts: -------------------------------------------------------------------------------- 1 | import { CfnOutput, Stack } from "aws-cdk-lib"; 2 | import { CfnFlow } from "aws-cdk-lib/aws-mediaconnect"; 3 | import { DEST_MEDIA_CONNECT_WHITELIST_CIDR } from "../config"; 4 | 5 | export function getMediaConnectOutputFlow(stack: Stack) { 6 | const flow = new CfnFlow(stack, "example-destination-flow", { 7 | name: `${stack.stackName}-EMX-destination-flow`, 8 | source: { 9 | name: "distribution-from-EML", 10 | description: "EML to EMX", 11 | protocol: "rtp-fec", 12 | whitelistCidr: DEST_MEDIA_CONNECT_WHITELIST_CIDR, 13 | }, 14 | availabilityZone: `${stack.region}a`, 15 | }); 16 | 17 | new CfnOutput(stack, "emx-destination-flow-output", { 18 | exportName: `${stack.stackName}-emx-destination-flow-output`, 19 | value: flow.ref, 20 | }); 21 | 22 | return flow; 23 | } 24 | -------------------------------------------------------------------------------- /SUSTAINABILITY_LIVE/lib/mediaservices/media-package.ts: -------------------------------------------------------------------------------- 1 | import { CfnOutput, Stack } from "aws-cdk-lib"; 2 | import { CfnChannel, CfnOriginEndpoint } from "aws-cdk-lib/aws-mediapackage"; 3 | 4 | export function createMediaPackage(stack: Stack) { 5 | const mp = new CfnChannel(stack, "sustainable-emp-channel", { 6 | id: `${stack.stackName}-EMP-channel`, 7 | }); 8 | const ep = new CfnOriginEndpoint(stack, "emp-endpoint", { 9 | channelId: mp.id, 10 | id: `${stack.stackName}-EMP-hls-output`, 11 | hlsPackage: {}, 12 | }); 13 | 14 | ep.addDependsOn(mp); 15 | 16 | new CfnOutput(stack, "emp-channel-output", { 17 | value: mp.ref, 18 | exportName: `${stack.stackName}-emp-channel-output`, 19 | }); 20 | 21 | new CfnOutput(stack, "emp-channel-hls-output", { 22 | value: ep.ref, 23 | exportName: `${stack.stackName}-emp-channel-hls-output`, 24 | }); 25 | 26 | return mp; 27 | } 28 | -------------------------------------------------------------------------------- /SUSTAINABILITY_LIVE/lib/stacks/media-services.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { createMediaPackage } from "../mediaservices/media-package"; 3 | import { createMediaLive, createMediaLiveInput } from "../mediaservices/media-live"; 4 | import { getMediaConnectOutputFlow } from "../mediaservices/media-connect"; 5 | import { INPUT_BUCKET_MP4_FILE, INPUT_BUCKET_NAME } from "../config"; 6 | import { Construct } from "constructs"; 7 | 8 | export const STACK_PREFIX_NAME = "MediaServicesRefArch"; 9 | 10 | export class MediaLiveStack extends Stack { 11 | constructor(app: Construct) { 12 | super(app, `${STACK_PREFIX_NAME}-sustainable-workflow`, { 13 | env: { 14 | region: process.env.CDK_DEFAULT_REGION, 15 | account: process.env.CDK_DEFAULT_ACCOUNT, 16 | }, 17 | }); 18 | } 19 | 20 | protected mc = getMediaConnectOutputFlow(this); 21 | protected input = createMediaLiveInput(this, INPUT_BUCKET_NAME, INPUT_BUCKET_MP4_FILE); 22 | protected mp = createMediaPackage(this); 23 | protected ml = createMediaLive(this, { 24 | mp: this.mp, 25 | mxIP: this.mc.attrSourceIngestIp, 26 | mxPort: this.mc.attrSourceSourceIngestPort, 27 | input: this.input, 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /SUSTAINABILITY_LIVE/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "medialive-sustainability", 3 | "version": "0.1.0", 4 | "engines": { 5 | "node": ">=14.0.0 <=18.12.0" 6 | }, 7 | "scripts": { 8 | "build": "rm -rf dist && tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@types/jest": "^27.5.2", 15 | "@types/node": "10.17.27", 16 | "@types/prettier": "2.6.0", 17 | "@typescript-eslint/eslint-plugin": "^5.40.1", 18 | "@typescript-eslint/parser": "^5.40.1", 19 | "jest": "^27.5.1", 20 | "ts-jest": "^27.1.4", 21 | "aws-cdk": "2.87.0", 22 | "cdk-nag": "2.21.50", 23 | "ts-node": "10.4.0", 24 | "typescript": "4.4.4" 25 | }, 26 | "dependencies": { 27 | "aws-cdk-lib": "2.87.0", 28 | "constructs": "^10.0.0", 29 | "source-map-support": "^0.5.21" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /SUSTAINABILITY_LIVE/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2018" 7 | ], 8 | "declaration": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": false, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "experimentalDecorators": true, 21 | "strictPropertyInitialization": false, 22 | "typeRoots": [ 23 | "./node_modules/@types" 24 | ], 25 | "rootDirs": ["./bin", "./lib"], 26 | }, 27 | "exclude": [ 28 | "node_modules", 29 | "cdk.out" 30 | ] 31 | } 32 | --------------------------------------------------------------------------------