├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── backend ├── CREATEIVS.md ├── README.md ├── README_HTTPS.md ├── create_ivs_channel.sh ├── docker_files │ ├── Dockerfile │ ├── buildspec.yml │ ├── health │ │ └── health-check.html │ └── src │ │ ├── package.json │ │ ├── transwrap.js │ │ └── transwrap_local.js ├── get_container_domain.sh ├── health-check.html ├── install_ivs_backend.sh ├── json_configs │ ├── dynamodb_table.json │ ├── ivs_codebuild_ecr_policy.json │ ├── ivs_dynamodb_populate.json │ ├── ivs_ecs_trust_policy.json │ ├── ivs_lambda_dynamodb_policy.json │ ├── ivs_lambda_resource_policy.json │ ├── ivs_lambda_trust_policy.json │ └── ivs_webrtc_codebuild_trust_policy.json ├── json_models │ ├── ecs_services_model.json │ ├── ivs_codebuild_base_policy_model.json │ ├── ivs_codebuild_log_policy_model.json │ ├── ivs_codebuild_model.json │ ├── ivs_codebuild_s3_policy_model.json │ ├── ivs_codebuild_vpc_policy_model.json │ ├── ivs_events_rule_model.json │ ├── ivs_lambda_resource_policy_model.json │ ├── ivs_ports.txt │ ├── ivs_task_definition_model.json │ └── lambda_model.json ├── lambda_function.py ├── package.json ├── temp_files │ └── .gitignore ├── transwrap_http.js ├── transwrap_https.js ├── transwrap_local _peer.js ├── transwrap_local.js └── uninstall_ivs_backend.sh ├── doc ├── IVSCopy.png ├── IVSCreateChannel_1.png ├── IVSCreateChannel_2.png ├── app01.png ├── arch.png ├── auth.png ├── auth01.png ├── codearr.png ├── coderemote.png ├── codevideo.png ├── config.png ├── front.png ├── golive.png ├── saveivs.png ├── sslerror.png └── videoworkflow.png └── frontend ├── .gitignore ├── BROWSER.md ├── README.md ├── amplify ├── .config │ └── project-config.json ├── backend │ ├── api │ │ ├── APIGatewayAuthStack.json │ │ └── saveIVSparam │ │ │ └── cli-inputs.json │ ├── auth │ │ └── simplewebstreaming178ae288 │ │ │ └── cli-inputs.json │ ├── backend-config.json │ ├── function │ │ ├── ISSgetServers │ │ │ ├── ISSgetServers-cloudformation-template.json │ │ │ ├── amplify.state │ │ │ ├── custom-policies.json │ │ │ ├── function-parameters.json │ │ │ └── src │ │ │ │ ├── event.json │ │ │ │ └── index.js │ │ └── saveIVSparam │ │ │ ├── amplify.state │ │ │ ├── custom-policies.json │ │ │ ├── function-parameters.json │ │ │ ├── saveIVSparam-cloudformation-template.json │ │ │ └── src │ │ │ ├── event.json │ │ │ └── index.js │ ├── storage │ │ ├── ISStaskdnstrack │ │ │ └── cli-inputs.json │ │ └── IVSparam │ │ │ └── cli-inputs.json │ ├── tags.json │ └── types │ │ └── amplify-dependent-resources-ref.d.ts └── hooks │ └── README.md ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt └── src ├── components ├── App.css ├── App.js ├── HomePage.js ├── PlayerView.js ├── StreamForm.js ├── UserMedia.js ├── player │ └── player.js └── styles │ ├── HomePage.style.css │ ├── PlayerView.style.css │ ├── StreamForm.style.css │ └── UserMedia.style.css └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /amplify 6 | /frontend/node_modules 7 | /backend/node_modules 8 | /backend/temp_files 9 | /backend/json_configs 10 | /.pnp 11 | .pnp.js 12 | .pem 13 | 14 | # testing 15 | /coverage 16 | /frontend/.vscode 17 | /frontend/js 18 | /frontend/index.html 19 | /UnderDevCustom 20 | 21 | #amplify 22 | #/frontend/amplify* 23 | /frontend/amplify/.config/local-* 24 | /frontend/amplify/\#current-cloud-backend 25 | /frontend/amplify/mock-data 26 | /frontend/amplify/backend/amplify-meta.json 27 | /frontend/amplify/backend/awscloudformation 28 | /frontend/amplify/logs/ 29 | /frontend/amplify/cli.json 30 | /frontend/amplify/team-provider-info.json 31 | /frontend/src/aws-video-exports.js 32 | /frontend/src/aws-exports.js 33 | /frontend/amplify/backend/function/ISSgetServers/dist/latest-build.zip 34 | /frontend/amplify/backend/function/saveIVSparam/dist/latest-build.zip 35 | /frontend/amplify/README.md 36 | 37 | #history 38 | .history 39 | 40 | 41 | 42 | # production 43 | /build 44 | /frontend/build 45 | 46 | # misc 47 | .DS_Store 48 | .env.local 49 | .env.development.local 50 | .env.test.local 51 | .env.production.local 52 | *.eslintcache 53 | .eslintcache 54 | ./eslintcache 55 | /.vscode 56 | 57 | npm-debug.log* 58 | yarn-debug.log* 59 | yarn-error.log* 60 | /doc/index.html 61 | /doc/plan.md 62 | 63 | #under dev 64 | /client* 65 | frontend/package-lock.json 66 | backend/package-lock.json 67 | frontend/stats.json 68 | backend/cert.pem 69 | backend/key.pem 70 | backend/lambda.zip 71 | backend/docker_files/docker.zip 72 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. -------------------------------------------------------------------------------- /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. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simplifying live streaming contribution 2 | 3 | A reference code and solution to simplify the live streaming by using the browser APIs and webRTC to capture the video. 4 | The solution is based on small, independent, and decoupled blocks to capture cameras and transwrap video into RTMPS, sending to (Amazon Interactive Video Service(https://aws.amazon.com/ivs/)). 5 | 6 | ## Solution Architecture 7 | 8 | Application Architecture 9 | 10 | ## Solution Components 11 | 1. Broadcaster app: This application uses basic browser APIs to capture audio and video media, and a client web socket to send data to the server web socket. It uses AWS Amplify, which offers tools to build scalable full-stack applications. 12 | 2. Amazon IVS: A managed live streaming solution that is quick and easy to set up and is ideal for creating interactive video experiences. 13 | 3. Proxy video transwrap: This application uses Node.js to open an HTTPS server for the WebSocket communication, and FFmpeg to transwrap the stream in RTMPS to Amazon IVS. 14 | 15 | ## This project is intended for education purposes only and not for production usage. 16 | A reminder that this is a reference solution is not for use in a production environment, but is ideal for testing and accelerating your cloud adoption. Please reach your AWS representative, [click or here](https://pages.awscloud.com/Media-and-Entertainment-Contact-Us.html), for more information on architecting a custom solution for large-scale production-grade events. 17 | 18 | This is a serverless application, leveraging [Amazon IVS](https://aws.amazon.com/ivs), [AWS Amplify](https://aws.amazon.com/amplify/), [Amazon API Gateway](https://aws.amazon.com/api-gateway/), [AWS Lambda](https://aws.amazon.com/lambda/), [Amazon DynamoDB](https://aws.amazon.com/dynamodb), [Amazon S3](https://aws.amazon.com/s3/). The web user interface is built using React.js. 19 | 20 | 21 | ## Deployment options: 22 | :warning: **NOTE:** Deploying this demo application in your AWS account will create and consume AWS resources. 23 | 24 | ### :video_camera: [1. To deploy the application API's and run the client app locally](/frontend/README.md) 25 | We will need to deploy the API's, Lambda Functions and Authentication resources, we will use AWS Amplify. 26 | 27 | ### :rocket: [2. Deployment of the Proxy AWS Fargate and web App Publish](/backend/README.md) 28 | This is an **optional step** to provisione a ECS Docker Container to work a remote transwrap proxy, that will translate from WebRTC to RTMPS. 29 | 30 | ## Simplifying live streaming contribution - run locally. 31 | This sample demo app, captures the video and use a proxy in node.js to transwrap the stream to Amazon IVS 32 | 33 | *Detailed Video Workflow:* 34 | Application Architecture 35 | 36 | The server - transwrap-http.js can be local or remote, according to the steps that you will follow. 37 | For improving browser compatibility, the video element was embedded in a canvas element, as HTMLMediaElement.captureStream() has limited support. HTMLCanvasElement.captureStream() compatibility can be observed in the link [captureStream()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/captureStream) 38 | 39 | ## Next step 40 | [Deploy the application API's and run the client app locally](/frontend/README.md) 41 | 42 | ## Additional guides in this repository 43 | * [**Backend: Transwraping Amazon ECS container:**](/backend/README.md)Install the remote video transwrap server. 44 | * [**Create a IVS Channel using a shell script**](/backend/CREATEIVS.md) 45 | * [**Customize the web app and compatibility discussions**](frontend/BROWSER.md) 46 | 47 | ## Notice 48 | This project uses FFMPG, http://www.ffmpeg.org, please check lisensing usage. 49 | 50 | ## Security 51 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 52 | 53 | ## License 54 | This library is licensed under the MIT-0 License. See the LICENSE file. -------------------------------------------------------------------------------- /backend/CREATEIVS.md: -------------------------------------------------------------------------------- 1 | # Simplifying live streaming contribution - Create a Amazon IVS Channel 2 | 3 | ## [Optional] Channel Creation and Application Configuration 4 | We provide a shell script to create your Amazon IVS Channel, you can use [this guide](https://docs.aws.amazon.com/ivs/latest/userguide/getting-started.html) if you prefer to create it using the AWS Console. 5 | Create a Channel on Amazon Interactive Video Service: 6 | 7 | ```sh 8 | cd simple-streaming-webapp/backend 9 | ./create_ivs_channel.sh ivs-webrtc 10 | ``` 11 | ## Copy and save the ingestEndpoint, streamKey value and playbackUrl 12 | 13 | ``` 14 | Copy EndPoint: rtmps://6f0eda55d6df.global-contribute.live-video.net:443/app/ 15 | Copy StreamKey: sk_us-east-1_siFTKMADpmqe_dzUYPKAZjbE1lcrbQdudLAxyzw 16 | Copy playbackUrl: https://8f97718d90a0.us-east-1.playback.live-video.net/api/video/v1/us-east-1.098435415742.channel.cxyzwe.m3u8 17 | ``` 18 | 19 | **Save the params and add to your user interface** 20 | Application Auth 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | # Simplifying live streaming contribution - running in Amazon ECS Container 2 | 3 | ## Deploy the backend environment 4 | 5 | The proxy transwapper is a compound of two containers running a NodeJS web server and FFmpeg. The containers are running on AWS Fargate for Amazon ECS. The AWS Fargate is a serverless compute engine for containers that work with Amazon Elastic Container Service (ECS) and Amazon Elastic Kubernetes Service (EKS). 6 | 7 | In Amazon ECS, you have three building blocks to run your containers successfully: 8 | 9 | Task definition: It is like a blueprint for your application. It is where you which docker images to use and the resources that your container will require. 10 | 11 | Task: It is the instantiation of your task definition. 12 | 13 | Service: It launches and maintains a specified number of copies of the task definition in your cluster. 14 | 15 | In our case, we have a service with two tasks, and each has its own public IP to receive the video stream via WebSocket. 16 | 17 | To track each task's public IP, we are using Amazon EventBridge, AWS Lambda, and Amazon DynamoDB. 18 | 19 | Amazon EventBridge: This is a serverless event bus that makes it easier to build event-driven applications at scale using events generated from your applications, integrated Software-as-a-Service (SaaS) applications, and AWS services. In this case, we built a rule in Amazon EventBridge to track our tasks' start and stop status and trigger the AWS Lambda function. 20 | 21 | AWS Lambda: This is a serverless compute service that lets you run code without provisioning or managing servers, creating workload-aware cluster scaling logic, maintaining event integrations, or managing runtimes. In this case, we built an AWS Lambda Function that, when triggered by the Amazon EventBridge, will update the public IP of the tasks in an Amazon DynamoDB table. 22 | 23 | Amazon DynamoDB: This is a key-value and document database that delivers single-digit millisecond performance at any scale. It's a fully managed, multi-region, multi-active, durable database with built-in security, backup and restores, and in-memory caching for internet-scale applications. In this case, we use an Amazon DynamoDB table to stores the metadata of the tasks. The Public IP of the tasks is part of the metadata. 24 | 25 | The backend deployment is built using a bash shell script located under aws-simple-streaming-webapp/backend that runs AWS CLI commands to build the environment. 26 | 27 | ```sh 28 | cd simple-streaming-webapp/backend 29 | ./install_ivs_backend.sh deploy all 30 | ``` 31 | 32 | ## HTTPS considerations 33 | The remote ECS container uses a self signed certificate, so you might have to allow your browser to accept the self signed certificate, or add an Amazon CloudFront distribution to handle HTTPS, or add your own valid certificate to the container. 34 | 35 | To discover the external IP address of your transwrap server you can run: 36 | 37 | ```sh 38 | ./get_container_domain.sh 39 | ``` 40 | 41 | :warning: **Note:** For test purpose only. 42 | Open it on your web browser add https:// in front of your domain and accept the self signed certificate. 43 | 44 | ssl error 45 | 46 | 47 | ## Cleanup - (Optional): Cleanup, removing the provisioned AWS resources. 48 | 49 | For removing the Transwrap proxy server, you can use the bash script uninstall_ivs_backend. 50 | 51 | ```sh 52 | ./uninstall_ivs_backend.sh clean all 53 | ``` 54 | 55 | After it completes, you can clean files 56 | 57 | ```sh 58 | ./uninstall_ivs_backend.sh clean files 59 | ``` 60 | 61 | 62 | -------------------------------------------------------------------------------- /backend/README_HTTPS.md: -------------------------------------------------------------------------------- 1 | ## Transwrap container server instructions 2 | 3 | The backend transwrap server (transwrap.js) is a simple socket server that receives a socket connection data in webm and transwrap / proxies the content to RTMP. 4 | 5 | ### General Instructions 6 | 7 | #### 1) Running in a local envirolment: localhost HTTP 8 | 9 | First install the dependencies and then use the file transwrap_local.js with node. 10 | ``` 11 | cd backend/ 12 | npm install 13 | npm start-test transwrap_local.js 14 | ``` 15 | 16 | it should give the following result 17 | 18 | Listening on port: 3004 19 | 20 | #### 2) Running in HTTPS 21 | 22 | The application frontent requires https, and the transwrap.js is built-in https Node.js module. 23 | For running your server, first we need to create or install a SSL certificate. 24 | 25 | The following instructions will generate the key.pem and cert.pem self-sigend certificates for our server. 26 | Note: Self-signed certificates are not recomended for a non-development evirolment. You can user AWS Certificate Manager (link) to provision, manage, and deploy public and private SSL/TLS 27 | 28 | #### Generating certificate for develoment tests locally in HTTPS 29 | 30 | ``` 31 | cd backend/ 32 | openssl genrsa -out key.pem 33 | openssl req -new -key key.pem -out csr.pem 34 | openssl x509 -req -days 9999 -in csr.pem -signkey key.pem -out cert.pem 35 | rm csr.pem 36 | ``` 37 | 38 | Running locally in HTTPS 39 | 40 | ``` 41 | npm run startDevs 42 | ``` 43 | 44 | The certificates has to be generated in the backend folder. 45 | 46 | :warning: **Note:** For test purpose only. 47 | Open it on your web browser type https://127.0.0.1:3004 and accept the self signed certificate. 48 | 49 | ssl error 50 | 51 | ## [Return to the frontend deployment](../frontend/README.md) 52 | 53 | To allow localhost on ssl, you can paste this line in the address bar and proceed to allow localhost, instructions for *chrome only*. 54 | 55 | ```chrome://flags/#allow-insecure-localhost``` 56 | 57 | :warning: **Note:** For test purpose only. remenber to restore this configuration after you finish your tests. 58 | 59 | ------- 60 | Note: This project uses FFMPEG please check lisencing aspects. 61 | FFmpeg is licensed under the GNU Lesser General Public License (LGPL) version 2.1 or later. However, FFmpeg incorporates several optional parts and optimizations that are covered by the GNU General Public License (GPL) version 2 or later. If those parts get used the GPL applies to all of FFmpeg. -------------------------------------------------------------------------------- /backend/create_ivs_channel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This script creates the IVS Channel and prints the EndPoint + StreamKey to the user 3 | 4 | # Define colors 5 | RED='\033[0;31m' 6 | GREEN='\033[0;32m' 7 | NC='\033[0m' 8 | 9 | # Creates the IVS channel 10 | aws ivs create-channel --name $1 > ./temp_files/ivs_channel_info.txt 11 | 12 | # Saves RTMPS EndPoint 13 | channelEndpoint=$(cat ./temp_files/ivs_channel_info.txt | jq '.channel.ingestEndpoint' | sed -e 's/"//g;s/^/rtmps:\/\//;s/$/:443\/app\//') 14 | 15 | # Saves StreamKey 16 | streamKey=$(cat ./temp_files/ivs_channel_info.txt | jq '.streamKey.value' | sed -e 's/"//g') 17 | 18 | # Saves PlaybackUrl 19 | playbackUrl=$(cat ./temp_files/ivs_channel_info.txt | jq '.channel.playbackUrl' | sed -e 's/"//g') 20 | 21 | echo -e "\nCopy EndPoint: ${GREEN}${channelEndpoint}${NC}\n" 22 | echo -e "Copy StreamKey: ${GREEN}${streamKey}${NC}\n" 23 | echo -e "Copy PaybackUrl: ${GREEN}${playbackUrl}${NC}\n" 24 | -------------------------------------------------------------------------------- /backend/docker_files/Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | # The line below states we will base our new image on the 18.04 Official Ubuntu 3 | 4 | FROM public.ecr.aws/ubuntu/ubuntu:18.04 5 | 6 | # Identify the maintainer of an image 7 | LABEL authors="Marlon P Campos,Osmar Bento da Silva Junior" 8 | 9 | # Installs new repos, ffmpeg, nodejs, npm and run npm to install modules and creates the certificates for the HTTPS transwrapper.js server 10 | RUN apt-get update \ 11 | && apt-get install -y software-properties-common \ 12 | && add-apt-repository ppa:jonathonf/ffmpeg-4 -y \ 13 | && apt-get update \ 14 | && apt-get install --no-install-recommends -y ffmpeg curl \ 15 | && curl -sL https://deb.nodesource.com/setup_14.x | bash - \ 16 | && apt-get install -y nodejs \ 17 | && apt-get clean \ 18 | && npm init --yes && npm install --save ws express \ 19 | && npm install pm2 -g \ 20 | && mkdir -p /opt/ivs-simple-webrtc/www \ 21 | && cd /opt/ivs-simple-webrtc \ 22 | && openssl genrsa -out /opt/ivs-simple-webrtc/key.pem \ 23 | && openssl req -new -key /opt/ivs-simple-webrtc/key.pem -out /opt/ivs-simple-webrtc/csr.pem -subj "/C=US" \ 24 | && openssl x509 -req -days 9999 -in /opt/ivs-simple-webrtc/csr.pem -signkey /opt/ivs-simple-webrtc/key.pem -out /opt/ivs-simple-webrtc/cert.pem \ 25 | && rm /opt/ivs-simple-webrtc/csr.pem 26 | 27 | # Expose port 80 28 | EXPOSE 80 29 | EXPOSE 443 30 | 31 | # Copy transwraper.js to /opt/ivs-simple-webrtc folder 32 | COPY src* /opt/ivs-simple-webrtc/ 33 | 34 | # Copy health-check.html to /opt/ivs-simple-webrtc/www/ folder 35 | COPY health* /opt/ivs-simple-webrtc/www/ 36 | 37 | # Start webserver 38 | CMD ["pm2-runtime","/opt/ivs-simple-webrtc/transwrap_local.js","-i max"] -------------------------------------------------------------------------------- /backend/docker_files/buildspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | phases: 4 | pre_build: 5 | commands: 6 | - echo Logging in to Amazon ECR... 7 | - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com 8 | build: 9 | commands: 10 | - echo Build started on `date` 11 | - echo Building the Docker image... 12 | - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG . 13 | - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG 14 | post_build: 15 | commands: 16 | - echo Build completed on `date` 17 | - echo Pushing the Docker image... 18 | - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG -------------------------------------------------------------------------------- /backend/docker_files/health/health-check.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Hello World

6 | 7 | 8 | -------------------------------------------------------------------------------- /backend/docker_files/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "transwrap-proxy", 3 | "version": "0.2.0", 4 | "description": "video proxy webm to rtmps", 5 | "main": "transwrap.js", 6 | "dependencies": { 7 | "express": "^4.17.1", 8 | "ws": "^7.4.4" 9 | }, 10 | "devDependencies": {}, 11 | "scripts": { 12 | "start": "node transwrap.js", 13 | "start-test": "node transwrap_local.js" 14 | }, 15 | "author": "osmarb marlonpc", 16 | "license": "MIT" 17 | } 18 | -------------------------------------------------------------------------------- /backend/docker_files/src/transwrap.js: -------------------------------------------------------------------------------- 1 | // transwrap.js https version 2 | 3 | /// import block 4 | const https = require("https"); 5 | const fs = require("fs"); 6 | const WebSocket = require("ws"); 7 | const express = require("express"); 8 | const callff = require("child_process"); 9 | 10 | /// app socket definition 11 | const app = express(); 12 | const port = 443; 13 | const servertype = "HTTPS"; 14 | const home = "wwww"; /// for heath check only, you can also use as standalone server, by moving the frontend folder to the home www 15 | 16 | // https certificates // Be sure to generate those before running 17 | const newCert = "/opt/ivs-simple-webrtc/cert.pem"; 18 | const newKey = "/opt/ivs-simple-webrtc/key.pem"; 19 | const certicates = { 20 | key: fs.readFileSync(newKey), 21 | cert: fs.readFileSync(newCert), 22 | }; 23 | 24 | // wrapper server 25 | const transwraper = https.createServer(certicates, app).listen(port, () => { 26 | console.log(`Listening on port: ${port} ${servertype}`); 27 | }); 28 | 29 | // websocket creation 30 | const wsRef = new WebSocket.Server({ server: transwraper }); 31 | 32 | app.use((req, res, next) => { 33 | console.log(`${servertype}:${req.method}:${req.originalUrl}`); 34 | return next(); 35 | }); 36 | 37 | app.use(express.static(__dirname + home)); 38 | 39 | // Streaming 40 | wsRef.on("connection", (ws, req) => { 41 | console.log("Loop 1"); 42 | ws.send("MSG: Connected to server"); /// Send this message to the app frontend 43 | console.log(`Got connection, URL: ${req.url}`); 44 | ws.on("message", (evt) => { 45 | console.log("Event", evt); 46 | ffmpeg.stdin.write(evt); 47 | }); 48 | 49 | ws.on("error", (err) => { 50 | console.log("ERROR on websocket", err); 51 | }); 52 | 53 | const rtmpURL = req.url.slice(12); 54 | console.log(`URL seted is ${rtmpURL}`); 55 | 56 | const codec = req.url.split("/")[2]; 57 | console.log("CODEC", codec); 58 | 59 | if (codec === "h264") { 60 | console.log("No video transcoding"); 61 | var ffArr = [ 62 | "-i", 63 | "-", 64 | "-vcodec", 65 | "copy", 66 | "-preset", 67 | "veryfast", 68 | "-tune", 69 | "zerolatency", 70 | "-acodec", 71 | "aac", 72 | "-ar", 73 | "44100", 74 | "-b:a", 75 | "128k", 76 | "-f", 77 | "flv", 78 | rtmpURL, 79 | "-reconnect", 80 | "3", 81 | "-reconnect_at_eof", 82 | "1", 83 | "-reconnect_streamed", 84 | "3", 85 | ]; 86 | } else { 87 | console.log("Transcoding true"); 88 | //ffmpeg -re -stream_loop -1 -i $VIDEO_FILEPATH -r 30 -c:v libx264 -pix_fmt yuv420p -profile:v main -preset veryfast -x264opts "nal-hrd=cbr:no-scenecut" -minrate 3000 -maxrate 3000 -g 60 -c:a aac -b:a 160k -ac 2 -ar 44100 -f flv rtmps://$INGEST_ENDPOINT:443/app/$STREAM_KEY 89 | 90 | var ffArr = [ 91 | "-fflags", 92 | "+genpts", 93 | "-i", 94 | "-", 95 | "-r", 96 | "30", 97 | "-c:v", 98 | "libx264", 99 | "-pix_fmt", 100 | "yuv420p", 101 | "-profile:v", 102 | "main", 103 | "-preset", 104 | "veryfast", 105 | "-x264opts", 106 | "nal-hrd=cbr:no-scenecut", 107 | "-minrate", 108 | "3000", 109 | "-maxrate", 110 | "3000", 111 | "-g", 112 | "60", 113 | "-c:a", 114 | "aac", 115 | "-b:a", 116 | "128k", 117 | "-ac", 118 | "2", 119 | "-ar", 120 | "44100", 121 | "-vf", 122 | "scale=1280:720,format=yuv420p", 123 | "-profile:v", 124 | "main", 125 | "-f", 126 | "flv", 127 | rtmpURL, 128 | "-reconnect", 129 | "3", 130 | "-reconnect_at_eof", 131 | "1", 132 | "-reconnect_streamed", 133 | "3", 134 | ]; 135 | } 136 | 137 | ws.on("close", (evt) => { 138 | ffmpeg.kill("SIGINT"); 139 | console.log(`Connection Closed: ${evt}`); 140 | }); 141 | 142 | const ffmpeg = callff.spawn("ffmpeg", ffArr); 143 | 144 | ffmpeg.on("close", (code, signal) => { 145 | console.log(`FFMPEG closed, reason ${code} , ${signal}`); 146 | ws.send("Closing Socket"); /// message to front end 147 | ws.terminate(); 148 | }); 149 | 150 | ffmpeg.stdin.on("error", (e) => { 151 | ws.send(e); 152 | console.log(`FFMPEG ERROR: ${e}`); 153 | ws.terminate(); 154 | }); 155 | 156 | ffmpeg.stderr.on("data", (data) => { 157 | console.log(`FFMPEG MSG: ${data.toString()}`); 158 | ws.send(data.toString()); 159 | }); 160 | }); 161 | -------------------------------------------------------------------------------- /backend/docker_files/src/transwrap_local.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | // transwrap.js http version 5 | 6 | /// import block 7 | const http = require("http"); 8 | //const fs = require('fs'); 9 | const WebSocket = require("ws"); 10 | const express = require("express"); 11 | const callff = require("child_process"); 12 | 13 | /// app socket definition 14 | const app = express(); 15 | const port = 80; 16 | const servertype = "HTTP"; 17 | const home = "/wwww"; /// for heath check only, you can also use as standalone server, by moving the frontend folder to the home www 18 | 19 | // https certificates // Be sure to generate those before running 20 | /* 21 | const newCert = 'cert.pem'; 22 | const newKey = 'key.pem' 23 | const certicates = { 24 | key: fs.readFileSync(newKey), 25 | cert: fs.readFileSync(newCert) 26 | }; 27 | */ 28 | 29 | // wrapper server 30 | const transwraper = http.createServer(app).listen(port, () => { 31 | console.log(`Listening on port: ${port} ${servertype}`); 32 | }); 33 | 34 | // websocket creation 35 | const wsRef = new WebSocket.Server({ server: transwraper }); 36 | 37 | app.use((req, res, next) => { 38 | console.log(`${servertype}:${req.method}:${req.originalUrl}`); 39 | return next(); 40 | }); 41 | 42 | app.use(express.static(__dirname + home)); 43 | 44 | // Streaming 45 | wsRef.on("connection", (ws, req) => { 46 | console.log("Loop 1"); 47 | ws.send("MSG: Connected to server"); /// Send this message to the app frontend 48 | console.log(`Got connection, URL: ${req.url}`); 49 | ws.on("message", (evt) => { 50 | console.log("Event", evt); 51 | ffmpeg.stdin.write(evt); 52 | }); 53 | 54 | ws.on("error", (err) => { 55 | console.log("ERROR on websocket", err); 56 | }); 57 | 58 | const rtmpURL = req.url.slice(12); 59 | console.log(`URL seted is ${rtmpURL}`); 60 | 61 | const codec = req.url.split("/")[2]; 62 | console.log("CODEC", codec); 63 | 64 | if (codec === "h264") { 65 | console.log("No video transcoding"); 66 | var ffArr = [ 67 | "-i", 68 | "-", 69 | "-vcodec", 70 | "copy", 71 | "-preset", 72 | "veryfast", 73 | "-tune", 74 | "zerolatency", 75 | "-acodec", 76 | "aac", 77 | "-ar", 78 | "44100", 79 | "-b:a", 80 | "128k", 81 | "-f", 82 | "flv", 83 | rtmpURL, 84 | "-reconnect", 85 | "3", 86 | "-reconnect_at_eof", 87 | "1", 88 | "-reconnect_streamed", 89 | "3", 90 | ]; 91 | } else { 92 | console.log("Transcoding true"); 93 | //ffmpeg -re -stream_loop -1 -i $VIDEO_FILEPATH -r 30 -c:v libx264 -pix_fmt yuv420p -profile:v main -preset veryfast -x264opts "nal-hrd=cbr:no-scenecut" -minrate 3000 -maxrate 3000 -g 60 -c:a aac -b:a 160k -ac 2 -ar 44100 -f flv rtmps://$INGEST_ENDPOINT:443/app/$STREAM_KEY 94 | 95 | var ffArr = [ 96 | "-fflags", 97 | "+genpts", 98 | "-i", 99 | "-", 100 | "-r", 101 | "30", 102 | "-c:v", 103 | "libx264", 104 | "-pix_fmt", 105 | "yuv420p", 106 | "-profile:v", 107 | "main", 108 | "-preset", 109 | "veryfast", 110 | "-x264opts", 111 | "nal-hrd=cbr:no-scenecut", 112 | "-minrate", 113 | "3000", 114 | "-maxrate", 115 | "3000", 116 | "-g", 117 | "60", 118 | "-c:a", 119 | "aac", 120 | "-b:a", 121 | "128k", 122 | "-ac", 123 | "2", 124 | "-ar", 125 | "44100", 126 | "-vf", 127 | "scale=1280:720,format=yuv420p", 128 | "-profile:v", 129 | "main", 130 | "-f", 131 | "flv", 132 | rtmpURL, 133 | "-reconnect", 134 | "3", 135 | "-reconnect_at_eof", 136 | "1", 137 | "-reconnect_streamed", 138 | "3", 139 | ]; 140 | } 141 | 142 | ws.on("close", (evt) => { 143 | ffmpeg.kill("SIGINT"); 144 | console.log(`Connection Closed: ${evt}`); 145 | }); 146 | 147 | const ffmpeg = callff.spawn("ffmpeg", ffArr); 148 | 149 | ffmpeg.on("close", (code, signal) => { 150 | console.log(`FFMPEG closed, reason ${code} , ${signal}`); 151 | ws.send("Closing Socket"); /// message to front end 152 | ws.terminate(); 153 | }); 154 | 155 | ffmpeg.stdin.on("error", (e) => { 156 | ws.send(e); 157 | console.log(`FFMPEG ERROR: ${e}`); 158 | ws.terminate(); 159 | }); 160 | 161 | ffmpeg.stderr.on("data", (data) => { 162 | console.log(`FFMPEG MSG: ${data.toString()}`); 163 | ws.send(data.toString()); 164 | }); 165 | }); 166 | -------------------------------------------------------------------------------- /backend/get_container_domain.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This script get the domain from the dynamo table and set as a env var for the CloudFront Distribution Creation 3 | 4 | # Define colors 5 | RED='\033[0;31m' 6 | GREEN='\033[0;32m' 7 | NC='\033[0m' 8 | 9 | # Var Definitons 10 | 11 | DyTable='ISS-task-dns-track-dev' 12 | AwsAccProfile='default' 13 | Region='us-east-1' 14 | 15 | # Scan the Dynamo Table 16 | aws dynamodb scan --table-name $DyTable --filter-expression "replaced = :n" --expression-attribute-values {\":n\":{\"S\":\"no\"}} --max-items 1 --profile $AwsAccProfile --region $Region > ./temp_files/dynamo_scan.txt 17 | 18 | 19 | # Saves RTMPS EndPoint 20 | DomainResults=$(cat ./temp_files/dynamo_scan.txt| jq '.Items[].dns.S') 21 | 22 | echo -e "\nCopy Domain: ${GREEN}${DomainResults}${NC}\n" 23 | export DOMAIN=${DomainResults} -------------------------------------------------------------------------------- /backend/health-check.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Hello World

6 | 7 | 8 | -------------------------------------------------------------------------------- /backend/install_ivs_backend.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Define colors 4 | RED='\033[0;31m' 5 | GREEN='\033[0;32m' 6 | YELLOW='\033[0;93m' 7 | NC='\033[0m' 8 | 9 | # This is a very simples scrip that uses aws cli + jq + sed to deploy the backend of aws-simples-streaming-app. You could easly deploy the whole back end by inserting individual commands 10 | # but it would consume more of your time. The script will allow you to save time during the deployment. 11 | 12 | 13 | ################################################################################ 14 | # Help # 15 | ################################################################################ 16 | 17 | function help () 18 | { 19 | # Display Help 20 | echo -e "${GREEN}This script deploys the backend of the aws-simple-streaming app." 21 | echo 22 | echo -e "Syntax: ./install_ivs_backend.sh deploy [ all | ecr | s3 | templates | codebuild_rp | codebuild | iam | lambda | dynamodb | sg | fargate | events | logs | tasks ]${NC}" 23 | echo -e "${YELLOW}options:" 24 | echo -e "all Deploys the whole backend at once" 25 | echo -e "ecr Creates the ecr repository" 26 | echo -e "s3 Creates s3 bucket and upload the docker files" 27 | echo -e "templates Prepares the templates files for codebuild configuration" 28 | echo -e "codebuild_rp Prepares all roles and policies required by codebuild" 29 | echo -e "codebuild Creates the codebuild project and build the container images" 30 | echo -e "iam Prepares all roles and policies required by lambda,dynamodb and fargate" 31 | echo -e "lambda Creates the lambda function" 32 | echo -e "dynamodb Check if the dynamodb table exists and populate it with initial values" 33 | echo -e "sg Creates the segurity groups required by fargate" 34 | echo -e "fargate Configures ECS cluster, task definitions and services" 35 | echo -e "events Configures the eventbridge rules and triggers" 36 | echo -e "tasks Configures ECS service to start 2 tasks${NC}\n\n" 37 | 38 | echo -e "${GREEN}Example on how to use this script to deploy the whole backend at once" 39 | echo -e "./install_ivs_backend.sh deploy all${NC}\n\n" 40 | 41 | echo -e "${YELLOW}Case you prefer to deploy the backend step by step, deploy the each item following the order showing in this help" 42 | echo -e "Example:" 43 | echo -e "./install_ivs_backend.sh deploy ecr" 44 | echo -e "./install_ivs_backend.sh deploy s3" 45 | echo -e "./install_ivs_backend.sh deploy templates" 46 | echo -e "...and so on...${NC}\n\n" 47 | 48 | 49 | } 50 | 51 | ################################################################################ 52 | # ECR Resources # 53 | ################################################################################ 54 | 55 | function ecr_resources () { 56 | 57 | # This function will create the ECR repository required by ivs-webrtc 58 | 59 | # Test if it exists 60 | aws ecr describe-repositories --repository-names ivs-webrtc > /dev/null 2>&1 && ecr='OK' || ecr='NOK' 61 | 62 | if [ $ecr = 'NOK' ] 63 | then 64 | 65 | echo -e "${GREEN}Creating ECR ivs-webrtc repository...${NC}" 66 | aws ecr create-repository --repository-name ivs-webrtc > ./temp_files/ecr_repository.txt 67 | jq '.repository.registryId' ./temp_files/ecr_repository.txt | sed 's/"//g' > ./temp_files/accountid.txt 68 | jq '.repository.repositoryUri' ./temp_files/ecr_repository.txt | sed 's/"//g' > ./temp_files/ecr_uri.txt 69 | 70 | else 71 | 72 | echo -e "${GREEN}ECR repository already exists. Capturing the registryId...${NC}" 73 | aws ecr describe-repositories --repository-names ivs-webrtc > ./temp_files/ecr_repository.txt 74 | jq '.repositories[0].registryId' ./temp_files/ecr_repository.txt | sed 's/"//g' > ./temp_files/accountid.txt 75 | jq '.repositories[0].repositoryUri' ./temp_files/ecr_repository.txt | sed 's/"//g' > ./temp_files/ecr_uri.txt 76 | 77 | fi 78 | 79 | unset ecr 80 | 81 | } 82 | 83 | ################################################################################ 84 | # S3 Resources # 85 | ################################################################################ 86 | 87 | function s3_resources () { 88 | 89 | # This function creates a s3 bucket to store the files required by codebuild 90 | 91 | # Creates a token 92 | token=$(uuidgen | sed -n 's/.*-//;p' | sed y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ ) 93 | 94 | # Creates a bucket 95 | aws s3 mb s3://ivs-webrtc-codebuild-${token} > /dev/null 2>&1 && s3='OK' || s3='NOK' 96 | 97 | copy_ready='NOK' 98 | 99 | if [ $s3 = 'NOK' ] 100 | then 101 | 102 | # Creates the S3 bucket 103 | echo -e "${GREEN}Creating S3 bucket...${NC}" 104 | token=$(uuidgen | sed -n 's/.*-//;p' | sed y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ ) 105 | aws s3 mb s3://ivs-webrtc-codebuild-${token} > /dev/null 2>&1 && s3='OK' || s3='NOK' 106 | echo "ivs-webrtc-codebuild-${token}" > ./temp_files/s3_bucket.txt 107 | copy_ready='OK' 108 | 109 | else 110 | 111 | # Case bucket already exists, it saves its name in ./temp_files/s3_bucket.txt 112 | echo -e "${GREEN}It seems that bucket ivs-webrtc-codebuild-${token} already exists. Capturing the details...${NC}" 113 | echo "ivs-webrtc-codebuild-${token}" > ./temp_files/s3_bucket.txt 114 | copy_ready='OK' 115 | 116 | fi 117 | 118 | unset s3 119 | 120 | if [ $copy_ready = 'OK' ] 121 | then 122 | 123 | # Zip dockerfiles and dependencies and copy them to s3 bucket 124 | echo -e "${GREEN}Generating docker.zip and upload to ivs-webrtc-codebuild-${token} bucket...${NC}" 125 | cd ./docker_files/ 126 | echo -e "${GREEN}Compacting files into docker.zip...${NC}" 127 | zip -r docker.zip * 128 | cd .. 129 | echo -e "${GREEN}Uploading docker.zip to ivs-webrtc-codebuild-${token} bucket...${NC}" 130 | aws s3 cp ./docker_files/docker.zip s3://ivs-webrtc-codebuild-${token} 131 | fi 132 | 133 | } 134 | 135 | ################################################################################ 136 | # Codebuild Templates # 137 | ################################################################################ 138 | 139 | function codebuild_adjust_templates () { 140 | 141 | # This function adjust the json_model templates required by codebuild deployment 142 | 143 | # Capture the subnet that will be used by codebuild project 144 | replace_subnets=($(aws ec2 describe-subnets --filter Name=vpc-id,Values=$(aws ec2 describe-vpcs | jq '.Vpcs[] | select(.IsDefault)' | jq '.VpcId' | sed 's/"//g') --query 'Subnets[?MapPublicIpOnLaunch==`true`].SubnetId' | sed -e 's/\[//g;s/\]//g;s/ //g;s/"//g;s/,//g;1d;$ d' | sed -e :a -e ';$!N;s/\n/ /;ta')) > ./temp_files/codebuild_subnets.txt 145 | sleep 2 146 | 147 | ### BEGIN - EXEMPTION THIS SHOULD BE HERE 148 | # Test if exists 149 | aws iam get-role --role-name ivs-webrtc-codebuild-role > /dev/null 2>&1 && codebuild='OK' || codebuild='NOK' 150 | sleep 2 151 | 152 | if [ $codebuild = 'NOK' ] 153 | then 154 | 155 | # Create the AWS Codebuild role that will be used on our Codebuild Project 156 | echo -e "${GREEN}Creating AWS Codebuild role...${NC}" 157 | aws iam create-role --role-name ivs-webrtc-codebuild-role --assume-role-policy-document file://json_configs/ivs_webrtc_codebuild_trust_policy.json | jq '.Role.Arn' | sed 's/"//g' > ./temp_files/ivs_webrtc_codebuild_role_arn.txt 158 | sleep 2 159 | else 160 | 161 | # If Codebuild role exist already, save its Arn in ./temp_files/ivs_webrtc_codebuild_role_arn.txt 162 | aws iam get-role --role-name ivs-webrtc-codebuild-role | jq '.Role.Arn' | sed 's/"//g' > ./temp_files/ivs_webrtc_codebuild_role_arn.txt 163 | sleep 2 164 | fi 165 | 166 | 167 | unset codebuild 168 | ### BEGIN - EXEMPTION THIS SHOULD BE HERE 169 | 170 | # Variables that will be used to replace the values in json_model templates 171 | replace_accountid=$(cat ./temp_files/accountid.txt) 172 | replace_location=$(cat ./temp_files/s3_bucket.txt)/docker.zip 173 | replace_bucket=$(cat ./temp_files/s3_bucket.txt) 174 | replace_kms="arn:aws:kms:us-east-1:${replace_accountid}:alias/aws/s3" 175 | replace_role=$(cat ./temp_files/ivs_webrtc_codebuild_role_arn.txt) 176 | 177 | # This will generate the ./json_configs/ivs_codebuild.json from the template ./json_models/ivs_codebuild_model.json 178 | jq --arg a "$replace_accountid" --arg l "$replace_location" --arg r "$replace_role" --arg k "$replace_kms" '.environment.environmentVariables[1].value = $a | .source.location = $l | .serviceRole = $r | .encryptionKey = $k ' ./json_models/ivs_codebuild_model.json > ./json_configs/ivs_codebuild.json 179 | 180 | # This will generate the ./json_configs/ivs_codebuild_base_policy.json from the template ./json_models/ivs_codebuild_base_policy_model.json 181 | sed -e "s/REPLACE_ACCOUNTID/$replace_accountid/g" ./json_models/ivs_codebuild_base_policy_model.json > ./json_configs/ivs_codebuild_base_policy.json 182 | 183 | # This will generate the ./json_configs/ivs_codebuild_s3_policy.json from the template ./json_models/ivs_codebuild_s3_policy_model.json 184 | sed -e "s/REPLACE_BUCKET/$replace_bucket/g" ./json_models/ivs_codebuild_s3_policy_model.json > ./json_configs/ivs_codebuild_s3_policy.json 185 | 186 | # This will generate the ./json_configs/ivs_codebuild_log_policy.json from the template ./json_models/ivs_codebuild_log_policy_model.json 187 | sed -e "s/REPLACE_ACCOUNTID/$replace_accountid/g" ./json_models/ivs_codebuild_log_policy_model.json > ./json_configs/ivs_codebuild_log_policy.json 188 | 189 | # This will generate the 190 | sed -e "s/REPLACE_ACCOUNTID/$replace_accountid/g;s/REPLACE_SUBNET0/${replace_subnets[0]}/g;s/REPLACE_SUBNET1/${replace_subnets[1]}/g;s/REPLACE_SUBNET2/${replace_subnets[2]}/g;" ./json_models/ivs_codebuild_vpc_policy_model.json > ./json_configs/ivs_codebuild_vpc_policy.json 191 | } 192 | 193 | ################################################################################ 194 | # Codebuild Roles and Policies # 195 | ################################################################################ 196 | 197 | function codebuild_iam () { 198 | 199 | # Test if exists 200 | codebuild=$(aws iam list-policies --scope Local | jq '.Policies[] | select(.PolicyName=="ivs_codebuild_ecr")' | jq '.Arn' | sed 's/"//g') 201 | sleep 2 202 | 203 | # If the policy do not exist, it should be created..Otherwise, it will just attach the policy 204 | if [ ! $codebuild ] 205 | then 206 | 207 | echo -e "${GREEN}Attaching ivs_codebuild_ecr policy to AWS Codebuild role...${NC}" 208 | aws iam create-policy --policy-name ivs_codebuild_ecr --policy-document file://json_configs/ivs_codebuild_ecr_policy.json | jq '.Policy.Arn' | sed 's/"//g' > ./temp_files/ivs_codebuild_ecr_arn.txt 209 | sleep 2 210 | aws iam attach-role-policy --role-name ivs-webrtc-codebuild-role --policy-arn $(cat ./temp_files/ivs_codebuild_ecr_arn.txt) 211 | sleep 2 212 | 213 | else 214 | 215 | echo -e "${GREEN}Attaching ivs_codebuild_ecr policy to AWS Codebuild role...${NC}" 216 | aws iam attach-role-policy --role-name ivs-webrtc-codebuild-role --policy-arn $codebuild 217 | sleep 2 218 | 219 | fi 220 | 221 | unset codebuild 222 | 223 | # Test if exists 224 | codebuild=$(aws iam list-policies --scope Local | jq '.Policies[] | select(.PolicyName=="ivs_codebuild_base")' | jq '.Arn' | sed 's/"//g') 225 | sleep 2 226 | 227 | # If the policy do not exist, it should be created..Otherwise, it will just attach the policy 228 | if [ ! $codebuild ] 229 | then 230 | 231 | echo -e "${GREEN}Attaching ivs_codebuild_base policy to AWS Codebuild role...${NC}" 232 | aws iam create-policy --policy-name ivs_codebuild_base --policy-document file://json_configs/ivs_codebuild_base_policy.json | jq '.Policy.Arn' | sed 's/"//g' > ./temp_files/ivs_codebuild_base_arn.txt 233 | sleep 2 234 | aws iam attach-role-policy --role-name ivs-webrtc-codebuild-role --policy-arn $(cat ./temp_files/ivs_codebuild_base_arn.txt) 235 | sleep 2 236 | 237 | else 238 | 239 | echo -e "${GREEN}Attaching ivs_codebuild_base policy to AWS Codebuild role...${NC}" 240 | aws iam attach-role-policy --role-name ivs-webrtc-codebuild-role --policy-arn $codebuild 241 | sleep 2 242 | 243 | fi 244 | 245 | unset codebuild 246 | 247 | # Test if exists 248 | codebuild=$(aws iam list-policies --scope Local | jq '.Policies[] | select(.PolicyName=="ivs_codebuild_vpc")' | jq '.Arn' | sed 's/"//g') 249 | sleep 2 250 | 251 | # If the policy do not exist, it should be created..Otherwise, it will just attach the policy 252 | if [ ! $codebuild ] 253 | then 254 | 255 | echo -e "${GREEN}Attaching ivs_codebuild_vpc policy to AWS Codebuild role...${NC}" 256 | aws iam create-policy --policy-name ivs_codebuild_vpc --policy-document file://json_configs/ivs_codebuild_vpc_policy.json | jq '.Policy.Arn' | sed 's/"//g' > ./temp_files/ivs_codebuild_vpc_arn.txt 257 | sleep 2 258 | aws iam attach-role-policy --role-name ivs-webrtc-codebuild-role --policy-arn $(cat ./temp_files/ivs_codebuild_vpc_arn.txt) 259 | sleep 2 260 | 261 | else 262 | 263 | echo -e "${GREEN}Attaching ivs_codebuild_vpc policy to AWS Codebuild role...${NC}" 264 | aws iam attach-role-policy --role-name ivs-webrtc-codebuild-role --policy-arn $codebuild 265 | sleep 2 266 | 267 | fi 268 | 269 | unset codebuild 270 | 271 | # Test if exists 272 | codebuild=$(aws iam list-policies --scope Local | jq '.Policies[] | select(.PolicyName=="ivs_codebuild_s3")' | jq '.Arn' | sed 's/"//g') 273 | sleep 2 274 | 275 | # If the policy do not exist, it should be created..Otherwise, it will just attach the policy 276 | if [ ! $codebuild ] 277 | then 278 | 279 | echo -e "${GREEN}Attaching ivs_codebuild_s3 policy to AWS Codebuild role...${NC}" 280 | aws iam create-policy --policy-name ivs_codebuild_s3 --policy-document file://json_configs/ivs_codebuild_s3_policy.json | jq '.Policy.Arn' | sed 's/"//g' > ./temp_files/ivs_codebuild_s3_arn.txt 281 | sleep 2 282 | aws iam attach-role-policy --role-name ivs-webrtc-codebuild-role --policy-arn $(cat ./temp_files/ivs_codebuild_s3_arn.txt) 283 | sleep 2 284 | 285 | else 286 | 287 | echo -e "${GREEN}Attaching ivs_codebuild_s3 policy to AWS Codebuild role...${NC}" 288 | aws iam attach-role-policy --role-name ivs-webrtc-codebuild-role --policy-arn $codebuild 289 | sleep 2 290 | 291 | fi 292 | 293 | unset codebuild 294 | 295 | # Test if exists 296 | codebuild=$(aws iam list-policies --scope Local | jq '.Policies[] | select(.PolicyName=="ivs_codebuild_log")' | jq '.Arn' | sed 's/"//g') 297 | sleep 2 298 | 299 | # If the policy do not exist, it should be created..Otherwise, it will just attach the policy 300 | if [ ! $codebuild ] 301 | then 302 | 303 | echo -e "${GREEN}Attaching ivs_codebuild_log policy to AWS Codebuild role...${NC}" 304 | aws iam create-policy --policy-name ivs_codebuild_log --policy-document file://json_configs/ivs_codebuild_log_policy.json | jq '.Policy.Arn' | sed 's/"//g' > ./temp_files/ivs_codebuild_log_arn.txt 305 | sleep 2 306 | aws iam attach-role-policy --role-name ivs-webrtc-codebuild-role --policy-arn $(cat ./temp_files/ivs_codebuild_log_arn.txt) 307 | sleep 2 308 | 309 | else 310 | 311 | echo -e "${GREEN}Attaching ivs_codebuild_log policy to AWS Codebuild role...${NC}" 312 | aws iam attach-role-policy --role-name ivs-webrtc-codebuild-role --policy-arn $codebuild 313 | sleep 2 314 | 315 | fi 316 | 317 | unset codebuild 318 | } 319 | 320 | ################################################################################ 321 | # Progress bar # 322 | ################################################################################ 323 | 324 | function print_progress () { 325 | 326 | # Print # on screen 327 | 328 | for i in {0..7} 329 | do 330 | sleep 2 331 | echo -n "#" 332 | 333 | done 334 | 335 | } 336 | 337 | 338 | ################################################################################ 339 | # Codebuild Resources # 340 | ################################################################################ 341 | 342 | function codebuild_resources () { 343 | 344 | # Create codebuild project 345 | 346 | aws codebuild create-project --name ivs-webrtc --cli-input-json file://json_configs/ivs_codebuild.json > /dev/null 2>&1 347 | 348 | build_current_phase='NOT_STARTED' 349 | 350 | aws codebuild start-build --project-name ivs-webrtc > ./temp_files/ivs_codebuild_build.json 351 | ivs_build=$(jq '.build.id' ./temp_files/ivs_codebuild_build.json | sed 's/"//g') 352 | 353 | echo -e "${GREEN}Creating the container image${NC}" 354 | 355 | print_progress 356 | 357 | while true 358 | do 359 | build_status=$(aws codebuild batch-get-builds --ids $ivs_build | jq '.builds[0].currentPhase' | sed 's/"//g') 360 | 361 | if [ $build_status = 'PROVISIONING' ] 362 | then 363 | 364 | echo -e "${YELLOW}${build_status}${NC}" 365 | print_progress 366 | 367 | elif [ $build_status = 'PRE_BUILD' ] 368 | then 369 | 370 | echo -e "${YELLOW}${build_status}${NC}" 371 | print_progress 372 | 373 | elif [ $build_status = 'BUILD' ] 374 | then 375 | 376 | echo -e "${YELLOW}${build_status}${NC}" 377 | print_progress 378 | 379 | elif [ $build_status = 'POST_BUILD' ] 380 | then 381 | 382 | echo -e "${YELLOW}${build_status}${NC}" 383 | print_progress 384 | 385 | elif [ $build_status = 'FINALIZING' ] 386 | then 387 | 388 | echo -e "${YELLOW}${build_status}${NC}" 389 | print_progress 390 | 391 | elif [ $build_status = 'COMPLETED' ] 392 | then 393 | 394 | phase_status=$(aws codebuild batch-get-builds --ids $ivs_build | jq '.builds[0].currentPhase' | sed 's/"//g') 395 | 396 | if [ $phase_status = 'FAILED' ] 397 | then 398 | 399 | echo -e "${RED}${phase_status}!!!${NC}" 400 | echo -e "${RED}Please, have a look at cloudwatch logs to understand why the codebuild failed, fix it and re-run the deployment.${NC}" 401 | break 402 | 403 | else 404 | 405 | echo -e "${YELLOW}${build_status}${NC}" 406 | echo -e "${GREEN}${phase_status}${NC}" 407 | break 408 | fi 409 | 410 | elif [ $build_status = 'FALIED' ] 411 | then 412 | 413 | echo -e "${RED}${build_status}!!!${NC}" 414 | echo -e "${RED}Please, have a look at cloudwatch logs to understand why the codebuild failed, fix it and re-run the deployment.${NC}" 415 | 416 | else 417 | echo -e "${RED}${build_status}!!!${NC}" 418 | echo -e "${RED}Something went wrong...Please hava a look at cloudwatchlogs to understand what happened.${NC}" 419 | break 420 | 421 | fi 422 | 423 | done 424 | 425 | } 426 | 427 | ################################################################################ 428 | # IAM Roles and Policies - ECS/LAMBDA/DYNAMODB # 429 | ################################################################################ 430 | 431 | function iam_resources () { 432 | 433 | # This function will create all IAM resources required by ivs-webrtc 434 | 435 | # Test if exists 436 | aws iam get-role --role-name ivs-ecs-execution-role > /dev/null 2>&1 && iam='OK' || iam='NOK' 437 | sleep 2 438 | 439 | if [ $iam = 'NOK' ] 440 | then 441 | 442 | # Create the Amazon ECS execution role that will be used on our ECS Container. 443 | echo -e "${GREEN}Creating Amazon ECS execution role...${NC}" 444 | aws iam create-role --role-name ivs-ecs-execution-role --assume-role-policy-document file://json_configs/ivs_ecs_trust_policy.json \ 445 | | jq '.Role.Arn' | sed 's/"//g' > ./temp_files/ivs_ecs_execution_role_arn.txt 446 | sleep 2 447 | 448 | else 449 | 450 | aws iam get-role --role-name ivs-ecs-execution-role | jq '.Role.Arn' | sed 's/"//g' > ./temp_files/ivs_ecs_execution_role_arn.txt 451 | sleep 2 452 | 453 | fi 454 | 455 | unset iam 456 | 457 | # Attach the required policies to Amazon ECS execution role you just created. 458 | echo -e "${GREEN}Attaching the required policies to Amazon ECS execution role...${NC}" 459 | aws iam attach-role-policy --role-name ivs-ecs-execution-role --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy 460 | sleep 2 461 | aws iam attach-role-policy --role-name ivs-ecs-execution-role --policy-arn arn:aws:iam::aws:policy/AWSOpsWorksCloudWatchLogs 462 | sleep 2 463 | 464 | # Test if exists 465 | aws iam get-role --role-name ivs-lambda-role > /dev/null 2>&1 && iam='OK' || iam='NOK' 466 | sleep 2 467 | 468 | if [ $iam = 'NOK' ] 469 | then 470 | 471 | # Create the AWS Lambda execution role that will allow lambda to access the required AWS resources. 472 | echo -e "${GREEN}Creating the AWS Lambda execution role that will allow lambda to access the required AWS resources${NC}" 473 | aws iam create-role --role-name ivs-lambda-role --assume-role-policy-document file://json_configs/ivs_lambda_trust_policy.json > /dev/null 474 | sleep 2 475 | 476 | fi 477 | 478 | unset iam 479 | 480 | # Attach the required policies to AWS Lambda execution role you just created. 481 | echo -e "${GREEN}Attaching AWSLambdaBasicExecutionRole policy to AWS Lambda execution role...${NC}" 482 | aws iam attach-role-policy --role-name ivs-lambda-role --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 483 | sleep 2 484 | echo -e "${GREEN}Attaching AmazonEC2ReadOnlyAccess policy to AWS Lambda execution role...${NC}" 485 | aws iam attach-role-policy --role-name ivs-lambda-role --policy-arn arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess 486 | sleep 2 487 | 488 | iam=$(aws iam list-policies --scope Local | jq '.Policies[] | select(.PolicyName=="ivs_dynamodb")' | jq '.Arn' | sed 's/"//g') 489 | 490 | # If the policy do not exist, it should be created..Otherwise, it will just attach the policy 491 | if [ ! $iam ] 492 | then 493 | 494 | echo -e "${GREEN}Attaching ivs_dynamodb policy to AWS Lambda execution role...${NC}" 495 | aws iam create-policy --policy-name ivs_dynamodb --policy-document file://json_configs/ivs_lambda_dynamodb_policy.json | jq '.Policy.Arn' | sed 's/"//g' > ./temp_files/lambda_policy_arn.txt 496 | sleep 2 497 | aws iam attach-role-policy --role-name ivs-lambda-role --policy-arn $(cat ./temp_files/lambda_policy_arn.txt) 498 | sleep 2 499 | 500 | else 501 | 502 | echo -e "${GREEN}Attaching ivs_dynamodb policy to AWS Lambda execution role...${NC}" 503 | aws iam attach-role-policy --role-name ivs-lambda-role --policy-arn $(aws iam list-policies --scope Local | jq '.Policies[] | select(.PolicyName=="ivs_dynamodb")' | jq '.Arn' | sed 's/"//g') 504 | sleep 2 505 | 506 | fi 507 | 508 | } 509 | 510 | 511 | ################################################################################ 512 | # Lambda Resources # 513 | ################################################################################ 514 | 515 | function lambda_resources () { 516 | 517 | unset lambda 518 | 519 | # This function will create all lambda resources required by ivs-webrtc 520 | 521 | #Test if exists 522 | aws iam get-role --role-name ivs-lambda-role > /dev/null 2>&1 && lambda='OK' || lambda='NOK' 523 | sleep 2 524 | 525 | if [ $lambda = 'OK' ] 526 | then 527 | 528 | #Creates lambda.json with our lambda configuration 529 | echo -e "${GREEN}Creating the lambda.json with our lambda configuration...${NC}" 530 | lambda_role_arn=$(aws iam get-role --role-name ivs-lambda-role | jq '.Role.Arn' | sed 's/"//g') 531 | jq --arg v "$lambda_role_arn" '. |= . + {"Role":$v}' ./json_models/lambda_model.json > ./json_configs/lambda.json 532 | sleep 2 533 | 534 | else 535 | 536 | error_exit 'The ivs-lambda-role was not found! Please re-run ./deployment.sh deploy iam' 537 | 538 | fi 539 | 540 | unset lambda 541 | sleep 10 542 | 543 | #Test if exists 544 | aws lambda get-function --function-name ivs-ip-register > /dev/null 2>&1 && lambda='OK' || lambda='NOK' 545 | sleep 2 546 | 547 | if [ $lambda = 'NOK' ] 548 | then 549 | 550 | #Creates the ivs-ip-register lambda function 551 | echo -e "${GREEN}Creating the the ivs-ip-register lambda function...${NC}" 552 | zip lambda.zip lambda_function.py 553 | aws lambda create-function --cli-input-json file://json_configs/lambda.json --zip-file fileb://lambda.zip > /dev/null 554 | sleep 2 555 | fi 556 | } 557 | 558 | ################################################################################ 559 | # DynamoDB Resources # 560 | ################################################################################ 561 | 562 | function dynamodb_resources () { 563 | 564 | # Ths function will create all dynamodb resources required by ivs-webrtc 565 | 566 | table=$(aws dynamodb list-tables | sed -n -e '/.*ivs/p' | sed -e 's/ //g;s/"//g;s/,//g') 567 | sleep 2 568 | 569 | if [ ! -z $table ] 570 | then 571 | 572 | #Creates the Amazon DynamoDB ISS-task-dns-track-dev table 573 | echo -e "${GREEN}Creating the Amazon DynamoDB ISS-task-dns-track-dev table...${NC}" 574 | aws dynamodb create-table --cli-input-json file://json_configs/dynamodb_table.json > /dev/null 575 | sleep 2 576 | aws dynamodb wait table-exists --table-name ISS-task-dns-track-dev 577 | 578 | #Populates the Amazon DynamoDB ISS-task-dns-track-dev table with the initial values 579 | echo -e "${GREEN}Populating the Amazon DynamoDB ISS-task-dns-track-dev table...${NC}" 580 | aws dynamodb batch-write-item --request-items file://json_configs/ivs_dynamodb_populate.json > /dev/null 581 | sleep 2 582 | 583 | else 584 | 585 | #Populates the Amazon DynamoDB ISS-task-dns-track-dev table with the initial values 586 | echo -e "${GREEN}Populating the Amazon DynamoDB ISS-task-dns-track-dev table...${NC}" 587 | aws dynamodb batch-write-item --request-items file://json_configs/ivs_dynamodb_populate.json > /dev/null 588 | sleep 2 589 | 590 | fi 591 | } 592 | 593 | ################################################################################ 594 | # Security Groups # 595 | ################################################################################ 596 | 597 | function vpc_resources () { 598 | 599 | # This function will create the security group required by ivs-webrtc 600 | 601 | unset sg 602 | 603 | # Test if exists 604 | aws ec2 create-security-group --group-name ivs-sg --description "IVS WetRTC Security Group" --vpc-id $(aws ec2 describe-vpcs | jq '.Vpcs[] | select(.IsDefault)' | jq '.VpcId' | sed 's/"//g') > /dev/null 2>&1 && sg='OK' || sg='NOK' 605 | sleep 2 606 | 607 | if [ sg = 'NOK' ] 608 | then 609 | 610 | #The Security Group' 611 | echo -e "${GREEN}Creating the ivs-sg security group...${NC}" 612 | aws ec2 create-security-group --group-name ivs-sg --description "IVS WetRTC Security Group" --vpc-id $(aws ec2 describe-vpcs | jq '.Vpcs[] | select(.IsDefault)' | jq '.VpcId' | sed 's/"//g') | jq '.GroupId' | sed 's/"//g' 613 | sleep 2 614 | aws ec2 describe-security-groups | jq '.SecurityGroups[] | select(.GroupName=="ivs-sg")' | jq '.GroupId' | sed 's/"//g' > ./temp_files/ivs_sg.txt 615 | sleep 2 616 | 617 | echo -e "${GREEN}Configuring ivs-sg security group...${NC}" 618 | while read line; do aws ec2 authorize-security-group-ingress --group-id $(cat ./temp_files/ivs_sg.txt) --protocol tcp --port $line --cidr 0.0.0.0/0; done < ./json_models/ivs_ports.txt 619 | 620 | else 621 | 622 | aws ec2 describe-security-groups | jq '.SecurityGroups[] | select(.GroupName=="ivs-sg")' | jq '.GroupId' | sed 's/"//g' > ./temp_files/ivs_sg.txt 623 | sleep 2 624 | 625 | echo -e "${GREEN}Applying HTTP rule to ivs-sg security group...${NC}" 626 | aws ec2 authorize-security-group-ingress --group-id $(cat ./temp_files/ivs_sg.txt) --protocol tcp --port 80 --cidr 0.0.0.0/0 627 | sleep 2 628 | 629 | echo -e "${GREEN}Applying HTTPS rule to ivs-sg security group...${NC}" 630 | aws ec2 authorize-security-group-ingress --group-id $(cat ./temp_files/ivs_sg.txt) --protocol tcp --port 443 --cidr 0.0.0.0/0 631 | sleep 2 632 | 633 | fi 634 | } 635 | 636 | ################################################################################ 637 | # ECS Resources # 638 | ################################################################################ 639 | 640 | function fargate_resources () { 641 | 642 | # This function will create all fargate resources required by ivs-webrtc 643 | 644 | unset ivs_service 645 | 646 | #Creates the Amazon ECS Cluster named ivs 647 | echo -e "${GREEN}Creating the Amazon ECS Cluster named ivs...${NC}" 648 | aws ecs create-cluster --cluster-name ivs | jq '.cluster.clusterArn' | sed 's/"//g' > ./temp_files/ecs_cluster_arn.txt 649 | sleep 2 650 | 651 | #Configures the ivs_task_definition.json file with the correct Amazon ECS execution previously created 652 | echo -e "${GREEN}Configuring the ivs_task_definition.json file with the correct Amazon ECS execution...${NC}" 653 | ecs_role_arn=$(cat ./temp_files/ivs_ecs_execution_role_arn.txt) 654 | ecr_uri=$(cat ./temp_files/ecr_uri.txt)":latest" 655 | jq --arg v "$ecs_role_arn" --arg f "$ecr_uri" '.taskRoleArn = $v | .executionRoleArn = $v | .containerDefinitions[0].image = $f' ./json_models/ivs_task_definition_model.json > ./json_configs/ivs_task_definition.json 656 | 657 | #Creates Amazon ECS Task Definition named ivs-webrtc 658 | echo -e "${GREEN}Creating the Amazon ECS Task Definition named ivs-webrtc...${NC}" 659 | aws ecs register-task-definition --cli-input-json file://json_configs/ivs_task_definition.json > /dev/null 660 | sleep 2 661 | 662 | #Select the proper subnets from your default vpc to be used by Amazon ECS Service 663 | echo -e "${GREEN}Selecting the proper subnets from your default vpc to be used by Amazon ECS Service...${NC}" 664 | aws ec2 describe-subnets --filter Name=vpc-id,Values=$(aws ec2 describe-vpcs | jq '.Vpcs[] | select(.IsDefault)' | jq '.VpcId' | sed 's/"//g') \ 665 | --query 'Subnets[?MapPublicIpOnLaunch==`true`].SubnetId' | sed -e '/^$/d;:a;N;$!ba;s/\n//g;s/ //g;s/[][]//g;s/.$//;s/^.//' > ./temp_files/my_subnets.txt 666 | sleep 2 667 | 668 | ivs_service=$(aws ecs list-services --cluster ivs | sed -n -e '/.*ivs/p' | sed -e 's/ //g;s/"//g;s/,//g') 669 | sleep 2 670 | 671 | if [ ! $ivs_service ] 672 | then 673 | 674 | #Creates the template ivs_ecs_service.json to be used by the Amazon ECS Server 675 | echo -e "${GREEN}Creating the template ivs_ecs_service.json to be used by the Amazon ECS Server...${NC}" 676 | store_sgid=$(cat ./temp_files/ivs_sg.txt) 677 | store_subnets=$(sed -e 'N;s/\n//;N;s/\n//;N;s/\n//' ./temp_files/my_subnets.txt | sed -e 'N;s/\n//;s/ //g;s/[][]//g;s/^"//g;s/"$//g') 678 | jq --arg v "$store_subnets" '.networkConfiguration.awsvpcConfiguration |= . + {"subnets":[$v]}' \ 679 | ./json_models/ecs_services_model.json | jq --arg v "$store_sgid" '.networkConfiguration.awsvpcConfiguration |= . + {"securityGroups":[$v]}' \ 680 | | sed -e 's/\\//g' > ./json_configs/ivs_ecs_service.json 681 | 682 | #Creates the Amazon ECS service named ivs-webrtc 683 | echo -e "${GREEN}Creating the Amazon ECS service named ivs-webrtc...${NC}" 684 | aws ecs create-service --cli-input-json file://json_configs/ivs_ecs_service.json > /dev/null 685 | sleep 2 686 | 687 | else 688 | 689 | error_exit "The ivs-webrtc does exist and cannot be automatically replaced! Try to delete it mannually and re-run this script." 690 | 691 | fi 692 | } 693 | 694 | ################################################################################ 695 | # EventBridge Resources # 696 | ################################################################################ 697 | 698 | function eventbridge_resources () { 699 | 700 | # This function will create all eventbridge resources required by ivs-webrtc 701 | 702 | unset lambda 703 | 704 | # Test if exists 705 | aws lambda get-function --function-name ivs-ip-register > /dev/null 2>&1 && lambda='OK' || lambda='NOK' 706 | sleep 2 707 | 708 | #Configures the ivs_events_rule.json with the correct ivs service configured in Amazon ECS Cluster 709 | echo -e "${GREEN}Configuring the ivs_events_rule.json with the correct ivs service configured in Amazon ECS Cluster...${NC}" 710 | ecs_arn=$(cat ./temp_files/ecs_cluster_arn.txt) 711 | sed -e "s@ARN_HERE@${ecs_arn}\\\@g" json_models/ivs_events_rule_model.json > ./json_configs/ivs_events_rule.json 712 | 713 | #Create the Amazon EventBridge rule named ip-register 714 | echo -e "${GREEN}Creating the Amazon EventBridge rule named ip-register...${NC}" 715 | aws events put-rule --cli-input-json file://json_configs/ivs_events_rule.json | jq '.RuleArn' | sed 's/"//g' > ./temp_files/ivs_event_rule_arn.txt 716 | sleep 2 717 | 718 | if [ $lambda = 'OK' ] 719 | then 720 | 721 | unset lambda 722 | 723 | #Creating the resource policy template 724 | echo -e "${GREEN}Creating the resource policy template...${NC}" 725 | aws lambda get-function --function-name ivs-ip-register | jq '.Configuration.FunctionArn' | sed 's/"//g' > ./temp_files/ivs_lambda_function_arn.txt 726 | sleep 2 727 | 728 | 729 | 730 | #Adding permission to ip-register rule invoke lambda funtion ivs-ip-register 731 | echo -e "${GREEN}Adding permission to ip-register rule invoke lambda funtion ivs-ip-register...${NC}" 732 | aws lambda add-permission --function-name ivs-ip-register --action lambda:InvokeFunction --statement-id events --principal events.amazonaws.com --source-arn=$(cat ./temp_files/ivs_event_rule_arn.txt) > /dev/null 2>&1 && lambda='OK' || lambda='NOK' 733 | sleep 2 734 | 735 | if [ $lambda = 'NOK' ] 736 | then 737 | 738 | echo -e "${GREEN}Permission was already in place!${NC}" 739 | fi 740 | 741 | #Add lambda function ivs-ip-register as a target to the event rule ip-register 742 | echo -e "${GREEN}Adding lambda function ivs-ip-register as a target to the event rule ip-register...${NC}" 743 | aws events remove-targets --rule ip-register --ids "1" > /dev/null 744 | sleep 2 745 | aws events put-targets --rule ip-register --targets "Id"="1","Arn"="$(cat ./temp_files/ivs_lambda_function_arn.txt)" > /dev/null 746 | sleep 2 747 | 748 | else 749 | 750 | error_exit 'Lambda ivs-ip-register not found! Please re-run ./backend deploy lambda' 751 | 752 | fi 753 | 754 | } 755 | 756 | ################################################################################ 757 | # CloudWatch Resources # 758 | ################################################################################ 759 | 760 | function cloudwatchlogs_resources () { 761 | 762 | # This function create all cloudwatchlogs resources required by ivs-webrtc 763 | 764 | unset logs 765 | 766 | #Creating cloudwatch log group name for ivs-webrtc tasks 767 | echo -e "${GREEN}Creating cloudwatch log group name for ivs-webrtc tasks...${NC}" 768 | aws logs create-log-group --log-group-name /ecs/ivs-webrtc > /dev/null 2>&1 && logs='OK' || logs='NOK' 769 | sleep 2 770 | 771 | if [ $logs = 'NOK' ] 772 | then 773 | 774 | echo -e "${GREEN}Log Group /ecs/ivs-webrtc OK!${NC}" 775 | fi 776 | 777 | } 778 | 779 | ################################################################################ 780 | # Adjust Fargate Tasks # 781 | ################################################################################ 782 | 783 | function fargate_adjust_service () { 784 | 785 | # This function will adjust the fargate service ivs-webrtc from 0 to 2 tasks 786 | echo -e "${GREEN}Adjusting fargate service to 2 tasks...${NC}" 787 | aws ecs update-service --cluster ivs --service ivs-webrtc --desired-count 2 > /dev/null 788 | sleep 2 789 | 790 | 791 | 792 | } 793 | 794 | ################################################################################ 795 | # Error messages # 796 | ################################################################################ 797 | 798 | function error_exit () { 799 | 800 | echo -e "${RED}$1${NC}" 1>&2 801 | exit 1 802 | 803 | } 804 | 805 | ################################################################################ 806 | # Options to deploy the backend # 807 | ################################################################################ 808 | 809 | function deploy () { 810 | 811 | # This function will receive a argument from the command line to start the cleaning process 812 | # It will be possible to uninstall everything or just pontual services 813 | 814 | option=$1 815 | 816 | case $option in 817 | all) 818 | ecr_resources || { error_exit 'ecr deployment failed!'; } 819 | s3_resources || { error_exit 's3 deployment failed!'; } 820 | codebuild_adjust_templates || { error_exit 'coldbuild templates adjustment failed!'; } 821 | codebuild_iam || { error_exit 'coldbuild roles and policies failed'; } 822 | codebuild_resources || { error_exit 'codebuild deployment failed!'; } 823 | iam_resources || { error_exit 'iam roles deployment failed!'; } 824 | lambda_resources || { error_exit 'lambda function deployment failed!'; } 825 | dynamodb_resources || { error_exit 'dynamodb table deployment failed!'; } 826 | vpc_resources || { error_exit 'security group deployment failed!'; } 827 | fargate_resources || { error_exit 'fargate deployment failed!'; } 828 | eventbridge_resources || { error_exit 'events deployment failed!'; } 829 | cloudwatchlogs_resources || { error_exit 'logs deployment failed!'; } 830 | fargate_adjust_service || { error_exit 'tasks deployment failed!'; } 831 | ;; 832 | 833 | iam) 834 | echo -e "${GREEN}###Deploying iam ivs-webrtc resources###${NC}" 835 | iam_resources 836 | ;; 837 | 838 | lambda) 839 | echo -e "${GREEN}###Deploying lambda ivs-webrtc resources###${NC}" 840 | lambda_resources 841 | ;; 842 | 843 | dynamodb) 844 | echo -e "${GREEN}###Deploying dynamodb ivs-webrtc resources###${NC}" 845 | dynamodb_resources 846 | ;; 847 | 848 | sg) 849 | echo -e "${GREEN}###Deploying security group ivs-webrtc resources###${NC}" 850 | vpc_resources 851 | ;; 852 | 853 | fargate) 854 | echo -e "${GREEN}###Deploying fargate ivs-webrtc resources###${NC}" 855 | fargate_resources 856 | ;; 857 | 858 | events) 859 | echo -e "${GREEN}###Deploying eventbridge ivs-webrtc resources###${NC}" 860 | eventbridge_resources 861 | ;; 862 | 863 | logs) 864 | echo -e "${GREEN}###Deploying cloudwatchlogs ivs-webrtc resources###${NC}" 865 | cloudwatchlogs_resources 866 | ;; 867 | 868 | tasks) 869 | echo -e "${GREEN}###Deploying tasks ivs-webrtc resources###${NC}" 870 | fargate_adjust_service 871 | ;; 872 | 873 | ecr) 874 | echo -e "${GREEN}###Deploying ecr resources###${NC}" 875 | ecr_resources 876 | ;; 877 | 878 | s3) 879 | echo -e "${GREEN}###Deploying s3 resources###${NC}" 880 | s3_resources 881 | ;; 882 | 883 | templates) 884 | echo -e "${GREEN}###Adjusting codebuild templates###${NC}" 885 | codebuild_adjust_templates 886 | ;; 887 | 888 | codebuild_rp) 889 | echo -e "${GREEN}###Deploying codebuild roles and policies###${NC}" 890 | codebuild_iam 891 | ;; 892 | 893 | codebuild) 894 | echo -e "${GREEN}###Deploying codebuild resources###${NC}" 895 | codebuild_resources 896 | ;; 897 | 898 | 899 | *) 900 | echo -e "${RED}This option is not valid!${NC}" 901 | ;; 902 | esac 903 | 904 | } 905 | 906 | # Enables script arguments 907 | "$@" 908 | 909 | # Prints help in case of calling the script without arguments 910 | if [ -z $1 ] 911 | then 912 | 913 | help 914 | fi 915 | -------------------------------------------------------------------------------- /backend/json_configs/dynamodb_table.json: -------------------------------------------------------------------------------- 1 | { 2 | "AttributeDefinitions": [ 3 | { 4 | "AttributeName": "role", 5 | "AttributeType": "S" 6 | } 7 | ], 8 | "TableName": "ISS-task-dns-track-dev", 9 | "KeySchema": [ 10 | { 11 | "AttributeName": "role", 12 | "KeyType": "HASH" 13 | } 14 | ], 15 | "ProvisionedThroughput": { 16 | "ReadCapacityUnits": 5, 17 | "WriteCapacityUnits": 5 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /backend/json_configs/ivs_codebuild_ecr_policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Action": [ 6 | "ecr:BatchCheckLayerAvailability", 7 | "ecr:CompleteLayerUpload", 8 | "ecr:GetAuthorizationToken", 9 | "ecr:InitiateLayerUpload", 10 | "ecr:PutImage", 11 | "ecr:UploadLayerPart" 12 | ], 13 | "Resource": "*", 14 | "Effect": "Allow" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /backend/json_configs/ivs_dynamodb_populate.json: -------------------------------------------------------------------------------- 1 | { 2 | "ISS-task-dns-track-dev": [ 3 | { 4 | "PutRequest": { 5 | "Item": { 6 | "role": {"S": "primary"}, 7 | "eventid": {"S": "ppp"}, 8 | "replaced": {"S": "yes"}, 9 | "dns": {"S": "ppp"} 10 | } 11 | } 12 | }, 13 | { 14 | "PutRequest": { 15 | "Item": { 16 | "role": {"S": "secondary"}, 17 | "eventid": {"S": "sss"}, 18 | "replaced": {"S": "yes"}, 19 | "dns": {"S": "yes"} 20 | } 21 | } 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /backend/json_configs/ivs_ecs_trust_policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2008-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "", 6 | "Effect": "Allow", 7 | "Principal": { 8 | "Service": "ecs-tasks.amazonaws.com" 9 | }, 10 | "Action": "sts:AssumeRole" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /backend/json_configs/ivs_lambda_dynamodb_policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "VisualEditor0", 6 | "Effect": "Allow", 7 | "Action": [ 8 | "dynamodb:PutItem", 9 | "dynamodb:DeleteItem", 10 | "dynamodb:GetItem", 11 | "dynamodb:Query", 12 | "dynamodb:UpdateItem" 13 | ], 14 | "Resource": [ 15 | "arn:aws:dynamodb:*:*:table/ISS-task-dns-track-dev", 16 | "arn:aws:dynamodb:*:*:table/ISS-task-dns-track-dev/index/*" 17 | ] 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /backend/json_configs/ivs_lambda_resource_policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Id": "default", 4 | "Statement": [ 5 | { 6 | "Sid": "ivs-webrtc-deployment", 7 | "Effect": "Allow", 8 | "Principal": { 9 | "Service": "events.amazonaws.com" 10 | }, 11 | "Action": "lambda:InvokeFunction", 12 | "Resource": "arn:aws:lambda:us-east-1:454679031165:function:ivs-ip-register", 13 | "Condition": { 14 | "ArnLike": { 15 | "AWS:SourceArn": "arn:aws:events:us-east-1:454679031165:rule/ip-register" 16 | } 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /backend/json_configs/ivs_lambda_trust_policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Principal": { 7 | "Service": "lambda.amazonaws.com" 8 | }, 9 | "Action": "sts:AssumeRole" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /backend/json_configs/ivs_webrtc_codebuild_trust_policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Principal": { 7 | "Service": "codebuild.amazonaws.com" 8 | }, 9 | "Action": "sts:AssumeRole" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /backend/json_models/ecs_services_model.json: -------------------------------------------------------------------------------- 1 | { 2 | "cluster": "ivs", 3 | "serviceName": "ivs-webrtc", 4 | "taskDefinition": "ivs-webrtc", 5 | "desiredCount": 0, 6 | "launchType": "FARGATE", 7 | "schedulingStrategy": "REPLICA", 8 | "platformVersion": "LATEST", 9 | "networkConfiguration": { 10 | "awsvpcConfiguration": { 11 | "subnets": [ ], 12 | "securityGroups": [ 13 | "ivs-sg" 14 | ], 15 | "assignPublicIp": "ENABLED" 16 | } 17 | }, 18 | "deploymentController": { 19 | "type": "ECS" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /backend/json_models/ivs_codebuild_base_policy_model.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Resource": [ 7 | "arn:aws:logs:us-east-1:REPLACE_ACCOUNTID:log-group:/aws/codebuild/ivs-webrtc", 8 | "arn:aws:logs:us-east-1:REPLACE_ACCOUNTID:log-group:/aws/codebuild/ivs-webrtc:*" 9 | ], 10 | "Action": [ 11 | "logs:CreateLogGroup", 12 | "logs:CreateLogStream", 13 | "logs:PutLogEvents" 14 | ] 15 | }, 16 | { 17 | "Effect": "Allow", 18 | "Resource": [ 19 | "arn:aws:s3:::codepipeline-us-east-1-*" 20 | ], 21 | "Action": [ 22 | "s3:PutObject", 23 | "s3:GetObject", 24 | "s3:GetObjectVersion", 25 | "s3:GetBucketAcl", 26 | "s3:GetBucketLocation" 27 | ] 28 | }, 29 | { 30 | "Effect": "Allow", 31 | "Action": [ 32 | "codebuild:CreateReportGroup", 33 | "codebuild:CreateReport", 34 | "codebuild:UpdateReport", 35 | "codebuild:BatchPutTestCases", 36 | "codebuild:BatchPutCodeCoverages" 37 | ], 38 | "Resource": [ 39 | "arn:aws:codebuild:us-east-1:REPLACE_ACCOUNTID:report-group/ivs-webrtc-*" 40 | ] 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /backend/json_models/ivs_codebuild_log_policy_model.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Resource": [ 7 | "arn:aws:logs:us-east-1:REPLACE_ACCOUNTID:log-group:*" 8 | ], 9 | "Action": [ 10 | "logs:CreateLogGroup", 11 | "logs:CreateLogStream", 12 | "logs:PutLogEvents" 13 | ] 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /backend/json_models/ivs_codebuild_model.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ivs-webrtc", 3 | "source": { 4 | "type": "S3", 5 | "location": "REPLACE_BUCKET/docker.zip" 6 | }, 7 | "artifacts": { 8 | "type": "NO_ARTIFACTS" 9 | }, 10 | "environment": { 11 | "type": "LINUX_CONTAINER", 12 | "image": "aws/codebuild/standard:4.0", 13 | "computeType": "BUILD_GENERAL1_SMALL", 14 | "environmentVariables": [ 15 | { 16 | "name": "AWS_DEFAULT_REGION", 17 | "value": "us-east-1" 18 | }, 19 | { 20 | "name": "AWS_ACCOUNT_ID", 21 | "value": "REPLACE_ACCOUNTID" 22 | }, 23 | { 24 | "name": "IMAGE_REPO_NAME", 25 | "value": "ivs-webrtc" 26 | }, 27 | { 28 | "name": "IMAGE_TAG", 29 | "value": "latest" 30 | } 31 | ], 32 | "privilegedMode": true 33 | }, 34 | "serviceRole": "REPLACE_ROLE", 35 | "encryptionKey": "arn:aws:kms:us-east-1:REPLACE_ACCOUNTID:alias/aws/s3" 36 | } -------------------------------------------------------------------------------- /backend/json_models/ivs_codebuild_s3_policy_model.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "s3:GetObject", 8 | "s3:GetObjectVersion" 9 | ], 10 | "Resource": [ 11 | "arn:aws:s3:::REPLACE_BUCKET/docker.zip", 12 | "arn:aws:s3:::REPLACE_BUCKET/docker.zip/*" 13 | ] 14 | }, 15 | { 16 | "Effect": "Allow", 17 | "Resource": [ 18 | "arn:aws:s3:::REPLACE_BUCKET" 19 | ], 20 | "Action": [ 21 | "s3:ListBucket", 22 | "s3:GetBucketAcl", 23 | "s3:GetBucketLocation" 24 | ] 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /backend/json_models/ivs_codebuild_vpc_policy_model.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "ec2:CreateNetworkInterface", 8 | "ec2:DescribeDhcpOptions", 9 | "ec2:DescribeNetworkInterfaces", 10 | "ec2:DeleteNetworkInterface", 11 | "ec2:DescribeSubnets", 12 | "ec2:DescribeSecurityGroups", 13 | "ec2:DescribeVpcs" 14 | ], 15 | "Resource": "*" 16 | }, 17 | { 18 | "Effect": "Allow", 19 | "Action": [ 20 | "ec2:CreateNetworkInterfacePermission" 21 | ], 22 | "Resource": "arn:aws:ec2:us-east-1:REPLACE_ACCOUNTID:network-interface/*", 23 | "Condition": { 24 | "StringEquals": { 25 | "ec2:Subnet": [ 26 | "arn:aws:ec2:us-east-1:REPLACE_ACCOUNTID:subnet/REPLACE_SUBNET0", 27 | "arn:aws:ec2:us-east-1:REPLACE_ACCOUNTID:subnet/REPLACE_SUBNET1", 28 | "arn:aws:ec2:us-east-1:REPLACE_ACCOUNTID:subnet/REPLACE_SUBNET2" 29 | ], 30 | "ec2:AuthorizedService": "codebuild.amazonaws.com" 31 | } 32 | } 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /backend/json_models/ivs_events_rule_model.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "ip-register", 3 | "EventPattern": "{\n \"source\": [\"aws.ecs\"],\n \"detail-type\": [\"ECS Task State Change\"],\n \"detail\": {\n \"clusterArn\": [\"ARN_HERE"],\n \"lastStatus\": [{\n \"anything-but\": [\"PENDING\", \"PROVISIONING\", \"STOPPED\",\"DEPROVISIONING\"]\n }]\n }\n}", 4 | "State": "ENABLED", 5 | "EventBusName": "default" 6 | } 7 | -------------------------------------------------------------------------------- /backend/json_models/ivs_lambda_resource_policy_model.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Id": "default", 4 | "Statement": [ 5 | { 6 | "Sid": "ivs-webrtc-deployment", 7 | "Effect": "Allow", 8 | "Principal": { 9 | "Service": "events.amazonaws.com" 10 | }, 11 | "Action": "lambda:InvokeFunction", 12 | "Resource": "lambda_arn", 13 | "Condition": { 14 | "ArnLike": { 15 | "AWS:SourceArn": "rule_arn" 16 | } 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /backend/json_models/ivs_ports.txt: -------------------------------------------------------------------------------- 1 | 80 2 | 443 3 | -------------------------------------------------------------------------------- /backend/json_models/ivs_task_definition_model.json: -------------------------------------------------------------------------------- 1 | { 2 | "containerDefinitions": [ 3 | { 4 | "name": "ivs-webrtc", 5 | "image": "862418150245.dkr.ecr.us-east-1.amazonaws.com/ivs-webrtc:latest", 6 | "portMappings": [ 7 | { 8 | "containerPort": 80, 9 | "hostPort": 80, 10 | "protocol": "tcp" 11 | } 12 | ], 13 | "essential": true, 14 | "readonlyRootFilesystem": false, 15 | "logConfiguration": { 16 | "logDriver": "awslogs", 17 | "options": { 18 | "awslogs-group": "/ecs/ivs-webrtc", 19 | "awslogs-region": "us-east-1", 20 | "awslogs-stream-prefix": "ecs" 21 | } 22 | } 23 | } 24 | ], 25 | "family": "ivs-webrtc", 26 | "taskRoleArn": "arn:aws:iam::862418150245:role/ecsTask", 27 | "executionRoleArn": "arn:aws:iam::862418150245:role/ecsTask", 28 | "networkMode": "awsvpc", 29 | "requiresCompatibilities": [ 30 | "FARGATE" 31 | ], 32 | "cpu": "256", 33 | "memory": "512" 34 | } 35 | 36 | -------------------------------------------------------------------------------- /backend/json_models/lambda_model.json: -------------------------------------------------------------------------------- 1 | { 2 | "FunctionName": "ivs-ip-register", 3 | "Runtime": "python3.8", 4 | "Role": "arn:aws:iam::862418150245:role/service-role/ivs-ip-register-role-992spxkd", 5 | "Handler": "lambda_function.lambda_handler", 6 | "Description": "", 7 | "Timeout": 3, 8 | "MemorySize": 128 9 | } 10 | 11 | -------------------------------------------------------------------------------- /backend/lambda_function.py: -------------------------------------------------------------------------------- 1 | import json 2 | import boto3 3 | 4 | # Set the EC2 Client 5 | ec2 = boto3.resource('ec2') 6 | 7 | # Set DynamoDB Client 8 | dynamodb = boto3.resource("dynamodb", region_name="us-east-1") 9 | 10 | # Set DynamoDB Table 11 | table = dynamodb.Table('ISS-task-dns-track-dev') 12 | 13 | def get_values(): 14 | 15 | # This function will: 1 - Populate DynamoDB if there is not items. 2 - Retrive the items from DynamoDB 16 | 17 | # Defines the list that will store metadata servers from DynamoDB Tale 18 | servers = [] 19 | 20 | # This is a list that will be set based on DynamoDB queries. 21 | items = [] 22 | 23 | # This will query dynamodb table looking for a pk named primary 24 | servers.append(table.get_item( 25 | Key={'role':'primary'} 26 | )) 27 | 28 | # In case of not finding pk primary, it will create a item with default values 29 | if not servers[0].get('Item'): 30 | set_default_values('primary','yes','ppp','ppp') 31 | else: 32 | items.append(servers[0].get('Item')) 33 | 34 | # This will query dynamodb table looking for a pk named secondary 35 | servers.append(table.get_item( 36 | Key={'role':'secondary'} 37 | )) 38 | 39 | # In case of not finding pk secondary, it will create a item with default values 40 | if not servers[1].get('Item'): 41 | set_default_values('secondary','yes','sss','sss') 42 | else: 43 | items.append(servers[1].get('Item')) 44 | 45 | return items 46 | 47 | 48 | def set_default_values(role,replaced,default_id,dns): 49 | 50 | # This function will be used to set default values on DynamoDB table. 51 | 52 | items = [] 53 | 54 | table.put_item( 55 | Item={ 56 | 'role': role, 57 | 'replaced': replaced, 58 | 'eventid': default_id, 59 | 'dns': dns 60 | } 61 | ) 62 | 63 | def update_values(role, replaced, eventid, dns='not_defined'): 64 | 65 | # This function will be used to update server values on DynamoDB table. 66 | 67 | table.update_item( 68 | Key={ 'role': role}, 69 | UpdateExpression="set replaced=:r, eventid=:e, dns=:d", 70 | ExpressionAttributeValues={ 71 | ':r': replaced, 72 | ':e': eventid, 73 | ':d': dns 74 | } 75 | ) 76 | 77 | def get_public_ip(eni_id): 78 | 79 | # This function will retrive the fqdn of the servers 80 | # InvalidNetworkInterfaceID.NotFound as error: 81 | 82 | try: 83 | eni = ec2.NetworkInterface(eni_id) 84 | fqdn = eni.association_attribute['PublicDnsName'] 85 | return fqdn 86 | except: 87 | print('The Network Interface does not exist! This lambda function will finish here') 88 | return None 89 | 90 | def set_to_replace(primary,secondary,public_dns_name,eventid): 91 | 92 | # This function will set the servers to be replaces by futures ones 93 | 94 | # This will configure the metadata of the primary server to be replaced by the new future server 95 | if (primary.get('dns') == public_dns_name) and (primary.get('role') == 'primary') and (primary.get('replaced') == 'no') and (primary.get('eventid') != eventid) and (secondary.get('eventid') != eventid): 96 | 97 | update_values('primary','yes',eventid) 98 | #print('First IF') 99 | return None 100 | 101 | # This will configure the metadata of the primary server to be replaced by the new future server 102 | elif (secondary.get('dns') == public_dns_name) and (secondary.get('role') == 'secondary') and (secondary.get('replaced') == 'no') and (secondary.get('eventid') != eventid) and (primary.get('eventid') != eventid): 103 | 104 | update_values('secondary','yes',eventid) 105 | #print('Second IF') 106 | return None 107 | 108 | 109 | def set_new_server(primary,secondary,public_dns_name,eventid): 110 | 111 | # This function will set the metadata for the new primary or secondary server 112 | 113 | # This will register the metadata of the new primary server 114 | if (primary.get('role') == 'primary') and (primary.get('replaced') == 'yes') and (primary.get('eventid') != eventid) and (secondary.get('eventid') != eventid): 115 | 116 | update_values('primary','no',eventid, public_dns_name) 117 | print('Primary DNS Updated!!!!') 118 | return None 119 | 120 | # This will register the metadata of the new secondary server 121 | elif (secondary.get('role') == 'secondary') and (secondary.get('replaced') == 'yes') and (secondary.get('eventid') != eventid) and (primary.get('eventid') != eventid): 122 | 123 | update_values('secondary','no',eventid, public_dns_name) 124 | print('Secondary DNS Updated!!!!') 125 | return None 126 | 127 | 128 | 129 | def lambda_handler(event, context): 130 | 131 | # Stores the event_id of the event 132 | event_id = event['id'] 133 | 134 | # Debug 135 | print('Tracking all Status') 136 | print(event) 137 | 138 | # This will be valid if the server transition from running to stop stage. 139 | if (event['detail']['lastStatus'] == 'RUNNING') and (event['detail']['desiredStatus'] == 'STOPPED'): 140 | eni_id = event['detail']['attachments'][0]['details'][1]['value'] 141 | public_dns_name = get_public_ip(eni_id) 142 | 143 | # Calls get_values to retrive server metadata 144 | items = get_values() 145 | 146 | # Calls set_to_replace when a server dies 147 | set_to_replace(items[0],items[1],public_dns_name,event_id) 148 | 149 | # This will run if the new server comes online 150 | elif (event['detail']['lastStatus'] == 'RUNNING') and (event['detail']['desiredStatus'] == 'RUNNING'): 151 | eni_id = event['detail']['attachments'][0]['details'][1]['value'] 152 | public_dns_name = get_public_ip(eni_id) 153 | 154 | # Calls get_values to retrive server metadata 155 | items = get_values() 156 | 157 | # Calls set_new_server function 158 | set_new_server(items[0],items[1],public_dns_name,event_id) 159 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "transwrap-proxy", 3 | "version": "0.2.0", 4 | "description": "video proxy webm to rtmps", 5 | "main": "transwrap.js", 6 | "dependencies": { 7 | "express": "^4.17.1", 8 | "ws": "^7.4.5" 9 | }, 10 | "devDependencies": { 11 | "nodemon": "^2.0.16" 12 | }, 13 | "scripts": { 14 | "start": "node transwrap_https.js", 15 | "start-test": "node transwrap_http.js", 16 | "startDev": "nodemon transwrap_http.js", 17 | "startDevs": "nodemon transwrap_https.js" 18 | }, 19 | "author": "osmarb marlonpc", 20 | "license": "MIT" 21 | } 22 | -------------------------------------------------------------------------------- /backend/temp_files/.gitignore: -------------------------------------------------------------------------------- 1 | *.txt 2 | -------------------------------------------------------------------------------- /backend/transwrap_http.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | // transwrap.js http version 5 | 6 | /// import block 7 | const http = require("http"); 8 | //const fs = require('fs'); 9 | const WebSocket = require("ws"); 10 | const express = require("express"); 11 | const callff = require("child_process"); 12 | 13 | /// app socket definition 14 | const app = express(); 15 | const port = 3004; 16 | const servertype = "HTTP"; 17 | const home = "/wwww"; /// for heath check only, you can also use as standalone server, by moving the frontend folder to the home www 18 | 19 | // https certificates // Be sure to generate those before running 20 | /* 21 | const newCert = 'cert.pem'; 22 | const newKey = 'key.pem' 23 | const certicates = { 24 | key: fs.readFileSync(newKey), 25 | cert: fs.readFileSync(newCert) 26 | }; 27 | */ 28 | 29 | // wrapper server 30 | const transwraper = http.createServer(app).listen(port, () => { 31 | console.log(`Listening on port: ${port} ${servertype}`); 32 | }); 33 | 34 | // websocket creation 35 | const wsRef = new WebSocket.Server({ server: transwraper }); 36 | 37 | app.use((req, res, next) => { 38 | console.log(`${servertype}:${req.method}:${req.originalUrl}`); 39 | return next(); 40 | }); 41 | 42 | app.use(express.static(__dirname + home)); 43 | 44 | // Streaming 45 | wsRef.on("connection", (ws, req) => { 46 | console.log("Loop 1"); 47 | ws.send("MSG: Connected to server"); /// Send this message to the app frontend 48 | console.log(`Got connection, URL: ${req.url}`); 49 | ws.on("message", (evt) => { 50 | console.log("Event", evt); 51 | ffmpeg.stdin.write(evt); 52 | }); 53 | 54 | ws.on("error", (err) => { 55 | console.log("ERROR on websocket", err); 56 | }); 57 | 58 | const rtmpURL = req.url.slice(12); 59 | console.log(`URL seted is ${rtmpURL}`); 60 | 61 | const codec = req.url.split("/")[2]; 62 | console.log("CODEC", codec); 63 | 64 | if (codec === "h264") { 65 | console.log("No video transcoding"); 66 | var ffArr = [ 67 | "-i", 68 | "-", 69 | "-vcodec", 70 | "copy", 71 | "-preset", 72 | "veryfast", 73 | "-tune", 74 | "zerolatency", 75 | "-acodec", 76 | "aac", 77 | "-ar", 78 | "44100", 79 | "-b:a", 80 | "128k", 81 | "-f", 82 | "flv", 83 | rtmpURL, 84 | "-reconnect", 85 | "3", 86 | "-reconnect_at_eof", 87 | "1", 88 | "-reconnect_streamed", 89 | "3", 90 | ]; 91 | } else { 92 | console.log("Transcoding true"); 93 | //ffmpeg -re -stream_loop -1 -i $VIDEO_FILEPATH -r 30 -c:v libx264 -pix_fmt yuv420p -profile:v main -preset veryfast -x264opts "nal-hrd=cbr:no-scenecut" -minrate 3000 -maxrate 3000 -g 60 -c:a aac -b:a 160k -ac 2 -ar 44100 -f flv rtmps://$INGEST_ENDPOINT:443/app/$STREAM_KEY 94 | 95 | var ffArr = [ 96 | "-fflags", 97 | "+genpts", 98 | "-i", 99 | "-", 100 | "-r", 101 | "30", 102 | "-c:v", 103 | "libx264", 104 | "-pix_fmt", 105 | "yuv420p", 106 | "-profile:v", 107 | "main", 108 | "-preset", 109 | "veryfast", 110 | "-x264opts", 111 | "nal-hrd=cbr:no-scenecut", 112 | "-minrate", 113 | "3000", 114 | "-maxrate", 115 | "3000", 116 | "-g", 117 | "60", 118 | "-c:a", 119 | "aac", 120 | "-b:a", 121 | "128k", 122 | "-ac", 123 | "2", 124 | "-ar", 125 | "44100", 126 | "-vf", 127 | "scale=1280:720,format=yuv420p", 128 | "-profile:v", 129 | "main", 130 | "-f", 131 | "flv", 132 | rtmpURL, 133 | "-reconnect", 134 | "3", 135 | "-reconnect_at_eof", 136 | "1", 137 | "-reconnect_streamed", 138 | "3", 139 | ]; 140 | } 141 | 142 | ws.on("close", (evt) => { 143 | ffmpeg.kill("SIGINT"); 144 | console.log(`Connection Closed: ${evt}`); 145 | }); 146 | 147 | const ffmpeg = callff.spawn("ffmpeg", ffArr); 148 | 149 | ffmpeg.on("close", (code, signal) => { 150 | console.log(`FFMPEG closed, reason ${code} , ${signal}`); 151 | ws.send("Closing Socket"); /// message to front end 152 | ws.terminate(); 153 | }); 154 | 155 | ffmpeg.stdin.on("error", (e) => { 156 | ws.send(e); 157 | console.log(`FFMPEG ERROR: ${e}`); 158 | ws.terminate(); 159 | }); 160 | 161 | ffmpeg.stderr.on("data", (data) => { 162 | console.log(`FFMPEG MSG: ${data.toString()}`); 163 | ws.send(data.toString()); 164 | }); 165 | }); 166 | -------------------------------------------------------------------------------- /backend/transwrap_https.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | // transwrap.js https version 5 | 6 | /// import block 7 | const https = require("https"); 8 | const fs = require("fs"); 9 | const WebSocket = require("ws"); 10 | const express = require("express"); 11 | const callff = require("child_process"); 12 | 13 | /// app socket definition 14 | const app = express(); 15 | const port = 3004; 16 | const servertype = "HTTPS"; 17 | const home = "wwww"; /// for heath check only, you can also use as standalone server, by moving the frontend folder to the home www 18 | 19 | // https certificates // Be sure to generate those before running 20 | const newCert = "cert.pem"; 21 | const newKey = "key.pem"; 22 | const certicates = { 23 | key: fs.readFileSync(newKey), 24 | cert: fs.readFileSync(newCert), 25 | }; 26 | 27 | // wrapper server 28 | const transwraper = https.createServer(certicates, app).listen(port, () => { 29 | console.log(`Listening on port: ${port} ${servertype}`); 30 | }); 31 | 32 | // websocket creation 33 | const wsRef = new WebSocket.Server({ server: transwraper }); 34 | 35 | app.use((req, res, next) => { 36 | console.log(`${servertype}:${req.method}:${req.originalUrl}`); 37 | return next(); 38 | }); 39 | 40 | app.use(express.static(__dirname + home)); 41 | 42 | // Streaming 43 | wsRef.on("connection", (ws, req) => { 44 | console.log("Loop 1"); 45 | ws.send("MSG: Connected to server"); /// Send this message to the app frontend 46 | console.log(`Got connection, URL: ${req.url}`); 47 | ws.on("message", (evt) => { 48 | console.log("Event", evt); 49 | ffmpeg.stdin.write(evt); 50 | }); 51 | 52 | ws.on("error", (err) => { 53 | console.log("ERROR on websocket", err); 54 | }); 55 | 56 | const rtmpURL = req.url.slice(12); 57 | console.log(`URL seted is ${rtmpURL}`); 58 | 59 | const codec = req.url.split("/")[2]; 60 | console.log("CODEC", codec); 61 | 62 | if (codec === "h264") { 63 | console.log("No video transcoding"); 64 | var ffArr = [ 65 | "-i", 66 | "-", 67 | "-vcodec", 68 | "copy", 69 | "-preset", 70 | "veryfast", 71 | "-tune", 72 | "zerolatency", 73 | "-acodec", 74 | "aac", 75 | "-ar", 76 | "44100", 77 | "-b:a", 78 | "128k", 79 | "-f", 80 | "flv", 81 | rtmpURL, 82 | "-reconnect", 83 | "3", 84 | "-reconnect_at_eof", 85 | "1", 86 | "-reconnect_streamed", 87 | "3", 88 | ]; 89 | } else { 90 | console.log("Transcoding true"); 91 | //ffmpeg -re -stream_loop -1 -i $VIDEO_FILEPATH -r 30 -c:v libx264 -pix_fmt yuv420p -profile:v main -preset veryfast -x264opts "nal-hrd=cbr:no-scenecut" -minrate 3000 -maxrate 3000 -g 60 -c:a aac -b:a 160k -ac 2 -ar 44100 -f flv rtmps://$INGEST_ENDPOINT:443/app/$STREAM_KEY 92 | 93 | var ffArr = [ 94 | "-fflags", 95 | "+genpts", 96 | "-i", 97 | "-", 98 | "-r", 99 | "30", 100 | "-c:v", 101 | "libx264", 102 | "-pix_fmt", 103 | "yuv420p", 104 | "-profile:v", 105 | "main", 106 | "-preset", 107 | "veryfast", 108 | "-x264opts", 109 | "nal-hrd=cbr:no-scenecut", 110 | "-minrate", 111 | "3000", 112 | "-maxrate", 113 | "3000", 114 | "-g", 115 | "60", 116 | "-c:a", 117 | "aac", 118 | "-b:a", 119 | "128k", 120 | "-ac", 121 | "2", 122 | "-ar", 123 | "44100", 124 | "-vf", 125 | "scale=1280:720,format=yuv420p", 126 | "-profile:v", 127 | "main", 128 | "-f", 129 | "flv", 130 | rtmpURL, 131 | "-reconnect", 132 | "3", 133 | "-reconnect_at_eof", 134 | "1", 135 | "-reconnect_streamed", 136 | "3", 137 | ]; 138 | } 139 | 140 | ws.on("close", (evt) => { 141 | ffmpeg.kill("SIGINT"); 142 | console.log(`Connection Closed: ${evt}`); 143 | }); 144 | 145 | const ffmpeg = callff.spawn("ffmpeg", ffArr); 146 | 147 | ffmpeg.on("close", (code, signal) => { 148 | console.log(`FFMPEG closed, reason ${code} , ${signal}`); 149 | ws.send("Closing Socket"); /// message to front end 150 | ws.terminate(); 151 | }); 152 | 153 | ffmpeg.stdin.on("error", (e) => { 154 | ws.send(e); 155 | console.log(`FFMPEG ERROR: ${e}`); 156 | ws.terminate(); 157 | }); 158 | 159 | ffmpeg.stderr.on("data", (data) => { 160 | console.log(`FFMPEG MSG: ${data.toString()}`); 161 | ws.send(data.toString()); 162 | }); 163 | }); 164 | -------------------------------------------------------------------------------- /backend/transwrap_local _peer.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | // transwrap.js http version 5 | 6 | /// import block 7 | const http = require('http'); 8 | //const fs = require('fs'); 9 | const WebSocket = require('ws'); 10 | const express = require('express'); 11 | const callff = require('child_process'); 12 | 13 | /// app socket definition 14 | const app = express(); 15 | const port = 3004 16 | const servertype = 'HTTP' 17 | const home = '/wwww' /// for heath check only, you can also use as standalone server, by moving the frontend folder to the home www 18 | 19 | // https certificates // Be sure to generate those before running 20 | /* 21 | const newCert = 'cert.pem'; 22 | const newKey = 'key.pem' 23 | const certicates = { 24 | key: fs.readFileSync(newKey), 25 | cert: fs.readFileSync(newCert) 26 | }; 27 | */ 28 | 29 | // wrapper server 30 | const transwraper = http.createServer(app).listen(port, () => {console.log(`Listening on port: ${port} ${servertype}`)}) 31 | 32 | // websocket creation 33 | const wsRef = new WebSocket.Server({ server: transwraper}); 34 | 35 | app.use((req, res, next) => { 36 | console.log(`${servertype}:${req.method}:${req.originalUrl}`); 37 | 38 | wsRef.on('connection', (ws, req) => { 39 | console.log("!!!!!! connection") 40 | }) 41 | 42 | wsRef.on('open', (ws, req) => { 43 | console.log("!!!!!! connection") 44 | }) 45 | 46 | 47 | return next(); 48 | 49 | 50 | 51 | }); 52 | 53 | app.use(express.static(__dirname + home)); 54 | 55 | // Streaming 56 | wsRef.on('connection', (ws, req) => { 57 | console.log("Loop 1") 58 | //ws.send('MSG: Connected to server'); /// Send this message to the app frontend 59 | console.log(`Got connection, URL: ${req.url}`) 60 | // em testes 61 | if (req.url === '/'){ 62 | //writeHead(200, {'Content-Type': 'text/html'}); 63 | console.log('é o /') 64 | 65 | ws.on('message', function(message) { 66 | // Broadcast any received message to all clients 67 | console.log('received: %s', message); 68 | ws.broadcast(message); 69 | }); 70 | 71 | 72 | ws.broadcast = function(data) { 73 | this.clients.forEach(function(client) { 74 | if(client.readyState === WebSocket.OPEN) { 75 | client.send(data); 76 | } 77 | }); 78 | }; 79 | 80 | /// em testes 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | } else { 89 | ws.on('message', (evt) => { 90 | console.log('Event', evt); 91 | ffmpeg.stdin.write(evt); 92 | }); 93 | 94 | ws.on('error', (err) => { 95 | console.log('ERROR on websocket', err); 96 | }); 97 | 98 | const rtmpURL = req.url.slice(7) 99 | console.log(`URL seted is ${rtmpURL}`) 100 | 101 | ws.on('close', (evt) => { 102 | ffmpeg.kill('SIGINT'); 103 | console.log(`Connection Closed: ${evt}`) 104 | }); 105 | 106 | const ffmpeg = callff.spawn( 107 | 'ffmpeg', 108 | ['-i', '-', '-vcodec', 'copy', '-preset', 'veryfast', '-tune', 'zerolatency', '-acodec', 'aac', '-reconnect', '3', '-reconnect_at_eof', '1', '-reconnect_streamed', '3', '-f', 'flv', rtmpURL 109 | ]); 110 | 111 | ffmpeg.on('close', (code, signal) => { 112 | console.log(`FFMPEG closed, reason ${code} , ${signal}`); 113 | ws.send('Closing Socket'); /// message to front end 114 | ws.terminate(); 115 | }); 116 | 117 | ffmpeg.stdin.on('error', (e) => { 118 | ws.send(e); 119 | console.log(`FFMPEG ERROR: ${e}`) 120 | ws.terminate(); 121 | }); 122 | 123 | ffmpeg.stderr.on('data', (data) => { 124 | console.log(`FFMPEG MSG: ${data.toString()}`) 125 | ws.send(data.toString()); 126 | }); 127 | } 128 | }); 129 | -------------------------------------------------------------------------------- /backend/transwrap_local.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | // transwrap.js http version 5 | 6 | /// import block 7 | const http = require("http"); 8 | //const fs = require('fs'); 9 | const WebSocket = require("ws"); 10 | const express = require("express"); 11 | const callff = require("child_process"); 12 | 13 | /// app socket definition 14 | const app = express(); 15 | const port = 3004; 16 | const servertype = "HTTP"; 17 | const home = "/wwww"; /// for heath check only, you can also use as standalone server, by moving the frontend folder to the home www 18 | 19 | // https certificates // Be sure to generate those before running 20 | /* 21 | const newCert = 'cert.pem'; 22 | const newKey = 'key.pem' 23 | const certicates = { 24 | key: fs.readFileSync(newKey), 25 | cert: fs.readFileSync(newCert) 26 | }; 27 | */ 28 | 29 | // wrapper server 30 | const transwraper = http.createServer(app).listen(port, () => { 31 | console.log(`Listening on port: ${port} ${servertype}`); 32 | }); 33 | 34 | // websocket creation 35 | const wsRef = new WebSocket.Server({ server: transwraper }); 36 | 37 | app.use((req, res, next) => { 38 | console.log(`${servertype}:${req.method}:${req.originalUrl}`); 39 | return next(); 40 | }); 41 | 42 | app.use(express.static(__dirname + home)); 43 | 44 | // Streaming 45 | wsRef.on("connection", (ws, req) => { 46 | console.log("Loop 1"); 47 | ws.send("MSG: Connected to server"); /// Send this message to the app frontend 48 | console.log(`Got connection, URL: ${req.url}`); 49 | ws.on("message", (evt) => { 50 | console.log("Event", evt); 51 | ffmpeg.stdin.write(evt); 52 | }); 53 | 54 | ws.on("error", (err) => { 55 | console.log("ERROR on websocket", err); 56 | }); 57 | 58 | const rtmpURL = req.url.slice(12); 59 | console.log(`URL seted is ${rtmpURL}`); 60 | 61 | const codec = req.url.split("/")[2]; 62 | console.log("CODEC", codec); 63 | 64 | if (codec === "h264") { 65 | console.log("No video transcoding"); 66 | var ffArr = [ 67 | "-i", 68 | "-", 69 | "-vcodec", 70 | "copy", 71 | "-preset", 72 | "veryfast", 73 | "-tune", 74 | "zerolatency", 75 | "-acodec", 76 | "aac", 77 | "-ar", 78 | "44100", 79 | "-b:a", 80 | "128k", 81 | "-f", 82 | "flv", 83 | rtmpURL, 84 | "-reconnect", 85 | "3", 86 | "-reconnect_at_eof", 87 | "1", 88 | "-reconnect_streamed", 89 | "3", 90 | ]; 91 | } else { 92 | console.log("Transcoding true"); 93 | //ffmpeg -re -stream_loop -1 -i $VIDEO_FILEPATH -r 30 -c:v libx264 -pix_fmt yuv420p -profile:v main -preset veryfast -x264opts "nal-hrd=cbr:no-scenecut" -minrate 3000 -maxrate 3000 -g 60 -c:a aac -b:a 160k -ac 2 -ar 44100 -f flv rtmps://$INGEST_ENDPOINT:443/app/$STREAM_KEY 94 | 95 | var ffArr = [ 96 | "-fflags", 97 | "+genpts", 98 | "-i", 99 | "-", 100 | "-r", 101 | "30", 102 | "-c:v", 103 | "libx264", 104 | "-pix_fmt", 105 | "yuv420p", 106 | "-profile:v", 107 | "main", 108 | "-preset", 109 | "veryfast", 110 | "-x264opts", 111 | "nal-hrd=cbr:no-scenecut", 112 | "-minrate", 113 | "3000", 114 | "-maxrate", 115 | "3000", 116 | "-g", 117 | "60", 118 | "-c:a", 119 | "aac", 120 | "-b:a", 121 | "128k", 122 | "-ac", 123 | "2", 124 | "-ar", 125 | "44100", 126 | "-vf", 127 | "scale=1280:720,format=yuv420p", 128 | "-profile:v", 129 | "main", 130 | "-f", 131 | "flv", 132 | rtmpURL, 133 | "-reconnect", 134 | "3", 135 | "-reconnect_at_eof", 136 | "1", 137 | "-reconnect_streamed", 138 | "3", 139 | ]; 140 | } 141 | 142 | ws.on("close", (evt) => { 143 | ffmpeg.kill("SIGINT"); 144 | console.log(`Connection Closed: ${evt}`); 145 | }); 146 | 147 | const ffmpeg = callff.spawn("ffmpeg", ffArr); 148 | 149 | ffmpeg.on("close", (code, signal) => { 150 | console.log(`FFMPEG closed, reason ${code} , ${signal}`); 151 | ws.send("Closing Socket"); /// message to front end 152 | ws.terminate(); 153 | }); 154 | 155 | ffmpeg.stdin.on("error", (e) => { 156 | ws.send(e); 157 | console.log(`FFMPEG ERROR: ${e}`); 158 | ws.terminate(); 159 | }); 160 | 161 | ffmpeg.stderr.on("data", (data) => { 162 | console.log(`FFMPEG MSG: ${data.toString()}`); 163 | ws.send(data.toString()); 164 | }); 165 | }); 166 | -------------------------------------------------------------------------------- /backend/uninstall_ivs_backend.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Define colors 4 | RED='\033[0;31m' 5 | GREEN='\033[0;32m' 6 | YELLOW='\033[0;93m' 7 | NC='\033[0m' 8 | 9 | # This is a very simples scrip that uses aws cli + jq + sed to clean your environment by removing all resources deploy by the install_ivs_backend.sh 10 | # If you wish to remove specific resources, go to the boton of this file and comment the function calls you wish to avoid. 11 | 12 | ################################################################################ 13 | # Help # 14 | ################################################################################ 15 | 16 | function help () 17 | { 18 | # Display Help 19 | echo -e "${GREEN}This script cleans your environment by removing all backend resources deployed by install_ivs_backend.sh" 20 | echo 21 | echo -e "Syntax: ./uninstall_ivs_backend.sh clean [ all | codebuild | codebuild_rp | s3 | ecr | codebuild | lambda | iam | fargate | sg | events | events | logs | files ]${NC}" 22 | echo -e "${YELLOW}options:" 23 | echo -e "all Cleans the whole environment by removing all resources installed by install_ivs_backend.sh" 24 | echo -e "codebuild Removes the codebuild project" 25 | echo -e "codebuild_rp Removes all roles and policies required by codebuild" 26 | echo -e "s3 Removes S3 bucket" 27 | echo -e "ecr Removes the ECR repository" 28 | echo -e "lambda Removes the lambda function" 29 | echo -e "iam Removes all roles and policies required by lambda,dynamodb and fargate" 30 | echo -e "fargate Removes ECS cluster, task definitions and services" 31 | echo -e "sg Removes the segurity groups required by fargate" 32 | echo -e "events Removes the eventbridge rules and triggers" 33 | echo -e "logs Removes the cloudwatch log group" 34 | echo -e "files Removes the tempoary files\n\n" 35 | 36 | echo -e "${GREEN}Example on how to use this script to deploy the whole backend at once" 37 | echo -e "./uninstall_ivs_backend.sh clean all${NC}\n\n" 38 | 39 | echo -e "${YELLOW}Case you prefer to deploy the backend step by step, deploy the each item following the order showing in this help" 40 | echo -e "Example:" 41 | echo -e "./uninstall_ivs_backend.sh clean codebuild" 42 | echo -e "./install_ivs_backend.sh deploy codebuild_rp" 43 | echo -e "./install_ivs_backend.sh deploy ecr" 44 | echo -e "...and so on...${NC}\n\n" 45 | 46 | 47 | } 48 | 49 | ################################################################################ 50 | # Codebuild Deprovisiong # 51 | ################################################################################ 52 | 53 | 54 | function codebuild_deprov () { 55 | 56 | # This function deletes the codebuild project 57 | 58 | echo -e "${GREEN}Deleting Codebuild ivs-webrtc project...${NC}" 59 | aws codebuild delete-project --name ivs-webrtc > /dev/null 2>&1 && echo -e "${GREEN}Codebuild ivs-webrtc project deleted.${NC}" \ 60 | || echo -e "${RED}It seems you have already deleted the codebuild ivs-webrtc project!!!${NC}" 61 | 62 | } 63 | 64 | ################################################################################ 65 | # Codebuild Roles and Policies Deprovisiong # 66 | ################################################################################ 67 | 68 | function codebuild_rp_deprov () { 69 | 70 | # This deletes the roles and policies required by codebuild 71 | 72 | # Array that stores all policies arn that should be detached 73 | codebuild_policies=($(cat ./temp_files/ivs_codebuild_log_arn.txt) \ 74 | $(cat ./temp_files/ivs_codebuild_s3_arn.txt) \ 75 | $(cat ./temp_files/ivs_codebuild_vpc_arn.txt) \ 76 | $(cat ./temp_files/ivs_codebuild_base_arn.txt) \ 77 | $(cat ./temp_files/ivs_codebuild_ecr_arn.txt)) 78 | 79 | # Array that stores all roles that should be deleted 80 | codebuild_roles_to_delete=('ivs-webrtc-codebuild-role') 81 | 82 | # Calls detach_policy passing the policies array as argument 83 | detach_policy ${codebuild_policies[@]} ivs-webrtc-codebuild-role 84 | 85 | # Calls delete_policy passing the policies array as argument 86 | delete_policy ${codebuild_policies[@]} 87 | 88 | # Calls delete_role passing the policies array as argument 89 | delete_role ${codebuild_roles_to_delete[@]} 90 | 91 | unset iam_role 92 | 93 | } 94 | 95 | ################################################################################ 96 | # S3 Bucket Deprovisiong # 97 | ################################################################################ 98 | 99 | function s3_deprov () { 100 | 101 | # This function removes the S3 bucket required by codebuild 102 | 103 | # Captures the bucket name 104 | s3_bucket=$(cat ./temp_files/s3_bucket.txt) 105 | 106 | # Remove the bucket and all of its objects 107 | echo -e "${GREEN}Removing S3 ${s3_bucket} bucket and objects...${NC}" 108 | aws s3 rb s3://${s3_bucket} --force > /dev/null 2>&1 && echo -e "${GREEN}S3 Bucket ${s3_bucket} deleted${NC}" \ 109 | || echo -e "${RED}It seems that ${s3_bucket} bucket was already deleted!!!${NC}" 110 | } 111 | 112 | ################################################################################ 113 | # ECR Deprovisiong # 114 | ################################################################################ 115 | 116 | function ecr_deprov () { 117 | 118 | # This function deletes the ECR repository required by codebuild 119 | 120 | echo -e "${GREEN}Removing ECR ivs-webrtc repository...${NC}" 121 | aws ecr delete-repository --repository-name ivs-webrtc --force > /dev/null 2>&1 && echo -e "${GREEN}ECR ivs-webrtc repository deleted${NC}" \ 122 | || echo -e "${RED}It seems that ivs-webrtc repository was already deleted!!!${NC}" 123 | 124 | } 125 | 126 | 127 | ################################################################################ 128 | # EventBridge Deprovisiong # 129 | ################################################################################ 130 | 131 | function eventbridge_deprov () { 132 | 133 | # This function will remove the eventbridge rule named ip-register 134 | 135 | echo -e "${GREEN}Deleting the EventBridge rules and targets...${NC}" 136 | 137 | # Check if the resource exist 138 | if [ $(aws events list-rules --name-prefix ip-register | jq '.Rules[0].Name') != "null" ] 139 | then 140 | if [ $(aws events list-targets-by-rule --rule ip-register | jq '.Targets[0].Id') != "null" ] 141 | then 142 | echo -e "${GREEN}Deleting the EventBridge Rule: ip-register...${NC}" 143 | aws events remove-targets --rule ip-register --ids "1" 1> /dev/null 144 | 145 | if $(aws events delete-rule --name ip-register > /dev/null 2>&1); 146 | then 147 | echo -e "${GREEN}EventBridge Rule Deleted!${NC}" 148 | else 149 | error_exit "The EventBrige Rule cannot be automatically deleted because it has dependencies. You may want to try remove it manually." 150 | fi 151 | 152 | else 153 | 154 | echo -e "${RED}Hmm...It seems you have already deleted the target...${NC}" 155 | echo -e "${RED}I will try to delete the event-rule then...${NC}" 156 | aws events delete-rule --name ip-register 157 | 158 | fi 159 | 160 | else 161 | 162 | echo -e "${RED}It seems you have already deleted the ip-register rule!!!${NC}" 163 | fi 164 | } 165 | 166 | ################################################################################ 167 | # Fargate Deprovisioning # 168 | ################################################################################ 169 | 170 | function fargate_deprov () { 171 | 172 | # Retrive the task definition version 173 | ivs_task_definition=($(aws ecs list-task-definitions | sed -n -e '/.*ivs/p' | sed -e 's/ //g;s/"//g;s/,//g' | sed 's/.*\(ivs.*\).*/\1/')) 174 | 175 | # Test if exists 176 | service_status=$(aws ecs describe-services --cluster ivs --services ivs-webrtc | jq '.services[].status' | sed 's/"//g') 177 | ivs_cluster=$(aws ecs describe-clusters --cluster ivs | jq '.clusters[].status' | sed 's/"//g') 178 | 179 | if [ $(aws ecs describe-services --cluster ivs --services ivs-webrtc | jq '.services[0].desiredCount') != 0 ] && [ $(aws ecs describe-services --cluster ivs --services ivs-webrtc | jq '.services[0].desiredCount') != 'null' ] 180 | then 181 | echo -e "${GREEN}Eliminating tasks from ivs-webrtc service...${NC}" 182 | aws ecs update-service --cluster ivs --service ivs-webrtc --desired-count 0 >> /dev/null 183 | 184 | tasks=$(aws ecs list-tasks --cluster ivs | jq '.taskArns' | sed -n -e 's/^.*\///p' | sed 's/"//' | sed -e 'N;s/\n//;s/,/ /') 185 | 186 | print_progress 187 | 188 | # Waits the task stop 189 | aws ecs wait tasks-stopped --cluster ivs --tasks $tasks 190 | 191 | # Deletes de ivs-webrtc service 192 | ecs_service_delete 193 | 194 | if [ ${ivs_task_definition[0]} ] 195 | then 196 | # De-register task definition ivs-webrtc 197 | deregister_task_definition 198 | fi 199 | 200 | # Deletes the ivs cluster 201 | ecs_cluster_delete 202 | 203 | 204 | else 205 | # Deletes de ivs-webrtc service 206 | ecs_service_delete 207 | 208 | if [ ${ivs_task_definition[0]} ] 209 | then 210 | # De-register task definition ivs-webrtc 211 | deregister_task_definition 212 | fi 213 | 214 | # Deletes the ivs cluster 215 | ecs_cluster_delete 216 | 217 | fi 218 | 219 | } 220 | 221 | ################################################################################ 222 | # Security Group Deprovisioning # 223 | ################################################################################ 224 | 225 | function security_group_deprov () { 226 | 227 | # This function deletes the security group ivs-sg 228 | 229 | # # Test if the security group exists and save the result on security_group variable 230 | aws ec2 describe-security-groups --group-name ivs-sg > /dev/null 2>&1 && security_group='OK' || security_group='NOK' 231 | 232 | if [ $security_group = 'OK' ] 233 | then 234 | 235 | echo -e "${GREEN}Deleting security group ivs-sg...${NC}" 236 | print_progress 237 | aws ec2 delete-security-group --group-name ivs-sg 238 | 239 | else 240 | 241 | echo -e "${RED}It seems you have already deleted the security group ivs-sg!!!${NC}" 242 | 243 | fi 244 | } 245 | 246 | ################################################################################ 247 | # Lambda Deprovisioning # 248 | ################################################################################ 249 | 250 | function lambda_deprov () { 251 | 252 | # This function deletes the Lambda function ivs-ip-register 253 | 254 | # Removes the lambda function ivs-ip-register 255 | echo -e "${GREEN}Deleting Lambda function ivs-ip-register...${NC}" 256 | aws lambda delete-function --function-name ivs-ip-register > /dev/null 2>&1 && echo -e "${GREEN}Lambda function ivs-ip-register deleted${NC}" \ 257 | || echo -e "${RED}It seems you have already deleted the Lambda function ivs-ip-register!!!${NC}" 258 | 259 | } 260 | 261 | ################################################################################ 262 | # IAM Deprovisioning # 263 | ################################################################################ 264 | 265 | function detach_policy () { 266 | 267 | # This function detaches policies from iam roles 268 | 269 | echo -e "${GREEN}Detaching policies...${NC}" 270 | 271 | # Iterate over the arguments and detaches policy by policy 272 | for i in ${@} 273 | do 274 | 275 | # Go to next register case file does not exist 276 | if [ $i = 'ivs-webrtc-codebuild-role' ] || [ $i = 'ivs-lambda-role' ] || [ $i = 'ivs-ecs-execution-role' ] 277 | then 278 | 279 | continue 280 | 281 | fi 282 | 283 | p_name=$(echo -e ${i} | sed 's:.*/::') 284 | 285 | # Detaches policies 286 | aws iam detach-role-policy --role-name ${@: -1} --policy-arn ${i} \ 287 | > /dev/null 2>&1 && echo -e "${GREEN}Policy ${p_name} detached${NC}" \ 288 | || echo -e "${RED}It seems that ${p_name} was already detached!!!${NC}" 289 | 290 | done 291 | } 292 | 293 | function delete_policy () { 294 | 295 | # This function deletes policies 296 | 297 | echo -e "${GREEN}Deleting policies...${NC}" 298 | 299 | # Iterate over the arguments and detaches policy by policy 300 | for i in ${@} 301 | do 302 | 303 | # Go to next register case file does not exist 304 | if [ $i = 'ivs-webrtc-codebuild-role' ] || [ $i = 'ivs-lambda-role' ] || [ $i = 'ivs-ecs-execution-role' ] 305 | then 306 | 307 | continue 308 | 309 | fi 310 | 311 | # Captures the name of the policies 312 | r_name=$(echo -e ${i} | sed 's:.*/::') 313 | 314 | # Deletes policies 315 | aws iam delete-policy --policy-arn ${i} > /dev/null 2>&1 \ 316 | && echo -e "${GREEN}Policy ${r_name} deleted${NC}" \ 317 | || echo -e "${RED}It seems that ${r_name} was already deleted!!!${NC}" 318 | 319 | done 320 | 321 | } 322 | 323 | function delete_role () { 324 | 325 | # This function deletes roles 326 | 327 | echo -e "${GREEN}Deleting roles...${NC}" 328 | 329 | # Iterate over the arguments and deletes role by role 330 | for i in ${@} 331 | do 332 | 333 | # Go to next register case file does not exist 334 | 335 | aws iam delete-role --role-name ${i} > /dev/null 2>&1 \ 336 | && echo -e "${GREEN}Role ${i} deleted${NC}" \ 337 | || echo -e "${RED}It seems that ${i} role was already deleted!!!${NC}" 338 | 339 | done 340 | 341 | } 342 | 343 | function iam_deprov () { 344 | 345 | # This function will delete all IAM roles and policies created to ivs-webrtc project 346 | 347 | # Array that stores all policies arn that should be detached 348 | policies_lambda=($(cat ./temp_files/lambda_policy_arn.txt) "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" \ 349 | "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess") 350 | 351 | policies_ecs=("arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" "arn:aws:iam::aws:policy/AWSOpsWorksCloudWatchLogs") 352 | 353 | # Array that stores all policies arn that should be deleted 354 | policies_to_delete=($(cat ./temp_files/lambda_policy_arn.txt)) 355 | 356 | # Array that stores all roles 357 | roles_to_delete=('ivs-ecs-execution-role' 'ivs-lambda-role') 358 | 359 | # Calls detach_policy passing the policies array as argument 360 | detach_policy ${policies_lambda[@]} ivs-lambda-role 361 | 362 | # Calls detach_policy passing the policies array as argument 363 | detach_policy ${policies_ecs[@]} ivs-ecs-execution-role 364 | 365 | # Calls delete_policy passing the policies array as argument 366 | delete_policy ${policies_to_delete[@]} 367 | 368 | # Calls delete_role passing the policies array as argument 369 | delete_role ${roles_to_delete[@]} 370 | 371 | unset iam_role 372 | 373 | } 374 | 375 | ################################################################################ 376 | # CloudWatch Log Group Deprovisioning # 377 | ################################################################################ 378 | 379 | function cloudwatchlogs_deprov () { 380 | 381 | # This function will delete the cloudwatch log group /ecs/ivs-webrtc 382 | 383 | # Removes /ecs/ivs-webrtc log group 384 | echo -e "${GREEN}Deleting cloudwatch log groups...${NC}" 385 | aws logs delete-log-group --log-group-name /ecs/ivs-webrtc > /dev/null 2>&1 \ 386 | && echo -e "${GREEN}Cloudwatch log group /ecs/ivs-webrtc deleted.${NC}" \ 387 | || echo -e "${RED}It seems you have already deleted the /ecs/ivs-webrtc log group!!!${NC}" 388 | 389 | # Removes /aws/codebuild/ivs-webrtc log group 390 | aws logs delete-log-group --log-group-name /aws/codebuild/ivs-webrtc > /dev/null 2>&1 \ 391 | && echo -e "${GREEN}Cloudwatch log group /aws/codebuild/ivs-webrtc deleted.${NC}" \ 392 | || echo -e "${RED}It seems you have already deleted the /aws/codebuild/ivs-webrtc log group!!!${NC}" 393 | 394 | # Removes /aws/lambda/ivs-ip-register log group 395 | aws logs delete-log-group --log-group-name /aws/lambda/ivs-ip-register > /dev/null 2>&1 \ 396 | && echo -e "${GREEN}Cloudwatch log group /aws/lambda/ivs-ip-register deleted.${NC}" \ 397 | || echo -e "${RED}It seems you have already deleted the /aws/lambda/ivs-ip-register log group!!!${NC}" 398 | 399 | } 400 | 401 | ################################################################################ 402 | # Clean Temporary Files # 403 | ################################################################################ 404 | 405 | function delete_temp_files () { 406 | 407 | # This function will delete all temporary files that was created by ivs-webrtc during the deployment 408 | 409 | files=('./json_configs/ivs_events_rule.json' './json_configs/ivs_ecs_service.json' './temp_files/my_subnets.txt' './json_configs/ivs_task_definition.json' \ 410 | './temp_files/ecs_cluster_arn.txt' './temp_files/ivs_sg.txt' './json_configs/lambda.json' './temp_files/ivs_ecs_execution_role_arn.txt' \ 411 | './temp_files/lambda_policy_arn.txt' './temp_files/ivs_lambda_function_arn.txt' './temp_files/ivs_event_rule_arn.txt' './temp_files/ecr_repository.txt' \ 412 | './temp_files/s3_bucket.txt' 'lambda.zip' './temp_files/ecr_repository.txt' './temp_files/s3_bucket.txt' './json_configs/ivs_codebuild_base_policy.json' \ 413 | './json_configs/ivs_codebuild_s3_policy.json' './json_configs/ivs_codebuild_log_policy.json' './json_configs/ivs_codebuild.json' './temp_files/codebuild_subnets.txt' \ 414 | './json_configs/ivs_codebuild_vpc_policy.json' './temp_files/ivs_webrtc_codebuild_role_arn.txt' './temp_files/ivs_codebuild_ecr_arn.txt' './temp_files/ivs_codebuild_base_arn.txt' \ 415 | './temp_files/ivs_codebuild_vpc_arn.txt' './temp_files/ivs_codebuild_s3_arn.txt' './temp_files/ivs_codebuild_log_arn.txt' './temp_files/ivs_codebuild_build.json' \ 416 | './temp_files/ecr_uri.txt' './temp_files/accountid.txt' './docker_files/docker.zip' ./temp_files/ivs_codebuild_arn.txt) 417 | 418 | delete_file ${files[@]} 419 | 420 | } 421 | 422 | ################################################################################ 423 | # Deletes Fargate Service # 424 | ################################################################################ 425 | 426 | function ecs_service_delete () { 427 | 428 | # This function deletes the ecs ivs-webrtc service 429 | 430 | if [ $ivs_cluster = 'ACTIVE' ] && [ $service_status = 'ACTIVE' ] 431 | then 432 | 433 | echo -e "${GREEN}Deleting service ivs-webrtc...${NC}" 434 | aws ecs delete-service --cluster ivs --service ivs-webrtc >> /dev/null 435 | else 436 | 437 | echo -e "${RED}It seems you have already deleted the ivs-webrtc service!!!${NC}" 438 | 439 | fi 440 | 441 | } 442 | 443 | ################################################################################ 444 | # Delete Files # 445 | ################################################################################ 446 | 447 | 448 | function delete_file () { 449 | 450 | # This function deletes the temporary files 451 | 452 | # Runs delete to each file present in the array 453 | for i in ${@} 454 | do 455 | 456 | echo -e "${GREEN}Deleting ${i}...${NC}" 457 | rm ${i} > /dev/null 2>&1 && echo -e "${GREEN}File ${i} deleted.${NC}" \ 458 | || echo -e "${RED}File ${i} does not exist${NC}" 459 | done 460 | 461 | } 462 | 463 | 464 | ################################################################################ 465 | # Delete ECS Cluster # 466 | ################################################################################ 467 | 468 | function ecs_cluster_delete () { 469 | 470 | # This function deletes the ecs ivs-webrtc service 471 | if [ $ivs_cluster = 'ACTIVE' ] 472 | then 473 | 474 | echo -e "${GREEN}Deleting cluster ivs...${NC}" 475 | aws ecs delete-cluster --cluster ivs >> /dev/null 476 | else 477 | 478 | echo -e "${RED}It seems you have already deleted the ivs cluster!!!${NC}" 479 | 480 | fi 481 | 482 | } 483 | 484 | ################################################################################ 485 | # Deletes ECS Task Definition # 486 | ################################################################################ 487 | 488 | function deregister_task_definition () { 489 | 490 | 491 | echo -e "${GREEN}Deregistering task definition ivs-webrtc...${NC}" 492 | for t in ${ivs_task_definition[@]}; 493 | do 494 | aws ecs deregister-task-definition --task-definition ${t} >> /dev/null 495 | done 496 | } 497 | 498 | ################################################################################ 499 | # Error handling # 500 | ################################################################################ 501 | 502 | function error_exit () { 503 | 504 | # This function is used for error handling 505 | 506 | echo -e "${RED}$1${NC}" 1>&2 507 | exit 1 508 | } 509 | 510 | ################################################################################ 511 | # Progress bar # 512 | ################################################################################ 513 | 514 | function print_progress () { 515 | 516 | # Print # on screen 517 | 518 | for i in {0..7} 519 | do 520 | sleep 2 521 | echo -n "#" 522 | 523 | done 524 | 525 | } 526 | 527 | ################################################################################ 528 | # Cleaning Options # 529 | ################################################################################ 530 | 531 | function clean () { 532 | 533 | # This function will receive a argument from the command line to start the cleaning process 534 | # It will be possible to uninstall everything or just pontual services 535 | 536 | option=$1 537 | 538 | case $option in 539 | all) 540 | echo -e "${GREEN}###Preparing to clean aws resources deployed by ivs-webrtc...###${NC}" 541 | codebuild_deprov || { error_exit 'Failed while removing codebuild project!'; } 542 | codebuild_rp_deprov || { error_exit 'Failed while removing codebuild roles and policies!'; } 543 | s3_deprov || { error_exit 'Failed while removing s3 bucket!'; } 544 | ecr_deprov || { error_exit 'Failed while removing ecr repository!'; } 545 | eventbridge_deprov || { error_exit 'Failed while removing eventbridge rules and triggers!'; } 546 | fargate_deprov || { error_exit 'Failed while removing Fargate ECS resources!'; } 547 | security_group_deprov || { error_exit 'Failed while removing security groups'; } 548 | #dynamodb_deprov || { error_exit 'dynamodb table deployment failed!'; } 549 | iam_deprov || { error_exit 'Failed while removing iam roles and policies'; } 550 | lambda_deprov || { error_exit 'Failed while removing lambda resources'; } 551 | cloudwatchlogs_deprov || { error_exit 'Failed while removing cloudwatch log group!'; } 552 | #delete_temp_files || { error_exit 'Failed while removing temporary files!'; } 553 | echo -e "${YELLOW}If you dont see any errors, run the command below to delete the temporary files${NC}" 554 | echo -e "${GREEN}./uninstall_ivs_backend.sh clean files${NC}" 555 | ;; 556 | 557 | codebuild) 558 | echo -e "${GREEN}###Cleaning codebuild ivs-webrtc project###${NC}" 559 | codebuild_deprov 560 | ;; 561 | 562 | codebuild_rp) 563 | echo -e "${GREEN}###Cleaning codebuild roles and policies###${NC}" 564 | codebuild_rp_deprov 565 | ;; 566 | s3) 567 | echo -e "${GREEN}###Cleaning S3 resources###${NC}" 568 | s3_deprov 569 | ;; 570 | ecr) 571 | echo -e "${GREEN}###Cleaning ECR resources###${NC}" 572 | ecr_deprov 573 | ;; 574 | events) 575 | echo -e "${GREEN}###Cleaning eventbridge ivs-webrtc resources###${NC}" 576 | eventbridge_deprov 577 | ;; 578 | 579 | fargate) 580 | echo -e "${GREEN}###Cleaning fargate ivs-webrtc resources###${NC}" 581 | fargate_deprov 582 | ;; 583 | 584 | sg) 585 | echo -e "${GREEN}###Cleaning security group ivs-webrtc resources###${NC}" 586 | security_group_deprov 587 | ;; 588 | 589 | dynamodb) 590 | echo -e "${GREEN}###Cleaning dybamodb ivs-webrtc resources###${NC}" 591 | dynamodb_deprov 592 | ;; 593 | 594 | lambda) 595 | echo -e "${GREEN}###Cleaning lambda ivs-webrtc resources###${NC}" 596 | lambda_deprov 597 | ;; 598 | 599 | iam) 600 | echo -e "${GREEN}###Cleaning iam ivs-webrtc resources###${NC}" 601 | iam_deprov 602 | ;; 603 | 604 | logs) 605 | echo -e "${GREEN}###Cleaning cloudwatchlogs ivs-webrtc resources###${NC}" 606 | cloudwatchlogs_deprov 607 | ;; 608 | 609 | files) 610 | echo -e "${GREEN}###Cleaning ivs-webrtc temporary files###${NC}" 611 | delete_temp_files 612 | ;; 613 | 614 | *) 615 | echo -e "${RED}This option is not valid!${NC}" 616 | ;; 617 | esac 618 | 619 | } 620 | 621 | # Enables script arguments 622 | "$@" 623 | 624 | 625 | # Prints help in case of calling the script without arguments 626 | if [ -z $1 ] 627 | then 628 | 629 | help 630 | fi -------------------------------------------------------------------------------- /doc/IVSCopy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-simple-streaming-webapp/c2d271f79527f20b75628247947bac7584aa3d40/doc/IVSCopy.png -------------------------------------------------------------------------------- /doc/IVSCreateChannel_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-simple-streaming-webapp/c2d271f79527f20b75628247947bac7584aa3d40/doc/IVSCreateChannel_1.png -------------------------------------------------------------------------------- /doc/IVSCreateChannel_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-simple-streaming-webapp/c2d271f79527f20b75628247947bac7584aa3d40/doc/IVSCreateChannel_2.png -------------------------------------------------------------------------------- /doc/app01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-simple-streaming-webapp/c2d271f79527f20b75628247947bac7584aa3d40/doc/app01.png -------------------------------------------------------------------------------- /doc/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-simple-streaming-webapp/c2d271f79527f20b75628247947bac7584aa3d40/doc/arch.png -------------------------------------------------------------------------------- /doc/auth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-simple-streaming-webapp/c2d271f79527f20b75628247947bac7584aa3d40/doc/auth.png -------------------------------------------------------------------------------- /doc/auth01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-simple-streaming-webapp/c2d271f79527f20b75628247947bac7584aa3d40/doc/auth01.png -------------------------------------------------------------------------------- /doc/codearr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-simple-streaming-webapp/c2d271f79527f20b75628247947bac7584aa3d40/doc/codearr.png -------------------------------------------------------------------------------- /doc/coderemote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-simple-streaming-webapp/c2d271f79527f20b75628247947bac7584aa3d40/doc/coderemote.png -------------------------------------------------------------------------------- /doc/codevideo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-simple-streaming-webapp/c2d271f79527f20b75628247947bac7584aa3d40/doc/codevideo.png -------------------------------------------------------------------------------- /doc/config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-simple-streaming-webapp/c2d271f79527f20b75628247947bac7584aa3d40/doc/config.png -------------------------------------------------------------------------------- /doc/front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-simple-streaming-webapp/c2d271f79527f20b75628247947bac7584aa3d40/doc/front.png -------------------------------------------------------------------------------- /doc/golive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-simple-streaming-webapp/c2d271f79527f20b75628247947bac7584aa3d40/doc/golive.png -------------------------------------------------------------------------------- /doc/saveivs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-simple-streaming-webapp/c2d271f79527f20b75628247947bac7584aa3d40/doc/saveivs.png -------------------------------------------------------------------------------- /doc/sslerror.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-simple-streaming-webapp/c2d271f79527f20b75628247947bac7584aa3d40/doc/sslerror.png -------------------------------------------------------------------------------- /doc/videoworkflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-simple-streaming-webapp/c2d271f79527f20b75628247947bac7584aa3d40/doc/videoworkflow.png -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | 3 | #amplify-do-not-edit-begin 4 | amplify/\#current-cloud-backend 5 | amplify/.config/local-* 6 | amplify/logs 7 | amplify/mock-data 8 | amplify/backend/amplify-meta.json 9 | amplify/backend/.temp 10 | build/ 11 | dist/ 12 | node_modules/ 13 | aws-exports.js 14 | awsconfiguration.json 15 | amplifyconfiguration.json 16 | amplifyconfiguration.dart 17 | amplify-build-config.json 18 | amplify-gradle-config.json 19 | amplifytools.xcconfig 20 | .secret-* 21 | **.sample 22 | #amplify-do-not-edit-end 23 | -------------------------------------------------------------------------------- /frontend/BROWSER.md: -------------------------------------------------------------------------------- 1 | # Simplifying live streaming contribution - Customize the APP 2 | 3 | ## Use the video MediaDevices.getUserMedia() instead of the Canvas element 4 | 5 | Due to browser compatibility, the video track has been wrapped into a canvas element, but it can impact on the video quality, if Chrome is the preferable browser, you can capture the stream using the video html component instead. 6 | 7 | You should change the file frontend/src/components/HomePage.js 8 | The affected function is handleCameraReady() 9 | 10 | Currently its using the element canvasVideo, change to use the video, instead as per bellow. 11 | 12 | codeblock 13 | 14 | You also need to change the function function startStream, to avoid audio duplication. 15 | Remove the audioStreaming from the Array in the forEach function. 16 | 17 | codeblock 18 | 19 | ## Change local app to stream to remote container 20 | 21 | The app is currently configure to take in consideration the protocol used, and if it's http, then set the local host as the endpoint. You can replace the testServer by the server, to stream to the remote server from your local machine. 22 | 23 | codeblock 24 | 25 | ## Additional information: 26 | 27 | * [HTMLCanvasElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement) 28 | * [HTMLMediaElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement) 29 | 30 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Simplifying live streaming contribution - run locally. 2 | 3 | This sample demo app, captures the video and use a proxy in node.js to transwrap the stream to Amazon IVS 4 | 5 | ## Deployment Steps: 6 | 7 | For building the integration with AWS components and host our web application, we will be using AWS Amplify. For more complete steps on installing and configuring AWS Amplify, please visit the [Amplify Documentation for React](https://docs.amplify.aws/start/getting-started/installation/q/integration/react#option-2-follow-the-instructions). 8 | 9 | ## Pre-requirements 10 | 11 | ```sh 12 | npm install -g @aws-amplify/cli 13 | amplify configure 14 | ``` 15 | 16 | For a more complete guide on how to get started with amplify CLI, please follow the instructions of the official [AWS Amplify Documentation](https://docs.amplify.aws/cli/start/install/#install-the-amplify-cli). 17 | 18 | ### Frontend and APIs: WebRTC video capture: 19 | version 2 preview 20 | 21 | ```sh 22 | git clone --branch v2preview https://github.com/aws-samples/aws-simple-streaming-webapp.git 23 | cd aws-simple-streaming-webpp/frontend/ 24 | npm install 25 | amplify init 26 | ``` 27 | 28 | As name for the environment, please select *dev* 29 | 30 | ```sh 31 | ### Configuration details 32 | ? Enter a name for the environment dev 33 | ? Choose your default editor: Visual Studio Code 34 | Using default provider awscloudformation 35 | ? Select the authentication method you want to use: AWS profile 36 | ``` 37 | 38 | With amplify init, the project and resources will be initialized in the AWS Cloud environment. 39 | Now you can push, with amplify push to create the resources. 40 | 41 | *Note:* This Command will deploy the following resources in your account: 42 | * API Gateway: Save and retrieve IVS Parameters and ECS Container availability information 43 | * DynamoDB: Store IVS and Container servers parameters 44 | * Lambda Functions: For checking stored parameters and check Event Bridge information 45 | To list the resources that will be created you can use the command ```amplify status``` 46 | 47 | ```sh 48 | amplify push 49 | ``` 50 | 51 | ### Run the solution in your local environment 52 | 53 | ```sh 54 | npm start 55 | ``` 56 | 57 | ### Create your user and password 58 | 59 | It should load the authentication page. Now you can create your first account and sign in. 60 | Application Auth 61 | 62 | ### Add the ingestEndpoint, streamKey value and playbackUrl to the interface 63 | **[OPTIONAL] If you don't have a Amazon IVS created, you can follow [this steps](link).** 64 | 4.2. In your local environment http://127.0.0.1:3000 (http://127.0.0.1:3000/) the following application will be loaded. 65 | 66 | Application Save 67 | 68 | ### Start the server in a local environment: localhost 69 | 70 | ```sh 71 | cd backend/ 72 | npm install 73 | npm run startDev 74 | ``` 75 | **Note:** if you are running locally in HTTPS, the socket needs to be HTTPS as well, [please follow this instruction instead.](../backend/README_HTTPS.md) 76 | 77 | ### Test your streaming directly from your browser 78 | 79 | Application 80 | 81 | ### [Optional]: Cleanup, removing the provisioned AWS resources. 82 | 83 | ```sh 84 | cd ../frontend 85 | amplify delete 86 | ``` 87 | 88 | ## Choose your next adventure: 89 | * [**Backend: Transwraping Amazon ECS container:**](/backend/README.md)Install the remote video transwrap server. 90 | * [**Create a IVS Channel using a shell script**](/backend/CREATEIVS.md) 91 | * [**Customize the web app and compatibility discussions**](BROWSER.md) 92 | -------------------------------------------------------------------------------- /frontend/amplify/.config/project-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "simplewebstreaming", 3 | "version": "3.1", 4 | "frontend": "javascript", 5 | "javascript": { 6 | "framework": "react", 7 | "config": { 8 | "SourceDir": "src", 9 | "DistributionDir": "build", 10 | "BuildCommand": "npm run-script build", 11 | "StartCommand": "npm run-script start" 12 | } 13 | }, 14 | "providers": [ 15 | "awscloudformation" 16 | ] 17 | } -------------------------------------------------------------------------------- /frontend/amplify/backend/api/APIGatewayAuthStack.json: -------------------------------------------------------------------------------- 1 | { 2 | "Description": "API Gateway policy stack created using Amplify CLI", 3 | "AWSTemplateFormatVersion": "2010-09-09", 4 | "Parameters": { 5 | "authRoleName": { 6 | "Type": "String" 7 | }, 8 | "unauthRoleName": { 9 | "Type": "String" 10 | }, 11 | "env": { 12 | "Type": "String" 13 | }, 14 | "saveIVSparam": { 15 | "Type": "String" 16 | } 17 | }, 18 | "Conditions": { 19 | "ShouldNotCreateEnvResources": { 20 | "Fn::Equals": [ 21 | { 22 | "Ref": "env" 23 | }, 24 | "NONE" 25 | ] 26 | } 27 | }, 28 | "Resources": { 29 | "PolicyAPIGWAuth1": { 30 | "Type": "AWS::IAM::ManagedPolicy", 31 | "Properties": { 32 | "PolicyDocument": { 33 | "Version": "2012-10-17", 34 | "Statement": [ 35 | { 36 | "Effect": "Allow", 37 | "Action": [ 38 | "execute-api:Invoke" 39 | ], 40 | "Resource": [ 41 | { 42 | "Fn::Join": [ 43 | "", 44 | [ 45 | "arn:aws:execute-api:", 46 | { 47 | "Ref": "AWS::Region" 48 | }, 49 | ":", 50 | { 51 | "Ref": "AWS::AccountId" 52 | }, 53 | ":", 54 | { 55 | "Ref": "saveIVSparam" 56 | }, 57 | "/", 58 | { 59 | "Fn::If": [ 60 | "ShouldNotCreateEnvResources", 61 | "Prod", 62 | { 63 | "Ref": "env" 64 | } 65 | ] 66 | }, 67 | "/*/putitens/*" 68 | ] 69 | ] 70 | }, 71 | { 72 | "Fn::Join": [ 73 | "", 74 | [ 75 | "arn:aws:execute-api:", 76 | { 77 | "Ref": "AWS::Region" 78 | }, 79 | ":", 80 | { 81 | "Ref": "AWS::AccountId" 82 | }, 83 | ":", 84 | { 85 | "Ref": "saveIVSparam" 86 | }, 87 | "/", 88 | { 89 | "Fn::If": [ 90 | "ShouldNotCreateEnvResources", 91 | "Prod", 92 | { 93 | "Ref": "env" 94 | } 95 | ] 96 | }, 97 | "/*/putitens" 98 | ] 99 | ] 100 | }, 101 | { 102 | "Fn::Join": [ 103 | "", 104 | [ 105 | "arn:aws:execute-api:", 106 | { 107 | "Ref": "AWS::Region" 108 | }, 109 | ":", 110 | { 111 | "Ref": "AWS::AccountId" 112 | }, 113 | ":", 114 | { 115 | "Ref": "saveIVSparam" 116 | }, 117 | "/", 118 | { 119 | "Fn::If": [ 120 | "ShouldNotCreateEnvResources", 121 | "Prod", 122 | { 123 | "Ref": "env" 124 | } 125 | ] 126 | }, 127 | "/*/getServers/*" 128 | ] 129 | ] 130 | }, 131 | { 132 | "Fn::Join": [ 133 | "", 134 | [ 135 | "arn:aws:execute-api:", 136 | { 137 | "Ref": "AWS::Region" 138 | }, 139 | ":", 140 | { 141 | "Ref": "AWS::AccountId" 142 | }, 143 | ":", 144 | { 145 | "Ref": "saveIVSparam" 146 | }, 147 | "/", 148 | { 149 | "Fn::If": [ 150 | "ShouldNotCreateEnvResources", 151 | "Prod", 152 | { 153 | "Ref": "env" 154 | } 155 | ] 156 | }, 157 | "/*/getServers" 158 | ] 159 | ] 160 | } 161 | ] 162 | } 163 | ] 164 | }, 165 | "Roles": [ 166 | { 167 | "Ref": "authRoleName" 168 | } 169 | ] 170 | } 171 | } 172 | } 173 | } -------------------------------------------------------------------------------- /frontend/amplify/backend/api/saveIVSparam/cli-inputs.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "paths": { 4 | "/putitens": { 5 | "name": "/putitens", 6 | "lambdaFunction": "saveIVSparam", 7 | "permissions": { 8 | "setting": "private", 9 | "auth": [ 10 | "create", 11 | "read", 12 | "update", 13 | "delete" 14 | ] 15 | } 16 | }, 17 | "/getServers": { 18 | "name": "/getServers", 19 | "lambdaFunction": "ISSgetServers", 20 | "permissions": { 21 | "setting": "private", 22 | "auth": [ 23 | "create", 24 | "read", 25 | "update", 26 | "delete" 27 | ] 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /frontend/amplify/backend/auth/simplewebstreaming178ae288/cli-inputs.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1", 3 | "cognitoConfig": { 4 | "identityPoolName": "simplewebstreaming178ae288_identitypool_178ae288", 5 | "allowUnauthenticatedIdentities": false, 6 | "resourceNameTruncated": "simple178ae288", 7 | "userPoolName": "simplewebstreaming178ae288_userpool_178ae288", 8 | "autoVerifiedAttributes": [ 9 | "email" 10 | ], 11 | "mfaConfiguration": "OFF", 12 | "mfaTypes": [ 13 | "SMS Text Message" 14 | ], 15 | "smsAuthenticationMessage": "Your authentication code is {####}", 16 | "smsVerificationMessage": "Your verification code is {####}", 17 | "emailVerificationSubject": "Your verification code", 18 | "emailVerificationMessage": "Your verification code is {####}", 19 | "defaultPasswordPolicy": false, 20 | "passwordPolicyMinLength": 8, 21 | "passwordPolicyCharacters": [], 22 | "requiredAttributes": [ 23 | "email" 24 | ], 25 | "aliasAttributes": [], 26 | "userpoolClientGenerateSecret": false, 27 | "userpoolClientRefreshTokenValidity": 30, 28 | "userpoolClientWriteAttributes": [ 29 | "email" 30 | ], 31 | "userpoolClientReadAttributes": [ 32 | "email" 33 | ], 34 | "userpoolClientLambdaRole": "simple178ae288_userpoolclient_lambda_role", 35 | "userpoolClientSetAttributes": false, 36 | "sharedId": "178ae288", 37 | "resourceName": "simplewebstreaming178ae288", 38 | "authSelections": "identityPoolAndUserPool", 39 | "useDefault": "default", 40 | "usernameAttributes": [ 41 | "email" 42 | ], 43 | "userPoolGroupList": [], 44 | "serviceName": "Cognito", 45 | "usernameCaseSensitive": false, 46 | "useEnabledMfas": true 47 | } 48 | } -------------------------------------------------------------------------------- /frontend/amplify/backend/backend-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "function": { 3 | "saveIVSparam": { 4 | "build": true, 5 | "providerPlugin": "awscloudformation", 6 | "service": "Lambda", 7 | "dependsOn": [ 8 | { 9 | "category": "storage", 10 | "resourceName": "IVSparam", 11 | "attributes": [ 12 | "Name", 13 | "Arn" 14 | ], 15 | "attributeEnvMap": { 16 | "Name": "TABLE_NAME", 17 | "Arn": "TABLE_ARN" 18 | } 19 | } 20 | ] 21 | }, 22 | "ISSgetServers": { 23 | "build": true, 24 | "providerPlugin": "awscloudformation", 25 | "service": "Lambda", 26 | "dependsOn": [ 27 | { 28 | "category": "storage", 29 | "resourceName": "ISStaskdnstrack", 30 | "attributes": [ 31 | "Name", 32 | "Arn" 33 | ], 34 | "attributeEnvMap": { 35 | "Name": "TABLE_NAME", 36 | "Arn": "TABLE_ARN" 37 | } 38 | } 39 | ] 40 | } 41 | }, 42 | "auth": { 43 | "simplewebstreaming178ae288": { 44 | "service": "Cognito", 45 | "providerPlugin": "awscloudformation", 46 | "dependsOn": [], 47 | "customAuth": false, 48 | "frontendAuthConfig": { 49 | "socialProviders": [], 50 | "usernameAttributes": [ 51 | "EMAIL" 52 | ], 53 | "signupAttributes": [ 54 | "EMAIL" 55 | ], 56 | "passwordProtectionSettings": { 57 | "passwordPolicyMinLength": 8, 58 | "passwordPolicyCharacters": [] 59 | }, 60 | "mfaConfiguration": "OFF", 61 | "mfaTypes": [ 62 | "SMS" 63 | ], 64 | "verificationMechanisms": [ 65 | "EMAIL" 66 | ] 67 | } 68 | } 69 | }, 70 | "storage": { 71 | "IVSparam": { 72 | "service": "DynamoDB", 73 | "providerPlugin": "awscloudformation" 74 | }, 75 | "ISStaskdnstrack": { 76 | "service": "DynamoDB", 77 | "providerPlugin": "awscloudformation" 78 | } 79 | }, 80 | "api": { 81 | "saveIVSparam": { 82 | "service": "API Gateway", 83 | "providerPlugin": "awscloudformation", 84 | "dependsOn": [ 85 | { 86 | "category": "function", 87 | "resourceName": "saveIVSparam", 88 | "attributes": [ 89 | "Name", 90 | "Arn" 91 | ] 92 | }, 93 | { 94 | "category": "function", 95 | "resourceName": "ISSgetServers", 96 | "attributes": [ 97 | "Name", 98 | "Arn" 99 | ] 100 | } 101 | ] 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /frontend/amplify/backend/function/ISSgetServers/ISSgetServers-cloudformation-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "{\"createdOn\":\"Mac\",\"createdBy\":\"Amplify\",\"createdWith\":\"8.3.1\",\"stackType\":\"function-Lambda\",\"metadata\":{}}", 4 | "Parameters": { 5 | "CloudWatchRule": { 6 | "Type": "String", 7 | "Default": "NONE", 8 | "Description": " Schedule Expression" 9 | }, 10 | "deploymentBucketName": { 11 | "Type": "String" 12 | }, 13 | "env": { 14 | "Type": "String" 15 | }, 16 | "s3Key": { 17 | "Type": "String" 18 | }, 19 | "storageISStaskdnstrackName": { 20 | "Type": "String", 21 | "Default": "storageISStaskdnstrackName" 22 | }, 23 | "storageISStaskdnstrackArn": { 24 | "Type": "String", 25 | "Default": "storageISStaskdnstrackArn" 26 | } 27 | }, 28 | "Conditions": { 29 | "ShouldNotCreateEnvResources": { 30 | "Fn::Equals": [ 31 | { 32 | "Ref": "env" 33 | }, 34 | "NONE" 35 | ] 36 | } 37 | }, 38 | "Resources": { 39 | "LambdaFunction": { 40 | "Type": "AWS::Lambda::Function", 41 | "Metadata": { 42 | "aws:asset:path": "./src", 43 | "aws:asset:property": "Code" 44 | }, 45 | "Properties": { 46 | "Code": { 47 | "S3Bucket": { 48 | "Ref": "deploymentBucketName" 49 | }, 50 | "S3Key": { 51 | "Ref": "s3Key" 52 | } 53 | }, 54 | "Handler": "index.handler", 55 | "FunctionName": { 56 | "Fn::If": [ 57 | "ShouldNotCreateEnvResources", 58 | "ISSgetServers", 59 | { 60 | "Fn::Join": [ 61 | "", 62 | [ 63 | "ISSgetServers", 64 | "-", 65 | { 66 | "Ref": "env" 67 | } 68 | ] 69 | ] 70 | } 71 | ] 72 | }, 73 | "Environment": { 74 | "Variables": { 75 | "ENV": { 76 | "Ref": "env" 77 | }, 78 | "REGION": { 79 | "Ref": "AWS::Region" 80 | } 81 | } 82 | }, 83 | "Role": { 84 | "Fn::GetAtt": [ 85 | "LambdaExecutionRole", 86 | "Arn" 87 | ] 88 | }, 89 | "Runtime": "nodejs14.x", 90 | "Layers": [], 91 | "Timeout": 25 92 | } 93 | }, 94 | "LambdaExecutionRole": { 95 | "Type": "AWS::IAM::Role", 96 | "Properties": { 97 | "RoleName": { 98 | "Fn::If": [ 99 | "ShouldNotCreateEnvResources", 100 | "simplewebstreamingLambdaRole1b3066e3", 101 | { 102 | "Fn::Join": [ 103 | "", 104 | [ 105 | "simplewebstreamingLambdaRole1b3066e3", 106 | "-", 107 | { 108 | "Ref": "env" 109 | } 110 | ] 111 | ] 112 | } 113 | ] 114 | }, 115 | "AssumeRolePolicyDocument": { 116 | "Version": "2012-10-17", 117 | "Statement": [ 118 | { 119 | "Effect": "Allow", 120 | "Principal": { 121 | "Service": [ 122 | "lambda.amazonaws.com" 123 | ] 124 | }, 125 | "Action": [ 126 | "sts:AssumeRole" 127 | ] 128 | } 129 | ] 130 | } 131 | } 132 | }, 133 | "lambdaexecutionpolicy": { 134 | "DependsOn": [ 135 | "LambdaExecutionRole" 136 | ], 137 | "Type": "AWS::IAM::Policy", 138 | "Properties": { 139 | "PolicyName": "lambda-execution-policy", 140 | "Roles": [ 141 | { 142 | "Ref": "LambdaExecutionRole" 143 | } 144 | ], 145 | "PolicyDocument": { 146 | "Version": "2012-10-17", 147 | "Statement": [ 148 | { 149 | "Effect": "Allow", 150 | "Action": [ 151 | "logs:CreateLogGroup", 152 | "logs:CreateLogStream", 153 | "logs:PutLogEvents" 154 | ], 155 | "Resource": { 156 | "Fn::Sub": [ 157 | "arn:aws:logs:${region}:${account}:log-group:/aws/lambda/${lambda}:log-stream:*", 158 | { 159 | "region": { 160 | "Ref": "AWS::Region" 161 | }, 162 | "account": { 163 | "Ref": "AWS::AccountId" 164 | }, 165 | "lambda": { 166 | "Ref": "LambdaFunction" 167 | } 168 | } 169 | ] 170 | } 171 | }, 172 | { 173 | "Effect": "Allow", 174 | "Action": [ 175 | "dynamodb:DescribeTable", 176 | "dynamodb:GetItem", 177 | "dynamodb:Query", 178 | "dynamodb:Scan", 179 | "dynamodb:PutItem", 180 | "dynamodb:UpdateItem", 181 | "dynamodb:DeleteItem" 182 | ], 183 | "Resource": [ 184 | { 185 | "Ref": "storageISStaskdnstrackArn" 186 | }, 187 | { 188 | "Fn::Join": [ 189 | "/", 190 | [ 191 | { 192 | "Ref": "storageISStaskdnstrackArn" 193 | }, 194 | "index/*" 195 | ] 196 | ] 197 | } 198 | ] 199 | } 200 | ] 201 | } 202 | } 203 | } 204 | }, 205 | "Outputs": { 206 | "Name": { 207 | "Value": { 208 | "Ref": "LambdaFunction" 209 | } 210 | }, 211 | "Arn": { 212 | "Value": { 213 | "Fn::GetAtt": [ 214 | "LambdaFunction", 215 | "Arn" 216 | ] 217 | } 218 | }, 219 | "Region": { 220 | "Value": { 221 | "Ref": "AWS::Region" 222 | } 223 | }, 224 | "LambdaExecutionRole": { 225 | "Value": { 226 | "Ref": "LambdaExecutionRole" 227 | } 228 | } 229 | } 230 | } -------------------------------------------------------------------------------- /frontend/amplify/backend/function/ISSgetServers/amplify.state: -------------------------------------------------------------------------------- 1 | { 2 | "pluginId": "amplify-nodejs-function-runtime-provider", 3 | "functionRuntime": "nodejs", 4 | "useLegacyBuild": true, 5 | "defaultEditorFile": "src/app.js" 6 | } -------------------------------------------------------------------------------- /frontend/amplify/backend/function/ISSgetServers/custom-policies.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Action": [], 4 | "Resource": [] 5 | } 6 | ] -------------------------------------------------------------------------------- /frontend/amplify/backend/function/ISSgetServers/function-parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "lambdaLayers": [] 3 | } -------------------------------------------------------------------------------- /frontend/amplify/backend/function/ISSgetServers/src/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "httpMethod": "POST", 3 | "path": "/item", 4 | "queryStringParameters": { 5 | "limit": "10" 6 | }, 7 | "headers": { 8 | "Content-Type": "application/json" 9 | }, 10 | "body": "{\"msg\":\"Hello from the event.json body\"}" 11 | } 12 | -------------------------------------------------------------------------------- /frontend/amplify/backend/function/ISSgetServers/src/index.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | console.log("Loading Event"); 4 | // aws sdk loading 5 | const AWS = require("aws-sdk"); 6 | const dynamodb = new AWS.DynamoDB.DocumentClient(); 7 | 8 | exports.handler = async (event) => { 9 | const headers = { 10 | "Access-Control-Allow-Origin": "*", // Required for CORS support to work 11 | "Access-Control-Allow-Credentials": true, // Required for cookies, authorization headers with HTTPS 12 | }; 13 | 14 | let params = { 15 | TableName: "ISS-task-dns-track-dev", 16 | FilterExpression: "replaced = :n", 17 | ExpressionAttributeValues: { 18 | ":n": "no", 19 | }, 20 | ExpressionAttributeNames: { 21 | "#serverPosition": "role", 22 | }, 23 | ProjectionExpression: "dns, #serverPosition, replaced, eventid", 24 | }; 25 | 26 | const scanResult = await scanDynamo(params); 27 | if (scanResult.Count < 1) { 28 | console.log("Nothing yet in the table"); 29 | let response = 30 | "You need to deploy the backend container before loading this page"; 31 | let resp = { 32 | statusCode: 503, 33 | headers: headers, 34 | body: JSON.stringify(response), 35 | }; 36 | return resp; 37 | } 38 | 39 | const response = { 40 | statusCode: 200, 41 | headers: headers, 42 | body: JSON.stringify(scanResult), 43 | }; 44 | return response; 45 | 46 | async function scanDynamo(params) { 47 | console.log("Scanning DynamoBD"); 48 | let result; 49 | await dynamodb 50 | .scan(params, function (err, data) { 51 | if (err) console.log(err, err.stack); 52 | else result = data; 53 | }) 54 | .promise(); 55 | return result; 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /frontend/amplify/backend/function/saveIVSparam/amplify.state: -------------------------------------------------------------------------------- 1 | { 2 | "pluginId": "amplify-nodejs-function-runtime-provider", 3 | "functionRuntime": "nodejs", 4 | "useLegacyBuild": true, 5 | "defaultEditorFile": "src/app.js" 6 | } -------------------------------------------------------------------------------- /frontend/amplify/backend/function/saveIVSparam/custom-policies.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Action": [], 4 | "Resource": [] 5 | } 6 | ] -------------------------------------------------------------------------------- /frontend/amplify/backend/function/saveIVSparam/function-parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "lambdaLayers": [] 3 | } -------------------------------------------------------------------------------- /frontend/amplify/backend/function/saveIVSparam/saveIVSparam-cloudformation-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "{\"createdOn\":\"Mac\",\"createdBy\":\"Amplify\",\"createdWith\":\"8.3.1\",\"stackType\":\"function-Lambda\",\"metadata\":{}}", 4 | "Parameters": { 5 | "CloudWatchRule": { 6 | "Type": "String", 7 | "Default": "NONE", 8 | "Description": " Schedule Expression" 9 | }, 10 | "deploymentBucketName": { 11 | "Type": "String" 12 | }, 13 | "env": { 14 | "Type": "String" 15 | }, 16 | "s3Key": { 17 | "Type": "String" 18 | }, 19 | "storageIVSparamName": { 20 | "Type": "String", 21 | "Default": "storageIVSparamName" 22 | }, 23 | "storageIVSparamArn": { 24 | "Type": "String", 25 | "Default": "storageIVSparamArn" 26 | } 27 | }, 28 | "Conditions": { 29 | "ShouldNotCreateEnvResources": { 30 | "Fn::Equals": [ 31 | { 32 | "Ref": "env" 33 | }, 34 | "NONE" 35 | ] 36 | } 37 | }, 38 | "Resources": { 39 | "LambdaFunction": { 40 | "Type": "AWS::Lambda::Function", 41 | "Metadata": { 42 | "aws:asset:path": "./src", 43 | "aws:asset:property": "Code" 44 | }, 45 | "Properties": { 46 | "Code": { 47 | "S3Bucket": { 48 | "Ref": "deploymentBucketName" 49 | }, 50 | "S3Key": { 51 | "Ref": "s3Key" 52 | } 53 | }, 54 | "Handler": "index.handler", 55 | "FunctionName": { 56 | "Fn::If": [ 57 | "ShouldNotCreateEnvResources", 58 | "saveIVSparam", 59 | { 60 | "Fn::Join": [ 61 | "", 62 | [ 63 | "saveIVSparam", 64 | "-", 65 | { 66 | "Ref": "env" 67 | } 68 | ] 69 | ] 70 | } 71 | ] 72 | }, 73 | "Environment": { 74 | "Variables": { 75 | "ENV": { 76 | "Ref": "env" 77 | }, 78 | "REGION": { 79 | "Ref": "AWS::Region" 80 | } 81 | } 82 | }, 83 | "Role": { 84 | "Fn::GetAtt": [ 85 | "LambdaExecutionRole", 86 | "Arn" 87 | ] 88 | }, 89 | "Runtime": "nodejs14.x", 90 | "Layers": [], 91 | "Timeout": 25 92 | } 93 | }, 94 | "LambdaExecutionRole": { 95 | "Type": "AWS::IAM::Role", 96 | "Properties": { 97 | "RoleName": { 98 | "Fn::If": [ 99 | "ShouldNotCreateEnvResources", 100 | "simplewebstreamingLambdaRole98f52dca", 101 | { 102 | "Fn::Join": [ 103 | "", 104 | [ 105 | "simplewebstreamingLambdaRole98f52dca", 106 | "-", 107 | { 108 | "Ref": "env" 109 | } 110 | ] 111 | ] 112 | } 113 | ] 114 | }, 115 | "AssumeRolePolicyDocument": { 116 | "Version": "2012-10-17", 117 | "Statement": [ 118 | { 119 | "Effect": "Allow", 120 | "Principal": { 121 | "Service": [ 122 | "lambda.amazonaws.com" 123 | ] 124 | }, 125 | "Action": [ 126 | "sts:AssumeRole" 127 | ] 128 | } 129 | ] 130 | } 131 | } 132 | }, 133 | "lambdaexecutionpolicy": { 134 | "DependsOn": [ 135 | "LambdaExecutionRole" 136 | ], 137 | "Type": "AWS::IAM::Policy", 138 | "Properties": { 139 | "PolicyName": "lambda-execution-policy", 140 | "Roles": [ 141 | { 142 | "Ref": "LambdaExecutionRole" 143 | } 144 | ], 145 | "PolicyDocument": { 146 | "Version": "2012-10-17", 147 | "Statement": [ 148 | { 149 | "Effect": "Allow", 150 | "Action": [ 151 | "logs:CreateLogGroup", 152 | "logs:CreateLogStream", 153 | "logs:PutLogEvents" 154 | ], 155 | "Resource": { 156 | "Fn::Sub": [ 157 | "arn:aws:logs:${region}:${account}:log-group:/aws/lambda/${lambda}:log-stream:*", 158 | { 159 | "region": { 160 | "Ref": "AWS::Region" 161 | }, 162 | "account": { 163 | "Ref": "AWS::AccountId" 164 | }, 165 | "lambda": { 166 | "Ref": "LambdaFunction" 167 | } 168 | } 169 | ] 170 | } 171 | }, 172 | { 173 | "Effect": "Allow", 174 | "Action": [ 175 | "dynamodb:DescribeTable", 176 | "dynamodb:GetItem", 177 | "dynamodb:Query", 178 | "dynamodb:Scan", 179 | "dynamodb:PutItem", 180 | "dynamodb:UpdateItem", 181 | "dynamodb:DeleteItem" 182 | ], 183 | "Resource": [ 184 | { 185 | "Ref": "storageIVSparamArn" 186 | }, 187 | { 188 | "Fn::Join": [ 189 | "/", 190 | [ 191 | { 192 | "Ref": "storageIVSparamArn" 193 | }, 194 | "index/*" 195 | ] 196 | ] 197 | } 198 | ] 199 | } 200 | ] 201 | } 202 | } 203 | } 204 | }, 205 | "Outputs": { 206 | "Name": { 207 | "Value": { 208 | "Ref": "LambdaFunction" 209 | } 210 | }, 211 | "Arn": { 212 | "Value": { 213 | "Fn::GetAtt": [ 214 | "LambdaFunction", 215 | "Arn" 216 | ] 217 | } 218 | }, 219 | "Region": { 220 | "Value": { 221 | "Ref": "AWS::Region" 222 | } 223 | }, 224 | "LambdaExecutionRole": { 225 | "Value": { 226 | "Ref": "LambdaExecutionRole" 227 | } 228 | } 229 | } 230 | } -------------------------------------------------------------------------------- /frontend/amplify/backend/function/saveIVSparam/src/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "httpMethod": "POST", 3 | "path": "/item", 4 | "queryStringParameters": { 5 | "limit": "10" 6 | }, 7 | "headers": { 8 | "Content-Type": "application/json" 9 | }, 10 | "body": "{\"msg\":\"Hello from the event.json body\"}" 11 | } 12 | -------------------------------------------------------------------------------- /frontend/amplify/backend/function/saveIVSparam/src/index.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | const AWS = require("aws-sdk"); 4 | const dynamodb = new AWS.DynamoDB(); 5 | 6 | exports.handler = async (event) => { 7 | var response; 8 | // GET Channel Configuration 9 | if (event.httpMethod == "GET") { 10 | const getChannel = async () => { 11 | var user = event.path.substring(event.path.lastIndexOf("/") + 1); 12 | var channelParams = { 13 | TableName: "IVSparam-dev", 14 | Key: { 15 | "username": { 16 | S: user, 17 | }, 18 | }, 19 | }; 20 | let result = await dynamodb 21 | .getItem(channelParams, function (err, data) { 22 | if (err) console.log(err, err.stack); 23 | }) 24 | .promise(); 25 | return result.Item; 26 | }; 27 | await getChannel() 28 | .then((result) => { 29 | //console.log("line 28 ~ awaitgetChannel ~ result", result); 30 | response = result; 31 | }) 32 | .catch(); 33 | } else { 34 | // Create Channel Configuration 35 | var body = JSON.parse(event.body); 36 | const createChannel = async () => { 37 | var channelParams = { 38 | TableName: "IVSparam-dev", 39 | Item: { 40 | "username": { S: body.username }, 41 | "rtmpURL": { S: body.rtmpURL }, 42 | "channelType": { S: body.channelType }, 43 | "streamKey": { S: body.streamKey }, 44 | "playURL": { S: body.playURL }, 45 | }, 46 | }; 47 | 48 | let result = await dynamodb 49 | .putItem(channelParams, function (err, data) { 50 | if (err) console.log(err, err.stack); 51 | }) 52 | .promise(); 53 | return result; 54 | }; 55 | 56 | await createChannel() 57 | .then((result) => { 58 | //console.log("line 62 ~ createChannel ~ resp", result); 59 | response = result; 60 | }) 61 | .catch(); 62 | } 63 | 64 | return { 65 | statusCode: 200, 66 | headers: { 67 | "Access-Control-Allow-Origin": "*", 68 | "Access-Control-Allow-Headers": "*", 69 | }, 70 | body: JSON.stringify(response), 71 | }; 72 | }; 73 | -------------------------------------------------------------------------------- /frontend/amplify/backend/storage/ISStaskdnstrack/cli-inputs.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceName": "ISStaskdnstrack", 3 | "tableName": "ISS-task-dns-track", 4 | "partitionKey": { 5 | "fieldName": "role", 6 | "fieldType": "string" 7 | }, 8 | "gsi": [], 9 | "triggerFunctions": [] 10 | } -------------------------------------------------------------------------------- /frontend/amplify/backend/storage/IVSparam/cli-inputs.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceName": "IVSparam", 3 | "tableName": "IVSparam", 4 | "partitionKey": { 5 | "fieldName": "username", 6 | "fieldType": "string" 7 | }, 8 | "gsi": [], 9 | "triggerFunctions": [] 10 | } -------------------------------------------------------------------------------- /frontend/amplify/backend/tags.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "user:Stack", 4 | "Value": "{project-env}" 5 | }, 6 | { 7 | "Key": "user:Application", 8 | "Value": "{project-name}" 9 | } 10 | ] -------------------------------------------------------------------------------- /frontend/amplify/backend/types/amplify-dependent-resources-ref.d.ts: -------------------------------------------------------------------------------- 1 | export type AmplifyDependentResourcesAttributes = { 2 | "function": { 3 | "saveIVSparam": { 4 | "Name": "string", 5 | "Arn": "string", 6 | "Region": "string", 7 | "LambdaExecutionRole": "string" 8 | }, 9 | "ISSgetServers": { 10 | "Name": "string", 11 | "Arn": "string", 12 | "Region": "string", 13 | "LambdaExecutionRole": "string" 14 | } 15 | }, 16 | "auth": { 17 | "simplewebstreaming178ae288": { 18 | "IdentityPoolId": "string", 19 | "IdentityPoolName": "string", 20 | "UserPoolId": "string", 21 | "UserPoolArn": "string", 22 | "UserPoolName": "string", 23 | "AppClientIDWeb": "string", 24 | "AppClientID": "string" 25 | } 26 | }, 27 | "storage": { 28 | "IVSparam": { 29 | "Name": "string", 30 | "Arn": "string", 31 | "StreamArn": "string", 32 | "PartitionKeyName": "string", 33 | "PartitionKeyType": "string", 34 | "Region": "string" 35 | }, 36 | "ISStaskdnstrack": { 37 | "Name": "string", 38 | "Arn": "string", 39 | "StreamArn": "string", 40 | "PartitionKeyName": "string", 41 | "PartitionKeyType": "string", 42 | "Region": "string" 43 | } 44 | }, 45 | "api": { 46 | "saveIVSparam": { 47 | "RootUrl": "string", 48 | "ApiName": "string", 49 | "ApiId": "string" 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /frontend/amplify/hooks/README.md: -------------------------------------------------------------------------------- 1 | # Command Hooks 2 | 3 | Command hooks can be used to run custom scripts upon Amplify CLI lifecycle events like pre-push, post-add-function, etc. 4 | 5 | To get started, add your script files based on the expected naming convention in this directory. 6 | 7 | Learn more about the script file naming convention, hook parameters, third party dependencies, and advanced configurations at https://docs.amplify.aws/cli/usage/command-hooks 8 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ivs-simple-streaming", 3 | "version": "0.2.0", 4 | "private": true, 5 | "dependencies": { 6 | "@aws-amplify/api": "^4.0.39", 7 | "@aws-amplify/auth": "^4.5.3", 8 | "@aws-amplify/cache": "^4.0.41", 9 | "@aws-amplify/core": "^4.5.3", 10 | "@aws-amplify/ui-react": "^2.17.0", 11 | "bootstrap-icons": "^1.8.1", 12 | "react": "^18.1.0", 13 | "react-dom": "^18.1.0", 14 | "react-router-dom": "^6.3.0", 15 | "react-scripts": "5.0.1" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test", 21 | "eject": "react-scripts eject" 22 | }, 23 | "browserslist": { 24 | "production": [ 25 | ">0.2%", 26 | "not dead", 27 | "not op_mini all" 28 | ], 29 | "development": [ 30 | "last 1 chrome version", 31 | "last 1 firefox version", 32 | "last 1 safari version" 33 | ] 34 | }, 35 | "devDependencies": { 36 | "webpack-bundle-analyzer": "^4.5.0", 37 | "webpack-cli": "^4.9.2" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-simple-streaming-webapp/c2d271f79527f20b75628247947bac7584aa3d40/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 27 | AWS - Simple Web Broadcasting IVS 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-simple-streaming-webapp/c2d271f79527f20b75628247947bac7584aa3d40/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-simple-streaming-webapp/c2d271f79527f20b75628247947bac7584aa3d40/frontend/public/logo512.png -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/src/components/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | text-align: center; 3 | color: white; 4 | } 5 | 6 | body nav a { 7 | font-size: large; 8 | padding-left: 10px; 9 | padding-right: 10px; 10 | } 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/src/components/App.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | import React, { useEffect, useState } from "react"; 5 | import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom"; 6 | import UserMedia from "./UserMedia"; 7 | import HomePage from "./HomePage"; 8 | import PlayerView from "./PlayerView"; 9 | import "./App.css"; 10 | 11 | import Amplify from "@aws-amplify/core"; 12 | import Auth from "@aws-amplify/auth"; 13 | import { withAuthenticator } from "@aws-amplify/ui-react"; 14 | import awsmobile from "../aws-exports"; 15 | import "@aws-amplify/ui-react/styles.css"; 16 | 17 | Amplify.configure(awsmobile); 18 | Auth.configure(awsmobile); 19 | 20 | function App(props) { 21 | const [username, setUsername] = useState(); 22 | 23 | useEffect(() => { 24 | (async function () { 25 | try { 26 | await Auth.currentAuthenticatedUser({ bypassCache: false }).then( 27 | (user) => { 28 | console.log(user); 29 | setUsername(user.username); 30 | } 31 | ); 32 | } catch (e) { 33 | console.error("Error, no logeeed user ", e); 34 | } 35 | })(); 36 | console.log("component mounted!"); 37 | }, []); 38 | 39 | const signOut = () => { 40 | Auth.signOut(); 41 | }; 42 | 43 | const openModal = () => { 44 | console.log("Ok!Here"); 45 | }; 46 | 47 | return username ? ( 48 | 49 |
50 | 59 | 60 | } /> 61 | } 65 | /> 66 | } 70 | /> 71 | } 75 | /> 76 | 77 |
78 |
79 | ) : ( 80 |
Loading
81 | ); 82 | } 83 | 84 | export default withAuthenticator(App); 85 | -------------------------------------------------------------------------------- /frontend/src/components/HomePage.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | import React, { useState, useEffect, useRef } from "react"; 5 | import UserMedia from "./UserMedia"; 6 | import StreamForm from "./StreamForm"; // add lazy load 7 | import { API } from "@aws-amplify/api"; 8 | 9 | import "./styles/HomePage.style.css"; 10 | 11 | export default function HomePage(props) { 12 | const username = props.username; 13 | const mediaRecorder = useRef(); 14 | const canvasRef = useRef(); 15 | const audioRef = useRef(); 16 | const [streamParams, setStreamParams] = useState({ url: "", key: "" }); 17 | const [streaming, setStreaming] = useState(false); 18 | const [configured, setConfigured] = useState(false); 19 | const [wrapServers, setWrapServers] = useState(""); 20 | const wsRef = useRef(); 21 | const btnPlayer = document.querySelector("#openplayer"); 22 | 23 | useEffect(() => { 24 | (async function () { 25 | if (!wrapServers) { 26 | try { 27 | let apiName = "saveIVSparam"; 28 | let path = `/getServers/`; 29 | await API.get(apiName, path).then((response) => { 30 | if (response.Items.length === 0) { 31 | console.err("Server is not defined"); 32 | } else { 33 | console.log("Remote server is:", response.Items[0].dns); 34 | setWrapServers(response.Items[0].dns); 35 | addDebugLine( 36 | Date.now(), 37 | `Remote server has been defined, using ${response.Items[0].dns}` 38 | ); 39 | } 40 | }); 41 | } catch (error) { 42 | console.log(error); 43 | handleError(error); 44 | } 45 | } else console.log("Remote server is, cached:", wrapServers); 46 | })(); 47 | }, [streaming, configured]); 48 | 49 | function handleError(e) { 50 | console.log("The server request returned null", e); 51 | console.log("Assuming localhost"); 52 | console.log("localhost:3004"); 53 | setWrapServers("127.0.0.1:3004"); 54 | addDebugLine( 55 | Date.now(), 56 | `No Remote server has been registered, assuming localhost:3004` 57 | ); 58 | } 59 | 60 | function handleFormReady(url, key, playurl) { 61 | setStreamParams({ url: url, key: key }); 62 | btnPlayer.addEventListener("click", () => openPlayer(playurl)); 63 | const btnSave = document.querySelector("#btnsave"); 64 | btnSave.addEventListener("click", (e) => { 65 | e.preventDefault(); 66 | btnSave.classList.add("saved"); 67 | setConfigured(true); 68 | addDebugLine(Date.now(), `Stream Params Saved, ready to stream`); 69 | }); 70 | } 71 | 72 | function openPlayer(url) { 73 | console.log(url); 74 | window.open(`/PlayerView?url=${url}`, "_blank"); 75 | } 76 | 77 | async function handleCameraReady(video, canvasVideo, audio) { 78 | if (!testBrowserSupport()) { 79 | console.log( 80 | "Browser does not support HTMLMediaElement.prototype.captureStream;" 81 | ); 82 | canvasRef.current = canvasVideo; 83 | } else { 84 | console.log("Browser support Ok!, using video element"); 85 | canvasRef.current = video; 86 | } 87 | audioRef.current = audio; 88 | } 89 | 90 | function testBrowserSupport() { 91 | return "function" === typeof HTMLMediaElement.prototype.captureStream; 92 | } 93 | 94 | async function startStream(e) { 95 | e.preventDefault(); 96 | if (!wsRef.current) 97 | await socketConnect(wrapServers, streamParams.url, streamParams.key); 98 | 99 | const videoStreaming = canvasRef.current.captureStream(30); 100 | const audioStreaming = new MediaStream(); 101 | 102 | audioRef.current.forEach(function (track) { 103 | console.log("track", track); 104 | audioStreaming.addTrack(track); 105 | }); 106 | 107 | console.log("audioStreaming", audioStreaming); 108 | const outputStream = new MediaStream(); 109 | [audioStreaming, videoStreaming].forEach(function (s) { 110 | s.getTracks().forEach(function (t) { 111 | outputStream.addTrack(t); 112 | }); 113 | }); 114 | 115 | let options = getmimeType(); 116 | console.log("mimeType is", options); 117 | 118 | mediaRecorder.current = new MediaRecorder(outputStream, options); 119 | 120 | mediaRecorder.current.addEventListener("dataavailable", (e) => { 121 | console.log("Stream data!!!", e); 122 | wsRef.current.send(e.data); 123 | }); 124 | mediaRecorder.current.start(1000); 125 | } 126 | 127 | function getmimeType() { 128 | let options = { 129 | mimeType: "video/webm;codecs=h264", 130 | videoBitsPerSecond: 3000000, 131 | }; 132 | options.codec = "h264"; 133 | 134 | if (!MediaRecorder.isTypeSupported(options.mimeType)) { 135 | addDebugLine(Date.now(), `${options.mimeType} + is not Supported`); 136 | options = { 137 | mimeType: "video/webm;codes=vp8,opus", 138 | videoBitsPerSecond: 3000000, 139 | }; 140 | options.codec = "vp8o"; 141 | 142 | if (!MediaRecorder.isTypeSupported(options.mimeType)) { 143 | addDebugLine(Date.now(), `${options.mimeType} + is not Supported`); 144 | options = { 145 | mimeType: "video/mp4;codecs=avc1,mp4a", 146 | videoBitsPerSecond: 3000000, 147 | }; 148 | options.codec = "mp4a"; 149 | } 150 | } 151 | return options; 152 | } 153 | 154 | function stopStreaming(e) { 155 | mediaRecorder.current.stop(); 156 | setStreaming(false); 157 | } 158 | 159 | async function socketConnect(server, rtmpURL, streamKey) { 160 | console.log("socketConnect"); 161 | let options = getmimeType(); 162 | let codec = options.codec; 163 | console.log("??", codec); 164 | if (window.location.protocol == "http:") { 165 | let protocol = window.location.protocol.replace("http", "ws"); 166 | let testServer = "127.0.0.1:3004"; 167 | var wsUrl = `${protocol}//${server}/rtmps/${codec}/${rtmpURL}${streamKey}`; // if you want to force streaming to a local server change to use testServer, instead of server 168 | } else { 169 | let protocol = window.location.protocol.replace("https", "wss"); 170 | var wsUrl = `${protocol}//${server}/rtmps/${codec}/${rtmpURL}${streamKey}`; 171 | } 172 | console.log("URL", wsUrl); 173 | wsRef.current = new WebSocket(wsUrl); 174 | 175 | wsRef.current.onerror = (err) => { 176 | console.error("Server not available", err); 177 | stopStreaming(); 178 | addDebugLine(Date.now(), `Error connecting to socket`); 179 | return; 180 | }; 181 | // onclose sub-function, please note::, Fargate container takes a few minutes to start at the deployment 182 | wsRef.current.onclose = (e) => { 183 | console.log("Socket Closed", server, e.code, e); 184 | if (e.code == 1006) { 185 | console.log("timeout"); 186 | wsRef.current = null; 187 | } 188 | if (e.code == 1015) { 189 | console.log("tls error"); 190 | wsRef.current = null; 191 | } 192 | addDebugLine(Date.now(), `Error on connecting to socket ${e.code}`); 193 | setStreaming(false); 194 | stopStreaming(); 195 | return; 196 | }; 197 | 198 | //wsRef.current.addEventListener("open", async function open(data) { 199 | // console.log("Open!!!!!", data); 200 | //}); 201 | 202 | wsRef.current.onmessage = (evt) => { 203 | addDebugLine(Date.now(), evt.data); 204 | }; 205 | } 206 | 207 | function addDebugLine(metadataTime, metadataText) { 208 | const domString = ` 209 | ${metadataTime} 210 | ${metadataText}`.trim(); 211 | 212 | const dataLine = document.createElement("div"); 213 | dataLine.classList.add("class", "data-line"); 214 | dataLine.innerHTML = domString; 215 | 216 | const debugData = document.querySelector(".debug-data"); 217 | debugData.appendChild(dataLine); 218 | } 219 | 220 | /*################################### WebSocket Client ###################################*/ 221 | return wrapServers ? ( 222 |
223 |
224 | {streamParams.key ? ( 225 |
226 |
227 | 238 |
239 | 240 |
241 | ) : ( 242 |
243 |
244 | IVS RTMPS params is not configured yet. 245 |
246 |
247 | )} 248 |
249 |
250 | 251 |
252 |
253 |
254 |
255 |
256 | ) : ( 257 |
Loading...
258 | ); 259 | } 260 | -------------------------------------------------------------------------------- /frontend/src/components/PlayerView.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | import React, { useRef } from "react"; 5 | import VideoPlayer from "./player/player"; 6 | import "./styles/PlayerView.style.css"; 7 | 8 | export default function PlayerView() { 9 | const urlSearchParams = new URLSearchParams(window.location.search); 10 | const params = Object.fromEntries(urlSearchParams.entries()); 11 | const playerRef = useRef(null); 12 | 13 | const videoURL = params.url; 14 | console.log(videoURL); 15 | 16 | const videoJsOptions = { 17 | autoplay: "muted", //mute audio when page loads, but auto play video 18 | controls: true, 19 | responsive: true, 20 | fluid: true, 21 | width: 896, 22 | height: 504, 23 | sources: [ 24 | { 25 | src: videoURL, 26 | type: "application/x-mpegURL", 27 | }, 28 | ], 29 | }; 30 | 31 | const handlePlayerReady = (player) => { 32 | player.on("waiting", () => { 33 | console.log("player is waiting"); 34 | }); 35 | 36 | player.on("dispose", () => { 37 | console.log("player will dispose"); 38 | }); 39 | 40 | player.on("playing", () => { 41 | console.log("player playing"); 42 | addDebugLine(Date.now(), "Player playing"); 43 | }); 44 | 45 | player.on("error", (err) => { 46 | console.log("Play Error", err); 47 | addDebugLine(Date.now(), `Player ${err.type} ${err.target.innerText}`); 48 | }); 49 | 50 | playerRef.current = player; 51 | }; 52 | 53 | function addDebugLine(metadataTime, metadataText) { 54 | const domString = ` 55 | ${metadataTime} 56 | ${metadataText}`.trim(); 57 | 58 | const dataLine = document.createElement("div"); 59 | dataLine.classList.add("class", "data-line"); 60 | dataLine.innerHTML = domString; 61 | 62 | const debugData = document.querySelector(".debug-data"); 63 | debugData.appendChild(dataLine); 64 | } 65 | 66 | return ( 67 |
68 |
69 |

Video Player

70 |
71 | 76 |
77 |
78 |
79 |
80 |
81 |
82 | ); 83 | } 84 | -------------------------------------------------------------------------------- /frontend/src/components/StreamForm.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | import React, { useEffect, useState } from "react"; 5 | import "./styles/StreamForm.style.css"; 6 | import { API } from "@aws-amplify/api"; 7 | 8 | export default function StreamForm(props) { 9 | const username = props.username; 10 | const [rtmpURL, setRtmpURL] = useState(""); 11 | const [channelType, setChannelType] = useState(""); 12 | const [streamKey, setStreamKey] = useState(""); 13 | const [playURL, setPlayURL] = useState(""); 14 | const [configured, isConfigure] = useState(null); 15 | const { onReady } = props; 16 | 17 | useEffect(() => { 18 | (async function () { 19 | await getStream(); 20 | })(); 21 | }, [configured]); 22 | 23 | async function getStream() { 24 | let apiName = "saveIVSparam"; 25 | let path = `/putitens/${username}`; 26 | await API.get(apiName, path) 27 | .then((ivsparams) => { 28 | if (ivsparams) { 29 | setChannelType(ivsparams.channelType.S); 30 | setRtmpURL(ivsparams.rtmpURL.S); 31 | setStreamKey(ivsparams.streamKey.S); 32 | setPlayURL(ivsparams.playURL.S); 33 | isConfigure(true); 34 | onReady && 35 | onReady( 36 | ivsparams.rtmpURL.S, 37 | ivsparams.streamKey.S, 38 | ivsparams.playURL.S, 39 | configured 40 | ); 41 | } else isConfigure(false); 42 | }) 43 | .catch((error) => { 44 | console.log(error); 45 | }); 46 | } 47 | 48 | const storeStream = (e) => { 49 | e.preventDefault(); 50 | let apiName = "saveIVSparam"; 51 | let path = "/putitens"; 52 | let data = { 53 | body: { 54 | username, 55 | channelType, 56 | rtmpURL, 57 | streamKey, 58 | playURL, 59 | }, 60 | }; 61 | API.post(apiName, path, data) 62 | .then((response) => { 63 | getStream(); 64 | }) 65 | .catch((error) => { 66 | console.log(error.response); 67 | }); 68 | }; 69 | 70 | return ( 71 |
72 |
73 |
74 |
75 |
76 | 99 | 112 | 124 | 125 | 137 |
138 |
139 | 147 |
148 |
149 |
150 |
151 |
152 | ); 153 | } 154 | -------------------------------------------------------------------------------- /frontend/src/components/UserMedia.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | import React, { useEffect, useRef, useState } from "react"; 5 | import "./styles/UserMedia.style.css"; 6 | 7 | export default function HomePage(props) { 8 | const { onReady } = props; 9 | const mediaStream = useRef(); 10 | const mediaDevices = useRef(); 11 | const localStream = useRef(); 12 | const canvasStream = useRef(); 13 | const animationRef = useRef(); 14 | const [vDevID, setVDevID] = useState(""); 15 | const [aDevID, setADevID] = useState(""); 16 | const [devices, setDevices] = useState({ 17 | videoin: null, 18 | audioin: null, 19 | audioout: null, 20 | }); 21 | const constraints = { 22 | audio: { autoplay: true, deviceId: aDevID, sampleRate: 44100 }, 23 | video: { width: 1280, height: 720, deviceId: vDevID }, 24 | }; 25 | const [errorMSG, setErrorMSG] = useState(null); 26 | 27 | useEffect(() => { 28 | (async function () { 29 | try { 30 | mediaStream.current = await navigator.mediaDevices.getUserMedia( 31 | constraints 32 | ); 33 | mediaDevices.current = await navigator.mediaDevices.enumerateDevices(); 34 | deviceList(); 35 | //getCamera(); 36 | } catch (e) { 37 | console.error("Device Error", e); 38 | handleError(e); 39 | } 40 | })(); 41 | }, [vDevID, aDevID]); 42 | 43 | useEffect(() => { 44 | window.addEventListener("resize", onResize); 45 | }, [localStream.current]); 46 | 47 | function onResize(e) { 48 | if ( 49 | canvasStream.current.width !== localStream.current.clientHeight || 50 | canvasStream.current.width !== localStream.current.clientWidth 51 | ) { 52 | console.log("Fixing video sizing"); 53 | canvasStream.current.width = localStream.current.clientHeight; 54 | canvasStream.current.width = localStream.current.clientWidth; 55 | } 56 | 57 | requestAnimationFrame(refreshCanvas); 58 | } 59 | 60 | /*################################### Function List Devices ###################################*/ 61 | function deviceList() { 62 | console.log("List Cameras", mediaDevices.current.length); 63 | let vidin = []; 64 | let auin = []; 65 | let audioOut = []; 66 | mediaDevices.current.forEach(function (gotDevice) { 67 | let i = 0; 68 | if (gotDevice.kind === "audioinput") { 69 | auin.push({ label: gotDevice.label, id: gotDevice.deviceId, len: i++ }); 70 | } else if (gotDevice.kind === "videoinput") { 71 | vidin.push({ label: gotDevice.label, id: gotDevice.deviceId }); 72 | } else if (gotDevice.kind === "audiooutput") { 73 | audioOut.push({ label: gotDevice.label, id: gotDevice.deviceId }); 74 | } else { 75 | console.log("Some other kind of source/device: ", gotDevice); 76 | } 77 | }); 78 | setDevices({ audioin: auin, videoin: vidin, audioout: audioOut }); 79 | getCamera(); 80 | } 81 | 82 | /*################################### Handle Device Change ###################################*/ 83 | const handleDevChange = (event) => { 84 | event.preventDefault(); 85 | console.log("Device Change block", vDevID, aDevID, constraints); 86 | console.log(event.target.value, event.target.id); 87 | if (event.target.id === "videoin") { 88 | console.log("set video", event.target.value); 89 | setVDevID(event.target.value); 90 | } 91 | if (event.target.id === "audioin") { 92 | console.log("set audio iN", aDevID); 93 | setADevID(event.target.value); 94 | } 95 | if (event.target.id === "audioout") { 96 | console.log("set audio out"); 97 | } 98 | getCamera(); 99 | }; 100 | 101 | /*################################### Function Display Camera ###################################*/ 102 | const getCamera = async () => { 103 | console.log("GET Local Camera"); 104 | window.localStream = mediaStream.current; 105 | localStream.current.srcObject = mediaStream.current; 106 | localStream.current.onloadedmetadata = async function (e) { 107 | await localStream.current.play(); 108 | }; 109 | 110 | canvasStream.current.height = localStream.current.clientHeight; 111 | canvasStream.current.width = localStream.current.clientWidth; 112 | 113 | let audioTracks = mediaStream.current.getAudioTracks(); 114 | requestAnimationFrame(refreshCanvas); 115 | onReady && onReady(localStream.current, canvasStream.current, audioTracks); 116 | }; 117 | 118 | //Add to canvas 119 | const refreshCanvas = (e) => { 120 | var element = document.getElementById("video"); 121 | if (element) { 122 | const ctx = canvasStream.current.getContext("2d"); 123 | 124 | let cW = canvasStream.current.width; 125 | let cH = canvasStream.current.height; 126 | 127 | draw(localStream.current, cW, cH); 128 | 129 | function draw(video, width, height) { 130 | ctx.drawImage(video, 0, 0, width, height); // canvas has a distort 131 | } 132 | requestAnimationFrame(refreshCanvas); 133 | } 134 | }; 135 | 136 | /*################################### Function Handle Cam Error ###################################*/ 137 | const handleError = (error) => { 138 | if (error.name === "ConstraintNotSatisfiedError") { 139 | let v = constraints.video; 140 | console.error( 141 | `The resolution ${v.width.exact}x${v.height.exact} px is not supported by your device.` 142 | ); 143 | } else if (error.name === "NotAllowedError") { 144 | console.error( 145 | "Permissions have not been granted to use your camera and " + 146 | "microphone, you need to allow the page access to your devices in " + 147 | "order for the demo to work." 148 | ); 149 | } 150 | console.error(`getUserMedia error: ${error.name}`, error); 151 | setErrorMSG(error.name); 152 | }; 153 | 154 | return devices.videoin ? ( 155 |
156 |
157 |
158 | 172 | 186 | 194 |
195 |
196 |
197 | 205 | 206 |
207 |
208 | ) : ( 209 |
loading...
210 | ); 211 | } 212 | -------------------------------------------------------------------------------- /frontend/src/components/player/player.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const VideoJS = (props) => { 4 | const videoRef = React.useRef(null); 5 | const playerRef = React.useRef(null); 6 | const { options, onReady } = props; 7 | 8 | React.useEffect(() => { 9 | // Make sure Video.js player is only initialized once 10 | if (!playerRef.current) { 11 | const videoElement = videoRef.current; 12 | 13 | if (!videoElement) return; 14 | 15 | const player = (playerRef.current = videojs(videoElement, options, () => { 16 | player.log("player is ready"); 17 | onReady && onReady(player); 18 | })); 19 | 20 | // You can update player in the `else` block here, for example: 21 | } else { 22 | player.autoplay(options.autoplay); 23 | player.src(options.sources); 24 | } 25 | }, [options, videoRef]); 26 | 27 | // Dispose the Video.js player when the functional component unmounts 28 | React.useEffect(() => { 29 | const player = playerRef.current; 30 | 31 | return () => { 32 | if (player) { 33 | player.dispose(); 34 | playerRef.current = null; 35 | } 36 | }; 37 | }, [playerRef]); 38 | 39 | return ( 40 |
41 |
43 | ); 44 | }; 45 | 46 | export default VideoJS; 47 | -------------------------------------------------------------------------------- /frontend/src/components/styles/HomePage.style.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: rgb(0, 0, 0); 3 | } 4 | 5 | .App{ 6 | position: relative; 7 | margin: auto; 8 | box-shadow: 0 20px 60px rgba(0, 0, 0, 0.30); 9 | border-radius: 6px; 10 | margin: auto; 11 | max-width: 1000px; 12 | width: 90%; 13 | display: flex; 14 | justify-content: center; 15 | flex-direction: column; 16 | } 17 | 18 | .stream-container { 19 | position: relative; 20 | margin: auto; 21 | display: flex; 22 | justify-content: center; 23 | flex-direction: column; 24 | border-radius: 6px; 25 | } 26 | 27 | .controls-container { 28 | position: absolute; 29 | bottom: 0; 30 | left: 0; 31 | right: 0; 32 | z-index: 100; 33 | opacity: 0.5; 34 | transition: opacity 150ms ease-in-out; 35 | outline: none; 36 | max-height: 100%; 37 | height: auto; 38 | } 39 | 40 | .rounded-btn { 41 | border-radius: 100%; 42 | height: 80px; 43 | width: 80px; 44 | font-size: 16px; 45 | font-weight: bold; 46 | color: white; 47 | background-color: #9146FF; 48 | opacity: 0.9; 49 | border: none; 50 | outline: none; 51 | } 52 | 53 | .controls-container:hover, 54 | .rounded-start-btn:hover { 55 | opacity: 1; 56 | outline: none; 57 | } 58 | 59 | .rounded-btn.stop { 60 | background-color: #ff4646; 61 | outline: none; 62 | } 63 | 64 | .placeholder-container{ 65 | position: relative; 66 | left: 0; 67 | top: 10px; 68 | right: 0; 69 | bottom: 0px; 70 | display: flex; 71 | justify-content: center; 72 | align-items: center; 73 | margin: auto; 74 | max-width: 800px; 75 | margin-bottom: 10px; 76 | box-shadow: 0 20px 60px rgba(0, 0, 0, 0.30); 77 | } 78 | 79 | .placeholder-content { 80 | position: relative; 81 | flex-shrink: 0; 82 | margin: center; 83 | word-break: break-all; 84 | transition: all 0.15s ease-in-out; 85 | animation: fadeIn 0.25s 1; 86 | width: 70%; 87 | padding: 40px; 88 | height: 100px; 89 | color: #4a4a4a; 90 | color: #4a4a4a; 91 | font-family: monospace; 92 | font-size:large; 93 | } 94 | 95 | .debug-container { 96 | padding-top: 10px; 97 | position: relative; 98 | max-width: 1000px; 99 | padding-bottom: 20px; 100 | } 101 | 102 | .debug-data { 103 | width: 80%; 104 | height: 150px; 105 | margin: auto; 106 | box-shadow: 0 20px 60px rgba(0, 0, 0, 0.30); 107 | justify-content: center; 108 | flex-direction: column; 109 | border: 0rem; 110 | color: #4a4a4a; 111 | font-family: monospace; 112 | background: #fff; 113 | border-radius: 0 0 6px 6px; 114 | display: flex; 115 | flex-shrink: 0; 116 | overflow-x: hidden; 117 | overflow-y: scroll; 118 | flex-direction: column; 119 | } 120 | 121 | .data-line { 122 | display: flex; 123 | flex-shrink: 0; 124 | margin: 0 5px 0 0; 125 | padding: 3px 5px 5px 10px; 126 | word-break: break-all; 127 | transition: all 0.15s ease-in-out; 128 | animation: fadeIn 0.25s 1; 129 | } 130 | 131 | .data-line:last-child { 132 | padding-bottom: 2px; 133 | } 134 | 135 | .debug-data__time { 136 | min-width: 5rem; 137 | font-weight: 300; 138 | font-size: 12px; 139 | padding-right: 5px; 140 | } 141 | 142 | .debug-data__value { 143 | font-weight: bold; 144 | font-size: 12px; 145 | } 146 | 147 | 148 | rounded-btn:focus { outline: none; } 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /frontend/src/components/styles/PlayerView.style.css: -------------------------------------------------------------------------------- 1 | .PlayerView { 2 | text-align: center; 3 | } 4 | 5 | .PlayerView h1 { 6 | color: black; 7 | } 8 | 9 | .videoborder{ 10 | position: relative; 11 | align-items: center; 12 | justify-content: center; 13 | display: flex; 14 | background-color: aqua; 15 | align-items: center; 16 | border-radius: 10px; 17 | } 18 | 19 | .video-js{ 20 | position: relative; 21 | width: 100%; 22 | box-shadow: 0 20px 60px rgba(0, 0, 0, 0.30); 23 | } 24 | 25 | .PlayerView .videoplayer { 26 | position: relative; 27 | margin: auto; 28 | align-items: center; 29 | justify-content: center; 30 | width: 90%; 31 | top: 0; 32 | left: 0; 33 | z-index: 100; 34 | } 35 | 36 | .PlayerView .debug-container { 37 | padding-top: 10px; 38 | position: relative; 39 | max-width: 1000px; 40 | padding-bottom: 20px; 41 | margin: auto; 42 | } 43 | 44 | .PlayerView .debug-data { 45 | width: 80%; 46 | height: 150px; 47 | margin: auto; 48 | box-shadow: 0 20px 60px rgba(0, 0, 0, 0.30); 49 | justify-content: center; 50 | flex-direction: column; 51 | border: 0rem; 52 | color: #4a4a4a; 53 | font-family: monospace; 54 | background: #fff; 55 | border-radius: 0 0 6px 6px; 56 | display: flex; 57 | flex-shrink: 0; 58 | overflow-x: hidden; 59 | overflow-y: scroll; 60 | flex-direction: column; 61 | } 62 | 63 | .PlayerView .data-line { 64 | display: flex; 65 | flex-shrink: 0; 66 | margin: 0 5px 0 0; 67 | padding: 3px 5px 5px 10px; 68 | word-break: break-all; 69 | transition: all 0.15s ease-in-out; 70 | animation: fadeIn 0.25s 1; 71 | } 72 | 73 | .PlayerView .data-line:last-child { 74 | padding-bottom: 2px; 75 | } 76 | 77 | .PlayerView .debug-data__time { 78 | min-width: 5rem; 79 | font-weight: 300; 80 | font-size: 12px; 81 | padding-right: 5px; 82 | } 83 | 84 | .PlayerView .debug-data__value { 85 | font-weight: bold; 86 | font-size: 12px; 87 | } -------------------------------------------------------------------------------- /frontend/src/components/styles/StreamForm.style.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #484F56; 3 | } 4 | 5 | .textFormivs { 6 | position: relative; 7 | display: flex; 8 | margin: auto; 9 | max-width: 1000px; 10 | flex-direction: column; 11 | } 12 | .textFormivs .form-ivs { 13 | padding-top: 20px; 14 | margin: auto; 15 | } 16 | 17 | .textFormivs .form-URL { 18 | position: relative; 19 | width: auto; 20 | display: flex; 21 | color: #484F56; 22 | box-sizing: border-box; 23 | } 24 | .textFormivs .formLabel { 25 | position: relative; 26 | text-align: center; 27 | color: #484F56; 28 | font-size: 0.8rem;; 29 | } 30 | .textFormivs .formURL { 31 | position: relative; 32 | display: flex; 33 | color: #484F56; 34 | width: auto; 35 | height: 38px; 36 | border-color: #F0F0F0; 37 | border-radius: 6px; 38 | padding-left: 1px ; 39 | font-size: 0.8rem; 40 | } 41 | 42 | .textFormivs .formSelect { 43 | display: flex; 44 | color: #484F56; 45 | width: auto; 46 | height: 38px; 47 | border-color: #F0F0F0; 48 | border-radius: 6px; 49 | font-size: 0.8rem; 50 | } 51 | 52 | .textFormivs .formBot { 53 | position: relative; 54 | display: flex; 55 | background-color: #9146FF; 56 | top: 16px; 57 | border: none; 58 | color: white; 59 | display: inline-block; 60 | font-size: 12px; 61 | height: 40px; 62 | border-radius: 8px; 63 | margin-left: 20px; 64 | } 65 | 66 | .textFormivs .formBot.saved{ 67 | background-color: forestgreen; 68 | } -------------------------------------------------------------------------------- /frontend/src/components/styles/UserMedia.style.css: -------------------------------------------------------------------------------- 1 | body { 2 | text-align: center; 3 | } 4 | 5 | .video-container { 6 | position: relative; 7 | margin: auto; 8 | max-width: 1000px; 9 | width: 90%; 10 | display: flex; 11 | justify-content: center; 12 | flex-direction: column; 13 | padding-bottom: 30px; 14 | margin-bottom: 30px; 15 | } 16 | 17 | .video { 18 | width: 100%; 19 | z-index: -9; 20 | visibility: hidden; 21 | } 22 | 23 | 24 | canvas { 25 | display: flex; 26 | width: 100%; 27 | height: 100%; 28 | position: absolute; 29 | margin: auto; 30 | top: 0; 31 | left: 0; 32 | z-index: 10; 33 | box-shadow: 0 16px 60px rgba(0, 0, 0, 0.30); 34 | } 35 | 36 | .form-control-select { 37 | position: relative; 38 | display: flex; 39 | max-width: 1000px; 40 | text-align: center; 41 | margin: auto; 42 | justify-content: center; 43 | } 44 | .form-control-select .form-control { 45 | box-sizing: border-box; 46 | width: 30%; 47 | margin: 10px; 48 | } 49 | .form-control-select .form-control-play { 50 | width: 540px; 51 | height: 40px; 52 | box-sizing: border-box; 53 | border: 1px solid lightgrey; 54 | border-radius: 5px; 55 | margin: 11px; 56 | } 57 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | import React from 'react'; 5 | import ReactDOM from 'react-dom'; 6 | import App from './components/App'; 7 | 8 | 9 | ReactDOM.render( 10 | 11 | 12 | , 13 | document.getElementById('root') 14 | ); 15 | --------------------------------------------------------------------------------