├── .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 |
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 |
--------------------------------------------------------------------------------