├── .github └── PULL_REQUEST_TEMPLATE.md ├── LICENSE ├── NOTICE ├── README.md ├── android └── VideoDemo │ ├── app │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── amazonaws │ │ │ └── videodemo │ │ │ └── videodemo │ │ │ └── ExampleInstrumentedTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── amazonaws │ │ │ │ └── videodemo │ │ │ │ └── videodemo │ │ │ │ ├── AuthenticatorActivity.java │ │ │ │ └── MainActivity.java │ │ └── res │ │ │ ├── layout │ │ │ ├── activity_authenticator.xml │ │ │ ├── activity_main.xml │ │ │ └── content_main.xml │ │ │ ├── menu │ │ │ └── menu_main.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── raw │ │ │ └── .keep │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── amazonaws │ │ └── videodemo │ │ └── videodemo │ │ └── ExampleUnitTest.java │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── cloudformation-lambda.yml ├── cloudformation-roles.yml ├── create-project.sh ├── custom-policy.json ├── index.js ├── readme-images ├── app-signin.png ├── app-start.png ├── architecture.png ├── cloudformation-console.png ├── hosting-console.png ├── iam-add-policies.png ├── iam-create-policy.png ├── iam-create-user.png ├── iam-edit-policy.png ├── lambda-console.png ├── servicerole.png └── transcoder-console.png ├── transcoder-setup.sh └── website ├── .babelrc ├── app ├── apple-touch-icon.png ├── favicon.ico ├── index.html ├── robots.txt ├── scripts │ └── main.js └── styles │ └── main.css ├── bower.json ├── gulpfile.js ├── package.json ├── publish.sh └── test ├── index.html └── spec └── test.js /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | AWS Mobile Hub Extensions - Simple Video Transcoding 2 | Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS Mobile Hub Extension - Simple Video Transcoding 2 | 3 | This project includes extensions to an AWS Mobile Hub project. These add capabilities to your Mobile App or Website. 4 | 5 | This extension adds automatic video transcoding of uploaded videos to adaptive multi-bitrate formats. When your mobile app users upload video files to the "userfiles" Amazon S3 bucket of your AWS Mobile Hub backend, the files will automatically be transcoded to HLS (HTTP Live Streaming) format, and they will be placed in the "hosting" Amazon S3 bucket. Your Amazon CloudFront distribution will stream the video files in the downlink direction to devices using adaptive multi-bitrate protocols in order to reduce bandwidth, reduce buffering, and optimize the user experience in watching the videos. 6 | 7 | ![image](readme-images/architecture.png?raw=true) 8 | 9 | This project includes a demo Android Mobile App project which uploads video files and plays the transcoded videos and a demo website which also plays the video files. 10 | 11 | ## Requirements 12 | 13 | * Mac OSX - The extension setup script was built for Mac OSX. It uses 'jq' to parse JSON output from the AWS CLI. It may work on other platforms with minor modification. 14 | * AWS Account - If you do not already have an account, following any of the AWS management console links below will prompt you to setup an account. 15 | * [JQ](https://stedolan.github.io/jq/) - for parsing JSON results from AWS CLI 16 | * [NPM](https://www.npmjs.com/) - for building demo website 17 | * [Gulp](https://gulpjs.com/) - for building demo website 18 | * [Bower](https://bower.io/) - for building demo website 19 | 20 | ## Steps 21 | 22 | * Install AWS CLI 23 | * Setup Permissions 24 | * Create AWS Mobile Hub Project 25 | * Install Mobile Backend Extension 26 | * Build and Deploy Website 27 | * Build Mobile App 28 | * Upload a Video 29 | 30 | ### 1. Install AWS CLI 31 | The setup script in this project uses the AWS CLI (Universal Command Line) tool. You can find installation instructions here: 32 | * [AWS CLI Installation Instructions - https://github.com/aws/aws-cli](https://github.com/aws/aws-cli) 33 | 34 | To verify if the AWS CLI is setup properly, you can run a command like this: 35 | 36 | aws --version 37 | It should output something like this. 38 | 39 | aws-cli/1.11.156 Python/2.7.10 Darwin/15.6.0 botocore/1.7.14 40 | 41 | ### 2. Setup Permissions 42 | 43 | #### Create Custom IAM Policy 44 | 45 | Launch a browser window with the following link: 46 | - [AWS IAM - Create Custom Policy - https://console.aws.amazon.com/iam/home?region=us-east-1#/policies$new](https://console.aws.amazon.com/iam/home?region=us-east-1#/policies$new) 47 | 48 | Select 'Create Your Own Policy'. 49 | 50 | --- 51 | ![image](readme-images/iam-create-policy.png?raw=true) 52 | 53 | --- 54 | 55 | Type a Policy Name and Description and then input the following Policy Document: 56 | 57 | { 58 | "Version": "2012-10-17", 59 | "Statement": [ 60 | { 61 | "Effect": "Allow", 62 | "Action": [ 63 | "cloudformation:*" 64 | ], 65 | "Resource": [ 66 | "arn:aws:cloudformation:*:*:stack/VideoTranscoderLambdaStack/*", 67 | "arn:aws:cloudformation:*:*:stack/VideoTranscoderRolesStack/*" 68 | ] 69 | }, 70 | { 71 | "Effect": "Allow", 72 | "Action": [ 73 | "s3:PutObjectAcl" 74 | ], 75 | "Resource": "arn:aws:s3:::*-mobilehub-*/*" 76 | }, 77 | { 78 | "Effect": "Allow", 79 | "Action": [ 80 | "s3:PutBucketNotification" 81 | ], 82 | "Resource": "arn:aws:s3:::*-mobilehub-*" 83 | }, 84 | { 85 | "Effect": "Allow", 86 | "Action": [ 87 | "iam:AttachRolePolicy", 88 | "iam:CreateRole", 89 | "iam:DeleteRole", 90 | "iam:DeleteRolePolicy", 91 | "iam:DetachRolePolicy", 92 | "iam:PassRole", 93 | "iam:PutRolePolicy" 94 | ], 95 | "Resource": [ 96 | "arn:aws:iam::*:role/TranscoderPipelineRole*", 97 | "arn:aws:iam::*:role/TranscoderLambdaExecutionRole*" 98 | ] 99 | }, 100 | { 101 | "Effect": "Allow", 102 | "Action": [ 103 | "lambda:*" 104 | ], 105 | "Resource": "arn:aws:lambda:*:*:function:TranscoderJobSubmitter" 106 | }, 107 | { 108 | "Effect": "Allow", 109 | "Action": [ 110 | "elastictranscoder:*" 111 | ], 112 | "Resource": "*" 113 | } 114 | ] 115 | } 116 | 117 | This policy is included in the repository as [custom-policy.json](./custom-policy.json). 118 | 119 | Click 'Validate Policy'. If everything is OK, then click 'Create Policy'. 120 | 121 | --- 122 | ![image](readme-images/iam-edit-policy.png?raw=true) 123 | 124 | --- 125 | 126 | #### Create AWS IAM (Identity & Access Management) User 127 | 128 | Launch a browser window with the following link: 129 | - [AWS IAM - Create User - https://console.aws.amazon.com/iam/home?region=us-east-1#/users$new?step=details](https://console.aws.amazon.com/iam/home?region=us-east-1#/users$new?step=details) 130 | 131 | --- 132 | ![image](readme-images/iam-create-user.png?raw=true) 133 | 134 | --- 135 | 136 | Enter a name for the user, select programmatic access and click 'Next: Permissions' 137 | 138 | #### Add Required IAM Policies 139 | 140 | Select 'Attach existing policies directly'. Check the checkbox for the following managed policies. 141 | - DeploymentPolicy (or whatever you called your custom policy in the 'Create Custom IAM Policy' step) 142 | - AWSMobileHub_FullAccess 143 | - IAMReadOnlyAccess 144 | 145 | --- 146 | ![image](readme-images/iam-add-policies.png?raw=true) 147 | 148 | --- 149 | 150 | #### Download Credentials 151 | 152 | Click the 'Show' link under 'Secret access key'. Copy the Access key ID and Secret access key to a file or click the 'Download .csv' link to download the values to your Downloads folder. 153 | 154 | #### Install Credentials in AWS CLI 155 | 156 | aws configure 157 | AWS Access Key ID: 158 | AWS Secret Access Key: 159 | Default region: us-east-1 160 | Default output format [None]: 161 | 162 | #### Verify AWS CLI permissions 163 | 164 | This command will show you which IAM user you are using when you make calls to AWS services from the AWS CLI. It is the AWS CLI equivalent of 'whoami'. 165 | 166 | aws iam get-user 167 | 168 | You should see something like this: 169 | 170 | { 171 | "User": { 172 | "UserName": "AWS-CLI-ACHUD", 173 | "Path": "/", 174 | "CreateDate": "2015-10-28T15:52:47Z", 175 | "UserId": "AIDAJ7A5TZOVP5A4DQ7OE", 176 | "Arn": "arn:aws:iam::207859480101:user/AWS-CLI-ACHUD" 177 | } 178 | } 179 | 180 | #### AWS CLI Multiple Profiles 181 | 182 | The AWS CLI allows you to configure multiple profiles for different IAM users. If you already have some AWS CLI profiles, you can rename them by editing the ~/.aws/credentials file. The instructions here use your 'default' profile. In general, you can run any AWS CLI command with a specific profile, like this: 183 | 184 | aws --profile some-other-profile-name iam get-user 185 | 186 | ### 3. Create an AWS Mobile Hub Project 187 | 188 | Choose a project name and region. The following command will create the project and enable User Sign-in, User Data Storage, and Hosting & Streaming in the AWS Mobile Hub Project. 189 | 190 | Note: Alternatively, you can run the 'create-project.sh' script contained in this repository. 191 | 192 | aws mobile create-project --snapshot-id yvsue7ksnvy9fo --name "Video Transcoder" --project-region us-east-1 193 | 194 | The command will output information about your project. 195 | 196 | { 197 | "details": { 198 | "name": "Video Transcoder", 199 | "projectId": "abc1234-ce7d-4093-b630-276a8505da3d", 200 | "region": "us-east-1", 201 | "state": "NORMAL", 202 | "consoleUrl": "https://console.aws.amazon.com/mobilehub/home#/abc1234-ce7d-4093-b630-276a8505da3d/build", 203 | "lastUpdatedDate": 1507827668.767, 204 | "createdDate": 1507827668.767, 205 | ... 206 | If this is your first time using AWS Mobile Hub, you may see an error like this: 207 | 208 | An error occurred (UnauthorizedException) when calling the 209 | CreateProject operation: You must first enable Mobile Hub 210 | in your account before using the service. 211 | 212 | Visit the below address to get started: 213 | https://console.aws.amazon.com/mobilehub/home?#/activaterole/ 214 | If this happens, simply follow link and click "Yes, activate role" to enable AWS Mobile Hub in your AWS account. 215 | 216 | --- 217 | ![image](readme-images/servicerole.png?raw=true) 218 | 219 | --- 220 | 221 | #### (Optional) Examine your AWS Mobile Hub project in the console. 222 | 223 | Take the "consoleUrl" value from the previous command and open it in a browser. 224 | 225 | open https://console.aws.amazon.com/mobilehub/home#/abc1234-ce7d-4093-b630-276a8505da3d/build 226 | 227 | #### Learn about your AWS Resources 228 | 229 | Your AWS Mobile Hub project created a number of AWS resources and configured them to work together. You can view these resources by using the "Resources" link in the Mobile Hub console (previous command). Here are some of the more important resources that were created. 230 | 231 | - Amazon Cognito User Pool - Your user pool allows your app users to authenticate (sign-in) to your mobile app. It also provides Multi-Factor Authentication (MFA) which uses a secondary channel, such as Email or SMS to verify the user's identity. 232 | 233 | - Amazon Cognito Identity Pool - Your identity pool federates user identities across all your identity providers, such as your user pool. You may enable other identity providers, such as Facebook, Google, or Active Directory (using SAML). The identity pool links these identities together, if for example, you allow your users to sign-in with more than one provider. It also provides credentials to your users with permissions provided by your IAM roles. 234 | 235 | - Amazon Identity & Access Management (IAM) Roles - Each role provides a set of permissions which are encapsulated in the attached IAM policies. 236 | - Unauth Role - This role is assumed by users of your app who are not signed in (guess access). It provides limited permissions for things like reporting analytics metrics. 237 | - Auth Role - This role is assumed by your signed-in users. It provides fine-grained access to resources, e.g., each user has their own folders in S3. 238 | - Lambda Execution Role - This role is assumed by AWS Lambda when your functions execute. It provides your lambda with the permissions it needs to access your resources, e.g., to read files from S3. 239 | 240 | 241 | - Amazon Identity & Access Management (IAM) policies - Each IAM role has a number of policies attached to it. These policies provide permissions to access AWS resources and in some cases, they provide fine-grained access control, such as restricting write access to only the specific user's folders in S3. 242 | 243 | - Amazon Simple Storage Service (S3) Buckets - Each S3 bucket is a repository of files in the cloud. 244 | - UserFiles - The UserFiles S3 bucket in an AWS Mobile Hub project provides specific behaviors in terms of access control. It contains the following folders: 245 | - uploads/ - Write by any app user. Readable by no one. 246 | - public/ - Readable/Writeable by any app user. 247 | - private/_user-identity_/ - Each user has their own private folder which is only readable/writeable by that user. 248 | - protected/_user-identity_/ - Each user has their own protected folder which is only writeable by that user, but may be read by any app user. 249 | 250 | - Hosting - The Hosting S3 bucket is a publicly accessible repository where you make files available for website hosting. App users have no permissions to write to this folder; they can only read. 251 | 252 | - Deployments - The Deployments S3 bucket is used to deploy build artifacts to AWS Lambda and Amazon API Gateway. 253 | 254 | - Amazon CloudFront Distribution - Your CloudFront distribution is an edge-cache on top of your Hosting S3 bucket. It provides fast access to cached content from the AWS global POP (points of presence) locations around the globe. It also has some built-in media streaming capabilities. 255 | 256 | #### (Optional) List your AWS Mobile Hub project details. 257 | 258 | aws mobile describe-project --project-id abc1234-ce7d-4093-b630-276a8505da3d 259 | { 260 | "details": { 261 | "name": "Video Transcoder", 262 | "projectId": "abc1234-ce7d-4093-b630-276a8505da3d", 263 | "region": "us-east-1", 264 | "state": "NORMAL", 265 | "consoleUrl": "https://console.aws.amazon.com/mobilehub/home#/abc1234-ce7d-4093-b630-276a8505da3d/build", 266 | "lastUpdatedDate": 1507827668.767, 267 | "createdDate": 1507827668.767, 268 | ... 269 | 270 | ### 4. Install Mobile Backend Extension 271 | 272 | #### Run the extension setup script 273 | 274 | chmod a+x ./transcoder-setup.sh 275 | ./transcoder-setup.sh 276 | 277 | The script will show you the list of AWS Mobile Hub projects you have available. 278 | 279 | ... 280 | { 281 | "projects": 282 | [ 283 | { 284 | "projectId": "abc123-801e-407b-a460-700eb4bc92cb", 285 | "name": "Video Transcoder" 286 | } 287 | ] 288 | } 289 | 290 | You can also list projects with this command. 291 | 292 | aws mobile list-projects 293 | 294 | #### Run the extension setup script with your Project ID 295 | 296 | Select the project you want to add the extension to and pass that into the script. 297 | 298 | ./transcoder-setup.sh abc123-801e-407b-a460-700eb4bc92cb 299 | 300 | The script will execute a number of commands using the AWS CLI. Each command it executes is printed with "EXECUTE>" shown as a prefix, like this. 301 | 302 | EXECUTE> aws cloudformation describe-stacks --stack-name VideoTranscoderRolesStack 303 | 304 | This will setup all the backend resources you need. When it is done, you should see this. 305 | 306 | ... 307 | DONE -- SUCCESS 308 | 309 | This will launch a browser window on the AWS Mobile Hub Hosting and Streaming feature page of your project, which contains links to launch your hosted website for both your (non-cached) Amazon S3 domain and (cached) Amazon CloudFront domain. 310 | 311 | --- 312 | ![image](readme-images/hosting-console.png?raw=true) 313 | 314 | --- 315 | 316 | This script also downloaded and install the following configuration files for your project. 317 | 318 | - __website/app/scripts/aws-config.js__ - Provides the demo website with configuration information about your project resources. 319 | 320 | - __android/VideoDemo/app/src/main/res/raw/awsconfiguration.json__ - Provides the demo Android app with configuration information about your project resources. 321 | 322 | #### (Optional) Examine your AWS CloudFormation stacks 323 | 324 | If you'd like to examine your AWS CloudFormation stacks, you can follow this link: 325 | - [AWS CloudFormation Console - https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks?filter=active](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks?filter=active) 326 | 327 | --- 328 | ![image](readme-images/cloudformation-console.png?raw=true) 329 | 330 | --- 331 | #### (Optional) Check the status of your Amazon ElasticTranscoder Jobs 332 | 333 | If you'd like to see the status of your video transcoding jobs, you can follow this link: 334 | - [Amazon Elastic Transcoder Console - https://console.aws.amazon.com/elastictranscoder/home?region=us-east-1#pipelines:](https://console.aws.amazon.com/elastictranscoder/home?region=us-east-1#pipelines:) 335 | 336 | --- 337 | ![image](readme-images/transcoder-console.png?raw=true) 338 | 339 | --- 340 | #### (Optional) Check your AWS Lambda function logs 341 | 342 | If you'd like to see the logs for your AWS Lambda function which kicks off the transcoder jobs, you can follow this link: 343 | - [AWS CloudWatch Logs - https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#logEventViewer:group=/aws/lambda/TranscoderJobSubmitter](https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#logEventViewer:group=/aws/lambda/TranscoderJobSubmitter) 344 | 345 | #### (Optional) Edit your AWS Lambda FunctionName 346 | 347 | If you'd like to edit your AWS Lambda function, you can follow this link: 348 | - [AWS Lambda Console - https://console.aws.amazon.com/lambda/home?region=us-east-1#/functions/TranscoderJobSubmitter?tab=configuration](https://console.aws.amazon.com/lambda/home?region=us-east-1#/functions/TranscoderJobSubmitter?tab=configuration) 349 | 350 | --- 351 | ![image](readme-images/lambda-console.png?raw=true) 352 | 353 | --- 354 | 355 | 356 | ### 5. Build and Deploy Website 357 | 358 | #### Install NPM packages 359 | 360 | cd website 361 | npm install --save-dev 362 | node_modules/.bin/bower install 363 | 364 | #### Build website 365 | 366 | node_modules/.bin/gulp 367 | 368 | #### Publish website distribution 369 | 370 | chmod a+x ./publish.sh 371 | ./publish.sh 372 | 373 | The script will show you the list of AWS Mobile Hub projects if you don't specify one. 374 | 375 | ... 376 | { 377 | "projects": 378 | [ 379 | { 380 | "projectId": "abc123-801e-407b-a460-700eb4bc92cb", 381 | "name": "Video Transcoder" 382 | } 383 | ] 384 | } 385 | 386 | You can also list projects with this command. 387 | 388 | aws mobile list-projects 389 | 390 | Run the publish script with your Project ID. 391 | 392 | ./publish.sh abc123-801e-407b-a460-700eb4bc92cb 393 | 394 | This will build your website using Gulp and copy the distribution (dist folder) contents into your project's 'hosting' Amazon S3 bucket. 395 | 396 | #### Restrict CORS - Cross-Origin Resource Sharing 397 | 398 | This project creates an Amazon S3 bucket which you can use to host assets for a website. The bucket is created with a wildcarded CORS policy. This policy removes any restrictions from scripts running in the website accessing contents in other domains. 399 | 400 | 401 | 402 | 403 | * 404 | HEAD 405 | GET 406 | PUT 407 | POST 408 | DELETE 409 | 3000 410 | x-amz-server-side-encryption 411 | x-amz-request-id 412 | x-amz-id-2 413 | * 414 | 415 | 416 | 417 | If you plan to use this to host a production website, then you should scope this policy down to allow only the domains and methods that your site requires. You can edit this policy in the Amazon S3 console under "Permissions -> CORS configuration". Simply clone the "*" domain rule once for each domain your website uses and remove any methods you do not want to allow. 418 | 419 | ### 6. Build Mobile App 420 | 421 | #### Open the Project 422 | Start [Android Studio](https://developer.android.com/studio/index.html). Choose "Import project (Gradle, Eclipse ADT, etc.)" and select the "android/VideoDemo/build.gradle" file. 423 | 424 | #### Run the App 425 | 426 | Click the play button to launch the app in the Android Emulator. The app will prompt you to create a user account and then sign in with the user account. This uses your Amazon Cognito User Pool which was created as part of your AWS Mobile Hub project. 427 | 428 | Once you create an account, you can see details of the created user in the Amazon Cognito console. 429 | * [Amazon Cognito User Pools Console - https://console.aws.amazon.com/cognito/users/?](https://console.aws.amazon.com/cognito/users/?) 430 | 431 | --- 432 | ![image](readme-images/app-signin.png?raw=true) 433 | 434 | --- 435 | 436 | When you first start the app, it will attempt to read the index of available video content from the 'hosting' Amazon S3 bucket that was created as part of your AWS Mobile Hub project. Initially, there is no content, so you will see a message that says 'CONTENT INDEX IS NOT AVAILABLE'. It will look for an update to the content index in S3 every 30 seconds. 437 | 438 | --- 439 | ![image](readme-images/app-start.png?raw=true) 440 | 441 | --- 442 | 443 | ### Upload a Video 444 | 445 | Click the '+' button to capture a video file to upload. This will upload the file to the 'userfiles' Amazon S3 bucket. It will trigger the AWS Lambda function that creates a job in Amazon Elastic Transcoder. The lambda function will also create a new record in the content index file (content/index.json) in your 'hosting' Amazon S3 bucket. The app will read the new index file and play the new video file after it has been transcoded to HLS (HTTP Live Streaming) format. 446 | 447 | Alternatively, you can use the generated 'upload.sh' script to upload a video file to your 'userfiles' S3 bucket like this... 448 | 449 | ./upload.sh example-video.mp4 450 | 451 | ## Talk to Us 452 | 453 | * [AWS Mobile Developer Forum - https://forums.aws.amazon.com/forum.jspa?forumID=88](https://forums.aws.amazon.com/forum.jspa?forumID=88) 454 | 455 | ## Author 456 | 457 | Andrew Chud - Amazon Web Services 458 | 459 | ## License 460 | 461 | This library is licensed under the Apache 2.0 License. See the [**LICENSE**](./LICENSE) file for more info. 462 | 463 | ## Attribution 464 | 465 | This project makes use of the following projects which are available under Apache 2.0 license. 466 | - [Hls.js - https://github.com/video-dev/hls.js/](https://github.com/video-dev/hls.js/) 467 | - [ExoMedia - https://github.com/brianwernick/ExoMedia](https://github.com/brianwernick/ExoMedia) 468 | -------------------------------------------------------------------------------- /android/VideoDemo/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | /* 4 | # Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | */ 18 | 19 | android { 20 | compileSdkVersion 27 21 | buildToolsVersion '27' 22 | defaultConfig { 23 | applicationId "com.amazonaws.videodemo.videodemo" 24 | minSdkVersion 23 25 | targetSdkVersion 27 26 | versionCode 1 27 | versionName "1.0" 28 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 29 | } 30 | buildTypes { 31 | release { 32 | minifyEnabled false 33 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 34 | } 35 | } 36 | } 37 | 38 | dependencies { 39 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 40 | compile fileTree(dir: 'libs', include: ['*.jar']) 41 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 42 | exclude group: 'com.android.support', module: 'support-annotations' 43 | }) 44 | compile 'com.android.support:appcompat-v7:27.0.1' 45 | compile 'com.android.support:design:27.0.1' 46 | compile 'com.devbrackets.android:exomedia:4.0.3' 47 | 48 | // Mobile Client for initializing the SDK 49 | compile('com.amazonaws:aws-android-sdk-mobile-client:2.6.+@aar') { transitive = true; } 50 | 51 | // Cognito UserPools for SignIn 52 | compile 'com.android.support:support-v4:27.0.1' 53 | compile('com.amazonaws:aws-android-sdk-auth-userpools:2.6.+@aar') { transitive = true; } 54 | 55 | // Sign in UI Library 56 | compile('com.amazonaws:aws-android-sdk-auth-ui:2.6.+@aar') { transitive = true; } 57 | 58 | // Amazon S3 59 | compile 'com.amazonaws:aws-android-sdk-s3:2.6.+' 60 | 61 | testCompile 'junit:junit:4.12' 62 | } 63 | -------------------------------------------------------------------------------- /android/VideoDemo/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/achud/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /android/VideoDemo/app/src/androidTest/java/com/amazonaws/videodemo/videodemo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.amazonaws.videodemo.videodemo; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.amazonaws.videodemo.videodemo", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /android/VideoDemo/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 48 | 49 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /android/VideoDemo/app/src/main/java/com/amazonaws/videodemo/videodemo/AuthenticatorActivity.java: -------------------------------------------------------------------------------- 1 | package com.amazonaws.videodemo.videodemo; 2 | 3 | /* 4 | # Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | */ 18 | 19 | import android.app.Activity; 20 | import android.os.Bundle; 21 | 22 | import com.amazonaws.mobile.auth.ui.SignInUI; 23 | import com.amazonaws.mobile.client.AWSMobileClient; 24 | import com.amazonaws.mobile.client.AWSStartupHandler; 25 | import com.amazonaws.mobile.client.AWSStartupResult; 26 | 27 | public class AuthenticatorActivity extends Activity { 28 | @Override 29 | protected void onCreate(final Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | setContentView(R.layout.activity_authenticator); 32 | 33 | AWSMobileClient.getInstance().initialize(this, new AWSStartupHandler() { 34 | @Override 35 | public void onComplete(final AWSStartupResult awsStartupResult) { 36 | SignInUI signin = (SignInUI) AWSMobileClient.getInstance().getClient(AuthenticatorActivity.this, SignInUI.class); 37 | signin.login(AuthenticatorActivity.this, MainActivity.class).execute(); 38 | } 39 | }).execute(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /android/VideoDemo/app/src/main/java/com/amazonaws/videodemo/videodemo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.amazonaws.videodemo.videodemo; 2 | /* 3 | # Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | */ 17 | 18 | import android.Manifest; 19 | import android.app.Activity; 20 | import android.content.DialogInterface; 21 | import android.content.Intent; 22 | import android.content.pm.PackageManager; 23 | import android.database.Cursor; 24 | import android.net.Uri; 25 | import android.os.Bundle; 26 | import android.os.Handler; 27 | import android.provider.MediaStore; 28 | import android.support.design.widget.FloatingActionButton; 29 | import android.support.v7.app.AlertDialog; 30 | import android.support.v7.app.AppCompatActivity; 31 | import android.support.v7.widget.Toolbar; 32 | import android.util.Log; 33 | import android.view.View; 34 | import android.view.Menu; 35 | import android.view.MenuItem; 36 | import android.widget.TextView; 37 | 38 | import com.amazonaws.mobile.auth.core.IdentityHandler; 39 | import com.amazonaws.mobile.auth.core.IdentityManager; 40 | import com.amazonaws.mobileconnectors.s3.transferutility.TransferListener; 41 | import com.amazonaws.mobileconnectors.s3.transferutility.TransferObserver; 42 | import com.amazonaws.mobileconnectors.s3.transferutility.TransferState; 43 | import com.amazonaws.mobileconnectors.s3.transferutility.TransferUtility; 44 | import com.amazonaws.regions.Region; 45 | import com.amazonaws.services.s3.AmazonS3; 46 | import com.amazonaws.services.s3.AmazonS3Client; 47 | import com.amazonaws.util.IOUtils; 48 | import com.devbrackets.android.exomedia.listener.OnCompletionListener; 49 | import com.devbrackets.android.exomedia.listener.OnErrorListener; 50 | import com.devbrackets.android.exomedia.ui.widget.VideoView; 51 | import com.devbrackets.android.exomedia.listener.OnPreparedListener; 52 | import com.amazonaws.mobile.client.AWSMobileClient; 53 | 54 | import org.json.JSONArray; 55 | import org.json.JSONException; 56 | import org.json.JSONObject; 57 | import org.json.JSONTokener; 58 | 59 | import java.io.File; 60 | import java.io.FileInputStream; 61 | import java.io.IOException; 62 | import java.util.Date; 63 | 64 | public class MainActivity extends AppCompatActivity implements OnPreparedListener, IdentityHandler, OnErrorListener, OnCompletionListener { 65 | private static final String LOG_TAG = MainActivity.class.getSimpleName(); 66 | private static final String CONTENT_INDEX_KEY = "content/index.json"; 67 | private static final String CONTENT_FOLDER = "/content/"; 68 | private static final String CONTENT_FILENAME = "/default.m3u8"; 69 | private static final int REQUEST_VIDEO = 308; // arbitrary request type id 70 | private static final int REQUEST_PERMISSIONS = 403; // arbitrary request type id 71 | 72 | private String userIdentity = ""; 73 | private volatile String loadingContentId = ""; 74 | private volatile String playingContentId = ""; 75 | private VideoView videoView; 76 | 77 | private Handler timerHandler; 78 | private Runnable timerRunnable; 79 | 80 | private void setupVideoView(final String hostingURL, final String contentId) { 81 | // Make sure to use the correct VideoView import 82 | videoView = (VideoView) findViewById(R.id.videoView); 83 | 84 | videoView.reset(); 85 | videoView.setVisibility(View.GONE); 86 | videoView.setOnPreparedListener(this); 87 | videoView.setOnErrorListener(this); 88 | videoView.setOnCompletionListener(this); 89 | 90 | loadingContentId = contentId; 91 | 92 | final Uri uri = 93 | Uri.parse(hostingURL + CONTENT_FOLDER + contentId + CONTENT_FILENAME); 94 | 95 | Log.d(LOG_TAG, "URI: " + uri); 96 | 97 | videoView.setVideoURI(uri); 98 | videoView.requestFocus(); 99 | } 100 | 101 | /** 102 | * Called when video is done loading and is ready to play. 103 | */ 104 | @Override 105 | public void onPrepared() { 106 | Log.d(LOG_TAG, "onPrepared"); 107 | 108 | playingContentId = loadingContentId; 109 | 110 | ((TextView) findViewById(R.id.textView_playing)) 111 | .setText("NOW PLAYING\n" + playingContentId + "\n"); 112 | 113 | videoView.setVisibility(View.VISIBLE); 114 | videoView.start(); 115 | } 116 | 117 | /** 118 | * Called when Activity is created. 119 | * 120 | * @param savedInstanceState bundle for state if resuming 121 | */ 122 | @Override 123 | protected void onCreate(final Bundle savedInstanceState) { 124 | super.onCreate(savedInstanceState); 125 | setContentView(R.layout.activity_main); 126 | 127 | final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 128 | setSupportActionBar(toolbar); 129 | 130 | final FloatingActionButton uploadButton = (FloatingActionButton) findViewById(R.id.uploadButton); 131 | uploadButton.setOnClickListener(new View.OnClickListener() { 132 | @Override 133 | public void onClick(final View view) { 134 | 135 | if (videoView != null) { 136 | videoView.pause(); 137 | } 138 | 139 | recordVideo(); 140 | } 141 | }); 142 | 143 | AWSMobileClient.getInstance().initialize(this).execute(); 144 | final IdentityManager identityManager = IdentityManager.getDefaultIdentityManager(); 145 | identityManager.getUserID(this); 146 | } 147 | 148 | @Override 149 | public boolean onCreateOptionsMenu(Menu menu) { 150 | // Inflate the menu; this adds items to the action bar if it is present. 151 | getMenuInflater().inflate(R.menu.menu_main, menu); 152 | return true; 153 | } 154 | 155 | @Override 156 | public boolean onOptionsItemSelected(MenuItem item) { 157 | // Handle action bar item clicks here. The action bar will 158 | // automatically handle clicks on the Home/Up button, so long 159 | // as you specify a parent activity in AndroidManifest.xml. 160 | int id = item.getItemId(); 161 | 162 | //noinspection SimplifiableIfStatement 163 | if (id == R.id.action_settings) { 164 | return true; 165 | } 166 | return super.onOptionsItemSelected(item); 167 | } 168 | 169 | @Override 170 | protected void onResume() { 171 | super.onResume(); 172 | Log.d(LOG_TAG, "onResume"); 173 | startTimer(); 174 | } 175 | 176 | @Override 177 | protected void onPause() { 178 | super.onPause(); 179 | Log.d(LOG_TAG, "onPause"); 180 | stopTimer(); 181 | } 182 | 183 | private void startTimer() { 184 | stopTimer(); 185 | timerHandler = new Handler(); 186 | 187 | timerRunnable = new Runnable() { 188 | @Override 189 | public void run() { 190 | Log.d(LOG_TAG, "timer expired"); 191 | reloadContentIndex(); 192 | timerHandler.postDelayed(this, 30000); 193 | } 194 | }; 195 | 196 | timerHandler.post(timerRunnable); 197 | } 198 | 199 | private void stopTimer() { 200 | if (timerHandler != null) { 201 | timerHandler.removeCallbacks(timerRunnable); 202 | timerHandler = null; 203 | } 204 | } 205 | 206 | private void reloadContentIndex() { 207 | Log.d(LOG_TAG, "reloadContentIndex"); 208 | 209 | try { 210 | final JSONObject contentManagerConfig = 211 | AWSMobileClient.getInstance() 212 | .getConfiguration() 213 | .optJsonObject("ContentManager"); 214 | final String bucket = contentManagerConfig.getString("Bucket"); 215 | final String region = contentManagerConfig.getString("Region"); 216 | final String cloudFrontURL = contentManagerConfig.getString("CloudFrontURL"); 217 | 218 | final File outputDir = getCacheDir(); 219 | final File outputFile = File.createTempFile("index", ".json", outputDir); 220 | 221 | final AmazonS3 s3 = 222 | new AmazonS3Client(AWSMobileClient.getInstance().getCredentialsProvider()); 223 | s3.setRegion(Region.getRegion(region)); 224 | final TransferUtility transferUtility = 225 | new TransferUtility(s3, getApplicationContext()); 226 | final TransferObserver observer = transferUtility.download( 227 | bucket, 228 | CONTENT_INDEX_KEY, 229 | outputFile); 230 | 231 | observer.setTransferListener(new TransferListener() { 232 | @Override 233 | public void onStateChanged(final int id, final TransferState state) { 234 | Log.d(LOG_TAG, "S3 Download state change : " + state); 235 | 236 | if (TransferState.COMPLETED == state) { 237 | 238 | try { 239 | final String contentsIndex = 240 | IOUtils.toString(new FileInputStream(outputFile)); 241 | 242 | final JSONArray jsonArray = 243 | (JSONArray) new JSONTokener(contentsIndex).nextValue(); 244 | 245 | Log.d(LOG_TAG, "CONTENTS INDEX = " + jsonArray); 246 | 247 | if (jsonArray.length() <= 0) { 248 | this.onError(id, new IllegalStateException("No videos available.")); 249 | return; 250 | } 251 | 252 | final String contentId = jsonArray.getString(0); 253 | 254 | Log.d(LOG_TAG, "Playing Content ID : " + playingContentId); 255 | 256 | if (!contentId.equalsIgnoreCase(playingContentId)) { 257 | Log.d(LOG_TAG, "New Content ID : " + contentId); 258 | setupVideoView(cloudFrontURL, contentId); 259 | } 260 | 261 | } catch (final IOException | JSONException e) { 262 | this.onError(id, e); 263 | } 264 | } 265 | } 266 | 267 | @Override 268 | public void onProgressChanged(final int id, final long bytesCurrent, final long bytesTotal) { 269 | Log.d(LOG_TAG, "S3 Download progress : " + bytesCurrent); 270 | } 271 | 272 | @Override 273 | public void onError(final int id, final Exception ex) { 274 | Log.e(LOG_TAG, "FAILED : " + ex.getMessage(), ex); 275 | 276 | if (ex.getMessage().contains("key does not exist")) { 277 | Log.d(LOG_TAG, "No content index"); 278 | ((TextView) findViewById(R.id.textView_playing)) 279 | .setText("CONTENT INDEX IS NOT AVAILABLE\n"); 280 | return; 281 | } 282 | } 283 | }); 284 | } catch (final JSONException | IOException e) { 285 | Log.e(LOG_TAG, e.getMessage(), e); 286 | } 287 | } 288 | 289 | /** 290 | * Called when Amazon Cognito User Identity has been loaded. 291 | * 292 | * @param identityId user identity 293 | */ 294 | @Override 295 | public void onIdentityId(final String identityId) { 296 | Log.d(LOG_TAG, "Identity : " + identityId); 297 | 298 | userIdentity = identityId; 299 | ((TextView) findViewById(R.id.textView_userId)) 300 | .setText("Amazon Cognito Identity\n" + userIdentity); 301 | } 302 | 303 | /** 304 | * Called when an error occurs while trying to load Amazon Cognito User Identity. 305 | * 306 | * @param exception exception 307 | */ 308 | @Override 309 | public void handleError(final Exception exception) { 310 | Log.e(LOG_TAG, exception.getMessage(), exception); 311 | } 312 | 313 | /** 314 | * Called when video view encounters an error. 315 | * 316 | * @param e exception 317 | * @return true if the error is handled, else false 318 | */ 319 | @Override 320 | public boolean onError(final Exception e) { 321 | Log.e(LOG_TAG, "Video Load Failed: " + e.getMessage(), e); 322 | 323 | ((TextView) findViewById(R.id.textView_playing)) 324 | .setText("COMING SOON\n" + loadingContentId + "\n"); 325 | 326 | return false; 327 | } 328 | 329 | @Override 330 | public void onActivityResult(int requestCode, int resultCode, final Intent data) { 331 | Log.d(LOG_TAG, "onActivityResult: " + resultCode + " " + data); 332 | 333 | if (resultCode == Activity.RESULT_OK) { 334 | if (requestCode == REQUEST_VIDEO) { 335 | final Uri videoUri = data.getData(); 336 | final String[] projection = {MediaStore.Video.VideoColumns.DATA}; 337 | final Cursor cursor = getContentResolver().query(videoUri, projection, null, null, null); 338 | 339 | int vidsCount = 0; 340 | String filename = null; 341 | 342 | if (cursor != null) { 343 | cursor.moveToFirst(); 344 | vidsCount = cursor.getCount(); 345 | 346 | do { 347 | filename = cursor.getString(0); 348 | } while (cursor.moveToNext()); 349 | 350 | final File file = new File(filename); 351 | 352 | if (file != null && 353 | file.exists() && 354 | file.length() > 0) { 355 | Log.d(LOG_TAG, "Video File : " + file.getName()); 356 | uploadVideoFile(file); 357 | } else { 358 | Log.d(LOG_TAG, "No video produced."); 359 | } 360 | } else { 361 | Log.d(LOG_TAG, "No video produced."); 362 | } 363 | } 364 | } 365 | } 366 | 367 | private void recordVideo() { 368 | if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED || 369 | checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { 370 | requestPermissions(new String[]{Manifest.permission.CAMERA, 371 | Manifest.permission.READ_EXTERNAL_STORAGE}, 372 | REQUEST_PERMISSIONS); 373 | return; 374 | } 375 | 376 | launchVideoRecord(); 377 | } 378 | 379 | @Override 380 | public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { 381 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 382 | if (requestCode == REQUEST_PERMISSIONS) { 383 | if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { 384 | launchVideoRecord(); 385 | } else { 386 | Log.e(LOG_TAG, "No permissions to use camera."); 387 | } 388 | } 389 | } 390 | 391 | private void launchVideoRecord() { 392 | final Intent intent = 393 | new Intent(MediaStore.ACTION_VIDEO_CAPTURE); 394 | 395 | if (intent.resolveActivity(getPackageManager()) != null) { 396 | startActivityForResult(intent, REQUEST_VIDEO); 397 | } 398 | } 399 | 400 | private void uploadVideoFile(final File file) { 401 | Log.d(LOG_TAG, "uploadVideoFile: " + file); 402 | 403 | final Activity activity = this; 404 | 405 | try { 406 | final JSONObject contentManagerConfig = 407 | AWSMobileClient.getInstance() 408 | .getConfiguration() 409 | .optJsonObject("S3TransferUtility"); 410 | final String bucket = contentManagerConfig.getString("Bucket"); 411 | final String region = contentManagerConfig.getString("Region"); 412 | 413 | final AmazonS3 s3 = 414 | new AmazonS3Client(AWSMobileClient.getInstance().getCredentialsProvider()); 415 | s3.setRegion(Region.getRegion(region)); 416 | final TransferUtility transferUtility = 417 | new TransferUtility(s3, getApplicationContext()); 418 | final String objectKey = "private/" + userIdentity + "/" + (new Date()).getTime() + ".mp4"; 419 | final TransferObserver observer = transferUtility.upload( 420 | bucket, 421 | objectKey, 422 | file); 423 | 424 | observer.setTransferListener(new TransferListener() { 425 | @Override 426 | public void onStateChanged(final int id, final TransferState state) { 427 | Log.d(LOG_TAG, "S3 Upload state change : " + state); 428 | 429 | if (TransferState.COMPLETED == state) { 430 | final AlertDialog.Builder builder = 431 | new AlertDialog.Builder(activity); 432 | builder.setTitle("Video Upload") 433 | .setMessage("Your file has been uploaded.") 434 | .setPositiveButton(android.R.string.ok, 435 | new DialogInterface.OnClickListener() { 436 | public void onClick(DialogInterface dialog, int which) { 437 | if (videoView != null) { 438 | videoView.restart(); 439 | } 440 | } 441 | }) 442 | .setIcon(android.R.drawable.ic_dialog_alert) 443 | .show(); 444 | } 445 | } 446 | 447 | @Override 448 | public void onProgressChanged(final int id, final long bytesCurrent, final long bytesTotal) { 449 | Log.d(LOG_TAG, "S3 Upload progress : " + bytesCurrent); 450 | } 451 | 452 | @Override 453 | public void onError(final int id, final Exception ex) { 454 | Log.e(LOG_TAG, "FAILED : " + ex.getMessage(), ex); 455 | 456 | final AlertDialog.Builder builder = 457 | new AlertDialog.Builder(activity); 458 | builder.setTitle("Video Upload") 459 | .setMessage("Your file upload has failed : " + ex.getMessage()) 460 | .setPositiveButton(android.R.string.ok, 461 | new DialogInterface.OnClickListener() { 462 | public void onClick(DialogInterface dialog, int which) { 463 | if (videoView != null) { 464 | videoView.restart(); 465 | } 466 | } 467 | }) 468 | .setIcon(android.R.drawable.ic_dialog_alert) 469 | .show(); 470 | } 471 | }); 472 | } catch (final JSONException e) { 473 | Log.e(LOG_TAG, e.getMessage(), e); 474 | } 475 | } 476 | 477 | @Override 478 | public void onCompletion() { 479 | Log.d(LOG_TAG, "onCompletion"); 480 | videoView.restart(); 481 | } 482 | } 483 | -------------------------------------------------------------------------------- /android/VideoDemo/app/src/main/res/layout/activity_authenticator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | 18 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /android/VideoDemo/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | 18 | 25 | 26 | 32 | 33 | 40 | 41 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /android/VideoDemo/app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | 18 | 26 | 27 | 30 | 31 | 38 | 39 | 47 | 48 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /android/VideoDemo/app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /android/VideoDemo/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-mobile-simple-video-transcoding/8ca2469749a0f29085cd1b0413d2d235cc5ba9c7/android/VideoDemo/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/VideoDemo/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-mobile-simple-video-transcoding/8ca2469749a0f29085cd1b0413d2d235cc5ba9c7/android/VideoDemo/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/VideoDemo/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-mobile-simple-video-transcoding/8ca2469749a0f29085cd1b0413d2d235cc5ba9c7/android/VideoDemo/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/VideoDemo/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-mobile-simple-video-transcoding/8ca2469749a0f29085cd1b0413d2d235cc5ba9c7/android/VideoDemo/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/VideoDemo/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-mobile-simple-video-transcoding/8ca2469749a0f29085cd1b0413d2d235cc5ba9c7/android/VideoDemo/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/VideoDemo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-mobile-simple-video-transcoding/8ca2469749a0f29085cd1b0413d2d235cc5ba9c7/android/VideoDemo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/VideoDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-mobile-simple-video-transcoding/8ca2469749a0f29085cd1b0413d2d235cc5ba9c7/android/VideoDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/VideoDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-mobile-simple-video-transcoding/8ca2469749a0f29085cd1b0413d2d235cc5ba9c7/android/VideoDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/VideoDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-mobile-simple-video-transcoding/8ca2469749a0f29085cd1b0413d2d235cc5ba9c7/android/VideoDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/VideoDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-mobile-simple-video-transcoding/8ca2469749a0f29085cd1b0413d2d235cc5ba9c7/android/VideoDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/VideoDemo/app/src/main/res/raw/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-mobile-simple-video-transcoding/8ca2469749a0f29085cd1b0413d2d235cc5ba9c7/android/VideoDemo/app/src/main/res/raw/.keep -------------------------------------------------------------------------------- /android/VideoDemo/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /android/VideoDemo/app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 180dp 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /android/VideoDemo/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Video Demo 3 | 4 | "Material is the metaphor.\n\n" 5 | 6 | "A material metaphor is the unifying theory of a rationalized space and a system of motion." 7 | "The material is grounded in tactile reality, inspired by the study of paper and ink, yet " 8 | "technologically advanced and open to imagination and magic.\n" 9 | "Surfaces and edges of the material provide visual cues that are grounded in reality. The " 10 | "use of familiar tactile attributes helps users quickly understand affordances. Yet the " 11 | "flexibility of the material creates new affordances that supercede those in the physical " 12 | "world, without breaking the rules of physics.\n" 13 | "The fundamentals of light, surface, and movement are key to conveying how objects move, " 14 | "interact, and exist in space and in relation to each other. Realistic lighting shows " 15 | "seams, divides space, and indicates moving parts.\n\n" 16 | 17 | "Bold, graphic, intentional.\n\n" 18 | 19 | "The foundational elements of print based design typography, grids, space, scale, color, " 20 | "and use of imagery guide visual treatments. These elements do far more than please the " 21 | "eye. They create hierarchy, meaning, and focus. Deliberate color choices, edge to edge " 22 | "imagery, large scale typography, and intentional white space create a bold and graphic " 23 | "interface that immerse the user in the experience.\n" 24 | "An emphasis on user actions makes core functionality immediately apparent and provides " 25 | "waypoints for the user.\n\n" 26 | 27 | "Motion provides meaning.\n\n" 28 | 29 | "Motion respects and reinforces the user as the prime mover. Primary user actions are " 30 | "inflection points that initiate motion, transforming the whole design.\n" 31 | "All action takes place in a single environment. Objects are presented to the user without " 32 | "breaking the continuity of experience even as they transform and reorganize.\n" 33 | "Motion is meaningful and appropriate, serving to focus attention and maintain continuity. " 34 | "Feedback is subtle yet clear. Transitions are efficient yet coherent.\n\n" 35 | 36 | "3D world.\n\n" 37 | 38 | "The material environment is a 3D space, which means all objects have x, y, and z " 39 | "dimensions. The z-axis is perpendicularly aligned to the plane of the display, with the " 40 | "positive z-axis extending towards the viewer. Every sheet of material occupies a single " 41 | "position along the z-axis and has a standard 1dp thickness.\n" 42 | "On the web, the z-axis is used for layering and not for perspective. The 3D world is " 43 | "emulated by manipulating the y-axis.\n\n" 44 | 45 | "Light and shadow.\n\n" 46 | 47 | "Within the material environment, virtual lights illuminate the scene. Key lights create " 48 | "directional shadows, while ambient light creates soft shadows from all angles.\n" 49 | "Shadows in the material environment are cast by these two light sources. In Android " 50 | "development, shadows occur when light sources are blocked by sheets of material at " 51 | "various positions along the z-axis. On the web, shadows are depicted by manipulating the " 52 | "y-axis only. The following example shows the card with a height of 6dp.\n\n" 53 | 54 | "Resting elevation.\n\n" 55 | 56 | "All material objects, regardless of size, have a resting elevation, or default elevation " 57 | "that does not change. If an object changes elevation, it should return to its resting " 58 | "elevation as soon as possible.\n\n" 59 | 60 | "Component elevations.\n\n" 61 | 62 | "The resting elevation for a component type is consistent across apps (e.g., FAB elevation " 63 | "does not vary from 6dp in one app to 16dp in another app).\n" 64 | "Components may have different resting elevations across platforms, depending on the depth " 65 | "of the environment (e.g., TV has a greater depth than mobile or desktop).\n\n" 66 | 67 | "Responsive elevation and dynamic elevation offsets.\n\n" 68 | 69 | "Some component types have responsive elevation, meaning they change elevation in response " 70 | "to user input (e.g., normal, focused, and pressed) or system events. These elevation " 71 | "changes are consistently implemented using dynamic elevation offsets.\n" 72 | "Dynamic elevation offsets are the goal elevation that a component moves towards, relative " 73 | "to the component’s resting state. They ensure that elevation changes are consistent " 74 | "across actions and component types. For example, all components that lift on press have " 75 | "the same elevation change relative to their resting elevation.\n" 76 | "Once the input event is completed or cancelled, the component will return to its resting " 77 | "elevation.\n\n" 78 | 79 | "Avoiding elevation interference.\n\n" 80 | 81 | "Components with responsive elevations may encounter other components as they move between " 82 | "their resting elevations and dynamic elevation offsets. Because material cannot pass " 83 | "through other material, components avoid interfering with one another any number of ways, " 84 | "whether on a per component basis or using the entire app layout.\n" 85 | "On a component level, components can move or be removed before they cause interference. " 86 | "For example, a floating action button (FAB) can disappear or move off screen before a " 87 | "user picks up a card, or it can move if a snackbar appears.\n" 88 | "On the layout level, design your app layout to minimize opportunities for interference. " 89 | "For example, position the FAB to one side of stream of a cards so the FAB won’t interfere " 90 | "when a user tries to pick up one of cards.\n\n" 91 | 92 | Settings 93 | 94 | -------------------------------------------------------------------------------- /android/VideoDemo/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 |