├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── example
│ │ └── andrioidexoplayertrackselection
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ └── media.exolist.json
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── andrioidexoplayertrackselection
│ │ │ ├── MainActivity.kt
│ │ │ ├── exoplayer2
│ │ │ ├── DemoDownloadService.java
│ │ │ ├── DownloadTracker.java
│ │ │ ├── PlayerActivity.java
│ │ │ └── VideoDownloadManager.java
│ │ │ └── track_selection_dialog
│ │ │ ├── FragmentAdapter.java
│ │ │ ├── TrackSelectionDialog.java
│ │ │ └── TrackSelectionViewFragment.java
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── ic_download.xml
│ │ ├── ic_download_done.xml
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── player_activity.xml
│ │ └── track_selection_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
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── example
│ └── andrioidexoplayertrackselection
│ └── ExampleUnitTest.kt
├── build.gradle
├── captures
├── screen1.png
└── selection.png
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | #built application files
2 | *.apk
3 | *.ap_
4 |
5 | # files for the dex VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # generated files
12 | bin/
13 | gen/
14 |
15 | # Local configuration file (sdk path, etc)
16 | local.properties
17 |
18 | # Windows thumbnail db
19 | Thumbs.db
20 |
21 | # OSX files
22 | .DS_Store
23 |
24 | # Eclipse project files
25 | .classpath
26 | .project
27 |
28 | # Android Studio
29 | *.iml
30 | .idea
31 | #.idea/workspace.xml - remove # and delete .idea if it better suit your needs.
32 | .gradle
33 | build/
34 |
35 | #NDK
36 | obj/
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Android ExoPlayer 2 track selection example
2 |
3 | https://www.codexpedia.com/android/android-exoplayer-2-track-selection-example/
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | apply plugin: 'kotlin-android'
4 |
5 | apply plugin: 'kotlin-android-extensions'
6 |
7 | android {
8 | compileSdkVersion 28
9 | defaultConfig {
10 | applicationId "com.example.andrioidexoplayertrackselection"
11 | minSdkVersion 16
12 | targetSdkVersion 28
13 | versionCode 1
14 | versionName "1.0"
15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 |
24 | compileOptions {
25 | sourceCompatibility JavaVersion.VERSION_1_8
26 | targetCompatibility JavaVersion.VERSION_1_8
27 | }
28 | }
29 |
30 | dependencies {
31 | implementation fileTree(dir: 'libs', include: ['*.jar'])
32 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
33 | implementation 'androidx.appcompat:appcompat:1.1.0'
34 | implementation 'androidx.core:core-ktx:1.1.0'
35 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
36 | testImplementation 'junit:junit:4.12'
37 | androidTestImplementation 'androidx.test.ext:junit:1.1.1'
38 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
39 |
40 | implementation 'com.google.android.material:material:1.0.0'
41 | implementation 'com.google.android.exoplayer:exoplayer:2.10.7'
42 | implementation 'com.google.code.gson:gson:2.8.6'
43 | }
44 |
--------------------------------------------------------------------------------
/app/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 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/example/andrioidexoplayertrackselection/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.andrioidexoplayertrackselection
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.example.andrioidexoplayertrackselection", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/app/src/main/assets/media.exolist.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "HLS",
4 | "samples": [
5 | {
6 | "name": "Apple master playlist advanced (fMP4)",
7 | "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8"
8 | },
9 | {
10 | "name": "Apple 4x3 basic stream",
11 | "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8"
12 | },
13 | {
14 | "name": "Apple 16x9 basic stream",
15 | "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
16 | },
17 | {
18 | "name": "Apple master playlist advanced (TS)",
19 | "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_ts/master.m3u8"
20 | },
21 | {
22 | "name": "Apple TS media playlist",
23 | "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/gear1/prog_index.m3u8"
24 | },
25 | {
26 | "name": "Apple AAC media playlist",
27 | "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/gear0/prog_index.m3u8"
28 | }
29 | ]
30 | },
31 | {
32 | "name": "YouTube DASH",
33 | "samples": [
34 | {
35 | "name": "Google Glass (MP4,H264)",
36 | "uri": "https://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=51AF5F39AB0CEC3E5497CD9C900EBFEAECCCB5C7.8506521BFC350652163895D4C26DEE124209AA9E&key=ik0",
37 | "extension": "mpd"
38 | },
39 | {
40 | "name": "Google Play (MP4,H264)",
41 | "uri": "https://www.youtube.com/api/manifest/dash/id/3aa39fa2cc27967f/source/youtube?as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=A2716F75795F5D2AF0E88962FFCD10DB79384F29.84308FF04844498CE6FBCE4731507882B8307798&key=ik0",
42 | "extension": "mpd"
43 | },
44 | {
45 | "name": "Google Glass (WebM,VP9)",
46 | "uri": "https://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?as=fmp4_audio_clear,webm2_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=249B04F79E984D7F86B4D8DB48AE6FAF41C17AB3.7B9F0EC0505E1566E59B8E488E9419F253DDF413&key=ik0",
47 | "extension": "mpd"
48 | },
49 | {
50 | "name": "Google Play (WebM,VP9)",
51 | "uri": "https://www.youtube.com/api/manifest/dash/id/3aa39fa2cc27967f/source/youtube?as=fmp4_audio_clear,webm2_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=B1C2A74783AC1CC4865EB312D7DD2D48230CC9FD.BD153B9882175F1F94BFE5141A5482313EA38E8D&key=ik0",
52 | "extension": "mpd"
53 | }
54 | ]
55 | },
56 | {
57 | "name": "Widevine DASH Policy Tests (GTS)",
58 | "samples": [
59 | {
60 | "name": "WV: HDCP not specified",
61 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
62 | "drm_scheme": "widevine",
63 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=d286538032258a1c&provider=widevine_test"
64 | },
65 | {
66 | "name": "WV: HDCP not required",
67 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
68 | "drm_scheme": "widevine",
69 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=48fcc369939ac96c&provider=widevine_test"
70 | },
71 | {
72 | "name": "WV: HDCP required",
73 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
74 | "drm_scheme": "widevine",
75 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=e06c39f1151da3df&provider=widevine_test"
76 | },
77 | {
78 | "name": "WV: Secure video path required (MP4,H264)",
79 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
80 | "drm_scheme": "widevine",
81 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=0894c7c8719b28a0&provider=widevine_test"
82 | },
83 | {
84 | "name": "WV: Secure video path required (WebM,VP9)",
85 | "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears.mpd",
86 | "drm_scheme": "widevine",
87 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=0894c7c8719b28a0&provider=widevine_test"
88 | },
89 | {
90 | "name": "WV: Secure video path required (MP4,H265)",
91 | "uri": "https://storage.googleapis.com/wvmedia/cenc/hevc/tears/tears.mpd",
92 | "drm_scheme": "widevine",
93 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=0894c7c8719b28a0&provider=widevine_test"
94 | },
95 | {
96 | "name": "WV: HDCP + secure video path required",
97 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
98 | "drm_scheme": "widevine",
99 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=efd045b1eb61888a&provider=widevine_test"
100 | },
101 | {
102 | "name": "WV: 30s license duration (fails at ~30s)",
103 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
104 | "drm_scheme": "widevine",
105 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=f9a34cab7b05881a&provider=widevine_test"
106 | }
107 | ]
108 | },
109 | {
110 | "name": "Widevine HDCP Capabilities Tests",
111 | "samples": [
112 | {
113 | "name": "WV: HDCP: None (not required)",
114 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
115 | "drm_scheme": "widevine",
116 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=HDCP_None&provider=widevine_test"
117 | },
118 | {
119 | "name": "WV: HDCP: 1.0 required",
120 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
121 | "drm_scheme": "widevine",
122 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=HDCP_V1&provider=widevine_test"
123 | },
124 | {
125 | "name": "WV: HDCP: 2.0 required",
126 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
127 | "drm_scheme": "widevine",
128 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=HDCP_V2&provider=widevine_test"
129 | },
130 | {
131 | "name": "WV: HDCP: 2.1 required",
132 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
133 | "drm_scheme": "widevine",
134 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=HDCP_V2_1&provider=widevine_test"
135 | },
136 | {
137 | "name": "WV: HDCP: 2.2 required",
138 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
139 | "drm_scheme": "widevine",
140 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=HDCP_V2_2&provider=widevine_test"
141 | },
142 | {
143 | "name": "WV: HDCP: No digital output",
144 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
145 | "drm_scheme": "widevine",
146 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=HDCP_NO_DIGTAL_OUTPUT&provider=widevine_test"
147 | }
148 | ]
149 | },
150 | {
151 | "name": "Widevine DASH: MP4,H264",
152 | "samples": [
153 | {
154 | "name": "WV: Clear SD & HD (MP4,H264)",
155 | "uri": "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd"
156 | },
157 | {
158 | "name": "WV: Clear SD (MP4,H264)",
159 | "uri": "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears_sd.mpd"
160 | },
161 | {
162 | "name": "WV: Clear HD (MP4,H264)",
163 | "uri": "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears_hd.mpd"
164 | },
165 | {
166 | "name": "WV: Clear UHD (MP4,H264)",
167 | "uri": "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears_uhd.mpd"
168 | },
169 | {
170 | "name": "WV: Secure SD & HD (cenc,MP4,H264)",
171 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
172 | "drm_scheme": "widevine",
173 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
174 | },
175 | {
176 | "name": "WV: Secure SD (cenc,MP4,H264)",
177 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_sd.mpd",
178 | "drm_scheme": "widevine",
179 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
180 | },
181 | {
182 | "name": "WV: Secure HD (cenc,MP4,H264)",
183 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_hd.mpd",
184 | "drm_scheme": "widevine",
185 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
186 | },
187 | {
188 | "name": "WV: Secure UHD (cenc,MP4,H264)",
189 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_uhd.mpd",
190 | "drm_scheme": "widevine",
191 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
192 | },
193 | {
194 | "name": "WV: Secure SD & HD (cbc1,MP4,H264)",
195 | "uri": "https://storage.googleapis.com/wvmedia/cbc1/h264/tears/tears_aes_cbc1.mpd",
196 | "drm_scheme": "widevine",
197 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
198 | },
199 | {
200 | "name": "WV: Secure SD (cbc1,MP4,H264)",
201 | "uri": "https://storage.googleapis.com/wvmedia/cbc1/h264/tears/tears_aes_cbc1_sd.mpd",
202 | "drm_scheme": "widevine",
203 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
204 | },
205 | {
206 | "name": "WV: Secure HD (cbc1,MP4,H264)",
207 | "uri": "https://storage.googleapis.com/wvmedia/cbc1/h264/tears/tears_aes_cbc1_hd.mpd",
208 | "drm_scheme": "widevine",
209 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
210 | },
211 | {
212 | "name": "WV: Secure UHD (cbc1,MP4,H264)",
213 | "uri": "https://storage.googleapis.com/wvmedia/cbc1/h264/tears/tears_aes_cbc1_uhd.mpd",
214 | "drm_scheme": "widevine",
215 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
216 | },
217 | {
218 | "name": "WV: Secure SD & HD (cbcs,MP4,H264)",
219 | "uri": "https://storage.googleapis.com/wvmedia/cbcs/h264/tears/tears_aes_cbcs.mpd",
220 | "drm_scheme": "widevine",
221 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
222 | },
223 | {
224 | "name": "WV: Secure SD (cbcs,MP4,H264)",
225 | "uri": "https://storage.googleapis.com/wvmedia/cbcs/h264/tears/tears_aes_cbcs_sd.mpd",
226 | "drm_scheme": "widevine",
227 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
228 | },
229 | {
230 | "name": "WV: Secure HD (cbcs,MP4,H264)",
231 | "uri": "https://storage.googleapis.com/wvmedia/cbcs/h264/tears/tears_aes_cbcs_hd.mpd",
232 | "drm_scheme": "widevine",
233 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
234 | },
235 | {
236 | "name": "WV: Secure UHD (cbcs,MP4,H264)",
237 | "uri": "https://storage.googleapis.com/wvmedia/cbcs/h264/tears/tears_aes_cbcs_uhd.mpd",
238 | "drm_scheme": "widevine",
239 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
240 | }
241 | ]
242 | },
243 | {
244 | "name": "Widevine DASH: WebM,VP9",
245 | "samples": [
246 | {
247 | "name": "WV: Clear SD & HD (WebM,VP9)",
248 | "uri": "https://storage.googleapis.com/wvmedia/clear/vp9/tears/tears.mpd"
249 | },
250 | {
251 | "name": "WV: Clear SD (WebM,VP9)",
252 | "uri": "https://storage.googleapis.com/wvmedia/clear/vp9/tears/tears_sd.mpd"
253 | },
254 | {
255 | "name": "WV: Clear HD (WebM,VP9)",
256 | "uri": "https://storage.googleapis.com/wvmedia/clear/vp9/tears/tears_hd.mpd"
257 | },
258 | {
259 | "name": "WV: Clear UHD (WebM,VP9)",
260 | "uri": "https://storage.googleapis.com/wvmedia/clear/vp9/tears/tears_uhd.mpd"
261 | },
262 | {
263 | "name": "WV: Secure Fullsample SD & HD (WebM,VP9)",
264 | "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears.mpd",
265 | "drm_scheme": "widevine",
266 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
267 | },
268 | {
269 | "name": "WV: Secure Fullsample SD (WebM,VP9)",
270 | "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears_sd.mpd",
271 | "drm_scheme": "widevine",
272 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
273 | },
274 | {
275 | "name": "WV: Secure Fullsample HD (WebM,VP9)",
276 | "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears_hd.mpd",
277 | "drm_scheme": "widevine",
278 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
279 | },
280 | {
281 | "name": "WV: Secure Fullsample UHD (WebM,VP9)",
282 | "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears_uhd.mpd",
283 | "drm_scheme": "widevine",
284 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
285 | },
286 | {
287 | "name": "WV: Secure Subsample SD & HD (WebM,VP9)",
288 | "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/subsample/24fps/tears/tears.mpd",
289 | "drm_scheme": "widevine",
290 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
291 | },
292 | {
293 | "name": "WV: Secure Subsample SD (WebM,VP9)",
294 | "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/subsample/24fps/tears/tears_sd.mpd",
295 | "drm_scheme": "widevine",
296 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
297 | },
298 | {
299 | "name": "WV: Secure Subsample HD (WebM,VP9)",
300 | "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/subsample/24fps/tears/tears_hd.mpd",
301 | "drm_scheme": "widevine",
302 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
303 | },
304 | {
305 | "name": "WV: Secure Subsample UHD (WebM,VP9)",
306 | "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/subsample/24fps/tears/tears_uhd.mpd",
307 | "drm_scheme": "widevine",
308 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
309 | }
310 | ]
311 | },
312 | {
313 | "name": "Widevine DASH: MP4,H265",
314 | "samples": [
315 | {
316 | "name": "WV: Clear SD & HD (MP4,H265)",
317 | "uri": "https://storage.googleapis.com/wvmedia/clear/hevc/tears/tears.mpd"
318 | },
319 | {
320 | "name": "WV: Clear SD (MP4,H265)",
321 | "uri": "https://storage.googleapis.com/wvmedia/clear/hevc/tears/tears_sd.mpd"
322 | },
323 | {
324 | "name": "WV: Clear HD (MP4,H265)",
325 | "uri": "https://storage.googleapis.com/wvmedia/clear/hevc/tears/tears_hd.mpd"
326 | },
327 | {
328 | "name": "WV: Clear UHD (MP4,H265)",
329 | "uri": "https://storage.googleapis.com/wvmedia/clear/hevc/tears/tears_uhd.mpd"
330 | },
331 | {
332 | "name": "WV: Secure SD & HD (MP4,H265)",
333 | "uri": "https://storage.googleapis.com/wvmedia/cenc/hevc/tears/tears.mpd",
334 | "drm_scheme": "widevine",
335 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
336 | },
337 | {
338 | "name": "WV: Secure SD (MP4,H265)",
339 | "uri": "https://storage.googleapis.com/wvmedia/cenc/hevc/tears/tears_sd.mpd",
340 | "drm_scheme": "widevine",
341 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
342 | },
343 | {
344 | "name": "WV: Secure HD (MP4,H265)",
345 | "uri": "https://storage.googleapis.com/wvmedia/cenc/hevc/tears/tears_hd.mpd",
346 | "drm_scheme": "widevine",
347 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
348 | },
349 | {
350 | "name": "WV: Secure UHD (MP4,H265)",
351 | "uri": "https://storage.googleapis.com/wvmedia/cenc/hevc/tears/tears_uhd.mpd",
352 | "drm_scheme": "widevine",
353 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
354 | }
355 | ]
356 | },
357 | {
358 | "name": "SmoothStreaming",
359 | "samples": [
360 | {
361 | "name": "Super speed",
362 | "uri": "https://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism/Manifest"
363 | },
364 | {
365 | "name": "Super speed (PlayReady)",
366 | "uri": "https://playready.directtaps.net/smoothstreaming/SSWSS720H264PR/SuperSpeedway_720.ism/Manifest",
367 | "drm_scheme": "playready"
368 | }
369 | ]
370 | },
371 | {
372 | "name": "Misc",
373 | "samples": [
374 | {
375 | "name": "Dizzy (MP4)",
376 | "uri": "https://html5demos.com/assets/dizzy.mp4"
377 | },
378 | {
379 | "name": "Apple AAC 10s",
380 | "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/gear0/fileSequence0.aac"
381 | },
382 | {
383 | "name": "Apple TS 10s",
384 | "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/gear1/fileSequence0.ts"
385 | },
386 | {
387 | "name": "Android screens (Matroska)",
388 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv"
389 | },
390 | {
391 | "name": "Screens 360P (WebM,VP9,No Audio)",
392 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-vp9-360.webm"
393 | },
394 | {
395 | "name": "Screens 480p (FMP4,H264,No Audio)",
396 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-avc-baseline-480.mp4"
397 | },
398 | {
399 | "name": "Screens 1080p (FMP4,H264, No Audio)",
400 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-137.mp4"
401 | },
402 | {
403 | "name": "Screens (FMP4,AAC Audio)",
404 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4"
405 | },
406 | {
407 | "name": "Google Play (MP3 Audio)",
408 | "uri": "https://storage.googleapis.com/exoplayer-test-media-0/play.mp3"
409 | },
410 | {
411 | "name": "Google Play (Ogg/Vorbis Audio)",
412 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/ogg/play.ogg"
413 | },
414 | {
415 | "name": "Big Buck Bunny (FLV Video)",
416 | "uri": "https://vod.leasewebcdn.com/bbb.flv?ri=1024&rs=150&start=0"
417 | }
418 | ]
419 | },
420 | {
421 | "name": "Playlists",
422 | "samples": [
423 | {
424 | "name": "Cats -> Dogs",
425 | "playlist": [
426 | {
427 | "uri": "https://html5demos.com/assets/dizzy.mp4"
428 | },
429 | {
430 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv"
431 | }
432 | ]
433 | },
434 | {
435 | "name": "Audio -> Video -> Audio",
436 | "playlist": [
437 | {
438 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4"
439 | },
440 | {
441 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv"
442 | },
443 | {
444 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4"
445 | }
446 | ]
447 | },
448 | {
449 | "name": "Clear -> Enc -> Clear -> Enc -> Enc",
450 | "drm_scheme": "widevine",
451 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test",
452 | "playlist": [
453 | {
454 | "uri": "https://html5demos.com/assets/dizzy.mp4"
455 | },
456 | {
457 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_sd.mpd"
458 | },
459 | {
460 | "uri": "https://html5demos.com/assets/dizzy.mp4"
461 | },
462 | {
463 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_sd.mpd"
464 | },
465 | {
466 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_sd.mpd"
467 | }
468 | ]
469 | }
470 | ]
471 | },
472 | {
473 | "name": "IMA sample ad tags",
474 | "samples": [
475 | {
476 | "name": "Single inline linear",
477 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
478 | "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear&correlator="
479 | },
480 | {
481 | "name": "Single skippable inline",
482 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
483 | "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dskippablelinear&correlator="
484 | },
485 | {
486 | "name": "Single redirect linear",
487 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
488 | "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dredirectlinear&correlator="
489 | },
490 | {
491 | "name": "Single redirect error",
492 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
493 | "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dredirecterror&nofb=1&correlator="
494 | },
495 | {
496 | "name": "Single redirect broken (fallback)",
497 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
498 | "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dredirecterror&correlator="
499 | },
500 | {
501 | "name": "VMAP pre-roll",
502 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
503 | "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpreonly&cmsid=496&vid=short_onecue&correlator="
504 | },
505 | {
506 | "name": "VMAP pre-roll + bumper",
507 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
508 | "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpreonlybumper&cmsid=496&vid=short_onecue&correlator="
509 | },
510 | {
511 | "name": "VMAP post-roll",
512 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
513 | "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpostonly&cmsid=496&vid=short_onecue&correlator="
514 | },
515 | {
516 | "name": "VMAP post-roll + bumper",
517 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
518 | "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpostonlybumper&cmsid=496&vid=short_onecue&correlator="
519 | },
520 | {
521 | "name": "VMAP pre-, mid- and post-rolls, single ads",
522 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
523 | "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpost&cmsid=496&vid=short_onecue&correlator="
524 | },
525 | {
526 | "name": "VMAP pre-roll single ad, mid-roll standard pod with 3 ads, post-roll single ad",
527 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
528 | "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostpod&cmsid=496&vid=short_onecue&correlator="
529 | },
530 | {
531 | "name": "VMAP pre-roll single ad, mid-roll optimized pod with 3 ads, post-roll single ad",
532 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
533 | "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostoptimizedpod&cmsid=496&vid=short_onecue&correlator="
534 | },
535 | {
536 | "name": "VMAP pre-roll single ad, mid-roll standard pod with 3 ads, post-roll single ad (bumpers around all ad breaks)",
537 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
538 | "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostpodbumper&cmsid=496&vid=short_onecue&correlator="
539 | },
540 | {
541 | "name": "VMAP pre-roll single ad, mid-roll optimized pod with 3 ads, post-roll single ad (bumpers around all ad breaks)",
542 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
543 | "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostoptimizedpodbumper&cmsid=496&vid=short_onecue&correlator="
544 | },
545 | {
546 | "name": "VMAP pre-roll single ad, mid-roll standard pods with 5 ads every 10 seconds for 1:40, post-roll single ad",
547 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
548 | "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostlongpod&cmsid=496&vid=short_tencue&correlator="
549 | },
550 | {
551 | "name": "VMAP empty midroll",
552 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
553 | "ad_tag_uri": "https://vastsynthesizer.appspot.com/empty-midroll"
554 | },
555 | {
556 | "name": "VMAP full, empty, full midrolls",
557 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
558 | "ad_tag_uri": "https://vastsynthesizer.appspot.com/empty-midroll-2"
559 | }
560 | ]
561 | },
562 | {
563 | "name": "360",
564 | "samples": [
565 | {
566 | "name": "Congo (360 top-bottom stereo)",
567 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/360/congo.mp4",
568 | "spherical_stereo_mode": "top_bottom"
569 | },
570 | {
571 | "name": "Sphericalv2 (180 top-bottom stereo)",
572 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/360/sphericalv2.mp4",
573 | "spherical_stereo_mode": "top_bottom"
574 | },
575 | {
576 | "name": "Iceland (360 top-bottom stereo ts)",
577 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/360/iceland0.ts",
578 | "spherical_stereo_mode": "top_bottom"
579 | }
580 | ]
581 | }
582 | ]
583 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/andrioidexoplayertrackselection/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.andrioidexoplayertrackselection
2 |
3 | import android.content.Intent
4 | import android.net.Uri
5 | import androidx.appcompat.app.AppCompatActivity
6 | import android.os.Bundle
7 | import com.example.andrioidexoplayertrackselection.exoplayer2.PlayerActivity
8 | import com.example.andrioidexoplayertrackselection.exoplayer2.PlayerActivity.EXTENSION_EXTRA
9 | import kotlinx.android.synthetic.main.activity_main.*
10 |
11 | class MainActivity : AppCompatActivity() {
12 |
13 | // Sample videos, more can be found in the media.exolist.json in the assets folder
14 | private val hls = "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
15 | private val dash = "http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=51AF5F39AB0CEC3E5497CD9C900EBFEAECCCB5C7.8506521BFC350652163895D4C26DEE124209AA9E&key=ik0"
16 | private val mp4 = "https://html5demos.com/assets/dizzy.mp4"
17 |
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | super.onCreate(savedInstanceState)
20 | setContentView(R.layout.activity_main)
21 |
22 | btn1.setOnClickListener { et_url.setText(hls) }
23 | btn2.setOnClickListener { et_url.setText(dash) }
24 | btn3.setOnClickListener { et_url.setText(mp4) }
25 |
26 | btn_play.setOnClickListener {
27 | val preferExtensionDecoders = false
28 | val abrAlgorithm = PlayerActivity.ABR_ALGORITHM_DEFAULT
29 | val intent = Intent(this, PlayerActivity::class.java)
30 |
31 | val videoUrl = et_url.text.toString()
32 | intent.putExtra(PlayerActivity.PREFER_EXTENSION_DECODERS_EXTRA, preferExtensionDecoders)
33 | intent.putExtra(PlayerActivity.ABR_ALGORITHM_EXTRA, abrAlgorithm)
34 | intent.data = Uri.parse(videoUrl)
35 | intent.action = PlayerActivity.ACTION_VIEW
36 |
37 | // this video url doesn't end with a file extension, therefore it needs a extension override
38 | // for buildMediaSource(Uri uri, @Nullable String overrideExtension) in the PlayerActivity
39 | if (videoUrl == dash) {
40 | intent.putExtra(EXTENSION_EXTRA, ".mpd")
41 | }
42 |
43 | startActivity(intent)
44 | }
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/andrioidexoplayertrackselection/exoplayer2/DemoDownloadService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.example.andrioidexoplayertrackselection.exoplayer2;
17 |
18 | import android.app.Notification;
19 |
20 | import com.example.andrioidexoplayertrackselection.R;
21 | import com.google.android.exoplayer2.offline.Download;
22 | import com.google.android.exoplayer2.offline.DownloadManager;
23 | import com.google.android.exoplayer2.offline.DownloadService;
24 | import com.google.android.exoplayer2.scheduler.PlatformScheduler;
25 | import com.google.android.exoplayer2.ui.DownloadNotificationHelper;
26 | import com.google.android.exoplayer2.util.NotificationUtil;
27 | import com.google.android.exoplayer2.util.Util;
28 |
29 | import java.util.List;
30 |
31 | /** A service for downloading media. */
32 | public class DemoDownloadService extends DownloadService {
33 |
34 | private static final String CHANNEL_ID = "download_channel";
35 | private static final int JOB_ID = 1;
36 | private static final int FOREGROUND_NOTIFICATION_ID = 1;
37 |
38 | private static int nextNotificationId = FOREGROUND_NOTIFICATION_ID + 1;
39 |
40 | private DownloadNotificationHelper notificationHelper;
41 |
42 | public DemoDownloadService() {
43 | super(
44 | FOREGROUND_NOTIFICATION_ID,
45 | DEFAULT_FOREGROUND_NOTIFICATION_UPDATE_INTERVAL,
46 | CHANNEL_ID,
47 | R.string.exo_download_notification_channel_name,
48 | /* channelDescriptionResourceId= */ 0);
49 | nextNotificationId = FOREGROUND_NOTIFICATION_ID + 1;
50 | }
51 |
52 | @Override
53 | public void onCreate() {
54 | super.onCreate();
55 | notificationHelper = new DownloadNotificationHelper(this, CHANNEL_ID);
56 | }
57 |
58 | @Override
59 | protected DownloadManager getDownloadManager() {
60 | return VideoDownloadManager.getInstance(getApplicationContext()).getDownloadManager();
61 | }
62 |
63 | @Override
64 | protected PlatformScheduler getScheduler() {
65 | return Util.SDK_INT >= 21 ? new PlatformScheduler(this, JOB_ID) : null;
66 | }
67 |
68 | @Override
69 | protected Notification getForegroundNotification(List downloads) {
70 | return notificationHelper.buildProgressNotification(
71 | R.drawable.ic_download, /* contentIntent= */ null, /* message= */ null, downloads);
72 | }
73 |
74 | @Override
75 | protected void onDownloadChanged(Download download) {
76 | Notification notification;
77 | if (download.state == Download.STATE_COMPLETED) {
78 | notification =
79 | notificationHelper.buildDownloadCompletedNotification(
80 | R.drawable.ic_download_done,
81 | /* contentIntent= */ null,
82 | Util.fromUtf8Bytes(download.request.data));
83 | } else if (download.state == Download.STATE_FAILED) {
84 | notification =
85 | notificationHelper.buildDownloadFailedNotification(
86 | R.drawable.ic_download_done,
87 | /* contentIntent= */ null,
88 | Util.fromUtf8Bytes(download.request.data));
89 | } else {
90 | return;
91 | }
92 | NotificationUtil.setNotification(this, nextNotificationId++, notification);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/andrioidexoplayertrackselection/exoplayer2/DownloadTracker.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.example.andrioidexoplayertrackselection.exoplayer2;
17 |
18 | import android.content.Context;
19 | import android.content.DialogInterface;
20 | import android.net.Uri;
21 | import android.widget.Toast;
22 |
23 | import androidx.annotation.Nullable;
24 | import androidx.fragment.app.FragmentManager;
25 |
26 | import com.example.andrioidexoplayertrackselection.R;
27 | import com.example.andrioidexoplayertrackselection.track_selection_dialog.TrackSelectionDialog;
28 | import com.google.android.exoplayer2.C;
29 | import com.google.android.exoplayer2.RenderersFactory;
30 | import com.google.android.exoplayer2.offline.Download;
31 | import com.google.android.exoplayer2.offline.DownloadCursor;
32 | import com.google.android.exoplayer2.offline.DownloadHelper;
33 | import com.google.android.exoplayer2.offline.DownloadIndex;
34 | import com.google.android.exoplayer2.offline.DownloadManager;
35 | import com.google.android.exoplayer2.offline.DownloadRequest;
36 | import com.google.android.exoplayer2.offline.DownloadService;
37 | import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
38 | import com.google.android.exoplayer2.upstream.DataSource;
39 | import com.google.android.exoplayer2.util.Log;
40 | import com.google.android.exoplayer2.util.Util;
41 |
42 | import java.io.IOException;
43 | import java.util.HashMap;
44 | import java.util.concurrent.CopyOnWriteArraySet;
45 |
46 | /** Tracks media that has been downloaded. */
47 | public class DownloadTracker {
48 |
49 | /** Listens for changes in the tracked downloads. */
50 | public interface Listener {
51 |
52 | /** Called when the tracked downloads changed. */
53 | void onDownloadsChanged();
54 | }
55 |
56 | private static final String TAG = "DownloadTracker";
57 |
58 | private final Context context;
59 | private final DataSource.Factory dataSourceFactory;
60 | private final CopyOnWriteArraySet listeners;
61 | private final HashMap downloads;
62 | private final DownloadIndex downloadIndex;
63 |
64 | @Nullable private StartDownloadDialogHelper startDownloadDialogHelper;
65 |
66 | public DownloadTracker(
67 | Context context, DataSource.Factory dataSourceFactory, DownloadManager downloadManager) {
68 | this.context = context.getApplicationContext();
69 | this.dataSourceFactory = dataSourceFactory;
70 | listeners = new CopyOnWriteArraySet<>();
71 | downloads = new HashMap<>();
72 | downloadIndex = downloadManager.getDownloadIndex();
73 | downloadManager.addListener(new DownloadManagerListener());
74 | loadDownloads();
75 | }
76 |
77 | public void addListener(Listener listener) {
78 | listeners.add(listener);
79 | }
80 |
81 | public void removeListener(Listener listener) {
82 | listeners.remove(listener);
83 | }
84 |
85 | public boolean isDownloaded(Uri uri) {
86 | Download download = downloads.get(uri);
87 | return download != null && download.state != Download.STATE_FAILED;
88 | }
89 |
90 | @SuppressWarnings("unchecked")
91 | public DownloadRequest getDownloadRequest(Uri uri) {
92 | Download download = downloads.get(uri);
93 | return download != null && download.state != Download.STATE_FAILED ? download.request : null;
94 | }
95 |
96 | public void toggleDownload(
97 | FragmentManager fragmentManager,
98 | String name,
99 | Uri uri,
100 | String extension,
101 | RenderersFactory renderersFactory) {
102 | Download download = downloads.get(uri);
103 | if (download != null) {
104 | DownloadService.sendRemoveDownload(
105 | context, DemoDownloadService.class, download.request.id, /* foreground= */ false);
106 | } else {
107 | if (startDownloadDialogHelper != null) {
108 | startDownloadDialogHelper.release();
109 | }
110 | startDownloadDialogHelper =
111 | new StartDownloadDialogHelper(
112 | fragmentManager, getDownloadHelper(uri, extension, renderersFactory), name);
113 | }
114 | }
115 |
116 | private void loadDownloads() {
117 | try (DownloadCursor loadedDownloads = downloadIndex.getDownloads()) {
118 | while (loadedDownloads.moveToNext()) {
119 | Download download = loadedDownloads.getDownload();
120 | downloads.put(download.request.uri, download);
121 | }
122 | } catch (IOException e) {
123 | Log.w(TAG, "Failed to query downloads", e);
124 | }
125 | }
126 |
127 | private DownloadHelper getDownloadHelper(
128 | Uri uri, String extension, RenderersFactory renderersFactory) {
129 | int type = Util.inferContentType(uri, extension);
130 | switch (type) {
131 | case C.TYPE_DASH:
132 | return DownloadHelper.forDash(uri, dataSourceFactory, renderersFactory);
133 | case C.TYPE_SS:
134 | return DownloadHelper.forSmoothStreaming(uri, dataSourceFactory, renderersFactory);
135 | case C.TYPE_HLS:
136 | return DownloadHelper.forHls(uri, dataSourceFactory, renderersFactory);
137 | case C.TYPE_OTHER:
138 | return DownloadHelper.forProgressive(uri);
139 | default:
140 | throw new IllegalStateException("Unsupported type: " + type);
141 | }
142 | }
143 |
144 | private class DownloadManagerListener implements DownloadManager.Listener {
145 |
146 | @Override
147 | public void onDownloadChanged(DownloadManager downloadManager, Download download) {
148 | downloads.put(download.request.uri, download);
149 | for (Listener listener : listeners) {
150 | listener.onDownloadsChanged();
151 | }
152 | }
153 |
154 | @Override
155 | public void onDownloadRemoved(DownloadManager downloadManager, Download download) {
156 | downloads.remove(download.request.uri);
157 | for (Listener listener : listeners) {
158 | listener.onDownloadsChanged();
159 | }
160 | }
161 | }
162 |
163 | private final class StartDownloadDialogHelper
164 | implements DownloadHelper.Callback,
165 | DialogInterface.OnClickListener,
166 | DialogInterface.OnDismissListener {
167 |
168 | private final FragmentManager fragmentManager;
169 | private final DownloadHelper downloadHelper;
170 | private final String name;
171 |
172 | private TrackSelectionDialog trackSelectionDialog;
173 | private MappedTrackInfo mappedTrackInfo;
174 |
175 | public StartDownloadDialogHelper(
176 | FragmentManager fragmentManager, DownloadHelper downloadHelper, String name) {
177 | this.fragmentManager = fragmentManager;
178 | this.downloadHelper = downloadHelper;
179 | this.name = name;
180 | downloadHelper.prepare(this);
181 | }
182 |
183 | public void release() {
184 | downloadHelper.release();
185 | if (trackSelectionDialog != null) {
186 | trackSelectionDialog.dismiss();
187 | }
188 | }
189 |
190 | // DownloadHelper.Callback implementation.
191 |
192 | @Override
193 | public void onPrepared(DownloadHelper helper) {
194 | if (helper.getPeriodCount() == 0) {
195 | Log.d(TAG, "No periods found. Downloading entire stream.");
196 | startDownload();
197 | downloadHelper.release();
198 | return;
199 | }
200 | mappedTrackInfo = downloadHelper.getMappedTrackInfo(/* periodIndex= */ 0);
201 | if (!TrackSelectionDialog.willHaveContent(mappedTrackInfo)) {
202 | Log.d(TAG, "No dialog content. Downloading entire stream.");
203 | startDownload();
204 | downloadHelper.release();
205 | return;
206 | }
207 | trackSelectionDialog =
208 | TrackSelectionDialog.createForMappedTrackInfoAndParameters(
209 | /* titleId= */ R.string.exo_download_description,
210 | mappedTrackInfo,
211 | /* initialParameters= */ DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS,
212 | /* allowAdaptiveSelections =*/ false,
213 | /* allowMultipleOverrides= */ true,
214 | /* onClickListener= */ this,
215 | /* onDismissListener= */ this);
216 | trackSelectionDialog.show(fragmentManager, /* tag= */ null);
217 | }
218 |
219 | @Override
220 | public void onPrepareError(DownloadHelper helper, IOException e) {
221 | Toast.makeText(
222 | context.getApplicationContext(), R.string.download_start_error, Toast.LENGTH_LONG)
223 | .show();
224 | Log.e(TAG, "Failed to start download", e);
225 | }
226 |
227 | // DialogInterface.OnClickListener implementation.
228 |
229 | @Override
230 | public void onClick(DialogInterface dialog, int which) {
231 | for (int periodIndex = 0; periodIndex < downloadHelper.getPeriodCount(); periodIndex++) {
232 | downloadHelper.clearTrackSelections(periodIndex);
233 | for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
234 | if (!trackSelectionDialog.getIsDisabled(/* rendererIndex= */ i)) {
235 | downloadHelper.addTrackSelectionForSingleRenderer(
236 | periodIndex,
237 | /* rendererIndex= */ i,
238 | DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS,
239 | trackSelectionDialog.getOverrides(/* rendererIndex= */ i));
240 | }
241 | }
242 | }
243 | DownloadRequest downloadRequest = buildDownloadRequest();
244 | if (downloadRequest.streamKeys.isEmpty()) {
245 | // All tracks were deselected in the dialog. Don't start the download.
246 | return;
247 | }
248 | startDownload(downloadRequest);
249 | }
250 |
251 | // DialogInterface.OnDismissListener implementation.
252 |
253 | @Override
254 | public void onDismiss(DialogInterface dialogInterface) {
255 | trackSelectionDialog = null;
256 | downloadHelper.release();
257 | }
258 |
259 | // Internal methods.
260 |
261 | private void startDownload() {
262 | startDownload(buildDownloadRequest());
263 | }
264 |
265 | private void startDownload(DownloadRequest downloadRequest) {
266 | DownloadService.sendAddDownload(
267 | context, DemoDownloadService.class, downloadRequest, /* foreground= */ false);
268 | }
269 |
270 | private DownloadRequest buildDownloadRequest() {
271 | return downloadHelper.getDownloadRequest(Util.getUtf8Bytes(name));
272 | }
273 | }
274 | }
275 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/andrioidexoplayertrackselection/exoplayer2/PlayerActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.example.andrioidexoplayertrackselection.exoplayer2;
17 |
18 | import android.content.Intent;
19 | import android.content.pm.PackageManager;
20 | import android.net.Uri;
21 | import android.os.Bundle;
22 | import android.util.Log;
23 | import android.util.Pair;
24 | import android.view.KeyEvent;
25 | import android.view.View;
26 | import android.view.View.OnClickListener;
27 | import android.widget.Button;
28 | import android.widget.LinearLayout;
29 | import android.widget.TextView;
30 | import android.widget.Toast;
31 |
32 | import androidx.annotation.NonNull;
33 | import androidx.annotation.Nullable;
34 | import androidx.appcompat.app.AppCompatActivity;
35 |
36 | import com.example.andrioidexoplayertrackselection.R;
37 | import com.example.andrioidexoplayertrackselection.track_selection_dialog.TrackSelectionDialog;
38 | import com.google.android.exoplayer2.C;
39 | import com.google.android.exoplayer2.C.ContentType;
40 | import com.google.android.exoplayer2.ExoPlaybackException;
41 | import com.google.android.exoplayer2.ExoPlayerFactory;
42 | import com.google.android.exoplayer2.PlaybackPreparer;
43 | import com.google.android.exoplayer2.Player;
44 | import com.google.android.exoplayer2.RendererCapabilities;
45 | import com.google.android.exoplayer2.RenderersFactory;
46 | import com.google.android.exoplayer2.SimpleExoPlayer;
47 | import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
48 | import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
49 | import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
50 | import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
51 | import com.google.android.exoplayer2.drm.UnsupportedDrmException;
52 | import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.DecoderInitializationException;
53 | import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
54 | import com.google.android.exoplayer2.offline.DownloadHelper;
55 | import com.google.android.exoplayer2.offline.DownloadRequest;
56 | import com.google.android.exoplayer2.source.BehindLiveWindowException;
57 | import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
58 | import com.google.android.exoplayer2.source.MediaSource;
59 | import com.google.android.exoplayer2.source.ProgressiveMediaSource;
60 | import com.google.android.exoplayer2.source.TrackGroupArray;
61 | import com.google.android.exoplayer2.source.ads.AdsLoader;
62 | import com.google.android.exoplayer2.source.ads.AdsMediaSource;
63 | import com.google.android.exoplayer2.source.dash.DashMediaSource;
64 | import com.google.android.exoplayer2.source.hls.HlsMediaSource;
65 | import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
66 | import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
67 | import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
68 | import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
69 | import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
70 | import com.google.android.exoplayer2.trackselection.RandomTrackSelection;
71 | import com.google.android.exoplayer2.trackselection.TrackSelection;
72 | import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
73 | import com.google.android.exoplayer2.ui.DebugTextViewHelper;
74 | import com.google.android.exoplayer2.ui.DefaultTrackNameProvider;
75 | import com.google.android.exoplayer2.ui.PlayerControlView;
76 | import com.google.android.exoplayer2.ui.PlayerView;
77 | import com.google.android.exoplayer2.ui.spherical.SphericalSurfaceView;
78 | import com.google.android.exoplayer2.upstream.DataSource;
79 | import com.google.android.exoplayer2.upstream.HttpDataSource;
80 | import com.google.android.exoplayer2.util.Assertions;
81 | import com.google.android.exoplayer2.util.ErrorMessageProvider;
82 | import com.google.android.exoplayer2.util.EventLogger;
83 | import com.google.android.exoplayer2.util.Util;
84 | import com.google.gson.Gson;
85 |
86 | import java.lang.reflect.Constructor;
87 | import java.net.CookieHandler;
88 | import java.net.CookieManager;
89 | import java.net.CookiePolicy;
90 | import java.util.UUID;
91 |
92 | /** An activity that plays media using {@link SimpleExoPlayer}. */
93 | public class PlayerActivity extends AppCompatActivity
94 | implements OnClickListener, PlaybackPreparer, PlayerControlView.VisibilityListener {
95 |
96 | public static final String TAG = "PlayerActivity";
97 | public static final String DRM_SCHEME_EXTRA = "drm_scheme";
98 | public static final String DRM_LICENSE_URL_EXTRA = "drm_license_url";
99 | public static final String DRM_KEY_REQUEST_PROPERTIES_EXTRA = "drm_key_request_properties";
100 | public static final String DRM_MULTI_SESSION_EXTRA = "drm_multi_session";
101 | public static final String PREFER_EXTENSION_DECODERS_EXTRA = "prefer_extension_decoders";
102 |
103 | public static final String ACTION_VIEW = "com.google.android.exoplayer.demo.action.VIEW";
104 | public static final String EXTENSION_EXTRA = "extension";
105 |
106 | public static final String ACTION_VIEW_LIST =
107 | "com.google.android.exoplayer.demo.action.VIEW_LIST";
108 | public static final String URI_LIST_EXTRA = "uri_list";
109 | public static final String EXTENSION_LIST_EXTRA = "extension_list";
110 |
111 | public static final String AD_TAG_URI_EXTRA = "ad_tag_uri";
112 |
113 | public static final String ABR_ALGORITHM_EXTRA = "abr_algorithm";
114 | public static final String ABR_ALGORITHM_DEFAULT = "default";
115 | public static final String ABR_ALGORITHM_RANDOM = "random";
116 |
117 | public static final String SPHERICAL_STEREO_MODE_EXTRA = "spherical_stereo_mode";
118 | public static final String SPHERICAL_STEREO_MODE_MONO = "mono";
119 | public static final String SPHERICAL_STEREO_MODE_TOP_BOTTOM = "top_bottom";
120 | public static final String SPHERICAL_STEREO_MODE_LEFT_RIGHT = "left_right";
121 |
122 | // For backwards compatibility only.
123 | private static final String DRM_SCHEME_UUID_EXTRA = "drm_scheme_uuid";
124 |
125 | // Saved instance state keys.
126 | private static final String KEY_TRACK_SELECTOR_PARAMETERS = "track_selector_parameters";
127 | private static final String KEY_WINDOW = "window";
128 | private static final String KEY_POSITION = "position";
129 | private static final String KEY_AUTO_PLAY = "auto_play";
130 |
131 | private static final CookieManager DEFAULT_COOKIE_MANAGER;
132 | static {
133 | DEFAULT_COOKIE_MANAGER = new CookieManager();
134 | DEFAULT_COOKIE_MANAGER.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
135 | }
136 |
137 | private PlayerView playerView;
138 | private LinearLayout debugRootView;
139 | private Button selectTracksButton;
140 | private Button logTracksButton;
141 | private Button showTextCaptionsButton;
142 | private Button removeTextCaptionsButton;
143 | private TextView debugTextView;
144 | private boolean isShowingTrackSelectionDialog;
145 |
146 | private DataSource.Factory dataSourceFactory;
147 | private SimpleExoPlayer player;
148 | private FrameworkMediaDrm mediaDrm;
149 | private MediaSource mediaSource;
150 | private DefaultTrackSelector trackSelector;
151 | private DefaultTrackSelector.Parameters trackSelectorParameters;
152 | private DebugTextViewHelper debugViewHelper;
153 | private TrackGroupArray lastSeenTrackGroupArray;
154 |
155 | private boolean startAutoPlay;
156 | private int startWindow;
157 | private long startPosition;
158 |
159 | // Fields used only for ad playback. The ads loader is loaded via reflection.
160 |
161 | private AdsLoader adsLoader;
162 | private Uri loadedAdTagUri;
163 |
164 | // Activity lifecycle
165 |
166 |
167 | private void initDebugViews() {
168 | logTracksButton.setOnClickListener( view -> {
169 | Log.d(TAG, "log tracks clicked");
170 | MappedTrackInfo mappedTrackInfo = Assertions.checkNotNull(trackSelector.getCurrentMappedTrackInfo());
171 | DefaultTrackSelector.Parameters parameters = trackSelector.getParameters();
172 |
173 | for (int rendererIndex = 0; rendererIndex < mappedTrackInfo.getRendererCount(); rendererIndex++) {
174 | if (TrackSelectionDialog.showTabForRenderer(mappedTrackInfo, rendererIndex)) {
175 | int trackType = mappedTrackInfo.getRendererType(rendererIndex);
176 | TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(rendererIndex);
177 | Boolean isRendererDisabled = parameters.getRendererDisabled(rendererIndex);
178 | DefaultTrackSelector.SelectionOverride selectionOverride = parameters.getSelectionOverride(rendererIndex, trackGroupArray);
179 |
180 | Log.d(TAG, "------------------------------------------------------Track item " + rendererIndex + "------------------------------------------------------");
181 | Log.d(TAG, "track type: " + trackTypeToName(trackType));
182 | Log.d(TAG, "track group array: " + new Gson().toJson(trackGroupArray));
183 | for (int groupIndex = 0; groupIndex < trackGroupArray.length; groupIndex++) {
184 | for (int trackIndex = 0; trackIndex < trackGroupArray.get(groupIndex).length; trackIndex++) {
185 | String trackName = new DefaultTrackNameProvider(getResources()).getTrackName(trackGroupArray.get(groupIndex).getFormat(trackIndex));
186 | Boolean isTrackSupported = mappedTrackInfo.getTrackSupport(rendererIndex, groupIndex, trackIndex) == RendererCapabilities.FORMAT_HANDLED;
187 | Log.d(TAG, "track item " + groupIndex +": trackName: " + trackName + ", isTrackSupported: " + isTrackSupported);
188 | }
189 | }
190 | Log.d(TAG, "isRendererDisabled: " + isRendererDisabled);
191 | Log.d(TAG, "selectionOverride: " + new Gson().toJson(selectionOverride));
192 | }
193 | }
194 |
195 | });
196 |
197 | showTextCaptionsButton.setOnClickListener(view -> {
198 | Log.d(TAG, "showTextCaptionsButton clicked");
199 |
200 | MappedTrackInfo mappedTrackInfo = Assertions.checkNotNull(trackSelector.getCurrentMappedTrackInfo());
201 | DefaultTrackSelector.Parameters parameters = trackSelector.getParameters();
202 | DefaultTrackSelector.ParametersBuilder builder = parameters.buildUpon();
203 | for (int rendererIndex = 0; rendererIndex < mappedTrackInfo.getRendererCount(); rendererIndex++) {
204 | int trackType = mappedTrackInfo.getRendererType(rendererIndex);
205 | if (trackType == C.TRACK_TYPE_TEXT) {
206 | builder.clearSelectionOverrides(rendererIndex).setRendererDisabled(rendererIndex, false);
207 | //{"data":0,"groupIndex":1,"length":1,"reason":2,"tracks":[0]}
208 | int groupIndex = 0;
209 | int [] tracks = {0};
210 | int reason = 2;
211 | int data = 0;
212 | DefaultTrackSelector.SelectionOverride override = new DefaultTrackSelector.SelectionOverride(groupIndex, tracks, reason, data);
213 |
214 | builder.setSelectionOverride(rendererIndex, mappedTrackInfo.getTrackGroups(rendererIndex), override);
215 | }
216 | }
217 |
218 | trackSelector.setParameters(builder);
219 |
220 | });
221 |
222 | removeTextCaptionsButton.setOnClickListener(view -> {
223 | Log.d(TAG, "removeTextCaptionsButton clicked");
224 | MappedTrackInfo mappedTrackInfo = Assertions.checkNotNull(trackSelector.getCurrentMappedTrackInfo());
225 | DefaultTrackSelector.Parameters parameters = trackSelector.getParameters();
226 | DefaultTrackSelector.ParametersBuilder builder = parameters.buildUpon();
227 | for (int rendererIndex = 0; rendererIndex < mappedTrackInfo.getRendererCount(); rendererIndex++) {
228 | int trackType = mappedTrackInfo.getRendererType(rendererIndex);
229 | if (trackType == C.TRACK_TYPE_TEXT) {
230 | builder.clearSelectionOverrides(rendererIndex).setRendererDisabled(rendererIndex, true);
231 | }
232 | }
233 | trackSelector.setParameters(builder);
234 | });
235 |
236 | }
237 |
238 | @Override
239 | public void onCreate(Bundle savedInstanceState) {
240 | String sphericalStereoMode = getIntent().getStringExtra(SPHERICAL_STEREO_MODE_EXTRA);
241 | if (sphericalStereoMode != null) {
242 | setTheme(R.style.PlayerTheme_Spherical);
243 | }
244 | super.onCreate(savedInstanceState);
245 | dataSourceFactory = buildDataSourceFactory();
246 | if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) {
247 | CookieHandler.setDefault(DEFAULT_COOKIE_MANAGER);
248 | }
249 |
250 | setContentView(R.layout.player_activity);
251 | debugRootView = findViewById(R.id.controls_root);
252 | debugTextView = findViewById(R.id.debug_text_view);
253 | selectTracksButton = findViewById(R.id.select_tracks_button);
254 | selectTracksButton.setOnClickListener(this);
255 |
256 | logTracksButton = findViewById(R.id.log_tracks);
257 | showTextCaptionsButton = findViewById(R.id.show_text_caption);
258 | removeTextCaptionsButton = findViewById(R.id.remove_text_caption);
259 | initDebugViews();
260 |
261 | playerView = findViewById(R.id.player_view);
262 | playerView.setControllerVisibilityListener(this);
263 | playerView.setErrorMessageProvider(new PlayerErrorMessageProvider());
264 | playerView.requestFocus();
265 | if (sphericalStereoMode != null) {
266 | int stereoMode;
267 | if (SPHERICAL_STEREO_MODE_MONO.equals(sphericalStereoMode)) {
268 | stereoMode = C.STEREO_MODE_MONO;
269 | } else if (SPHERICAL_STEREO_MODE_TOP_BOTTOM.equals(sphericalStereoMode)) {
270 | stereoMode = C.STEREO_MODE_TOP_BOTTOM;
271 | } else if (SPHERICAL_STEREO_MODE_LEFT_RIGHT.equals(sphericalStereoMode)) {
272 | stereoMode = C.STEREO_MODE_LEFT_RIGHT;
273 | } else {
274 | showToast(R.string.error_unrecognized_stereo_mode);
275 | finish();
276 | return;
277 | }
278 | ((SphericalSurfaceView) playerView.getVideoSurfaceView()).setDefaultStereoMode(stereoMode);
279 | }
280 |
281 | if (savedInstanceState != null) {
282 | trackSelectorParameters = savedInstanceState.getParcelable(KEY_TRACK_SELECTOR_PARAMETERS);
283 | startAutoPlay = savedInstanceState.getBoolean(KEY_AUTO_PLAY);
284 | startWindow = savedInstanceState.getInt(KEY_WINDOW);
285 | startPosition = savedInstanceState.getLong(KEY_POSITION);
286 | } else {
287 | trackSelectorParameters = new DefaultTrackSelector.ParametersBuilder().build();
288 | clearStartPosition();
289 | }
290 | }
291 |
292 | private String trackTypeToName(int trackType) {
293 | switch(trackType) {
294 | case C.TRACK_TYPE_VIDEO:
295 | return "TRACK_TYPE_VIDEO";
296 | case C.TRACK_TYPE_AUDIO:
297 | return "TRACK_TYPE_AUDIO";
298 | case C.TRACK_TYPE_TEXT:
299 | return "TRACK_TYPE_TEXT";
300 | default:
301 | return "Invalid track type";
302 | }
303 | }
304 |
305 |
306 | @Override
307 | public void onNewIntent(Intent intent) {
308 | super.onNewIntent(intent);
309 | releasePlayer();
310 | releaseAdsLoader();
311 | clearStartPosition();
312 | setIntent(intent);
313 | }
314 |
315 | @Override
316 | public void onStart() {
317 | super.onStart();
318 | if (Util.SDK_INT > 23) {
319 | initializePlayer();
320 | if (playerView != null) {
321 | playerView.onResume();
322 | }
323 | }
324 | }
325 |
326 | @Override
327 | public void onResume() {
328 | super.onResume();
329 | if (Util.SDK_INT <= 23 || player == null) {
330 | initializePlayer();
331 | if (playerView != null) {
332 | playerView.onResume();
333 | }
334 | }
335 | }
336 |
337 | @Override
338 | public void onPause() {
339 | super.onPause();
340 | if (Util.SDK_INT <= 23) {
341 | if (playerView != null) {
342 | playerView.onPause();
343 | }
344 | releasePlayer();
345 | }
346 | }
347 |
348 | @Override
349 | public void onStop() {
350 | super.onStop();
351 | if (Util.SDK_INT > 23) {
352 | if (playerView != null) {
353 | playerView.onPause();
354 | }
355 | releasePlayer();
356 | }
357 | }
358 |
359 | @Override
360 | public void onDestroy() {
361 | super.onDestroy();
362 | releaseAdsLoader();
363 | }
364 |
365 | @Override
366 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
367 | @NonNull int[] grantResults) {
368 | if (grantResults.length == 0) {
369 | // Empty results are triggered if a permission is requested while another request was already
370 | // pending and can be safely ignored in this case.
371 | return;
372 | }
373 | if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
374 | initializePlayer();
375 | } else {
376 | showToast(R.string.storage_permission_denied);
377 | finish();
378 | }
379 | }
380 |
381 | @Override
382 | public void onSaveInstanceState(Bundle outState) {
383 | super.onSaveInstanceState(outState);
384 | updateTrackSelectorParameters();
385 | updateStartPosition();
386 | outState.putParcelable(KEY_TRACK_SELECTOR_PARAMETERS, trackSelectorParameters);
387 | outState.putBoolean(KEY_AUTO_PLAY, startAutoPlay);
388 | outState.putInt(KEY_WINDOW, startWindow);
389 | outState.putLong(KEY_POSITION, startPosition);
390 | }
391 |
392 | // Activity input
393 |
394 | @Override
395 | public boolean dispatchKeyEvent(KeyEvent event) {
396 | // See whether the player view wants to handle media or DPAD keys events.
397 | return playerView.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
398 | }
399 |
400 | // OnClickListener methods
401 |
402 | @Override
403 | public void onClick(View view) {
404 | if (view == selectTracksButton && !isShowingTrackSelectionDialog && TrackSelectionDialog.willHaveContent(trackSelector)) {
405 | isShowingTrackSelectionDialog = true;
406 | TrackSelectionDialog trackSelectionDialog = TrackSelectionDialog.createForTrackSelector(trackSelector, dismissedDialog ->
407 | isShowingTrackSelectionDialog = false
408 | );
409 | trackSelectionDialog.show(getSupportFragmentManager(),null);
410 | }
411 | }
412 |
413 | // PlaybackControlView.PlaybackPreparer implementation
414 |
415 | @Override
416 | public void preparePlayback() {
417 | player.retry();
418 | }
419 |
420 | // PlaybackControlView.VisibilityListener implementation
421 |
422 | @Override
423 | public void onVisibilityChange(int visibility) {
424 | debugRootView.setVisibility(visibility);
425 | }
426 |
427 | // Internal methods
428 |
429 | private void initializePlayer() {
430 | if (player == null) {
431 | Intent intent = getIntent();
432 | String action = intent.getAction();
433 | Uri[] uris;
434 | String[] extensions;
435 | if (ACTION_VIEW.equals(action)) {
436 | uris = new Uri[] {intent.getData()};
437 | extensions = new String[] {intent.getStringExtra(EXTENSION_EXTRA)};
438 | } else if (ACTION_VIEW_LIST.equals(action)) {
439 | String[] uriStrings = intent.getStringArrayExtra(URI_LIST_EXTRA);
440 | uris = new Uri[uriStrings.length];
441 | for (int i = 0; i < uriStrings.length; i++) {
442 | uris[i] = Uri.parse(uriStrings[i]);
443 | }
444 | extensions = intent.getStringArrayExtra(EXTENSION_LIST_EXTRA);
445 | if (extensions == null) {
446 | extensions = new String[uriStrings.length];
447 | }
448 | } else {
449 | showToast(getString(R.string.unexpected_intent_action, action));
450 | finish();
451 | return;
452 | }
453 | if (!Util.checkCleartextTrafficPermitted(uris)) {
454 | showToast(R.string.error_cleartext_not_permitted);
455 | return;
456 | }
457 | if (Util.maybeRequestReadExternalStoragePermission(/* activity= */ this, uris)) {
458 | // The player will be reinitialized if the permission is granted.
459 | return;
460 | }
461 |
462 | DefaultDrmSessionManager drmSessionManager = null;
463 | if (intent.hasExtra(DRM_SCHEME_EXTRA) || intent.hasExtra(DRM_SCHEME_UUID_EXTRA)) {
464 | String drmLicenseUrl = intent.getStringExtra(DRM_LICENSE_URL_EXTRA);
465 | String[] keyRequestPropertiesArray =
466 | intent.getStringArrayExtra(DRM_KEY_REQUEST_PROPERTIES_EXTRA);
467 | boolean multiSession = intent.getBooleanExtra(DRM_MULTI_SESSION_EXTRA, false);
468 | int errorStringId = R.string.error_drm_unknown;
469 | if (Util.SDK_INT < 18) {
470 | errorStringId = R.string.error_drm_not_supported;
471 | } else {
472 | try {
473 | String drmSchemeExtra = intent.hasExtra(DRM_SCHEME_EXTRA) ? DRM_SCHEME_EXTRA
474 | : DRM_SCHEME_UUID_EXTRA;
475 | UUID drmSchemeUuid = Util.getDrmUuid(intent.getStringExtra(drmSchemeExtra));
476 | if (drmSchemeUuid == null) {
477 | errorStringId = R.string.error_drm_unsupported_scheme;
478 | } else {
479 | drmSessionManager =
480 | buildDrmSessionManagerV18(
481 | drmSchemeUuid, drmLicenseUrl, keyRequestPropertiesArray, multiSession);
482 | }
483 | } catch (UnsupportedDrmException e) {
484 | errorStringId = e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
485 | ? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown;
486 | }
487 | }
488 | if (drmSessionManager == null) {
489 | showToast(errorStringId);
490 | finish();
491 | return;
492 | }
493 | }
494 |
495 | TrackSelection.Factory trackSelectionFactory;
496 | String abrAlgorithm = intent.getStringExtra(ABR_ALGORITHM_EXTRA);
497 | if (abrAlgorithm == null || ABR_ALGORITHM_DEFAULT.equals(abrAlgorithm)) {
498 | trackSelectionFactory = new AdaptiveTrackSelection.Factory();
499 | } else if (ABR_ALGORITHM_RANDOM.equals(abrAlgorithm)) {
500 | trackSelectionFactory = new RandomTrackSelection.Factory();
501 | } else {
502 | showToast(R.string.error_unrecognized_abr_algorithm);
503 | finish();
504 | return;
505 | }
506 |
507 | boolean preferExtensionDecoders =
508 | intent.getBooleanExtra(PREFER_EXTENSION_DECODERS_EXTRA, false);
509 | RenderersFactory renderersFactory = VideoDownloadManager.getInstance(getApplicationContext()).buildRenderersFactory(preferExtensionDecoders);
510 |
511 | trackSelector = new DefaultTrackSelector(trackSelectionFactory);
512 | trackSelector.setParameters(trackSelectorParameters);
513 | lastSeenTrackGroupArray = null;
514 |
515 | player = ExoPlayerFactory.newSimpleInstance(this, renderersFactory, trackSelector, drmSessionManager);
516 | player.addListener(new PlayerEventListener());
517 | player.setPlayWhenReady(startAutoPlay);
518 | player.addAnalyticsListener(new EventLogger(trackSelector));
519 | playerView.setPlayer(player);
520 | playerView.setPlaybackPreparer(this);
521 | debugViewHelper = new DebugTextViewHelper(player, debugTextView);
522 | debugViewHelper.start();
523 |
524 | MediaSource[] mediaSources = new MediaSource[uris.length];
525 | for (int i = 0; i < uris.length; i++) {
526 | mediaSources[i] = buildMediaSource(uris[i], extensions[i]);
527 | }
528 | mediaSource =
529 | mediaSources.length == 1 ? mediaSources[0] : new ConcatenatingMediaSource(mediaSources);
530 | String adTagUriString = intent.getStringExtra(AD_TAG_URI_EXTRA);
531 | if (adTagUriString != null) {
532 | Uri adTagUri = Uri.parse(adTagUriString);
533 | if (!adTagUri.equals(loadedAdTagUri)) {
534 | releaseAdsLoader();
535 | loadedAdTagUri = adTagUri;
536 | }
537 | MediaSource adsMediaSource = createAdsMediaSource(mediaSource, Uri.parse(adTagUriString));
538 | if (adsMediaSource != null) {
539 | mediaSource = adsMediaSource;
540 | } else {
541 | showToast(R.string.ima_not_loaded);
542 | }
543 | } else {
544 | releaseAdsLoader();
545 | }
546 | }
547 | boolean haveStartPosition = startWindow != C.INDEX_UNSET;
548 | if (haveStartPosition) {
549 | player.seekTo(startWindow, startPosition);
550 | }
551 | player.prepare(mediaSource, !haveStartPosition, false);
552 | updateButtonVisibility();
553 | }
554 |
555 | private MediaSource buildMediaSource(Uri uri) {
556 | return buildMediaSource(uri, null);
557 | }
558 |
559 | private MediaSource buildMediaSource(Uri uri, @Nullable String overrideExtension) {
560 | Log.d(TAG, "buildMediaSource uri: " + uri.toString() + " overrideExtension: " + overrideExtension);
561 | DownloadRequest downloadRequest = VideoDownloadManager.getInstance(getApplicationContext()).getDownloadTracker().getDownloadRequest(uri);
562 | if (downloadRequest != null) {
563 | return DownloadHelper.createMediaSource(downloadRequest, dataSourceFactory);
564 | }
565 | @ContentType int type = Util.inferContentType(uri, overrideExtension);
566 | switch (type) {
567 | case C.TYPE_DASH:
568 | Log.d(TAG, "buildMediaSource TYPE_DASH: TYPE_DASH");
569 | return new DashMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
570 | case C.TYPE_SS:
571 | Log.d(TAG, "buildMediaSource TYPE_DASH: TYPE_SS");
572 | return new SsMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
573 | case C.TYPE_HLS:
574 | Log.d(TAG, "buildMediaSource TYPE_DASH: TYPE_HLS");
575 | return new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
576 | case C.TYPE_OTHER:
577 | Log.d(TAG, "buildMediaSource TYPE_DASH: TYPE_OTHER");
578 | return new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
579 | default:
580 | throw new IllegalStateException("Unsupported type: " + type);
581 | }
582 | }
583 |
584 | private DefaultDrmSessionManager buildDrmSessionManagerV18(UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray, boolean multiSession) throws UnsupportedDrmException {
585 | HttpDataSource.Factory licenseDataSourceFactory = VideoDownloadManager.getInstance(getApplicationContext()).buildHttpDataSourceFactory();
586 | HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl, licenseDataSourceFactory);
587 | if (keyRequestPropertiesArray != null) {
588 | for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) {
589 | drmCallback.setKeyRequestProperty(keyRequestPropertiesArray[i],
590 | keyRequestPropertiesArray[i + 1]);
591 | }
592 | }
593 | releaseMediaDrm();
594 | mediaDrm = FrameworkMediaDrm.newInstance(uuid);
595 | return new DefaultDrmSessionManager<>(uuid, mediaDrm, drmCallback, null, multiSession);
596 | }
597 |
598 | private void releasePlayer() {
599 | if (player != null) {
600 | updateTrackSelectorParameters();
601 | updateStartPosition();
602 | debugViewHelper.stop();
603 | debugViewHelper = null;
604 | player.release();
605 | player = null;
606 | mediaSource = null;
607 | trackSelector = null;
608 | }
609 | if (adsLoader != null) {
610 | adsLoader.setPlayer(null);
611 | }
612 | releaseMediaDrm();
613 | }
614 |
615 | private void releaseMediaDrm() {
616 | if (mediaDrm != null) {
617 | mediaDrm.release();
618 | mediaDrm = null;
619 | }
620 | }
621 |
622 | private void releaseAdsLoader() {
623 | if (adsLoader != null) {
624 | adsLoader.release();
625 | adsLoader = null;
626 | loadedAdTagUri = null;
627 | playerView.getOverlayFrameLayout().removeAllViews();
628 | }
629 | }
630 |
631 | private void updateTrackSelectorParameters() {
632 | if (trackSelector != null) {
633 | trackSelectorParameters = trackSelector.getParameters();
634 | }
635 | }
636 |
637 | private void updateStartPosition() {
638 | if (player != null) {
639 | startAutoPlay = player.getPlayWhenReady();
640 | startWindow = player.getCurrentWindowIndex();
641 | startPosition = Math.max(0, player.getContentPosition());
642 | }
643 | }
644 |
645 | private void clearStartPosition() {
646 | startAutoPlay = true;
647 | startWindow = C.INDEX_UNSET;
648 | startPosition = C.TIME_UNSET;
649 | }
650 |
651 | /** Returns a new DataSource factory. */
652 | private DataSource.Factory buildDataSourceFactory() {
653 | return VideoDownloadManager.getInstance(getApplicationContext()).buildDataSourceFactory();
654 | }
655 |
656 | /** Returns an ads media source, reusing the ads loader if one exists. */
657 | private @Nullable
658 | MediaSource createAdsMediaSource(MediaSource mediaSource, Uri adTagUri) {
659 | // Load the extension source using reflection so the demo app doesn't have to depend on it.
660 | // The ads loader is reused for multiple playbacks, so that ad playback can resume.
661 | try {
662 | Class> loaderClass = Class.forName("com.google.android.exoplayer2.ext.ima.ImaAdsLoader");
663 | if (adsLoader == null) {
664 | // Full class names used so the LINT.IfChange rule triggers should any of the classes move.
665 | // LINT.IfChange
666 | Constructor extends AdsLoader> loaderConstructor =
667 | loaderClass
668 | .asSubclass(AdsLoader.class)
669 | .getConstructor(android.content.Context.class, Uri.class);
670 | // LINT.ThenChange(../../../../../../../../proguard-rules.txt)
671 | adsLoader = loaderConstructor.newInstance(this, adTagUri);
672 | }
673 | adsLoader.setPlayer(player);
674 | AdsMediaSource.MediaSourceFactory adMediaSourceFactory =
675 | new AdsMediaSource.MediaSourceFactory() {
676 | @Override
677 | public MediaSource createMediaSource(Uri uri) {
678 | return PlayerActivity.this.buildMediaSource(uri);
679 | }
680 |
681 | @Override
682 | public int[] getSupportedTypes() {
683 | return new int[] {C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER};
684 | }
685 | };
686 | return new AdsMediaSource(mediaSource, adMediaSourceFactory, adsLoader, playerView);
687 | } catch (ClassNotFoundException e) {
688 | // IMA extension not loaded.
689 | return null;
690 | } catch (Exception e) {
691 | throw new RuntimeException(e);
692 | }
693 | }
694 |
695 | // User controls
696 |
697 | private void updateButtonVisibility() {
698 | if (player != null && TrackSelectionDialog.willHaveContent(trackSelector)) {
699 | selectTracksButton.setEnabled(true);
700 | logTracksButton.setEnabled(true);
701 | showTextCaptionsButton.setEnabled(true);
702 | removeTextCaptionsButton.setEnabled(true);
703 | } else {
704 | selectTracksButton.setEnabled(false);
705 | logTracksButton.setEnabled(false);
706 | showTextCaptionsButton.setEnabled(false);
707 | removeTextCaptionsButton.setEnabled(false);
708 | }
709 | }
710 |
711 | private void showControls() {
712 | debugRootView.setVisibility(View.VISIBLE);
713 | }
714 |
715 | private void showToast(int messageId) {
716 | showToast(getString(messageId));
717 | }
718 |
719 | private void showToast(String message) {
720 | Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
721 | }
722 |
723 | private static boolean isBehindLiveWindow(ExoPlaybackException e) {
724 | if (e.type != ExoPlaybackException.TYPE_SOURCE) {
725 | return false;
726 | }
727 | Throwable cause = e.getSourceException();
728 | while (cause != null) {
729 | if (cause instanceof BehindLiveWindowException) {
730 | return true;
731 | }
732 | cause = cause.getCause();
733 | }
734 | return false;
735 | }
736 |
737 | private class PlayerEventListener implements Player.EventListener {
738 |
739 | @Override
740 | public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
741 | if (playbackState == Player.STATE_ENDED) {
742 | showControls();
743 | }
744 | updateButtonVisibility();
745 | }
746 |
747 | @Override
748 | public void onPlayerError(ExoPlaybackException e) {
749 | if (isBehindLiveWindow(e)) {
750 | clearStartPosition();
751 | initializePlayer();
752 | } else {
753 | updateButtonVisibility();
754 | showControls();
755 | }
756 | }
757 |
758 | @Override
759 | @SuppressWarnings("ReferenceEquality")
760 | public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
761 | updateButtonVisibility();
762 | if (trackGroups != lastSeenTrackGroupArray) {
763 | MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo();
764 | if (mappedTrackInfo != null) {
765 | if (mappedTrackInfo.getTypeSupport(C.TRACK_TYPE_VIDEO)
766 | == MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS) {
767 | showToast(R.string.error_unsupported_video);
768 | }
769 | if (mappedTrackInfo.getTypeSupport(C.TRACK_TYPE_AUDIO)
770 | == MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS) {
771 | showToast(R.string.error_unsupported_audio);
772 | }
773 | }
774 | lastSeenTrackGroupArray = trackGroups;
775 | }
776 | }
777 | }
778 |
779 | private class PlayerErrorMessageProvider implements ErrorMessageProvider {
780 |
781 | @Override
782 | public Pair getErrorMessage(ExoPlaybackException e) {
783 | String errorString = getString(R.string.error_generic);
784 | if (e.type == ExoPlaybackException.TYPE_RENDERER) {
785 | Exception cause = e.getRendererException();
786 | if (cause instanceof DecoderInitializationException) {
787 | // Special case for decoder initialization failures.
788 | DecoderInitializationException decoderInitializationException =
789 | (DecoderInitializationException) cause;
790 | if (decoderInitializationException.decoderName == null) {
791 | if (decoderInitializationException.getCause() instanceof DecoderQueryException) {
792 | errorString = getString(R.string.error_querying_decoders);
793 | } else if (decoderInitializationException.secureDecoderRequired) {
794 | errorString =
795 | getString(
796 | R.string.error_no_secure_decoder, decoderInitializationException.mimeType);
797 | } else {
798 | errorString =
799 | getString(R.string.error_no_decoder, decoderInitializationException.mimeType);
800 | }
801 | } else {
802 | errorString =
803 | getString(
804 | R.string.error_instantiating_decoder,
805 | decoderInitializationException.decoderName);
806 | }
807 | }
808 | }
809 | return Pair.create(0, errorString);
810 | }
811 | }
812 |
813 | }
814 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/andrioidexoplayertrackselection/exoplayer2/VideoDownloadManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.example.andrioidexoplayertrackselection.exoplayer2;
17 |
18 | import android.content.Context;
19 |
20 | import com.example.andrioidexoplayertrackselection.BuildConfig;
21 | import com.google.android.exoplayer2.DefaultRenderersFactory;
22 | import com.google.android.exoplayer2.RenderersFactory;
23 | import com.google.android.exoplayer2.database.DatabaseProvider;
24 | import com.google.android.exoplayer2.database.ExoDatabaseProvider;
25 | import com.google.android.exoplayer2.offline.ActionFileUpgradeUtil;
26 | import com.google.android.exoplayer2.offline.DefaultDownloadIndex;
27 | import com.google.android.exoplayer2.offline.DefaultDownloaderFactory;
28 | import com.google.android.exoplayer2.offline.DownloadManager;
29 | import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
30 | import com.google.android.exoplayer2.upstream.DataSource;
31 | import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
32 | import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
33 | import com.google.android.exoplayer2.upstream.FileDataSourceFactory;
34 | import com.google.android.exoplayer2.upstream.HttpDataSource;
35 | import com.google.android.exoplayer2.upstream.cache.Cache;
36 | import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
37 | import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
38 | import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor;
39 | import com.google.android.exoplayer2.upstream.cache.SimpleCache;
40 | import com.google.android.exoplayer2.util.Log;
41 | import com.google.android.exoplayer2.util.Util;
42 |
43 | import java.io.File;
44 | import java.io.IOException;
45 |
46 | /**
47 | * Placeholder application to facilitate overriding Application methods for debugging and testing.
48 | */
49 | public class VideoDownloadManager {
50 |
51 | private static final String TAG = "DemoApplication";
52 | private static final String DOWNLOAD_ACTION_FILE = "actions";
53 | private static final String DOWNLOAD_TRACKER_ACTION_FILE = "tracked_actions";
54 | private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
55 |
56 | protected String userAgent;
57 |
58 | private DatabaseProvider databaseProvider;
59 | private File downloadDirectory;
60 | private Cache downloadCache;
61 | private DownloadManager downloadManager;
62 | private DownloadTracker downloadTracker;
63 |
64 |
65 | private static VideoDownloadManager INSTANCE = null;
66 | private Context context = null;
67 |
68 | private VideoDownloadManager(){}
69 |
70 | private VideoDownloadManager(Context context){
71 | this.context = context;
72 | userAgent = Util.getUserAgent(context, "ExoPlayer2DownloadManager");
73 | }
74 |
75 | public static VideoDownloadManager getInstance(Context context) {
76 | if (INSTANCE == null) {
77 | synchronized(VideoDownloadManager.class) {
78 | if (INSTANCE == null) {
79 | INSTANCE = new VideoDownloadManager(context);
80 | }
81 | }
82 | }
83 | return INSTANCE;
84 | }
85 | /** Returns a {@link DataSource.Factory}. */
86 | public DataSource.Factory buildDataSourceFactory() {
87 | DefaultDataSourceFactory upstreamFactory =
88 | new DefaultDataSourceFactory(context, buildHttpDataSourceFactory());
89 | return buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache());
90 | }
91 |
92 | /** Returns a {@link HttpDataSource.Factory}. */
93 | public HttpDataSource.Factory buildHttpDataSourceFactory() {
94 | return new DefaultHttpDataSourceFactory(userAgent);
95 | }
96 |
97 | /** Returns whether extension renderers should be used. */
98 | public boolean useExtensionRenderers() {
99 | return "withExtensions".equals(BuildConfig.FLAVOR);
100 | }
101 |
102 | public RenderersFactory buildRenderersFactory(boolean preferExtensionRenderer) {
103 | @DefaultRenderersFactory.ExtensionRendererMode
104 | int extensionRendererMode =
105 | useExtensionRenderers()
106 | ? (preferExtensionRenderer
107 | ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER
108 | : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON)
109 | : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF;
110 | return new DefaultRenderersFactory(/* context= */ context)
111 | .setExtensionRendererMode(extensionRendererMode);
112 | }
113 |
114 | public DownloadManager getDownloadManager() {
115 | initDownloadManager();
116 | return downloadManager;
117 | }
118 |
119 | public DownloadTracker getDownloadTracker() {
120 | initDownloadManager();
121 | return downloadTracker;
122 | }
123 |
124 | protected synchronized Cache getDownloadCache() {
125 | if (downloadCache == null) {
126 | File downloadContentDirectory = new File(getDownloadDirectory(), DOWNLOAD_CONTENT_DIRECTORY);
127 | downloadCache =
128 | new SimpleCache(downloadContentDirectory, new NoOpCacheEvictor(), getDatabaseProvider());
129 | }
130 | return downloadCache;
131 | }
132 |
133 | private synchronized void initDownloadManager() {
134 | if (downloadManager == null) {
135 | DefaultDownloadIndex downloadIndex = new DefaultDownloadIndex(getDatabaseProvider());
136 | upgradeActionFile(
137 | DOWNLOAD_ACTION_FILE, downloadIndex, /* addNewDownloadsAsCompleted= */ false);
138 | upgradeActionFile(
139 | DOWNLOAD_TRACKER_ACTION_FILE, downloadIndex, /* addNewDownloadsAsCompleted= */ true);
140 | DownloaderConstructorHelper downloaderConstructorHelper =
141 | new DownloaderConstructorHelper(getDownloadCache(), buildHttpDataSourceFactory());
142 | downloadManager =
143 | new DownloadManager(
144 | context, downloadIndex, new DefaultDownloaderFactory(downloaderConstructorHelper));
145 | downloadTracker =
146 | new DownloadTracker(/* context= */ context, buildDataSourceFactory(), downloadManager);
147 | }
148 | }
149 |
150 | private void upgradeActionFile(
151 | String fileName, DefaultDownloadIndex downloadIndex, boolean addNewDownloadsAsCompleted) {
152 | try {
153 | ActionFileUpgradeUtil.upgradeAndDelete(
154 | new File(getDownloadDirectory(), fileName),
155 | /* downloadIdProvider= */ null,
156 | downloadIndex,
157 | /* deleteOnFailure= */ true,
158 | addNewDownloadsAsCompleted);
159 | } catch (IOException e) {
160 | Log.e(TAG, "Failed to upgrade action file: " + fileName, e);
161 | }
162 | }
163 |
164 | private DatabaseProvider getDatabaseProvider() {
165 | if (databaseProvider == null) {
166 | databaseProvider = new ExoDatabaseProvider(context);
167 | }
168 | return databaseProvider;
169 | }
170 |
171 | private File getDownloadDirectory() {
172 | if (downloadDirectory == null) {
173 | downloadDirectory = context.getExternalFilesDir(null);
174 | if (downloadDirectory == null) {
175 | downloadDirectory = context.getFilesDir();
176 | }
177 | }
178 | return downloadDirectory;
179 | }
180 |
181 | protected static CacheDataSourceFactory buildReadOnlyCacheDataSource(
182 | DataSource.Factory upstreamFactory, Cache cache) {
183 | return new CacheDataSourceFactory(
184 | cache,
185 | upstreamFactory,
186 | new FileDataSourceFactory(),
187 | /* cacheWriteDataSinkFactory= */ null,
188 | CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR,
189 | /* eventListener= */ null);
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/andrioidexoplayertrackselection/track_selection_dialog/FragmentAdapter.java:
--------------------------------------------------------------------------------
1 | package com.example.andrioidexoplayertrackselection.track_selection_dialog;
2 |
3 | import android.content.Context;
4 | import android.util.SparseArray;
5 |
6 | import androidx.annotation.Nullable;
7 | import androidx.fragment.app.Fragment;
8 | import androidx.fragment.app.FragmentManager;
9 | import androidx.fragment.app.FragmentPagerAdapter;
10 |
11 | import com.example.andrioidexoplayertrackselection.R;
12 | import com.google.android.exoplayer2.C;
13 |
14 | import java.util.ArrayList;
15 |
16 | public class FragmentAdapter extends FragmentPagerAdapter {
17 |
18 | private final SparseArray tabFragments;
19 | private final ArrayList tabTrackTypes;
20 | private Context context;
21 |
22 | public FragmentAdapter(FragmentManager fragmentManager, Context context, SparseArray tabFragments, ArrayList tabTrackTypes) {
23 | super(fragmentManager);
24 | this.context = context.getApplicationContext();
25 | this.tabFragments = tabFragments;
26 | this.tabTrackTypes = tabTrackTypes;
27 | }
28 |
29 | @Override
30 | public Fragment getItem(int position) {
31 | return tabFragments.valueAt(position);
32 | }
33 |
34 | @Override
35 | public int getCount() {
36 | return tabFragments.size();
37 | }
38 |
39 | @Nullable
40 | @Override
41 | public CharSequence getPageTitle(int position) {
42 | Integer trackType = tabTrackTypes.get(position);
43 | switch (trackType) {
44 | case C.TRACK_TYPE_VIDEO:
45 | return context.getResources().getString(R.string.exo_track_selection_title_video);
46 | case C.TRACK_TYPE_AUDIO:
47 | return context.getResources().getString(R.string.exo_track_selection_title_audio);
48 | case C.TRACK_TYPE_TEXT:
49 | return context.getResources().getString(R.string.exo_track_selection_title_text);
50 | default:
51 | throw new IllegalArgumentException();
52 | }
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/andrioidexoplayertrackselection/track_selection_dialog/TrackSelectionDialog.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.example.andrioidexoplayertrackselection.track_selection_dialog;
17 |
18 | import android.app.Dialog;
19 | import android.content.DialogInterface;
20 | import android.os.Bundle;
21 | import android.util.Log;
22 | import android.util.SparseArray;
23 | import android.view.LayoutInflater;
24 | import android.view.View;
25 | import android.view.ViewGroup;
26 | import android.widget.Button;
27 |
28 | import androidx.annotation.Nullable;
29 | import androidx.appcompat.app.AppCompatDialog;
30 | import androidx.fragment.app.DialogFragment;
31 | import androidx.viewpager.widget.ViewPager;
32 |
33 | import com.example.andrioidexoplayertrackselection.R;
34 | import com.google.android.exoplayer2.C;
35 | import com.google.android.exoplayer2.source.TrackGroupArray;
36 | import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
37 | import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride;
38 | import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
39 | import com.google.android.exoplayer2.util.Assertions;
40 | import com.google.android.material.tabs.TabLayout;
41 | import com.google.gson.Gson;
42 |
43 | import java.util.ArrayList;
44 | import java.util.Collections;
45 | import java.util.List;
46 |
47 | /** Dialog to select tracks. */
48 | public final class TrackSelectionDialog extends DialogFragment {
49 | private static String TAG = "TrackSelectionDialog";
50 | private final SparseArray tabFragments;
51 | private final ArrayList tabTrackTypes;
52 |
53 | private int titleId;
54 | private DialogInterface.OnClickListener onClickListener;
55 | private DialogInterface.OnDismissListener onDismissListener;
56 |
57 | /**
58 | * Returns whether a track selection dialog will have content to display if initialized with the
59 | * specified {@link DefaultTrackSelector} in its current state.
60 | */
61 | public static boolean willHaveContent(DefaultTrackSelector trackSelector) {
62 | MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo();
63 | return mappedTrackInfo != null && willHaveContent(mappedTrackInfo);
64 | }
65 |
66 | /**
67 | * Returns whether a track selection dialog will have content to display if initialized with the
68 | * specified {@link MappedTrackInfo}.
69 | */
70 | public static boolean willHaveContent(MappedTrackInfo mappedTrackInfo) {
71 | for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
72 | if (showTabForRenderer(mappedTrackInfo, i)) {
73 | return true;
74 | }
75 | }
76 | return false;
77 | }
78 |
79 | /**
80 | * Creates a dialog for a given {@link DefaultTrackSelector}, whose parameters will be
81 | * automatically updated when tracks are selected.
82 | *
83 | * @param trackSelector The {@link DefaultTrackSelector}.
84 | * @param onDismissListener A {@link DialogInterface.OnDismissListener} to call when the dialog is
85 | * dismissed.
86 | */
87 | public static TrackSelectionDialog createForTrackSelector(DefaultTrackSelector trackSelector, DialogInterface.OnDismissListener onDismissListener) {
88 | MappedTrackInfo mappedTrackInfo = Assertions.checkNotNull(trackSelector.getCurrentMappedTrackInfo());
89 | TrackSelectionDialog trackSelectionDialog = new TrackSelectionDialog();
90 | DefaultTrackSelector.Parameters parameters = trackSelector.getParameters();
91 | trackSelectionDialog.init(
92 | /* titleId= */ R.string.track_selection_title,
93 | mappedTrackInfo,
94 | /* initialParameters = */ parameters,
95 | /* allowAdaptiveSelections =*/ true,
96 | /* allowMultipleOverrides= */ false,
97 | /* onClickListener= */ (dialog, which) -> {
98 | DefaultTrackSelector.ParametersBuilder builder = parameters.buildUpon();
99 | for (int rendererIndex = 0; rendererIndex < mappedTrackInfo.getRendererCount(); rendererIndex++) {
100 | builder.clearSelectionOverrides(rendererIndex).setRendererDisabled(rendererIndex, trackSelectionDialog.getIsDisabled(rendererIndex));
101 | List overrides = trackSelectionDialog.getOverrides(rendererIndex);
102 | if (!overrides.isEmpty()) {
103 | Log.d(TAG, "override: " + new Gson().toJson(overrides.get(0)));
104 | builder.setSelectionOverride(
105 | rendererIndex,
106 | mappedTrackInfo.getTrackGroups(rendererIndex),
107 | overrides.get(0));
108 | }
109 | }
110 | trackSelector.setParameters(builder);
111 | },
112 | onDismissListener);
113 | return trackSelectionDialog;
114 | }
115 |
116 | /**
117 | * Creates a dialog for given {@link MappedTrackInfo} and {@link DefaultTrackSelector.Parameters}.
118 | *
119 | * @param titleId The resource id of the dialog title.
120 | * @param mappedTrackInfo The {@link MappedTrackInfo} to display.
121 | * @param initialParameters The {@link DefaultTrackSelector.Parameters} describing the initial
122 | * track selection.
123 | * @param allowAdaptiveSelections Whether adaptive selections (consisting of more than one track)
124 | * can be made.
125 | * @param allowMultipleOverrides Whether tracks from multiple track groups can be selected.
126 | * @param onClickListener {@link DialogInterface.OnClickListener} called when tracks are selected.
127 | * @param onDismissListener {@link DialogInterface.OnDismissListener} called when the dialog is
128 | * dismissed.
129 | */
130 | public static TrackSelectionDialog createForMappedTrackInfoAndParameters(
131 | int titleId,
132 | MappedTrackInfo mappedTrackInfo,
133 | DefaultTrackSelector.Parameters initialParameters,
134 | boolean allowAdaptiveSelections,
135 | boolean allowMultipleOverrides,
136 | DialogInterface.OnClickListener onClickListener,
137 | DialogInterface.OnDismissListener onDismissListener) {
138 | TrackSelectionDialog trackSelectionDialog = new TrackSelectionDialog();
139 | trackSelectionDialog.init(
140 | titleId,
141 | mappedTrackInfo,
142 | initialParameters,
143 | allowAdaptiveSelections,
144 | allowMultipleOverrides,
145 | onClickListener,
146 | onDismissListener);
147 | return trackSelectionDialog;
148 | }
149 |
150 | public TrackSelectionDialog() {
151 | tabFragments = new SparseArray<>();
152 | tabTrackTypes = new ArrayList<>();
153 | // Retain instance across activity re-creation to prevent losing access to init data.
154 | setRetainInstance(true);
155 | }
156 |
157 | private void init(
158 | int titleId,
159 | MappedTrackInfo mappedTrackInfo,
160 | DefaultTrackSelector.Parameters initialParameters,
161 | boolean allowAdaptiveSelections,
162 | boolean allowMultipleOverrides,
163 | DialogInterface.OnClickListener onClickListener,
164 | DialogInterface.OnDismissListener onDismissListener) {
165 | this.titleId = titleId;
166 | this.onClickListener = onClickListener;
167 | this.onDismissListener = onDismissListener;
168 | for (int rendererIndex = 0; rendererIndex < mappedTrackInfo.getRendererCount(); rendererIndex++) {
169 | if (showTabForRenderer(mappedTrackInfo, rendererIndex)) {
170 | int trackType = mappedTrackInfo.getRendererType(rendererIndex);
171 | TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(rendererIndex);
172 | TrackSelectionViewFragment tabFragment = new TrackSelectionViewFragment();
173 | tabFragment.init(
174 | mappedTrackInfo,
175 | rendererIndex,
176 | initialParameters.getRendererDisabled(rendererIndex),
177 | initialParameters.getSelectionOverride(rendererIndex, trackGroupArray),
178 | allowAdaptiveSelections,
179 | allowMultipleOverrides);
180 | tabFragments.put(rendererIndex, tabFragment);
181 | tabTrackTypes.add(trackType);
182 | }
183 | }
184 | }
185 |
186 | /**
187 | * Returns whether a renderer is disabled.
188 | *
189 | * @param rendererIndex Renderer index.
190 | * @return Whether the renderer is disabled.
191 | */
192 | public boolean getIsDisabled(int rendererIndex) {
193 | TrackSelectionViewFragment rendererView = tabFragments.get(rendererIndex);
194 | return rendererView != null && rendererView.isDisabled;
195 | }
196 |
197 | /**
198 | * Returns the list of selected track selection overrides for the specified renderer. There will
199 | * be at most one override for each track group.
200 | *
201 | * @param rendererIndex Renderer index.
202 | * @return The list of track selection overrides for this renderer.
203 | */
204 | public List getOverrides(int rendererIndex) {
205 | TrackSelectionViewFragment rendererView = tabFragments.get(rendererIndex);
206 | return rendererView == null ? Collections.emptyList() : rendererView.overrides;
207 | }
208 |
209 | @Override
210 | public Dialog onCreateDialog(Bundle savedInstanceState) {
211 | // We need to own the view to let tab layout work correctly on all API levels. We can't use
212 | // AlertDialog because it owns the view itself, so we use AppCompatDialog instead, themed using
213 | // the AlertDialog theme overlay with force-enabled title.
214 | AppCompatDialog dialog =
215 | new AppCompatDialog(getActivity(), R.style.TrackSelectionDialogThemeOverlay);
216 | dialog.setTitle(titleId);
217 | return dialog;
218 | }
219 |
220 | @Override
221 | public void onDismiss(DialogInterface dialog) {
222 | super.onDismiss(dialog);
223 | onDismissListener.onDismiss(dialog);
224 | }
225 |
226 | @Nullable
227 | @Override
228 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
229 |
230 | View dialogView = inflater.inflate(R.layout.track_selection_dialog, container, false);
231 | TabLayout tabLayout = dialogView.findViewById(R.id.track_selection_dialog_tab_layout);
232 | ViewPager viewPager = dialogView.findViewById(R.id.track_selection_dialog_view_pager);
233 | Button cancelButton = dialogView.findViewById(R.id.track_selection_dialog_cancel_button);
234 | Button okButton = dialogView.findViewById(R.id.track_selection_dialog_ok_button);
235 | viewPager.setAdapter(new FragmentAdapter(getChildFragmentManager(), requireContext(), tabFragments, tabTrackTypes));
236 | tabLayout.setupWithViewPager(viewPager);
237 | tabLayout.setVisibility(tabFragments.size() > 1 ? View.VISIBLE : View.GONE);
238 | cancelButton.setOnClickListener(view -> dismiss());
239 | okButton.setOnClickListener(
240 | view -> {
241 | onClickListener.onClick(getDialog(), DialogInterface.BUTTON_POSITIVE);
242 | dismiss();
243 | });
244 | return dialogView;
245 | }
246 |
247 | public static boolean showTabForRenderer(MappedTrackInfo mappedTrackInfo, int rendererIndex) {
248 | TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(rendererIndex);
249 | if (trackGroupArray.length == 0) {
250 | return false;
251 | }
252 | int trackType = mappedTrackInfo.getRendererType(rendererIndex);
253 | return isSupportedTrackType(trackType);
254 | }
255 |
256 | private static boolean isSupportedTrackType(int trackType) {
257 | switch (trackType) {
258 | case C.TRACK_TYPE_VIDEO:
259 | case C.TRACK_TYPE_AUDIO:
260 | case C.TRACK_TYPE_TEXT:
261 | return true;
262 | default:
263 | return false;
264 | }
265 | }
266 |
267 | }
268 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/andrioidexoplayertrackselection/track_selection_dialog/TrackSelectionViewFragment.java:
--------------------------------------------------------------------------------
1 | package com.example.andrioidexoplayertrackselection.track_selection_dialog;
2 |
3 | import android.os.Bundle;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 |
8 | import androidx.annotation.Nullable;
9 | import androidx.fragment.app.Fragment;
10 |
11 | import com.example.andrioidexoplayertrackselection.R;
12 | import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
13 | import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
14 | import com.google.android.exoplayer2.ui.TrackSelectionView;
15 |
16 | import java.util.Collections;
17 | import java.util.List;
18 |
19 | /** Fragment to show a track selection in tab of the track selection dialog. */
20 | public final class TrackSelectionViewFragment extends Fragment implements TrackSelectionView.TrackSelectionListener {
21 |
22 | private MappingTrackSelector.MappedTrackInfo mappedTrackInfo;
23 | private int rendererIndex;
24 | private boolean allowAdaptiveSelections;
25 | private boolean allowMultipleOverrides;
26 |
27 | /* package */ boolean isDisabled;
28 | /* package */ List overrides;
29 |
30 | public TrackSelectionViewFragment() {
31 | // Retain instance across activity re-creation to prevent losing access to init data.
32 | setRetainInstance(true);
33 | }
34 |
35 | public void init(
36 | MappingTrackSelector.MappedTrackInfo mappedTrackInfo,
37 | int rendererIndex,
38 | boolean initialIsDisabled,
39 | @Nullable DefaultTrackSelector.SelectionOverride initialOverride,
40 | boolean allowAdaptiveSelections,
41 | boolean allowMultipleOverrides) {
42 | this.mappedTrackInfo = mappedTrackInfo;
43 | this.rendererIndex = rendererIndex;
44 | this.isDisabled = initialIsDisabled;
45 | this.overrides = initialOverride == null ? Collections.emptyList() : Collections.singletonList(initialOverride);
46 | this.allowAdaptiveSelections = allowAdaptiveSelections;
47 | this.allowMultipleOverrides = allowMultipleOverrides;
48 | }
49 |
50 | @Nullable
51 | @Override
52 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
53 | View rootView = inflater.inflate(R.layout.exo_track_selection_dialog, container, /* attachToRoot= */ false);
54 | TrackSelectionView trackSelectionView = rootView.findViewById(R.id.exo_track_selection_view);
55 | trackSelectionView.setShowDisableOption(true);
56 | trackSelectionView.setAllowMultipleOverrides(allowMultipleOverrides);
57 | trackSelectionView.setAllowAdaptiveSelections(allowAdaptiveSelections);
58 | trackSelectionView.init(mappedTrackInfo, rendererIndex, isDisabled, overrides, /* listener= */ this);
59 | return rootView;
60 | }
61 |
62 | @Override
63 | public void onTrackSelectionChanged(boolean isDisabled, List overrides) {
64 | this.isDisabled = isDisabled;
65 | this.overrides = overrides;
66 | }
67 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_download.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_download_done.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/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 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
20 |
21 |
22 |
26 |
27 |
32 |
33 |
38 |
39 |
44 |
45 |
46 |
47 |
48 |
49 |
55 |
56 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/player_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
12 |
13 |
18 |
19 |
26 |
27 |
32 |
36 |
41 |
42 |
47 |
48 |
49 |
53 |
58 |
59 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/track_selection_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
20 |
21 |
22 |
23 |
28 |
29 |
35 |
36 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codexpedia/android_exoplayer_track_selection/919335ec5d4c343c8a6b3b87f363e45b4da258fd/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codexpedia/android_exoplayer_track_selection/919335ec5d4c343c8a6b3b87f363e45b4da258fd/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codexpedia/android_exoplayer_track_selection/919335ec5d4c343c8a6b3b87f363e45b4da258fd/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codexpedia/android_exoplayer_track_selection/919335ec5d4c343c8a6b3b87f363e45b4da258fd/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codexpedia/android_exoplayer_track_selection/919335ec5d4c343c8a6b3b87f363e45b4da258fd/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codexpedia/android_exoplayer_track_selection/919335ec5d4c343c8a6b3b87f363e45b4da258fd/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codexpedia/android_exoplayer_track_selection/919335ec5d4c343c8a6b3b87f363e45b4da258fd/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codexpedia/android_exoplayer_track_selection/919335ec5d4c343c8a6b3b87f363e45b4da258fd/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codexpedia/android_exoplayer_track_selection/919335ec5d4c343c8a6b3b87f363e45b4da258fd/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codexpedia/android_exoplayer_track_selection/919335ec5d4c343c8a6b3b87f363e45b4da258fd/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Andrioid Exoplayer Track Selection
3 | Select tracks
4 | Log track info to logcat
5 | Remove closed caption
6 | Show closed caption
7 | Unrecognized stereo mode
8 | Permission to access storage was denied
9 | Unexpected intent action: %1$s
10 | Cleartext traffic not permitted
11 | An unknown DRM error occurred
12 | Protected content not supported on API levels below 18
13 | This device does not support the required DRM scheme
14 | Unrecognized ABR algorithm
15 | Playing sample without ads, as the IMA extension was not loaded
16 | Playback failed
17 | Unable to query device decoders
18 | This device does not provide a secure decoder for %1$s
19 | This device does not provide a decoder for %1$s
20 | Unable to instantiate decoder %1$s
21 | Media includes video tracks, but none are playable by this device
22 | Media includes audio tracks, but none are playable by this device
23 | Failed to start download
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
14 |
15 |
18 |
19 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/test/java/com/example/andrioidexoplayertrackselection/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.andrioidexoplayertrackselection
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.3.50'
5 | repositories {
6 | google()
7 | jcenter()
8 |
9 | }
10 | dependencies {
11 | classpath 'com.android.tools.build:gradle:3.5.2'
12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | google()
21 | jcenter()
22 |
23 | }
24 | }
25 |
26 | task clean(type: Delete) {
27 | delete rootProject.buildDir
28 | }
29 |
--------------------------------------------------------------------------------
/captures/screen1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codexpedia/android_exoplayer_track_selection/919335ec5d4c343c8a6b3b87f363e45b4da258fd/captures/screen1.png
--------------------------------------------------------------------------------
/captures/selection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codexpedia/android_exoplayer_track_selection/919335ec5d4c343c8a6b3b87f363e45b4da258fd/captures/selection.png
--------------------------------------------------------------------------------
/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/codexpedia/android_exoplayer_track_selection/919335ec5d4c343c8a6b3b87f363e45b4da258fd/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Nov 09 14:51:42 PST 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | rootProject.name='Andrioid Exoplayer Track Selection'
3 |
--------------------------------------------------------------------------------