├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── basicplayback ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── amazonaws │ │ └── ivs │ │ └── player │ │ └── basicplayback │ │ └── MainActivity.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ └── activity_main.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.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 │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── customui ├── .gitignore ├── build.gradle ├── dependencies.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── amazonaws │ │ └── ivs │ │ └── player │ │ └── customui │ │ ├── App.kt │ │ ├── activities │ │ ├── MainActivity.kt │ │ ├── adapters │ │ │ ├── PlayerOptionAdapter.kt │ │ │ └── SourceOptionAdapter.kt │ │ └── dialogs │ │ │ ├── QualityDialog.kt │ │ │ ├── RateDialog.kt │ │ │ └── SourceDialog.kt │ │ ├── common │ │ ├── CommonExtensions.kt │ │ ├── Configuration.kt │ │ ├── LongExtensions.kt │ │ ├── PlayerExtensions.kt │ │ ├── ViewModelExtensions.kt │ │ └── enums │ │ │ └── PlayingState.kt │ │ ├── data │ │ ├── LocalCacheProvider.kt │ │ ├── PlayerSourceDao.kt │ │ └── entity │ │ │ ├── OptionDataItem.kt │ │ │ └── SourceDataItem.kt │ │ ├── injection │ │ ├── InjectionComponent.kt │ │ └── InjectionModule.kt │ │ ├── viewModel │ │ └── MainViewModel.kt │ │ └── views │ │ └── MaxHeightRecyclerView.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ ├── ic_close.xml │ ├── ic_close_selected.xml │ ├── ic_done.xml │ ├── ic_launcher_background.xml │ ├── ic_launcher_foreground.xml │ ├── ic_more.xml │ ├── ic_more_button.xml │ ├── ic_more_pressed.xml │ ├── ic_pause.xml │ ├── ic_pause_button.xml │ ├── ic_pause_pressed.xml │ ├── ic_play.xml │ ├── ic_play_button.xml │ ├── ic_play_pressed.xml │ ├── ic_settings.xml │ ├── ic_source_close.xml │ ├── option_background.xml │ ├── option_title_background.xml │ ├── player_controls_background.xml │ ├── round.xml │ ├── seekbar_progress.xml │ ├── seekbar_thumb.xml │ ├── selector_close.xml │ ├── selector_source_close.xml │ └── source_background.xml │ ├── layout-land │ ├── player_quality_sheet.xml │ ├── player_rate_sheet.xml │ └── view_player_controls.xml │ ├── layout │ ├── activity_main.xml │ ├── player_quality_sheet.xml │ ├── player_rate_sheet.xml │ ├── player_source_sheet.xml │ ├── selector_list_item.xml │ ├── source_item.xml │ ├── view_dialog.xml │ └── view_player_controls.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.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 │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimen.xml │ ├── strings.xml │ └── styles.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── ivs-logo.svg ├── quizdemo ├── .gitignore ├── build.gradle ├── dependencies.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── amazonaws │ │ └── ivs │ │ └── player │ │ └── quizdemo │ │ ├── App.kt │ │ ├── activities │ │ ├── MainActivity.kt │ │ ├── adapters │ │ │ ├── AnswerAdapter.kt │ │ │ └── SourceOptionAdapter.kt │ │ └── dialogs │ │ │ └── SourceDialog.kt │ │ ├── common │ │ ├── CommonExtensions.kt │ │ ├── Configuration.kt │ │ ├── PlayerExtensions.kt │ │ └── ViewModelExtensions.kt │ │ ├── data │ │ ├── LocalCacheProvider.kt │ │ ├── QuizSourceDao.kt │ │ └── entity │ │ │ └── SourceDataItem.kt │ │ ├── injection │ │ ├── InjectionComponent.kt │ │ └── InjectionModule.kt │ │ ├── models │ │ ├── AnswerViewItem.kt │ │ └── QuestionModel.kt │ │ └── viewModels │ │ └── MainViewModel.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ ├── answer_background.xml │ ├── correct_answer_background.xml │ ├── ic_close.xml │ ├── ic_close_selected.xml │ ├── ic_done.xml │ ├── ic_launcher_background.xml │ ├── ic_more.xml │ ├── ic_more_button.xml │ ├── ic_more_pressed.xml │ ├── ic_source_close.xml │ ├── incorrect_answer_background.xml │ ├── more_circle_background.xml │ ├── player_live_background.xml │ ├── quiz_background.xml │ ├── selector_close.xml │ ├── selector_source_close.xml │ └── source_background.xml │ ├── layout │ ├── activity_main.xml │ ├── answer_list_item.xml │ ├── player_source_sheet.xml │ ├── source_item.xml │ └── view_dialog.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.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 │ └── values │ ├── colors.xml │ ├── dimen.xml │ ├── strings.xml │ └── styles.xml └── settings.gradle /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve our sample code 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 13 | 14 | **Describe the bug** 15 | A clear and concise description of what the bug is. 16 | 17 | **To Reproduce** 18 | 1. Share sample code 19 | 2. Steps to reproduce 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Screenshots** 25 | If applicable, add screenshots to help explain your problem, e.g. of the player behavior you're seeing or debugger output. 26 | 27 | **Device details (please complete the following information):** 28 | - Device: [e.g. Pixel 5] 29 | - OS: [e.g. Android] 30 | - Version [e.g. 11] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea/* 5 | /.idea/* 6 | /.idea/caches 7 | /.idea/libraries 8 | /.idea/modules.xml 9 | /.idea/workspace.xml 10 | /.idea/navEditor.xml 11 | /.idea/assetWizardSettings.xml 12 | .DS_Store 13 | /build 14 | /captures 15 | .externalNativeBuild 16 | .cxx 17 | 18 | # Compiled class file 19 | *.class 20 | 21 | # Log file 22 | *.log 23 | 24 | # Package Files # 25 | *.war 26 | *.nar 27 | *.ear 28 | *.zip 29 | *.tar.gz 30 | 31 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 32 | hs_err_pid* 33 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, 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 *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' 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. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Amazon IVS Player Android SDK Sample Apps 4 | 5 | This repository contains sample apps which use the IVS Player Android SDK. 6 | 7 | ## Samples 8 | 9 | + **BasicPlayback**: This is the most basic example of how to get started with the SDK. 10 | + **CustomUI**: This is a more advanced example that shows how to build a custom UI on top of the SDK. 11 | + **QuizDemo**: This is a trivia game example in which the questions are sent over timed metadata embedded in the stream. 12 | 13 | ## Setup 14 | 15 | 1. Install and setup [Android Studio](https://developer.android.com/studio/install) 16 | 2. Clone the repository to your local machine 17 | 3. Open the project in Android Studio 18 | 4. You can now build and run the sample projects. 19 | 20 | ## Test Streams 21 | * 1080p30 - https://fcc3ddae59ed.us-west-2.playback.live-video.net/api/video/v1/us-west-2.893648527354.channel.DmumNckWFTqz.m3u8 22 | * Square Video - https://fcc3ddae59ed.us-west-2.playback.live-video.net/api/video/v1/us-west-2.893648527354.channel.XFAcAcypUxQm.m3u8 23 | * Vertical Video - https://fcc3ddae59ed.us-west-2.playback.live-video.net/api/video/v1/us-west-2.893648527354.channel.YtnrVcQbttF0.m3u8 24 | * Quiz - https://4c62a87c1810.us-west-2.playback.live-video.net/api/video/v1/us-west-2.049054135175.channel.GHRwjPylmdXm.m3u8 25 | * VOD - https://d6hwdeiig07o4.cloudfront.net/ivs/956482054022/cTo5UpKS07do/2020-07-13T22-54-42.188Z/OgRXMLtq8M11/media/hls/master.m3u8 26 | 27 | ## License 28 | This project is licensed under the MIT-0 License. See the LICENSE file. 29 | -------------------------------------------------------------------------------- /basicplayback/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /basicplayback/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-kapt' 4 | 5 | android { 6 | compileSdkVersion = 33 7 | 8 | defaultConfig { 9 | applicationId "com.amazonaws.ivs.player.basicplayback" 10 | minSdkVersion 21 11 | targetSdkVersion 33 12 | versionCode 1 13 | versionName "1.0" 14 | } 15 | 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | 23 | namespace 'com.amazonaws.ivs.player.basicplayback' 24 | 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_11 27 | targetCompatibility JavaVersion.VERSION_11 28 | } 29 | 30 | buildFeatures { 31 | dataBinding = true 32 | viewBinding = true 33 | } 34 | } 35 | 36 | dependencies { 37 | implementation "com.amazonaws:ivs-player:$player_version" 38 | implementation 'androidx.appcompat:appcompat:1.6.1' 39 | implementation 'androidx.core:core-ktx:1.10.0' 40 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 41 | implementation 'com.google.android.material:material:1.9.0' 42 | } 43 | -------------------------------------------------------------------------------- /basicplayback/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /basicplayback/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /basicplayback/src/main/java/com/amazonaws/ivs/player/basicplayback/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.basicplayback 2 | 3 | import android.net.Uri 4 | import android.os.Bundle 5 | import android.text.TextUtils 6 | import android.util.Log 7 | import android.view.View 8 | import android.view.WindowManager 9 | import android.widget.AdapterView 10 | import android.widget.AdapterView.OnItemSelectedListener 11 | import android.widget.ArrayAdapter 12 | import android.widget.Spinner 13 | import androidx.appcompat.app.AppCompatActivity 14 | import com.amazonaws.ivs.player.* 15 | import java.nio.ByteBuffer 16 | import java.util.* 17 | 18 | 19 | class MainActivity : AppCompatActivity() { 20 | 21 | // PlayerView is an easy to use wrapper around the Player object. 22 | // If you want to use the Player object directly, you can instantiate a 23 | // Player object and attach it to a SurfaceView with Player.setSurface() 24 | private lateinit var playerView : PlayerView 25 | override fun onCreate(savedInstanceState: Bundle?) { 26 | super.onCreate(savedInstanceState) 27 | setContentView(R.layout.activity_main) 28 | window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) 29 | 30 | playerView = findViewById(R.id.playerView) 31 | 32 | // Load Uri to play 33 | playerView.player.load(Uri.parse("https://fcc3ddae59ed.us-west-2.playback.live-video.net/api/video/v1/us-west-2.893648527354.channel.DmumNckWFTqz.m3u8")) 34 | 35 | // Set PlaybackRate 36 | setPlaybackrate() 37 | 38 | // Set Listener for Player callback events 39 | handlePlayerEvents() 40 | } 41 | 42 | override fun onStart() { 43 | super.onStart() 44 | playerView.player.play() 45 | } 46 | 47 | override fun onStop() { 48 | super.onStop() 49 | playerView.player.pause() 50 | } 51 | 52 | override fun onDestroy() { 53 | super.onDestroy() 54 | playerView.player.release() 55 | } 56 | 57 | private fun setPlaybackrate() { 58 | val rateSpinner = findViewById(R.id.rate_spinner) as Spinner 59 | // Set playback rate, must be a floating point value 60 | val rates: List = Arrays.asList(0.5f, 1.0f, 1.5f, 2.0f) 61 | val rateAdapter: ArrayAdapter = ArrayAdapter(this, 62 | android.R.layout.simple_spinner_item, rates) 63 | rateAdapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line) 64 | rateSpinner.adapter = rateAdapter 65 | rateSpinner.setSelection(1) 66 | rateSpinner.onItemSelectedListener = object : OnItemSelectedListener { 67 | override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { 68 | val rate = rateAdapter.getItem(position) 69 | if (rate != null) { 70 | playerView.player.setPlaybackRate(rate) 71 | } 72 | } 73 | 74 | override fun onNothingSelected(parent: AdapterView<*>?) {} 75 | } 76 | } 77 | 78 | private fun updateQuality() { 79 | val qualitySpinner = findViewById(R.id.quality_spinner) as Spinner 80 | var auto = "auto" 81 | val currentQuality: String = playerView.player.getQuality().getName() 82 | if (playerView.player.isAutoQualityMode() && !TextUtils.isEmpty(currentQuality)) { 83 | auto += " ($currentQuality)" 84 | } 85 | var selected = 0 86 | val names: ArrayList = ArrayList() 87 | for (quality in playerView.player.getQualities()) { 88 | names.add(quality.name) 89 | } 90 | names.add(0, auto) 91 | if (!playerView.player.isAutoQualityMode()) { 92 | for (i in 0 until names.size) { 93 | if (names.get(i).equals(currentQuality)) { 94 | selected = i 95 | } 96 | } 97 | } 98 | val qualityAdapter: ArrayAdapter = ArrayAdapter(this, 99 | android.R.layout.simple_spinner_item, names) 100 | qualityAdapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line) 101 | qualitySpinner.setOnItemSelectedListener(null) 102 | qualitySpinner.setAdapter(qualityAdapter) 103 | qualitySpinner.setSelection(selected, false) 104 | qualitySpinner.setOnItemSelectedListener(object : OnItemSelectedListener { 105 | override fun onItemSelected(parent: AdapterView<*>?, view: View, position: Int, id: Long) { 106 | val name = qualityAdapter.getItem(position) 107 | if (name != null && name.startsWith("auto")) { 108 | playerView.player.setAutoQualityMode(true) 109 | } else { 110 | for (quality in playerView.player.getQualities()) { 111 | if (quality.name.equals(name, ignoreCase = true)) { 112 | Log.i("IVSPlayer", "Quality Selected: " + quality); 113 | playerView.player.setQuality(quality) 114 | break 115 | } 116 | } 117 | } 118 | } 119 | 120 | override fun onNothingSelected(parent: AdapterView<*>?) {} 121 | }) 122 | } 123 | 124 | /** 125 | * Demonstration for what callback APIs are available to Listen for Player events. 126 | */ 127 | private fun handlePlayerEvents() { 128 | playerView.player.apply { 129 | // Listen to changes on the player 130 | addListener(object : Player.Listener() { 131 | override fun onAnalyticsEvent(p0: String, p1: String) {} 132 | override fun onDurationChanged(p0: Long) { 133 | // If the video is a VOD, you can seek to a duration in the video 134 | Log.i("IVSPlayer", "New duration: $duration") 135 | seekTo(p0) 136 | } 137 | override fun onError(p0: PlayerException) {} 138 | override fun onMetadata(type: String, data: ByteBuffer) {} 139 | override fun onQualityChanged(p0: Quality) { 140 | Log.i("IVSPlayer", "Quality changed to " + p0); 141 | updateQuality() 142 | } 143 | override fun onRebuffering() {} 144 | override fun onSeekCompleted(p0: Long) {} 145 | override fun onVideoSizeChanged(p0: Int, p1: Int) {} 146 | override fun onCue(cue: Cue) { 147 | when (cue) { 148 | is TextMetadataCue -> Log.i("IVSPlayer","Received Text Metadata: ${cue.text}") 149 | } 150 | } 151 | 152 | override fun onStateChanged(state: Player.State) { 153 | Log.i("PlayerLog", "Current state: ${state}") 154 | when (state) { 155 | Player.State.BUFFERING, 156 | Player.State.READY -> { 157 | updateQuality() 158 | } 159 | Player.State.IDLE, 160 | Player.State.ENDED -> { 161 | // no-op 162 | } 163 | Player.State.PLAYING -> { 164 | // Qualities will be dependent on the video loaded, and can 165 | // be retrieved from the player 166 | Log.i("IVSPlayer", "Available Qualities: ${qualities}") 167 | } 168 | } 169 | } 170 | }) 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /basicplayback/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /basicplayback/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /basicplayback/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 25 | 26 | 35 | 36 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /basicplayback/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /basicplayback/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /basicplayback/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/basicplayback/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /basicplayback/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/basicplayback/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /basicplayback/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/basicplayback/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /basicplayback/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/basicplayback/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /basicplayback/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/basicplayback/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /basicplayback/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/basicplayback/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /basicplayback/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/basicplayback/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /basicplayback/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/basicplayback/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /basicplayback/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/basicplayback/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /basicplayback/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/basicplayback/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /basicplayback/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #1B1B1B 4 | #2D2E30 5 | #ED7E18 6 | 7 | -------------------------------------------------------------------------------- /basicplayback/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | BasicPlayback 3 | 4 | -------------------------------------------------------------------------------- /basicplayback/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | buildscript { 4 | ext.default_player_version = "1.41.0" 5 | ext.player_version = findProperty("playerVersion") ?: default_player_version 6 | 7 | repositories { 8 | google() 9 | mavenCentral() 10 | } 11 | } 12 | 13 | plugins { 14 | id 'com.android.application' version '8.5.2' apply false 15 | id 'com.android.library' version '8.5.2' apply false 16 | id 'org.jetbrains.kotlin.android' version '1.9.22' apply false 17 | } 18 | 19 | subprojects { 20 | tasks.withType(KotlinCompile).configureEach { 21 | kotlinOptions.jvmTarget = "11" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /customui/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /customui/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-kapt' 4 | 5 | android { 6 | compileSdkVersion = 33 7 | 8 | defaultConfig { 9 | applicationId "com.amazonaws.ivs.player.customui" 10 | minSdkVersion 21 11 | targetSdkVersion 33 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | 17 | javaCompileOptions { 18 | annotationProcessorOptions { 19 | arguments = [ 20 | "room.schemaLocation":"$projectDir/schemas".toString(), 21 | "room.incremental":"true", 22 | "room.expandProjection":"true"] 23 | } 24 | } 25 | } 26 | 27 | buildTypes { 28 | release { 29 | minifyEnabled false 30 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 31 | } 32 | } 33 | 34 | compileOptions { 35 | sourceCompatibility JavaVersion.VERSION_11 36 | targetCompatibility JavaVersion.VERSION_11 37 | } 38 | 39 | buildFeatures { 40 | dataBinding = true 41 | viewBinding = true 42 | } 43 | 44 | namespace 'com.amazonaws.ivs.player.customui' 45 | } 46 | 47 | apply from: 'dependencies.gradle' 48 | -------------------------------------------------------------------------------- /customui/dependencies.gradle: -------------------------------------------------------------------------------- 1 | def appCompatVersion = '1.6.1' 2 | def coreVersion = '1.10.0' 3 | def constraintLayoutVersion = '2.1.4' 4 | def materialDesignVersion = '1.9.0' 5 | def recyclerViewVersion = '1.3.2' 6 | def roomVersion = '2.4.3' 7 | def cardViewVersion = '1.0.0' 8 | def lifecycleVersion = '2.5.1' 9 | def daggerVersion = '2.46.1' 10 | 11 | dependencies { 12 | 13 | // Libraries 14 | implementation "com.amazonaws:ivs-player:$player_version" 15 | 16 | // Material design 17 | implementation "com.google.android.material:material:$materialDesignVersion" 18 | 19 | // Android 20 | implementation "androidx.recyclerview:recyclerview:$recyclerViewVersion" 21 | implementation "androidx.cardview:cardview:$cardViewVersion" 22 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion" 23 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion" 24 | 25 | // Room components 26 | implementation "androidx.room:room-runtime:$roomVersion" 27 | implementation "androidx.room:room-ktx:$roomVersion" 28 | kapt "androidx.room:room-compiler:$roomVersion" 29 | 30 | // Dagger 31 | kapt "com.google.dagger:dagger-compiler:$daggerVersion" 32 | kapt "com.google.dagger:dagger-android-processor:$daggerVersion" 33 | implementation "com.google.dagger:dagger-android-support:$daggerVersion" 34 | 35 | implementation "androidx.appcompat:appcompat:$appCompatVersion" 36 | implementation "androidx.core:core-ktx:$coreVersion" 37 | implementation "androidx.constraintlayout:constraintlayout:$constraintLayoutVersion" 38 | } 39 | -------------------------------------------------------------------------------- /customui/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /customui/src/main/java/com/amazonaws/ivs/player/customui/App.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.customui 2 | 3 | import android.app.Application 4 | import com.amazonaws.ivs.player.customui.injection.DaggerInjectionComponent 5 | import com.amazonaws.ivs.player.customui.injection.InjectionComponent 6 | import com.amazonaws.ivs.player.customui.injection.InjectionModule 7 | 8 | class App : Application() { 9 | 10 | override fun onCreate() { 11 | super.onCreate() 12 | component = DaggerInjectionComponent.builder().injectionModule(InjectionModule(this)).build() 13 | } 14 | 15 | companion object { 16 | lateinit var component: InjectionComponent 17 | private set 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /customui/src/main/java/com/amazonaws/ivs/player/customui/activities/adapters/PlayerOptionAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.customui.activities.adapters 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.DiffUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.amazonaws.ivs.player.customui.data.entity.OptionDataItem 8 | import com.amazonaws.ivs.player.customui.databinding.SelectorListItemBinding 9 | import kotlin.properties.Delegates 10 | 11 | /** 12 | * Player quality and rate option selection adapter 13 | */ 14 | class PlayerOptionAdapter( 15 | private val callback: PlayerOptionCallback 16 | ) : RecyclerView.Adapter() { 17 | 18 | var items: List by Delegates.observable(emptyList()) { _, old, new -> 19 | DiffUtil.calculateDiff(PlayerOptionDiff(old, new)).dispatchUpdatesTo(this) 20 | } 21 | 22 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 23 | val binding = SelectorListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) 24 | return ViewHolder(binding) 25 | } 26 | 27 | override fun getItemCount(): Int = items.size 28 | 29 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 30 | holder.binding.data = items[position] 31 | } 32 | 33 | inner class ViewHolder(val binding: SelectorListItemBinding) : RecyclerView.ViewHolder(binding.root) { 34 | init { 35 | itemView.setOnClickListener { 36 | callback.onOptionClicked(bindingAdapterPosition) 37 | } 38 | } 39 | } 40 | 41 | interface PlayerOptionCallback { 42 | fun onOptionClicked(position: Int) 43 | } 44 | 45 | inner class PlayerOptionDiff(private val oldItems: List, 46 | private val newItems: List 47 | ) : DiffUtil.Callback() { 48 | 49 | override fun getOldListSize() = oldItems.size 50 | 51 | override fun getNewListSize() = newItems.size 52 | 53 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 54 | return oldItems[oldItemPosition].option == newItems[newItemPosition].option 55 | } 56 | 57 | override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 58 | return oldItems[oldItemPosition] == newItems[newItemPosition] 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /customui/src/main/java/com/amazonaws/ivs/player/customui/activities/adapters/SourceOptionAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.customui.activities.adapters 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.DiffUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.amazonaws.ivs.player.customui.data.entity.SourceDataItem 8 | import com.amazonaws.ivs.player.customui.databinding.SourceItemBinding 9 | import kotlin.properties.Delegates 10 | 11 | /** 12 | * Player source option selection adapter 13 | */ 14 | class SourceOptionAdapter( 15 | private val callback: PlayerOptionCallback 16 | ) : RecyclerView.Adapter() { 17 | 18 | var items: List by Delegates.observable(emptyList()) { _, old, new -> 19 | DiffUtil.calculateDiff(SourceOptionDiff(old, new)).dispatchUpdatesTo(this) 20 | } 21 | 22 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 23 | val binding = SourceItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) 24 | return ViewHolder(binding) 25 | } 26 | 27 | override fun getItemCount(): Int = items.size 28 | 29 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 30 | holder.binding.data = items[position] 31 | val dataItem = items[position] 32 | 33 | holder.binding.sourceName.setOnClickListener { 34 | callback.onOptionClicked(dataItem.url) 35 | } 36 | 37 | holder.binding.deleteBtn.setOnClickListener { 38 | callback.onOptionDelete(dataItem.url) 39 | } 40 | } 41 | 42 | inner class ViewHolder(val binding: SourceItemBinding) : RecyclerView.ViewHolder(binding.root) 43 | 44 | interface PlayerOptionCallback { 45 | 46 | fun onOptionClicked(url: String) 47 | 48 | fun onOptionDelete(url: String) 49 | } 50 | 51 | inner class SourceOptionDiff(private val oldItems: List, 52 | private val newItems: List 53 | ) : DiffUtil.Callback() { 54 | 55 | override fun getOldListSize() = oldItems.size 56 | 57 | override fun getNewListSize() = newItems.size 58 | 59 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 60 | return oldItems[oldItemPosition].url == newItems[newItemPosition].url 61 | } 62 | 63 | override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 64 | return oldItems[oldItemPosition] == newItems[newItemPosition] 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /customui/src/main/java/com/amazonaws/ivs/player/customui/activities/dialogs/QualityDialog.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.customui.activities.dialogs 2 | 3 | import android.view.View 4 | import androidx.lifecycle.Observer 5 | import com.amazonaws.ivs.player.customui.R 6 | import com.amazonaws.ivs.player.customui.activities.MainActivity 7 | import com.amazonaws.ivs.player.customui.activities.adapters.PlayerOptionAdapter 8 | import com.amazonaws.ivs.player.customui.common.Configuration 9 | import com.amazonaws.ivs.player.customui.common.hide 10 | import com.amazonaws.ivs.player.customui.common.isOpened 11 | import com.amazonaws.ivs.player.customui.common.open 12 | import com.amazonaws.ivs.player.customui.viewModel.MainViewModel 13 | import com.google.android.material.bottomsheet.BottomSheetBehavior 14 | 15 | class QualityDialog( 16 | private val activity: MainActivity, 17 | private val viewModel: MainViewModel 18 | ) : PlayerOptionAdapter.PlayerOptionCallback { 19 | 20 | private val qualityMenu by lazy { BottomSheetBehavior.from(activity.findViewById(R.id.quality_sheet)) } 21 | private val qualityAdapter by lazy { PlayerOptionAdapter(this) } 22 | 23 | init { 24 | initViews() 25 | } 26 | 27 | private fun initViews() { 28 | 29 | viewModel.selectedQualityValue.observe(activity, Observer { 30 | viewModel.getPlayerQualities() 31 | }) 32 | 33 | activity.binding.qualitySheet.qualityOptionList.apply { 34 | adapter = qualityAdapter 35 | } 36 | 37 | viewModel.qualities.observe(activity, Observer { 38 | qualityAdapter.items = it 39 | }) 40 | 41 | activity.binding.qualitySheet.qualityCloseBtn.setOnClickListener { 42 | dismiss() 43 | } 44 | 45 | qualityMenu.addBottomSheetCallback(activity.sheetListener) 46 | } 47 | 48 | fun show() { 49 | qualityMenu.open() 50 | } 51 | 52 | fun dismiss() { 53 | qualityMenu.hide() 54 | } 55 | 56 | fun isOpened() = qualityMenu.isOpened() 57 | 58 | fun release() { 59 | qualityMenu.removeBottomSheetCallback(activity.sheetListener) 60 | } 61 | 62 | override fun onOptionClicked(position: Int) { 63 | val option = qualityAdapter.items[position].option 64 | if (option == Configuration.AUTO) { 65 | viewModel.selectAuto() 66 | } else { 67 | viewModel.selectQuality(option) 68 | } 69 | dismiss() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /customui/src/main/java/com/amazonaws/ivs/player/customui/activities/dialogs/RateDialog.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.customui.activities.dialogs 2 | 3 | import android.view.View 4 | import androidx.lifecycle.Observer 5 | import com.amazonaws.ivs.player.customui.R 6 | import com.amazonaws.ivs.player.customui.activities.MainActivity 7 | import com.amazonaws.ivs.player.customui.activities.adapters.PlayerOptionAdapter 8 | import com.amazonaws.ivs.player.customui.common.hide 9 | import com.amazonaws.ivs.player.customui.common.isOpened 10 | import com.amazonaws.ivs.player.customui.common.open 11 | import com.amazonaws.ivs.player.customui.viewModel.MainViewModel 12 | import com.google.android.material.bottomsheet.BottomSheetBehavior 13 | class RateDialog( 14 | private val activity: MainActivity, 15 | private val viewModel: MainViewModel 16 | ) : PlayerOptionAdapter.PlayerOptionCallback { 17 | 18 | private val rateMenu by lazy { BottomSheetBehavior.from(activity.findViewById(R.id.rate_sheet)) } 19 | private val rateAdapter by lazy { PlayerOptionAdapter(this) } 20 | 21 | init { 22 | initViews() 23 | } 24 | 25 | private fun initViews() { 26 | 27 | viewModel.selectedRateValue.observe(activity, Observer { 28 | rateAdapter.items = viewModel.getPlayBackRates().toMutableList() 29 | }) 30 | 31 | activity.binding.rateSheet.rateOptionList.apply { 32 | adapter = rateAdapter 33 | } 34 | 35 | rateAdapter.items = viewModel.getPlayBackRates().toMutableList() 36 | 37 | activity.binding.rateSheet.rateCloseBtn.setOnClickListener { 38 | dismiss() 39 | } 40 | 41 | rateMenu.addBottomSheetCallback(activity.sheetListener) 42 | } 43 | 44 | fun show() { 45 | rateMenu.open() 46 | } 47 | 48 | fun dismiss() { 49 | rateMenu.hide() 50 | } 51 | 52 | fun isOpened() = rateMenu.isOpened() 53 | 54 | fun release() { 55 | rateMenu.removeBottomSheetCallback(activity.sheetListener) 56 | } 57 | 58 | override fun onOptionClicked(position: Int) { 59 | viewModel.setPlaybackRate(rateAdapter.items[position].option) 60 | dismiss() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /customui/src/main/java/com/amazonaws/ivs/player/customui/activities/dialogs/SourceDialog.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.customui.activities.dialogs 2 | 3 | import android.net.Uri 4 | import android.util.Log 5 | import android.view.View 6 | import android.view.inputmethod.EditorInfo 7 | import android.widget.Button 8 | import android.widget.EditText 9 | import android.widget.TextView 10 | import androidx.lifecycle.Observer 11 | import androidx.recyclerview.widget.RecyclerView 12 | import com.amazonaws.ivs.player.customui.R 13 | import com.amazonaws.ivs.player.customui.activities.MainActivity 14 | import com.amazonaws.ivs.player.customui.activities.adapters.SourceOptionAdapter 15 | import com.amazonaws.ivs.player.customui.common.* 16 | import com.amazonaws.ivs.player.customui.data.entity.SourceDataItem 17 | import com.amazonaws.ivs.player.customui.viewModel.MainViewModel 18 | import com.google.android.material.bottomsheet.BottomSheetBehavior 19 | 20 | class SourceDialog( 21 | private val activity: MainActivity, 22 | private val viewModel: MainViewModel 23 | ) : SourceOptionAdapter.PlayerOptionCallback { 24 | 25 | private val sourceMenu by lazy { BottomSheetBehavior.from(activity.findViewById(R.id.source_sheet)) } 26 | private val optionsAdapter by lazy { SourceOptionAdapter(this) } 27 | 28 | init { 29 | sourceMenu.isFitToContents = false 30 | initViews() 31 | } 32 | 33 | private fun initViews() { 34 | val sourceView = activity.findViewById(R.id.source) 35 | val okBtn = activity.findViewById(R.id.ok_btn) 36 | val closeBtn = activity.findViewById(R.id.close_btn) 37 | 38 | okBtn.setOnClickListener { 39 | val selectedItem = sourceView.text.toString() 40 | val source = SourceDataItem(selectedItem, selectedItem) 41 | if (selectedItem.isNotEmpty()) { 42 | viewModel.addSource(source) 43 | onOptionClicked(selectedItem) 44 | dismiss() 45 | } 46 | } 47 | 48 | closeBtn.setOnClickListener { 49 | dismiss() 50 | } 51 | 52 | sourceView.setOnEditorActionListener { view, actionId, _ -> 53 | if (actionId == EditorInfo.IME_ACTION_DONE) { 54 | val selectedItem = view.text.toString() 55 | val source = SourceDataItem(selectedItem, selectedItem) 56 | viewModel.addSource(source) 57 | onOptionClicked(selectedItem) 58 | dismiss() 59 | } 60 | false 61 | } 62 | 63 | initAdapter() 64 | 65 | sourceMenu.addBottomSheetCallback(activity.sheetListener) 66 | } 67 | 68 | fun show() { 69 | sourceMenu.open() 70 | } 71 | 72 | fun dismiss() { 73 | sourceMenu.hide() 74 | activity.hideKeyboard() 75 | } 76 | 77 | fun isOpened() = sourceMenu.isOpened() 78 | 79 | fun release() { 80 | sourceMenu.removeBottomSheetCallback(activity.sheetListener) 81 | } 82 | 83 | private fun initAdapter() { 84 | // Default option list 85 | activity.findViewById(R.id.option_list).apply { 86 | adapter = optionsAdapter 87 | } 88 | 89 | viewModel.sources.observe(activity) { 90 | optionsAdapter.items = it 91 | } 92 | } 93 | 94 | override fun onOptionClicked(url: String) { 95 | Log.d(Configuration.TAG, "Url selected $url") 96 | viewModel.playerLoadStream(Uri.parse(url)) 97 | viewModel.play() 98 | dismiss() 99 | } 100 | 101 | override fun onOptionDelete(url: String) { 102 | Log.d(Configuration.TAG, "Url deleted $url") 103 | viewModel.deleteSource(url) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /customui/src/main/java/com/amazonaws/ivs/player/customui/common/CommonExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.customui.common 2 | 3 | import android.app.Activity 4 | import android.app.Dialog 5 | import android.view.View 6 | import android.view.Window 7 | import android.view.inputmethod.InputMethodManager 8 | import androidx.core.content.ContextCompat 9 | import com.amazonaws.ivs.player.customui.R 10 | import com.amazonaws.ivs.player.customui.databinding.ViewDialogBinding 11 | import com.google.android.material.bottomsheet.BottomSheetBehavior 12 | 13 | fun BottomSheetBehavior.isOpened() = 14 | state == BottomSheetBehavior.STATE_EXPANDED || state == BottomSheetBehavior.STATE_HALF_EXPANDED 15 | 16 | fun BottomSheetBehavior.open() { 17 | if (!isOpened()) { 18 | state = BottomSheetBehavior.STATE_EXPANDED 19 | } 20 | } 21 | 22 | fun BottomSheetBehavior.hide() { 23 | if (isOpened()) { 24 | state = BottomSheetBehavior.STATE_HIDDEN 25 | } 26 | } 27 | 28 | fun Activity.hideKeyboard() { 29 | val view = currentFocus ?: window.decorView 30 | val token = view.windowToken 31 | view.clearFocus() 32 | ContextCompat.getSystemService(this, InputMethodManager::class.java)?.hideSoftInputFromWindow(token, 0) 33 | } 34 | 35 | fun Activity.showDialog(title: String, message: String) { 36 | val dialog = Dialog(this) 37 | dialog.requestWindowFeature(Window.FEATURE_NO_TITLE) 38 | dialog.setCancelable(false) 39 | 40 | val binding = ViewDialogBinding.inflate(layoutInflater) 41 | 42 | dialog.setContentView(binding.root) 43 | binding.title.text = getString(R.string.error_happened_template, title) 44 | binding.message.text = message 45 | binding.dismissBtn.setOnClickListener { 46 | dialog.dismiss() 47 | } 48 | dialog.show() 49 | } 50 | -------------------------------------------------------------------------------- /customui/src/main/java/com/amazonaws/ivs/player/customui/common/Configuration.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.customui.common 2 | 3 | object Configuration { 4 | const val TAG = "CustomUI" 5 | const val LINK = "https://fcc3ddae59ed.us-west-2.playback.live-video.net/api/video/v1/us-west-2.893648527354.channel.DmumNckWFTqz.m3u8" 6 | const val PORTRAIT_OPTION = "Live stream Portrait" 7 | const val LANDSCAPE_OPTION = "Recorded video Landscape" 8 | const val LIVE_PORTRAIT_LINK = "https://fcc3ddae59ed.us-west-2.playback.live-video.net/api/video/v1/us-west-2.893648527354.channel.YtnrVcQbttF0.m3u8" 9 | const val RECORDED_LANDSCAPE_LINK = "https://d6hwdeiig07o4.cloudfront.net/ivs/956482054022/cTo5UpKS07do/2020-07-13T22-54-42.188Z/OgRXMLtq8M11/media/hls/master.m3u8" 10 | const val AUTO = "Auto" 11 | val PlaybackRate = listOf("2.0", "1.5", "1.0", "0.5") 12 | 13 | const val PLAYBACK_RATE_DEFAULT = "1.0" 14 | 15 | const val HIDE_CONTROLS_DELAY = 5000L 16 | } 17 | -------------------------------------------------------------------------------- /customui/src/main/java/com/amazonaws/ivs/player/customui/common/LongExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.customui.common 2 | 3 | import java.util.* 4 | 5 | fun Long.timeString(): String { 6 | var totalSeconds: Int = this.toInt() / 1000 7 | val hours: Int = totalSeconds / 3600 8 | totalSeconds -= hours * 3600 9 | val minutes: Int = totalSeconds / 60 10 | totalSeconds -= minutes * 60 11 | return String.format(Locale.ENGLISH, "%02d:%02d:%02d", hours, minutes, totalSeconds) 12 | } 13 | -------------------------------------------------------------------------------- /customui/src/main/java/com/amazonaws/ivs/player/customui/common/PlayerExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.customui.common 2 | 3 | import android.util.Log 4 | import com.amazonaws.ivs.player.Cue 5 | import com.amazonaws.ivs.player.Player 6 | import com.amazonaws.ivs.player.PlayerException 7 | import com.amazonaws.ivs.player.Quality 8 | import kotlinx.coroutines.* 9 | import java.nio.ByteBuffer 10 | 11 | private val ioScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) 12 | private val mainScope = CoroutineScope(Dispatchers.Main + SupervisorJob()) 13 | 14 | fun launchIO(block: suspend CoroutineScope.() -> Unit) = ioScope.launch( 15 | context = CoroutineExceptionHandler { _, e -> Log.d(Configuration.TAG,"Coroutine failed ${e.localizedMessage}") }, 16 | block = block 17 | ) 18 | 19 | fun launchMain(block: suspend CoroutineScope.() -> Unit) = mainScope.launch( 20 | context = CoroutineExceptionHandler { _, e -> Log.d(Configuration.TAG,"Coroutine failed ${e.localizedMessage}") }, 21 | block = block 22 | ) 23 | 24 | /** 25 | * Player listener extension function 26 | */ 27 | inline fun Player.setListener( 28 | crossinline onAnalyticsEvent: (key: String, value: String) -> Unit = { _,_ -> Unit }, 29 | crossinline onRebuffering: () -> Unit = {}, 30 | crossinline onSeekCompleted: (value: Long) -> Unit = { _ -> Unit }, 31 | crossinline onQualityChanged: (quality: Quality) -> Unit = { _ -> Unit }, 32 | crossinline onVideoSizeChanged: (width: Int, height: Int) -> Unit = { _,_ -> Unit }, 33 | crossinline onCue: (cue: Cue) -> Unit = { _ -> Unit }, 34 | crossinline onDurationChanged: (duration: Long) -> Unit = { _ -> Unit }, 35 | crossinline onStateChanged: (state: Player.State) -> Unit = { _ -> Unit }, 36 | crossinline onError: (exception: PlayerException) -> Unit = { _ -> Unit }, 37 | crossinline onMetadata: (data: String, buffer: ByteBuffer) -> Unit = { _,_ -> Unit } 38 | ): Player.Listener { 39 | val listener = playerListener( 40 | onAnalyticsEvent, onRebuffering, onSeekCompleted, onQualityChanged, onVideoSizeChanged, 41 | onCue, onDurationChanged, onStateChanged, onError, onMetadata 42 | ) 43 | 44 | addListener(listener) 45 | return listener 46 | } 47 | 48 | /** 49 | * Player.Listener provides an implementation of this interface to addListener(Listener) to receive events from a Player instance. 50 | */ 51 | inline fun playerListener( 52 | crossinline onAnalyticsEvent: (key: String, value: String) -> Unit = { _,_ -> Unit }, 53 | crossinline onRebuffering: () -> Unit = {}, 54 | crossinline onSeekCompleted: (value: Long) -> Unit = { _ -> Unit }, 55 | crossinline onQualityChanged: (quality: Quality) -> Unit = { _ -> Unit }, 56 | crossinline onVideoSizeChanged: (width: Int, height: Int) -> Unit = { _,_ -> Unit }, 57 | crossinline onCue: (cue: Cue) -> Unit = { _ -> Unit }, 58 | crossinline onDurationChanged: (duration: Long) -> Unit = { _ -> Unit }, 59 | crossinline onStateChanged: (state: Player.State) -> Unit = { _ -> Unit }, 60 | crossinline onError: (exception: PlayerException) -> Unit = { _ -> Unit }, 61 | crossinline onMetadata: (data: String, buffer: ByteBuffer) -> Unit = { _,_ -> Unit } 62 | ): Player.Listener = object : Player.Listener() { 63 | // Indicates that a video analytics tracking event occurred. 64 | override fun onAnalyticsEvent(key: String, value: String) = onAnalyticsEvent(key, value) 65 | // Indicates that the player is buffering from a previous PLAYING state. 66 | override fun onRebuffering() = onRebuffering() 67 | // Indicates that the player has seeked to a given position as requested from seekTo(long). 68 | override fun onSeekCompleted(value: Long) = onSeekCompleted(value) 69 | // Indicates that the playing quality changed either from a user action or from an internal adaptive quality switch. 70 | override fun onQualityChanged(quality: Quality) = onQualityChanged(quality) 71 | // Indicates that the video dimensions changed. 72 | override fun onVideoSizeChanged(width: Int, height: Int) = onVideoSizeChanged(width, height) 73 | // Indicates that a timed cue was received. 74 | override fun onCue(cue: Cue) = onCue(cue) 75 | // Indicates that source duration changed. 76 | override fun onDurationChanged(duration: Long) = onDurationChanged(duration) 77 | // Indicates that the player state changed. 78 | override fun onStateChanged(state: Player.State) = onStateChanged(state) 79 | // Indicates that an error occurred. 80 | override fun onError(exception: PlayerException) = onError(exception) 81 | // Indicates that a metadata event occurred. 82 | override fun onMetadata(data: String, buffer: ByteBuffer) = onMetadata(data, buffer) 83 | } 84 | -------------------------------------------------------------------------------- /customui/src/main/java/com/amazonaws/ivs/player/customui/common/ViewModelExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.customui.common 2 | 3 | import androidx.fragment.app.FragmentActivity 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.ViewModelProvider 6 | 7 | inline fun FragmentActivity.getViewModel(noinline creator: (() -> T)? = null): T { 8 | return if (creator == null) 9 | ViewModelProvider(this).get(T::class.java) 10 | else 11 | ViewModelProvider(this, BaseViewModelFactory(creator)).get(T::class.java) 12 | } 13 | 14 | inline fun FragmentActivity.lazyViewModel(noinline creator: (() -> T)? = null) = lazy { 15 | getViewModel(creator) 16 | } 17 | 18 | class BaseViewModelFactory(val creator: () -> T) : ViewModelProvider.Factory { 19 | override fun create(modelClass: Class): T { 20 | @Suppress("UNCHECKED_CAST") 21 | return creator() as T 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /customui/src/main/java/com/amazonaws/ivs/player/customui/common/enums/PlayingState.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.customui.common.enums 2 | 3 | /** 4 | * Player play button states 5 | */ 6 | enum class PlayingState { 7 | PLAYING, 8 | PAUSED 9 | } 10 | -------------------------------------------------------------------------------- /customui/src/main/java/com/amazonaws/ivs/player/customui/data/LocalCacheProvider.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.customui.data 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import com.amazonaws.ivs.player.customui.data.entity.SourceDataItem 6 | 7 | @Database(entities = [SourceDataItem::class], version = 1, exportSchema = false) 8 | abstract class LocalCacheProvider : RoomDatabase() { 9 | 10 | abstract fun sourcesDao(): PlayerSourceDao 11 | 12 | } 13 | -------------------------------------------------------------------------------- /customui/src/main/java/com/amazonaws/ivs/player/customui/data/PlayerSourceDao.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.customui.data 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import com.amazonaws.ivs.player.customui.data.entity.SourceDataItem 8 | import kotlinx.coroutines.flow.Flow 9 | 10 | @Dao 11 | interface PlayerSourceDao { 12 | 13 | // Get source 14 | @Query("SELECT * FROM source_table") 15 | fun getAll(): Flow> 16 | 17 | // Insert source 18 | @Insert(onConflict = OnConflictStrategy.IGNORE) 19 | fun insert(sourceDataItem: SourceDataItem) 20 | 21 | // Delete source 22 | @Query("DELETE FROM source_table WHERE url = :url") 23 | fun delete(url: String) 24 | } 25 | -------------------------------------------------------------------------------- /customui/src/main/java/com/amazonaws/ivs/player/customui/data/entity/OptionDataItem.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.customui.data.entity 2 | 3 | data class OptionDataItem(val option: String, val isDefault: Boolean) 4 | -------------------------------------------------------------------------------- /customui/src/main/java/com/amazonaws/ivs/player/customui/data/entity/SourceDataItem.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.customui.data.entity 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import com.amazonaws.ivs.player.customui.common.Configuration 6 | 7 | @Entity(tableName = "source_table") 8 | data class SourceDataItem( 9 | @PrimaryKey 10 | val url: String, 11 | val title: String = "", 12 | val isDefault: Boolean = false 13 | ) { 14 | fun isDefaultOption(): Boolean = (title == Configuration.LANDSCAPE_OPTION || title == Configuration.PORTRAIT_OPTION) 15 | } 16 | -------------------------------------------------------------------------------- /customui/src/main/java/com/amazonaws/ivs/player/customui/injection/InjectionComponent.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.customui.injection 2 | 3 | import com.amazonaws.ivs.player.customui.activities.MainActivity 4 | import dagger.Component 5 | import javax.inject.Singleton 6 | 7 | @Singleton 8 | @Component(modules = [InjectionModule::class]) 9 | interface InjectionComponent { 10 | fun inject(target: MainActivity) 11 | } 12 | -------------------------------------------------------------------------------- /customui/src/main/java/com/amazonaws/ivs/player/customui/injection/InjectionModule.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.customui.injection 2 | 3 | import androidx.room.Room 4 | import com.amazonaws.ivs.player.customui.App 5 | import com.amazonaws.ivs.player.customui.data.LocalCacheProvider 6 | import dagger.Module 7 | import dagger.Provides 8 | import javax.inject.Singleton 9 | 10 | @Module 11 | class InjectionModule(private val context: App) { 12 | 13 | @Provides 14 | @Singleton 15 | fun provideLocalCacheProvider(): LocalCacheProvider 16 | = Room.databaseBuilder(context, LocalCacheProvider::class.java, "twitch_database").build() 17 | 18 | } 19 | -------------------------------------------------------------------------------- /customui/src/main/java/com/amazonaws/ivs/player/customui/views/MaxHeightRecyclerView.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.customui.views 2 | 3 | import android.content.Context 4 | import android.content.res.TypedArray 5 | import android.util.AttributeSet 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.amazonaws.ivs.player.customui.R 8 | 9 | /** 10 | * Used to limit the max height of a recycler view 11 | */ 12 | class MaxHeightRecyclerView : RecyclerView { 13 | 14 | private var maxHeight = 0 15 | 16 | constructor(context: Context?) : super(context!!) 17 | 18 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { 19 | initialize(context, attrs) 20 | } 21 | 22 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { 23 | initialize(context, attrs) 24 | } 25 | 26 | private fun initialize(context: Context, attrs: AttributeSet?) { 27 | val arr: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.MaxHeightRecyclerView) 28 | maxHeight = arr.getLayoutDimension(R.styleable.MaxHeightRecyclerView_maxHeight, maxHeight) 29 | arr.recycle() 30 | } 31 | 32 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 33 | var heightSpec = heightMeasureSpec 34 | if (maxHeight > 0) { 35 | heightSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST) 36 | } 37 | super.onMeasure(widthMeasureSpec, heightSpec) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/ic_close.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/ic_close_selected.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/ic_done.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 12 | 13 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 33 | 34 | 35 | 36 | 37 | 38 | 40 | 41 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 62 | 68 | 74 | 80 | 86 | 92 | 98 | 102 | 103 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 120 | 121 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 8 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/ic_more.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/ic_more_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/ic_more_pressed.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/ic_pause.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/ic_pause_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/ic_pause_pressed.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/ic_play.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/ic_play_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/ic_play_pressed.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/ic_source_close.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/option_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/option_title_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/player_controls_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/seekbar_progress.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/seekbar_thumb.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/selector_close.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/selector_source_close.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /customui/src/main/res/drawable/source_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /customui/src/main/res/layout-land/player_quality_sheet.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 22 | 23 | 31 | 32 | 41 | 42 | -------------------------------------------------------------------------------- /customui/src/main/res/layout-land/player_rate_sheet.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 25 | 26 | 34 | 35 | 44 | 45 | -------------------------------------------------------------------------------- /customui/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 21 | 22 | 26 | 27 | 32 | 33 | 38 | 39 | 48 | 49 | 50 | 53 | 54 | 65 | 66 | 67 | 68 | 77 | 78 | 81 | 82 | 85 | 86 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /customui/src/main/res/layout/player_quality_sheet.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 22 | 23 | 31 | 32 | 41 | 42 | -------------------------------------------------------------------------------- /customui/src/main/res/layout/player_rate_sheet.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 25 | 26 | 34 | 35 | 44 | 45 | -------------------------------------------------------------------------------- /customui/src/main/res/layout/player_source_sheet.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 17 | 18 | 29 | 30 | 37 | 38 | 39 | 40 | 47 | 48 | 58 | 59 | 66 | 67 | 68 | 69 | 77 | 78 | -------------------------------------------------------------------------------- /customui/src/main/res/layout/selector_list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /customui/src/main/res/layout/source_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 23 | 24 | 39 | 40 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /customui/src/main/res/layout/view_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 20 | 21 | 32 | 33 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /customui/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /customui/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /customui/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/customui/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /customui/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/customui/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /customui/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/customui/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /customui/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/customui/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /customui/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/customui/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /customui/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/customui/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /customui/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/customui/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /customui/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/customui/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /customui/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/customui/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /customui/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/customui/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /customui/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /customui/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ED7E18 4 | #1B1B1B 5 | #ED7E18 6 | #1B1B1B 7 | #FA2D2E30 8 | #99FFFFFF 9 | #FFFFFF 10 | #00FFFFFF 11 | #99ED7E18 12 | #FFFFFF 13 | #80000000 14 | #000000 15 | 16 | -------------------------------------------------------------------------------- /customui/src/main/res/values/dimen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 40dp 4 | 50dp 5 | 38dp 6 | 48dp 7 | 12dp 8 | 100dp 9 | 400dp 10 | 11 | 8dp 12 | 12dp 13 | 16dp 14 | 15 | 10sp 16 | 14sp 17 | 16sp 18 | 19 | -------------------------------------------------------------------------------- /customui/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | CustomUI 3 | Close 4 | Buffering… 5 | Paused 6 | Ended 7 | ● LIVE 8 | ◼︎ RECORDED VIDEO 9 | Playback speed 10 | Quality options 11 | Select source 12 | Enter a custom playback URL 13 | OK 14 | Error happened (Code - %1$s) 15 | 16 | -------------------------------------------------------------------------------- /customui/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 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 | # https://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 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | @rem Execute Gradle 88 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 89 | 90 | :end 91 | @rem End local scope for the variables with windows NT shell 92 | if "%ERRORLEVEL%"=="0" goto mainEnd 93 | 94 | :fail 95 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 96 | rem the _cmd.exe /c_ return code! 97 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 98 | exit /b 1 99 | 100 | :mainEnd 101 | if "%OS%"=="Windows_NT" endlocal 102 | 103 | :omega 104 | -------------------------------------------------------------------------------- /ivs-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 25 | 26 | -------------------------------------------------------------------------------- /quizdemo/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /quizdemo/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-kapt' 4 | 5 | android { 6 | compileSdkVersion = 33 7 | 8 | defaultConfig { 9 | applicationId "com.amazonaws.ivs.player.quizdemo" 10 | minSdkVersion 21 11 | targetSdkVersion 33 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | 17 | javaCompileOptions { 18 | annotationProcessorOptions { 19 | arguments = [ 20 | "room.schemaLocation":"$projectDir/schemas".toString(), 21 | "room.incremental":"true", 22 | "room.expandProjection":"true"] 23 | } 24 | } 25 | } 26 | 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_11 29 | targetCompatibility JavaVersion.VERSION_11 30 | } 31 | 32 | buildTypes { 33 | release { 34 | minifyEnabled false 35 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 36 | } 37 | } 38 | 39 | buildFeatures { 40 | dataBinding = true 41 | viewBinding = true 42 | } 43 | 44 | namespace 'com.amazonaws.ivs.player.quizdemo' 45 | } 46 | 47 | apply from: 'dependencies.gradle' 48 | -------------------------------------------------------------------------------- /quizdemo/dependencies.gradle: -------------------------------------------------------------------------------- 1 | def appCompatVersion = '1.6.1' 2 | def coreVersion = '1.10.0' 3 | def constraintLayoutVersion = '2.1.4' 4 | def materialDesignVersion = '1.9.0' 5 | def recyclerViewVersion = '1.3.2' 6 | def roomVersion = '2.4.3' 7 | def cardViewVersion = '1.0.0' 8 | def lifecycleVersion = '2.5.1' 9 | def daggerVersion = '2.46.1' 10 | def gsonVersion = '2.8.9' 11 | 12 | dependencies { 13 | 14 | // Libraries 15 | implementation "com.amazonaws:ivs-player:$player_version" 16 | 17 | // Material design 18 | implementation "com.google.android.material:material:$materialDesignVersion" 19 | 20 | // Android 21 | implementation "androidx.recyclerview:recyclerview:$recyclerViewVersion" 22 | implementation "androidx.cardview:cardview:$cardViewVersion" 23 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion" 24 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion" 25 | 26 | // Gson 27 | implementation "com.google.code.gson:gson:$gsonVersion" 28 | 29 | // Room components 30 | implementation "androidx.room:room-runtime:$roomVersion" 31 | implementation "androidx.room:room-ktx:$roomVersion" 32 | kapt "androidx.room:room-compiler:$roomVersion" 33 | 34 | // Dagger 35 | kapt "com.google.dagger:dagger-compiler:$daggerVersion" 36 | kapt "com.google.dagger:dagger-android-processor:$daggerVersion" 37 | implementation "com.google.dagger:dagger-android-support:$daggerVersion" 38 | 39 | implementation "androidx.appcompat:appcompat:$appCompatVersion" 40 | implementation "androidx.core:core-ktx:$coreVersion" 41 | implementation "androidx.constraintlayout:constraintlayout:$constraintLayoutVersion" 42 | } 43 | -------------------------------------------------------------------------------- /quizdemo/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /quizdemo/src/main/java/com/amazonaws/ivs/player/quizdemo/App.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.quizdemo 2 | 3 | import android.app.Application 4 | import com.amazonaws.ivs.player.quizdemo.injection.DaggerInjectionComponent 5 | import com.amazonaws.ivs.player.quizdemo.injection.InjectionComponent 6 | import com.amazonaws.ivs.player.quizdemo.injection.InjectionModule 7 | 8 | class App : Application() { 9 | 10 | override fun onCreate() { 11 | super.onCreate() 12 | component = DaggerInjectionComponent.builder().injectionModule(InjectionModule(this)).build() 13 | } 14 | 15 | companion object { 16 | lateinit var component: InjectionComponent 17 | private set 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /quizdemo/src/main/java/com/amazonaws/ivs/player/quizdemo/activities/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.quizdemo.activities 2 | 3 | import android.content.res.Configuration 4 | import android.os.Bundle 5 | import android.util.Log 6 | import android.view.SurfaceHolder 7 | import android.view.SurfaceView 8 | import android.view.View 9 | import androidx.activity.OnBackPressedCallback 10 | import androidx.appcompat.app.AppCompatActivity 11 | import androidx.databinding.DataBindingUtil 12 | import androidx.lifecycle.Observer 13 | import com.amazonaws.ivs.player.quizdemo.App 14 | import com.amazonaws.ivs.player.quizdemo.R 15 | import com.amazonaws.ivs.player.quizdemo.activities.adapters.AnswerAdapter 16 | import com.amazonaws.ivs.player.quizdemo.activities.dialogs.SourceDialog 17 | import com.amazonaws.ivs.player.quizdemo.common.* 18 | import com.amazonaws.ivs.player.quizdemo.common.Configuration.TAG 19 | import com.amazonaws.ivs.player.quizdemo.data.LocalCacheProvider 20 | import com.amazonaws.ivs.player.quizdemo.databinding.ActivityMainBinding 21 | import com.amazonaws.ivs.player.quizdemo.viewModels.MainViewModel 22 | import com.google.android.material.bottomsheet.BottomSheetBehavior 23 | import javax.inject.Inject 24 | 25 | class MainActivity : AppCompatActivity(), SurfaceHolder.Callback { 26 | 27 | @Inject 28 | lateinit var cacheProvider: LocalCacheProvider 29 | 30 | private val sourceDialog by lazy { SourceDialog(this, viewModel) } 31 | private val answerAdapter by lazy { 32 | AnswerAdapter { position -> 33 | viewModel.checkAnswer(position) 34 | } 35 | } 36 | 37 | private val viewModel: MainViewModel by lazyViewModel { 38 | MainViewModel(application, cacheProvider) 39 | } 40 | 41 | val sheetListener by lazy { 42 | object : BottomSheetBehavior.BottomSheetCallback() { 43 | override fun onSlide(bottomSheet: View, slideOffset: Float) { 44 | binding.sheetBackground.visibility = View.VISIBLE 45 | binding.sheetBackground.alpha = slideOffset 46 | } 47 | 48 | override fun onStateChanged(bottomSheet: View, newState: Int) { 49 | if (newState == BottomSheetBehavior.STATE_HIDDEN || newState == BottomSheetBehavior.STATE_COLLAPSED) { 50 | binding.sheetBackground.visibility = View.GONE 51 | binding.sheetBackground.alpha = 0f 52 | } 53 | } 54 | } 55 | } 56 | 57 | lateinit var binding: ActivityMainBinding 58 | 59 | override fun onConfigurationChanged(newConfig: Configuration) { 60 | super.onConfigurationChanged(newConfig) 61 | changeQuizMargin(newConfig) 62 | } 63 | 64 | override fun onCreate(savedInstanceState: Bundle?) { 65 | super.onCreate(savedInstanceState) 66 | App.component.inject(this) 67 | DataBindingUtil.setContentView(this, R.layout.activity_main).apply { 68 | binding = this 69 | data = viewModel 70 | lifecycleOwner = this@MainActivity 71 | } 72 | 73 | viewModel.playerParamsChanged.observe(this, Observer { 74 | Log.d(TAG, "Player layout params changed ${it.first} ${it.second}") 75 | fitSurfaceToView(binding.surfaceView, it.first, it.second) 76 | }) 77 | 78 | viewModel.errorHappened.observe(this, Observer { 79 | Log.d(TAG, "Error dialog is shown") 80 | showDialog(it.first, it.second) 81 | }) 82 | 83 | viewModel.showQuestions.observe(this, Observer { 84 | if (it == true) { 85 | binding.quizRoot.fadeIn() 86 | } else { 87 | if (viewModel.questionChanged.value == false) { 88 | binding.quizRoot.fadeOut() 89 | } 90 | } 91 | }) 92 | 93 | initUi() 94 | viewModel.playerStart(binding.surfaceView.holder.surface) 95 | 96 | onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { 97 | override fun handleOnBackPressed() { 98 | when { 99 | sourceDialog.isOpened() -> sourceDialog.dismiss() 100 | else -> finish() 101 | } 102 | } 103 | }) 104 | } 105 | 106 | override fun onResume() { 107 | super.onResume() 108 | viewModel.play() 109 | } 110 | 111 | override fun onPause() { 112 | super.onPause() 113 | viewModel.pause() 114 | } 115 | 116 | override fun onDestroy() { 117 | super.onDestroy() 118 | viewModel.release() 119 | sourceDialog.release() 120 | binding.surfaceView.holder.removeCallback(this) 121 | } 122 | 123 | private fun changeQuizMargin(newConfig: Configuration) { 124 | if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { 125 | binding.quizRoot.setBottomMargin(resources.getDimension(R.dimen.quiz_root_portrait_margin).toInt()) 126 | } else { 127 | binding.quizRoot.setBottomMargin(resources.getDimension(R.dimen.quiz_root_landscape_margin).toInt()) 128 | } 129 | } 130 | 131 | private fun initUi() { 132 | binding.surfaceView.holder.addCallback(this) 133 | changeQuizMargin(resources.configuration) 134 | 135 | binding.answerView.apply { 136 | adapter = answerAdapter 137 | } 138 | 139 | viewModel.answers.observe(this, Observer { items -> 140 | Log.d(TAG, "Answers updated: $items") 141 | answerAdapter.items = items 142 | }) 143 | 144 | binding.sheetBackground.setOnClickListener { 145 | sourceDialog.dismiss() 146 | } 147 | 148 | binding.sourceButton.setOnClickListener { 149 | sourceDialog.show() 150 | } 151 | 152 | // Surface view listener for rotation handling 153 | binding.surfaceView.addOnLayoutChangeListener { _, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom -> 154 | if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) { 155 | val width = viewModel.playerParamsChanged.value?.first 156 | val height = viewModel.playerParamsChanged.value?.second 157 | if (width != null && height != null) { 158 | binding.surfaceView.post { 159 | Log.d(TAG, "On rotation player layout params changed $width $height") 160 | fitSurfaceToView(binding.surfaceView, width, height) 161 | } 162 | } 163 | } 164 | } 165 | 166 | } 167 | 168 | private fun fitSurfaceToView(surfaceView: SurfaceView, width: Int, height: Int) { 169 | val parent = surfaceView.parent as View 170 | val oldWidth = parent.width 171 | val oldHeight = parent.height 172 | val newWidth: Int 173 | val newHeight: Int 174 | val ratio = height.toFloat() / width.toFloat() 175 | if (oldHeight.toFloat() > oldWidth.toFloat() * ratio) { 176 | newWidth = oldWidth 177 | newHeight = (oldWidth.toFloat() * ratio).toInt() 178 | } else { 179 | newWidth = (oldHeight.toFloat() / ratio).toInt() 180 | newHeight = oldHeight 181 | } 182 | val layoutParams = surfaceView.layoutParams 183 | layoutParams.width = newWidth 184 | layoutParams.height = newHeight 185 | surfaceView.layoutParams = layoutParams 186 | } 187 | 188 | override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { 189 | /* Ignored */ 190 | } 191 | 192 | override fun surfaceDestroyed(holder: SurfaceHolder) { 193 | Log.d(TAG, "Surface destroyed") 194 | viewModel.updateSurface(null) 195 | } 196 | 197 | override fun surfaceCreated(holder: SurfaceHolder) { 198 | Log.d(TAG, "Surface created") 199 | viewModel.updateSurface(holder.surface) 200 | } 201 | 202 | } 203 | -------------------------------------------------------------------------------- /quizdemo/src/main/java/com/amazonaws/ivs/player/quizdemo/activities/adapters/AnswerAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.quizdemo.activities.adapters 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.amazonaws.ivs.player.quizdemo.databinding.AnswerListItemBinding 7 | import com.amazonaws.ivs.player.quizdemo.models.AnswerViewItem 8 | import kotlin.properties.Delegates 9 | 10 | class AnswerAdapter(private val onAnswerClicked: (position: Int) -> Unit) : 11 | RecyclerView.Adapter() { 12 | 13 | var items: List by Delegates.observable(emptyList()) { _, _, _ -> 14 | notifyDataSetChanged() 15 | } 16 | 17 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 18 | val binding = 19 | AnswerListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) 20 | return ViewHolder(binding) 21 | } 22 | 23 | override fun getItemCount(): Int = items.size 24 | 25 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 26 | holder.binding.data = items[position] 27 | holder.binding.optionItem.setOnClickListener { 28 | onAnswerClicked(position) 29 | } 30 | } 31 | 32 | inner class ViewHolder( 33 | val binding: AnswerListItemBinding 34 | ) : RecyclerView.ViewHolder(binding.root) 35 | 36 | } 37 | -------------------------------------------------------------------------------- /quizdemo/src/main/java/com/amazonaws/ivs/player/quizdemo/activities/adapters/SourceOptionAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.quizdemo.activities.adapters 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.DiffUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.amazonaws.ivs.player.quizdemo.data.entity.SourceDataItem 8 | import com.amazonaws.ivs.player.quizdemo.databinding.SourceItemBinding 9 | import kotlin.properties.Delegates 10 | 11 | /** 12 | * Player source option selection adapter 13 | */ 14 | class SourceOptionAdapter( 15 | private val callback: PlayerOptionCallback 16 | ) : RecyclerView.Adapter() { 17 | 18 | var items: List by Delegates.observable(emptyList()) { _, old, new -> 19 | DiffUtil.calculateDiff(SourceOptionDiff(old, new)).dispatchUpdatesTo(this) 20 | } 21 | 22 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 23 | val binding = SourceItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) 24 | return ViewHolder(binding) 25 | } 26 | 27 | override fun getItemCount(): Int = items.size 28 | 29 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 30 | holder.binding.data = items[position] 31 | val dataItem = items[position] 32 | 33 | holder.binding.sourceName.setOnClickListener { 34 | callback.onOptionClicked(dataItem.url) 35 | } 36 | 37 | holder.binding.deleteBtn.setOnClickListener { 38 | callback.onOptionDelete(dataItem.url) 39 | } 40 | } 41 | 42 | inner class ViewHolder(val binding: SourceItemBinding) : RecyclerView.ViewHolder(binding.root) 43 | 44 | interface PlayerOptionCallback { 45 | 46 | fun onOptionClicked(url: String) 47 | 48 | fun onOptionDelete(url: String) 49 | } 50 | 51 | inner class SourceOptionDiff( 52 | private val oldItems: List, 53 | private val newItems: List 54 | ) : DiffUtil.Callback() { 55 | 56 | override fun getOldListSize() = oldItems.size 57 | 58 | override fun getNewListSize() = newItems.size 59 | 60 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 61 | return oldItems[oldItemPosition].url == newItems[newItemPosition].url 62 | } 63 | 64 | override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 65 | return oldItems[oldItemPosition] == newItems[newItemPosition] 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /quizdemo/src/main/java/com/amazonaws/ivs/player/quizdemo/activities/dialogs/SourceDialog.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.quizdemo.activities.dialogs 2 | 3 | import android.net.Uri 4 | import android.util.Log 5 | import android.view.View 6 | import android.view.inputmethod.EditorInfo 7 | import android.widget.Button 8 | import android.widget.EditText 9 | import android.widget.TextView 10 | import androidx.lifecycle.Observer 11 | import androidx.recyclerview.widget.RecyclerView 12 | import com.amazonaws.ivs.player.quizdemo.R 13 | import com.amazonaws.ivs.player.quizdemo.activities.MainActivity 14 | import com.amazonaws.ivs.player.quizdemo.activities.adapters.SourceOptionAdapter 15 | import com.amazonaws.ivs.player.quizdemo.common.* 16 | import com.amazonaws.ivs.player.quizdemo.data.entity.SourceDataItem 17 | import com.amazonaws.ivs.player.quizdemo.viewModels.MainViewModel 18 | import com.google.android.material.bottomsheet.BottomSheetBehavior 19 | 20 | class SourceDialog( 21 | private val activity: MainActivity, 22 | private val viewModel: MainViewModel 23 | ) : SourceOptionAdapter.PlayerOptionCallback { 24 | 25 | private val sourceMenu by lazy { BottomSheetBehavior.from(activity.findViewById(R.id.source_sheet)) } 26 | private val optionsAdapter by lazy { SourceOptionAdapter(this) } 27 | 28 | init { 29 | sourceMenu.isFitToContents = false 30 | initViews() 31 | } 32 | 33 | private fun initViews() { 34 | val sourceView = activity.findViewById(R.id.source) 35 | val okBtn = activity.findViewById(R.id.ok_btn) 36 | val closeBtn = activity.findViewById(R.id.close_btn) 37 | 38 | okBtn.setOnClickListener { 39 | val selectedItem = sourceView.text.toString() 40 | val source = SourceDataItem(selectedItem, selectedItem) 41 | viewModel.addSource(source) 42 | onOptionClicked(selectedItem) 43 | dismiss() 44 | } 45 | 46 | closeBtn.setOnClickListener{ 47 | dismiss() 48 | } 49 | 50 | sourceView.setOnEditorActionListener { view, actionId, _ -> 51 | if (actionId == EditorInfo.IME_ACTION_DONE) { 52 | val selectedItem = view.text.toString() 53 | val source = SourceDataItem(selectedItem, selectedItem) 54 | viewModel.addSource(source) 55 | onOptionClicked(selectedItem) 56 | dismiss() 57 | } 58 | false 59 | } 60 | 61 | initAdapter() 62 | 63 | sourceMenu.addBottomSheetCallback(activity.sheetListener) 64 | } 65 | 66 | fun show() { 67 | sourceMenu.open() 68 | } 69 | 70 | fun dismiss() { 71 | sourceMenu.hide() 72 | activity.hideKeyboard() 73 | } 74 | 75 | fun isOpened() = sourceMenu.isOpened() 76 | 77 | fun release() { 78 | sourceMenu.removeBottomSheetCallback(activity.sheetListener) 79 | } 80 | 81 | private fun initAdapter() { 82 | // Default option list 83 | activity.findViewById(R.id.option_list).apply { 84 | adapter = optionsAdapter 85 | } 86 | 87 | viewModel.sources.observe(activity) { 88 | optionsAdapter.items = it 89 | } 90 | } 91 | 92 | override fun onOptionClicked(url: String) { 93 | Log.d(Configuration.TAG,"Url selected $url") 94 | viewModel.url.value = url 95 | viewModel.playerLoadStream(Uri.parse(url)) 96 | dismiss() 97 | } 98 | 99 | override fun onOptionDelete(url: String) { 100 | Log.d(Configuration.TAG,"Url deleted $url") 101 | viewModel.deleteSource(url) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /quizdemo/src/main/java/com/amazonaws/ivs/player/quizdemo/common/CommonExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.quizdemo.common 2 | 3 | import android.app.Activity 4 | import android.app.Dialog 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.view.Window 8 | import android.view.animation.AlphaAnimation 9 | import android.view.animation.DecelerateInterpolator 10 | import android.view.inputmethod.InputMethodManager 11 | import androidx.core.content.ContextCompat 12 | import com.amazonaws.ivs.player.quizdemo.R 13 | import com.amazonaws.ivs.player.quizdemo.databinding.ViewDialogBinding 14 | import com.amazonaws.ivs.player.quizdemo.models.AnswerViewItem 15 | import com.amazonaws.ivs.player.quizdemo.models.QuestionModel 16 | import com.google.android.material.bottomsheet.BottomSheetBehavior 17 | 18 | 19 | fun BottomSheetBehavior.isOpened() = 20 | state == BottomSheetBehavior.STATE_EXPANDED || state == BottomSheetBehavior.STATE_HALF_EXPANDED 21 | 22 | fun BottomSheetBehavior.open() { 23 | if (!isOpened()) { 24 | state = BottomSheetBehavior.STATE_EXPANDED 25 | } 26 | } 27 | 28 | fun BottomSheetBehavior.hide() { 29 | if (isOpened()) { 30 | state = BottomSheetBehavior.STATE_HIDDEN 31 | } 32 | } 33 | 34 | fun Activity.hideKeyboard() { 35 | val view = currentFocus ?: window.decorView 36 | val token = view.windowToken 37 | view.clearFocus() 38 | ContextCompat.getSystemService(this, InputMethodManager::class.java) 39 | ?.hideSoftInputFromWindow(token, 0) 40 | } 41 | 42 | fun Activity.showDialog(title: String, message: String) { 43 | val dialog = Dialog(this) 44 | dialog.requestWindowFeature(Window.FEATURE_NO_TITLE) 45 | dialog.setCancelable(false) 46 | 47 | val binding = ViewDialogBinding.inflate(layoutInflater) 48 | dialog.setContentView(binding.root) 49 | 50 | 51 | binding.title.text = getString(R.string.error_happened_template, title) 52 | binding.message.text = message 53 | binding.dismissBtn.setOnClickListener { 54 | dialog.dismiss() 55 | } 56 | dialog.show() 57 | } 58 | 59 | fun View.fadeIn() { 60 | if (this.visibility == View.INVISIBLE) { 61 | val fadeIn = AlphaAnimation(0f, 1f) 62 | fadeIn.interpolator = DecelerateInterpolator() 63 | fadeIn.duration = Configuration.ANIMATION_DURATION 64 | startAnimation(fadeIn) 65 | this.visibility = View.VISIBLE 66 | } 67 | } 68 | 69 | fun View.fadeOut() { 70 | if (this.visibility == View.VISIBLE) { 71 | val fadeOut = AlphaAnimation(1f, 0f) 72 | fadeOut.interpolator = DecelerateInterpolator() 73 | fadeOut.startOffset = Configuration.ANIMATION_DURATION 74 | fadeOut.duration = Configuration.ANIMATION_DURATION 75 | startAnimation(fadeOut) 76 | this.visibility = View.INVISIBLE 77 | } 78 | } 79 | 80 | fun QuestionModel.toAnswerList(): List = 81 | answers.mapIndexed { index, answer -> AnswerViewItem(answer, correctIndex == index) } 82 | 83 | fun View.setBottomMargin(margin: Int) { 84 | val params = this.layoutParams as ViewGroup.MarginLayoutParams 85 | params.bottomMargin = margin 86 | layoutParams = params 87 | } 88 | -------------------------------------------------------------------------------- /quizdemo/src/main/java/com/amazonaws/ivs/player/quizdemo/common/Configuration.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.quizdemo.common 2 | 3 | object Configuration { 4 | const val TAG = "QuizDemo" 5 | 6 | const val DEFAULT = "Pre-defined stream 1" 7 | const val LINK = "https://4c62a87c1810.us-west-2.playback.live-video.net/api/video/v1/us-west-2.049054135175.channel.GHRwjPylmdXm.m3u8" 8 | 9 | const val PORTRAIT_OPTION = "Pre-defined stream 2" 10 | const val LIVE_PORTRAIT_LINK = "https://4c62a87c1810.us-west-2.playback.live-video.net/api/video/v1/us-west-2.049054135175.channel.GHRwjPylmdXm.m3u8?allow_source=true" 11 | 12 | const val ANSWER_DELAY = 1500L 13 | const val ANIMATION_DURATION = 300L 14 | } 15 | -------------------------------------------------------------------------------- /quizdemo/src/main/java/com/amazonaws/ivs/player/quizdemo/common/PlayerExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.quizdemo.common 2 | 3 | import android.util.Log 4 | import com.amazonaws.ivs.player.Cue 5 | import com.amazonaws.ivs.player.Player 6 | import com.amazonaws.ivs.player.PlayerException 7 | import com.amazonaws.ivs.player.Quality 8 | import kotlinx.coroutines.* 9 | import java.nio.ByteBuffer 10 | 11 | private val ioScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) 12 | private val mainScope = CoroutineScope(Dispatchers.Main + SupervisorJob()) 13 | 14 | fun launchIO(block: suspend CoroutineScope.() -> Unit) = ioScope.launch( 15 | context = CoroutineExceptionHandler { _, e -> Log.d(Configuration.TAG,"Coroutine failed ${e.localizedMessage}") }, 16 | block = block 17 | ) 18 | 19 | fun launchMain(block: suspend CoroutineScope.() -> Unit) = mainScope.launch( 20 | context = CoroutineExceptionHandler { _, e -> Log.d(Configuration.TAG,"Coroutine failed ${e.localizedMessage}") }, 21 | block = block 22 | ) 23 | 24 | /** 25 | * Player listener extension function 26 | */ 27 | inline fun Player.setListener( 28 | crossinline onAnalyticsEvent: (key: String, value: String) -> Unit = { _,_ -> Unit }, 29 | crossinline onRebuffering: () -> Unit = {}, 30 | crossinline onSeekCompleted: (value: Long) -> Unit = { _ -> Unit }, 31 | crossinline onQualityChanged: (quality: Quality) -> Unit = { _ -> Unit }, 32 | crossinline onVideoSizeChanged: (width: Int, height: Int) -> Unit = { _,_ -> Unit }, 33 | crossinline onCue: (cue: Cue) -> Unit = { _ -> Unit }, 34 | crossinline onDurationChanged: (duration: Long) -> Unit = { _ -> Unit }, 35 | crossinline onStateChanged: (state: Player.State) -> Unit = { _ -> Unit }, 36 | crossinline onError: (exception: PlayerException) -> Unit = { _ -> Unit }, 37 | crossinline onMetadata: (data: String, buffer: ByteBuffer) -> Unit = { _,_ -> Unit } 38 | ): Player.Listener { 39 | val listener = playerListener( 40 | onAnalyticsEvent, onRebuffering, onSeekCompleted, onQualityChanged, onVideoSizeChanged, 41 | onCue, onDurationChanged, onStateChanged, onError, onMetadata 42 | ) 43 | 44 | addListener(listener) 45 | return listener 46 | } 47 | 48 | /** 49 | * Player.Listener provides an implementation of this interface to addListener(Listener) to receive events from a Player instance. 50 | */ 51 | inline fun playerListener( 52 | crossinline onAnalyticsEvent: (key: String, value: String) -> Unit = { _,_ -> Unit }, 53 | crossinline onRebuffering: () -> Unit = {}, 54 | crossinline onSeekCompleted: (value: Long) -> Unit = { _ -> Unit }, 55 | crossinline onQualityChanged: (quality: Quality) -> Unit = { _ -> Unit }, 56 | crossinline onVideoSizeChanged: (width: Int, height: Int) -> Unit = { _,_ -> Unit }, 57 | crossinline onCue: (cue: Cue) -> Unit = { _ -> Unit }, 58 | crossinline onDurationChanged: (duration: Long) -> Unit = { _ -> Unit }, 59 | crossinline onStateChanged: (state: Player.State) -> Unit = { _ -> Unit }, 60 | crossinline onError: (exception: PlayerException) -> Unit = { _ -> Unit }, 61 | crossinline onMetadata: (data: String, buffer: ByteBuffer) -> Unit = { _,_ -> Unit } 62 | ): Player.Listener = object : Player.Listener() { 63 | // Indicates that a video analytics tracking event occurred. 64 | override fun onAnalyticsEvent(key: String, value: String) = onAnalyticsEvent(key, value) 65 | // Indicates that the player is buffering from a previous PLAYING state. 66 | override fun onRebuffering() = onRebuffering() 67 | // Indicates that the player has seeked to a given position as requested from seekTo(long). 68 | override fun onSeekCompleted(value: Long) = onSeekCompleted(value) 69 | // Indicates that the playing quality changed either from a user action or from an internal adaptive quality switch. 70 | override fun onQualityChanged(quality: Quality) = onQualityChanged(quality) 71 | // Indicates that the video dimensions changed. 72 | override fun onVideoSizeChanged(width: Int, height: Int) = onVideoSizeChanged(width, height) 73 | // Indicates that a timed cue was received. 74 | override fun onCue(cue: Cue) = onCue(cue) 75 | // Indicates that source duration changed 76 | override fun onDurationChanged(duration: Long) = onDurationChanged(duration) 77 | // Indicates that the player state changed. 78 | override fun onStateChanged(state: Player.State) = onStateChanged(state) 79 | // Indicates that an error occurred. 80 | override fun onError(exception: PlayerException) = onError(exception) 81 | // Indicates that a metadata event occurred. 82 | override fun onMetadata(data: String, buffer: ByteBuffer) = onMetadata(data, buffer) 83 | } 84 | -------------------------------------------------------------------------------- /quizdemo/src/main/java/com/amazonaws/ivs/player/quizdemo/common/ViewModelExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.quizdemo.common 2 | 3 | import androidx.fragment.app.FragmentActivity 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.ViewModelProvider 6 | 7 | inline fun FragmentActivity.getViewModel(noinline creator: (() -> T)? = null): T { 8 | return if (creator == null) 9 | ViewModelProvider(this).get(T::class.java) 10 | else 11 | ViewModelProvider(this, BaseViewModelFactory(creator)).get(T::class.java) 12 | } 13 | 14 | inline fun FragmentActivity.lazyViewModel(noinline creator: (() -> T)? = null) = lazy { 15 | getViewModel(creator) 16 | } 17 | 18 | class BaseViewModelFactory(val creator: () -> T) : ViewModelProvider.Factory { 19 | override fun create(modelClass: Class): T { 20 | @Suppress("UNCHECKED_CAST") 21 | return creator() as T 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /quizdemo/src/main/java/com/amazonaws/ivs/player/quizdemo/data/LocalCacheProvider.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.quizdemo.data 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import com.amazonaws.ivs.player.quizdemo.data.entity.SourceDataItem 6 | 7 | @Database(entities = [SourceDataItem::class], version = 1, exportSchema = false) 8 | abstract class LocalCacheProvider : RoomDatabase() { 9 | 10 | abstract fun sourcesDao(): QuizSourceDao 11 | 12 | } 13 | -------------------------------------------------------------------------------- /quizdemo/src/main/java/com/amazonaws/ivs/player/quizdemo/data/QuizSourceDao.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.quizdemo.data 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import com.amazonaws.ivs.player.quizdemo.data.entity.SourceDataItem 8 | import kotlinx.coroutines.flow.Flow 9 | 10 | @Dao 11 | interface QuizSourceDao { 12 | 13 | // Get source 14 | @Query("SELECT * FROM source_table") 15 | fun getAll(): Flow> 16 | 17 | // Insert source 18 | @Insert(onConflict = OnConflictStrategy.IGNORE) 19 | fun insert(sourceDataItem: SourceDataItem) 20 | 21 | // Delete source 22 | @Query("DELETE FROM source_table WHERE url = :url") 23 | fun delete(url: String) 24 | } 25 | -------------------------------------------------------------------------------- /quizdemo/src/main/java/com/amazonaws/ivs/player/quizdemo/data/entity/SourceDataItem.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.quizdemo.data.entity 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import com.amazonaws.ivs.player.quizdemo.common.Configuration 6 | 7 | @Entity(tableName = "source_table") 8 | data class SourceDataItem( 9 | @PrimaryKey 10 | val url: String, 11 | val title: String = "", 12 | val isDefault: Boolean = false 13 | ) { 14 | fun isDefaultOption(): Boolean = (title == Configuration.DEFAULT) || (title == Configuration.PORTRAIT_OPTION) 15 | } 16 | -------------------------------------------------------------------------------- /quizdemo/src/main/java/com/amazonaws/ivs/player/quizdemo/injection/InjectionComponent.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.quizdemo.injection 2 | 3 | import com.amazonaws.ivs.player.quizdemo.activities.MainActivity 4 | import dagger.Component 5 | import javax.inject.Singleton 6 | 7 | @Singleton 8 | @Component(modules = [InjectionModule::class]) 9 | interface InjectionComponent { 10 | fun inject(target: MainActivity) 11 | } 12 | -------------------------------------------------------------------------------- /quizdemo/src/main/java/com/amazonaws/ivs/player/quizdemo/injection/InjectionModule.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.quizdemo.injection 2 | 3 | import androidx.room.Room 4 | import com.amazonaws.ivs.player.quizdemo.App 5 | import com.amazonaws.ivs.player.quizdemo.data.LocalCacheProvider 6 | import dagger.Module 7 | import dagger.Provides 8 | import javax.inject.Singleton 9 | 10 | @Module 11 | class InjectionModule(private val context: App) { 12 | 13 | @Provides 14 | @Singleton 15 | fun provideLocalCacheProvider(): LocalCacheProvider = 16 | Room.databaseBuilder(context, LocalCacheProvider::class.java, "quiz_database").build() 17 | 18 | } 19 | -------------------------------------------------------------------------------- /quizdemo/src/main/java/com/amazonaws/ivs/player/quizdemo/models/AnswerViewItem.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.quizdemo.models 2 | 3 | data class AnswerViewItem( 4 | val answer: String, 5 | val isCorrect: Boolean, 6 | var isSelected: Boolean = false, 7 | var isAnsweredCorrect: Boolean = false 8 | ) 9 | -------------------------------------------------------------------------------- /quizdemo/src/main/java/com/amazonaws/ivs/player/quizdemo/models/QuestionModel.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.quizdemo.models 2 | 3 | data class QuestionModel( 4 | val question: String, 5 | val answers: List, 6 | val correctIndex: Int 7 | ) 8 | -------------------------------------------------------------------------------- /quizdemo/src/main/java/com/amazonaws/ivs/player/quizdemo/viewModels/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.amazonaws.ivs.player.quizdemo.viewModels 2 | 3 | import android.app.Application 4 | import android.net.Uri 5 | import android.util.Log 6 | import android.view.Surface 7 | import androidx.lifecycle.MutableLiveData 8 | import androidx.lifecycle.ViewModel 9 | import com.amazonaws.ivs.player.MediaType 10 | import com.amazonaws.ivs.player.Player 11 | import com.amazonaws.ivs.player.quizdemo.common.* 12 | import com.amazonaws.ivs.player.quizdemo.common.Configuration.TAG 13 | import com.amazonaws.ivs.player.quizdemo.data.LocalCacheProvider 14 | import com.amazonaws.ivs.player.quizdemo.models.QuestionModel 15 | import com.amazonaws.ivs.player.quizdemo.data.entity.SourceDataItem 16 | import com.amazonaws.ivs.player.quizdemo.models.AnswerViewItem 17 | import com.google.gson.Gson 18 | import kotlinx.coroutines.delay 19 | import kotlinx.coroutines.flow.collect 20 | 21 | class MainViewModel( 22 | private val context: Application, 23 | private val cacheProvider: LocalCacheProvider 24 | ) : ViewModel() { 25 | 26 | private var player: Player? = null 27 | private var playerListener: Player.Listener? = null 28 | 29 | val liveStream = MutableLiveData() 30 | val showLabel = MutableLiveData() 31 | val url = MutableLiveData() 32 | val buffering = MutableLiveData() 33 | val showQuestions = MutableLiveData() 34 | val playerParamsChanged = MutableLiveData>() 35 | val errorHappened = MutableLiveData>() 36 | val sources = MutableLiveData>() 37 | val answers = MutableLiveData>() 38 | val question = MutableLiveData() 39 | val questionChanged = MutableLiveData() 40 | 41 | init { 42 | url.value = Configuration.LINK 43 | initPlayer() 44 | getSources() 45 | } 46 | 47 | private fun initPlayer() { 48 | // Media player initialization 49 | player = Player.Factory.create(context) 50 | 51 | player?.setListener( 52 | onVideoSizeChanged = { width, height -> 53 | Log.d(TAG, "Video size changed: $width $height") 54 | playerParamsChanged.value = Pair(width, height) 55 | }, 56 | onDurationChanged = { durationValue -> 57 | Log.d(TAG, "Duration changed: $durationValue") 58 | liveStream.value = !(player?.duration != null && player!!.duration > 0L) 59 | }, 60 | onStateChanged = { state -> 61 | Log.d(TAG, "State changed: $state") 62 | when (state) { 63 | Player.State.BUFFERING -> { 64 | buffering.value = true 65 | showLabel.value = false 66 | } 67 | else -> { 68 | buffering.value = false 69 | showLabel.value = true 70 | } 71 | } 72 | }, 73 | onMetadata = { data, buffer -> 74 | if (MediaType.TEXT_PLAIN == data) { 75 | try { 76 | // Get question data item from buffer 77 | val questionModel = Gson().fromJson( 78 | String(buffer.array(), Charsets.UTF_8), 79 | QuestionModel::class.java 80 | ) 81 | Log.d(TAG, "Received quiz data: $questionModel") 82 | question.value = questionModel.question 83 | answers.value = questionModel.toAnswerList() 84 | showQuestions() 85 | } catch (exception: Exception) { 86 | Log.d(TAG, "Error happened: $exception") 87 | } 88 | } 89 | }, 90 | onError = { exception -> 91 | Log.d(TAG, "Error happened: $exception") 92 | errorHappened.value = Pair(exception.code.toString(), exception.errorMessage) 93 | } 94 | ) 95 | } 96 | 97 | fun playerStart(surface: Surface) { 98 | Log.d(TAG, "Starting player") 99 | updateSurface(surface) 100 | playerLoadStream(Uri.parse(url.value)) 101 | play() 102 | } 103 | 104 | fun playerLoadStream(uri: Uri) { 105 | Log.d(TAG, "Loading stream URI: $uri") 106 | // Loads the specified stream 107 | player?.load(uri) 108 | hideQuestions() 109 | player?.play() 110 | } 111 | 112 | fun updateSurface(surface: Surface?) { 113 | Log.d(TAG, "Updating player surface: $surface") 114 | // Sets the Surface to use for rendering video 115 | player?.setSurface(surface) 116 | } 117 | 118 | private fun showQuestions() = launchMain { 119 | questionChanged.value = true 120 | showQuestions.value = true 121 | } 122 | 123 | private fun hideQuestions() = launchMain { 124 | questionChanged.value = false 125 | showQuestions.value = false 126 | } 127 | 128 | fun checkAnswer(position: Int) = launchMain { 129 | // Check answers if no answer is already selected 130 | answers.value?.takeIf { it.find { answer -> answer.isSelected } == null }?.let { answerList -> 131 | Log.d(TAG,"Selecting answer") 132 | answers.value = answerList.apply { 133 | forEachIndexed { index, item -> 134 | if (index == position || item.isCorrect) item.isSelected = true 135 | item.isAnsweredCorrect = item.isCorrect 136 | } 137 | } 138 | } 139 | questionChanged.value = false 140 | if (showQuestions.value == true) { 141 | delay(Configuration.ANSWER_DELAY) 142 | showQuestions.value = questionChanged.value 143 | } 144 | } 145 | 146 | fun play() { 147 | Log.d(TAG, "Starting playback") 148 | // Starts or resumes playback of the stream. 149 | player?.play() 150 | } 151 | 152 | fun pause() { 153 | Log.d(TAG, "Pausing playback") 154 | // Pauses playback of the stream. 155 | player?.pause() 156 | } 157 | 158 | fun release() { 159 | Log.d(TAG, "Releasing player") 160 | // Removes a playback state listener 161 | playerListener?.let { player?.removeListener(it) } 162 | // Releases the player instance 163 | player?.release() 164 | player = null 165 | } 166 | 167 | private fun getSources() { 168 | Log.d(TAG, "Collecting sources") 169 | launchMain { 170 | cacheProvider.sourcesDao().getAll().collect { 171 | val itemList: MutableList = mutableListOf( 172 | SourceDataItem(Configuration.LINK, Configuration.DEFAULT), 173 | SourceDataItem(Configuration.LIVE_PORTRAIT_LINK, Configuration.PORTRAIT_OPTION) 174 | ) 175 | itemList.addAll(it) 176 | sources.value = itemList 177 | } 178 | } 179 | } 180 | 181 | fun deleteSource(url: String) { 182 | Log.d(TAG, "Deleting source: $url") 183 | launchIO { 184 | cacheProvider.sourcesDao().delete(url) 185 | } 186 | } 187 | 188 | fun addSource(source: SourceDataItem) { 189 | Log.d(TAG, "Adding source: $source") 190 | launchIO { 191 | cacheProvider.sourcesDao().insert(source) 192 | } 193 | } 194 | 195 | } 196 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/drawable/answer_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/drawable/correct_answer_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/drawable/ic_close.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/drawable/ic_close_selected.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/drawable/ic_done.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/drawable/ic_more.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/drawable/ic_more_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/drawable/ic_more_pressed.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/drawable/ic_source_close.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/drawable/incorrect_answer_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/drawable/more_circle_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 10 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/drawable/player_live_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/drawable/quiz_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/drawable/selector_close.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/drawable/selector_source_close.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/drawable/source_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 21 | 22 | 25 | 26 | 37 | 38 | 44 | 45 | 53 | 54 | 70 | 71 | 84 | 85 | 97 | 98 | 113 | 114 | 124 | 125 | 131 | 132 | 133 | 134 | 135 | 136 | 145 | 146 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/layout/answer_list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/layout/player_source_sheet.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 17 | 18 | 30 | 31 | 38 | 39 | 40 | 41 | 48 | 49 | 61 | 62 | 69 | 70 | 71 | 72 | 80 | 81 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/layout/source_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 23 | 24 | 40 | 41 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/layout/view_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 20 | 21 | 32 | 33 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/quizdemo/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /quizdemo/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/quizdemo/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /quizdemo/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/quizdemo/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /quizdemo/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/quizdemo/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /quizdemo/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/quizdemo/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /quizdemo/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/quizdemo/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /quizdemo/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/quizdemo/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /quizdemo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/quizdemo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /quizdemo/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/quizdemo/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /quizdemo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-ivs-player-android-sample/b874ac68e0a1ca2945d4698063d18ee4d5029da3/quizdemo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /quizdemo/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ED7E18 4 | #1B1B1B 5 | #ED7E18 6 | #FA353C48 7 | #FA424B5A 8 | #99FFFFFF 9 | #99ED7E18 10 | #FFFFFF 11 | #80000000 12 | #000000 13 | #FF0000 14 | #05AC1B 15 | #DFDFDF 16 | #F4F4F4 17 | 18 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/values/dimen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8dp 5 | 12dp 6 | 16dp 7 | 8 | 10sp 9 | 14sp 10 | 16sp 11 | 12 | 25dp 13 | 14 | 40dp 15 | 50dp 16 | 0dp 17 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | QuizDemo 3 | OK 4 | Error happened (Code - %1$s) 5 | 6 | ● LIVE 7 | Waiting for the next question 8 | Select source 9 | Enter a custom playback URL 10 | 11 | -------------------------------------------------------------------------------- /quizdemo/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | 9 | dependencyResolutionManagement { 10 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 11 | repositories { 12 | google() 13 | mavenCentral() 14 | 15 | def customMavenUrl = System.getenv('CUSTOM_MAVEN_URL') 16 | if (customMavenUrl) { 17 | println(">>> Using custom maven URL ${customMavenUrl}") 18 | maven { 19 | url customMavenUrl 20 | } 21 | } 22 | } 23 | } 24 | 25 | include ':basicplayback', ':customui', ':quizdemo' 26 | rootProject.name='AndroidPlayerSample' 27 | --------------------------------------------------------------------------------