├── screens
└── charts.jpg
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── demo
├── src
│ └── main
│ │ ├── res
│ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xhdpi
│ │ │ └── ic_banner.png
│ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── layout
│ │ │ ├── list_divider.xml
│ │ │ ├── track_selection_dialog.xml
│ │ │ ├── sample_chooser_activity.xml
│ │ │ ├── include_player_stats.xml
│ │ │ └── player_activity.xml
│ │ └── values
│ │ │ ├── styles.xml
│ │ │ └── strings.xml
│ │ ├── java
│ │ └── com
│ │ │ └── google
│ │ │ └── android
│ │ │ └── exoplayer2
│ │ │ └── demo
│ │ │ ├── DemoApplication.java
│ │ │ ├── DemoUtil.java
│ │ │ ├── CustomLoadControl.java
│ │ │ ├── TrackSelectionHelper.java
│ │ │ ├── SampleChooserActivity.java
│ │ │ ├── EventLogger.java
│ │ │ └── PlayerActivity.java
│ │ ├── AndroidManifest.xml
│ │ └── assets
│ │ └── media.exolist.json
└── build.gradle
├── .gitignore
├── settings.gradle
├── publish.gradle
├── LICENSE
├── README.md
├── gradlew.bat
└── gradlew
/screens/charts.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sriharia/ExoPlayer-StatsForNerds/HEAD/screens/charts.jpg
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | ## Project-wide Gradle settings.
2 | android.useDeprecatedNdk=true
3 | buildDir=buildout
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sriharia/ExoPlayer-StatsForNerds/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sriharia/ExoPlayer-StatsForNerds/HEAD/demo/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sriharia/ExoPlayer-StatsForNerds/HEAD/demo/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-xhdpi/ic_banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sriharia/ExoPlayer-StatsForNerds/HEAD/demo/src/main/res/drawable-xhdpi/ic_banner.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sriharia/ExoPlayer-StatsForNerds/HEAD/demo/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sriharia/ExoPlayer-StatsForNerds/HEAD/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sriharia/ExoPlayer-StatsForNerds/HEAD/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Mar 13 11:17:14 GMT 2017
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-3.3-all.zip
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Android generated
2 | bin
3 | gen
4 | libs
5 | obj
6 | lint.xml
7 |
8 | # IntelliJ IDEA
9 | .idea
10 | *.iml
11 | *.ipr
12 | *.iws
13 | classes
14 | gen-external-apklibs
15 |
16 | # Eclipse
17 | .project
18 | .classpath
19 | .settings
20 | .checkstyle
21 | .cproject
22 |
23 | # Gradle
24 | .gradle
25 | build
26 | buildout
27 | out
28 |
29 | # Maven
30 | target
31 | release.properties
32 | pom.xml.*
33 |
34 | # Ant
35 | ant.properties
36 | local.properties
37 | proguard.cfg
38 | proguard-project.txt
39 |
40 | # Other
41 | .DS_Store
42 | dist
43 | tmp
44 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2016 The Android Open Source Project
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | include ':demo'
15 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/list_divider.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
20 |
--------------------------------------------------------------------------------
/publish.gradle:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2017 The Android Open Source Project
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | apply plugin: 'bintray-release'
15 |
16 | publish {
17 | artifactId = releaseArtifact
18 | description = releaseDescription
19 | repoName = releaseRepoName
20 | userOrg = releaseUserOrg
21 | groupId = releaseGroupId
22 | version = releaseVersion
23 | website = releaseWebsite
24 | }
25 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
19 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/track_selection_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
19 |
20 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) [2017] [Srihari Yachamaneni]
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/demo/src/main/res/layout/sample_chooser_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
20 |
21 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Demo app to showcase Google ExoPlayer customisations like player stats extractions, Improved buffering.
2 |
3 | More information on ExoPlayer documentation can be found [here][]
4 |
5 | [here]: http://google.github.io/ExoPlayer/
6 |
7 | ### Developed by
8 | [Srihari Yachamaneni](https://github.com/Sriharia) ([@srihari_y](https://twitter.com/srihari_y))
9 |
10 | # Features #
11 | ## Stats for Nerds ##
12 | This demo app depicts ExoPlayer internal stats in dynamic charts using [MPAndroidChart][] library.
13 | These stats include:
14 |
15 | - Connection Speed
16 | - Buffer Size in Seconds
17 | - Network Activity
18 | - Dropped Frames
19 |
20 |
21 | 
22 |
23 | This is achieved by injecting a listener to a [Customised][] version of [LoadControl][] component of ExoPlayer
24 |
25 | [MPAndroidChart]: https://github.com/PhilJay/MPAndroidChart
26 | [Customised]:
27 | https://github.com/Sriharia/ExoPlayer-StatsForNerds/blob/master/demo/src/main/java/com/google/android/exoplayer2/demo/CustomLoadControl.java
28 | [LoadControl]: https://github.com/google/ExoPlayer/blob/d979469659861f7fe1d39d153b90bdff1ab479cc/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java
29 |
30 | ## Improved Buffering ##
31 | This demo also helps you to find a way to change max buffer time by applying "drip-feeding" method.
32 | This is achieved by changing certain parameters in custom LoadControl component of ExoPlayer
33 |
34 | Check [CustomLoadControl][] class for more info on how buffer improvements are handled.
35 |
36 | [CustomLoadControl]: https://github.com/Sriharia/ExoPlayer-StatsForNerds/blob/master/demo/src/main/java/com/google/android/exoplayer2/demo/CustomLoadControl.java
37 |
--------------------------------------------------------------------------------
/demo/build.gradle:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2016 The Android Open Source Project
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | apply plugin: 'com.android.application'
15 |
16 | android {
17 | compileSdkVersion project.ext.compileSdkVersion
18 | buildToolsVersion project.ext.buildToolsVersion
19 |
20 | defaultConfig {
21 | minSdkVersion 16
22 | targetSdkVersion project.ext.targetSdkVersion
23 | }
24 |
25 | buildTypes {
26 | release {
27 | shrinkResources true
28 | minifyEnabled true
29 | proguardFiles getDefaultProguardFile('proguard-android.txt')
30 | }
31 | debug {
32 | jniDebuggable = true
33 | }
34 | }
35 |
36 | lintOptions {
37 | // The demo app does not have translations.
38 | disable 'MissingTranslation'
39 | }
40 |
41 | productFlavors {
42 | noExtensions
43 | withExtensions
44 | }
45 | }
46 |
47 | dependencies {
48 | compile fileTree(dir: 'libs', include: ['*.jar'])
49 | compile 'com.google.android.exoplayer:exoplayer:r2.4.0'
50 | // withExtensionsCompile project(path: ':extension-ffmpeg')
51 | // withExtensionsCompile project(path: ':extension-flac')
52 | // withExtensionsCompile project(path: ':extension-opus')
53 | // withExtensionsCompile project(path: ':extension-vp9')
54 | }
55 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.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.google.android.exoplayer2.demo;
17 |
18 | import android.app.Application;
19 | import com.google.android.exoplayer2.upstream.DataSource;
20 | import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
21 | import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
22 | import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
23 | import com.google.android.exoplayer2.upstream.HttpDataSource;
24 | import com.google.android.exoplayer2.util.Util;
25 |
26 | /**
27 | * Placeholder application to facilitate overriding Application methods for debugging and testing.
28 | */
29 | public class DemoApplication extends Application {
30 |
31 | protected String userAgent;
32 |
33 | @Override
34 | public void onCreate() {
35 | super.onCreate();
36 | userAgent = Util.getUserAgent(this, "ExoPlayerDemo");
37 | }
38 |
39 | public DataSource.Factory buildDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
40 | return new DefaultDataSourceFactory(this, bandwidthMeter,
41 | buildHttpDataSourceFactory(bandwidthMeter));
42 | }
43 |
44 | public HttpDataSource.Factory buildHttpDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
45 | return new DefaultHttpDataSourceFactory(userAgent, bandwidthMeter);
46 | }
47 |
48 | public boolean useExtensionRenderers() {
49 | return BuildConfig.FLAVOR.equals("withExtensions");
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/include_player_stats.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
22 |
23 |
24 |
28 |
29 |
33 |
34 |
35 |
43 |
44 |
51 |
52 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
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 Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
19 | ExoPlayer
20 |
21 | Video
22 |
23 | Audio
24 |
25 | Text
26 |
27 | Retry
28 |
29 | Disabled
30 |
31 | Default
32 |
33 | Default (none)
34 |
35 | Unexpected intent action: %1$s
36 |
37 | Enable random adaptation
38 |
39 | Protected content not supported on API levels below 18
40 |
41 | This device does not support the required DRM scheme
42 |
43 | An unknown DRM error occurred
44 |
45 | This device does not provide a decoder for %1$s
46 |
47 | This device does not provide a secure decoder for %1$s
48 |
49 | Unable to query device decoders
50 |
51 | Unable to instantiate decoder %1$s
52 |
53 | Media includes video tracks, but none are playable by this device
54 |
55 | Media includes audio tracks, but none are playable by this device
56 |
57 | Permission to access storage was denied
58 |
59 | One or more sample lists failed to load
60 |
61 |
62 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/player_activity.xml:
--------------------------------------------------------------------------------
1 |
15 |
21 |
22 |
26 |
27 |
31 |
32 |
41 |
42 |
48 |
49 |
57 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/demo/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
35 |
36 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/google/android/exoplayer2/demo/DemoUtil.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.google.android.exoplayer2.demo;
17 |
18 | import android.text.TextUtils;
19 | import com.google.android.exoplayer2.Format;
20 | import com.google.android.exoplayer2.util.MimeTypes;
21 |
22 | import java.text.DecimalFormat;
23 | import java.util.Locale;
24 |
25 | /**
26 | * Utility methods for demo application.
27 | */
28 | /*package*/ final class DemoUtil {
29 |
30 | /**
31 | * Builds a track name for display.
32 | *
33 | * @param format {@link Format} of the track.
34 | * @return a generated name specific to the track.
35 | */
36 | public static String buildTrackName(Format format) {
37 | String trackName;
38 | if (MimeTypes.isVideo(format.sampleMimeType)) {
39 | trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(
40 | buildResolutionString(format), buildBitrateString(format)), buildTrackIdString(format)),
41 | buildSampleMimeTypeString(format));
42 | } else if (MimeTypes.isAudio(format.sampleMimeType)) {
43 | trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(joinWithSeparator(
44 | buildLanguageString(format), buildAudioPropertyString(format)),
45 | buildBitrateString(format)), buildTrackIdString(format)),
46 | buildSampleMimeTypeString(format));
47 | } else {
48 | trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(buildLanguageString(format),
49 | buildBitrateString(format)), buildTrackIdString(format)),
50 | buildSampleMimeTypeString(format));
51 | }
52 | return trackName.length() == 0 ? "unknown" : trackName;
53 | }
54 |
55 | private static String buildResolutionString(Format format) {
56 | return format.width == Format.NO_VALUE || format.height == Format.NO_VALUE
57 | ? "" : format.width + "x" + format.height;
58 | }
59 |
60 | private static String buildAudioPropertyString(Format format) {
61 | return format.channelCount == Format.NO_VALUE || format.sampleRate == Format.NO_VALUE
62 | ? "" : format.channelCount + "ch, " + format.sampleRate + "Hz";
63 | }
64 |
65 | private static String buildLanguageString(Format format) {
66 | return TextUtils.isEmpty(format.language) || "und".equals(format.language) ? ""
67 | : format.language;
68 | }
69 |
70 | private static String buildBitrateString(Format format) {
71 | return format.bitrate == Format.NO_VALUE ? ""
72 | : String.format(Locale.US, "%.2fMbit", format.bitrate / 1000000f);
73 | }
74 |
75 | private static String joinWithSeparator(String first, String second) {
76 | return first.length() == 0 ? second : (second.length() == 0 ? first : first + ", " + second);
77 | }
78 |
79 | private static String buildTrackIdString(Format format) {
80 | return format.id == null ? "" : ("id:" + format.id);
81 | }
82 |
83 | private static String buildSampleMimeTypeString(Format format) {
84 | return format.sampleMimeType == null ? "" : format.sampleMimeType;
85 | }
86 |
87 | public static String getFormattedDouble(double value, int precision) {
88 | return new DecimalFormat("#0." + (precision <= 1 ? "0" : precision == 2 ? "00" : "000")).format(value);
89 | }
90 |
91 | public static String humanReadableByteCount(long bytes, boolean si) {
92 | return humanReadableByteCount(bytes, si, false);
93 | }
94 |
95 | public static String humanReadableByteCount(long bytes, boolean si, boolean isBits) {
96 | int unit = !si ? 1000 : 1024;
97 | if (bytes < unit)
98 | return bytes + " KB";
99 | int exp = (int) (Math.log(bytes) / Math.log(unit));
100 | String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1)
101 | + (si ? "" : "i");
102 | return isBits ? String.format("%.1f %sb", bytes / Math.pow(unit, exp), pre) : String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
103 | }
104 |
105 | private DemoUtil() {}
106 | }
107 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
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 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/google/android/exoplayer2/demo/CustomLoadControl.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.google.android.exoplayer2.demo;
17 |
18 | import android.os.Handler;
19 |
20 | import com.google.android.exoplayer2.C;
21 | import com.google.android.exoplayer2.LoadControl;
22 | import com.google.android.exoplayer2.Renderer;
23 | import com.google.android.exoplayer2.source.TrackGroupArray;
24 | import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
25 | import com.google.android.exoplayer2.upstream.Allocator;
26 | import com.google.android.exoplayer2.upstream.DefaultAllocator;
27 | import com.google.android.exoplayer2.util.PriorityTaskManager;
28 | import com.google.android.exoplayer2.util.Util;
29 |
30 | /**
31 | * The default {@link LoadControl} implementation.
32 | */
33 | public final class CustomLoadControl implements LoadControl {
34 |
35 | /**
36 | * The default minimum duration of media that the player will attempt to ensure is buffered at all
37 | * times, in milliseconds.
38 | */
39 | public static final int DEFAULT_MIN_BUFFER_MS = 15000;
40 |
41 | /**
42 | * The default maximum duration of media that the player will attempt to buffer, in milliseconds.
43 | */
44 | public static final int DEFAULT_MAX_BUFFER_MS = 30000;
45 |
46 | /**
47 | * The default duration of media that must be buffered for playback to start or resume following a
48 | * user action such as a seek, in milliseconds.
49 | */
50 | public static final int DEFAULT_BUFFER_FOR_PLAYBACK_MS = 2500;
51 |
52 | /**
53 | * The default duration of media that must be buffered for playback to resume after a rebuffer,
54 | * in milliseconds. A rebuffer is defined to be caused by buffer depletion rather than a user
55 | * action.
56 | */
57 | public static final int DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = 5000;
58 |
59 | /**
60 | * To increase buffer time and size.
61 | * Added by Sri
62 | */
63 | public static int VIDEO_BUFFER_SCALE_UP_FACTOR = 4;
64 |
65 | /**
66 | * Priority for media loading.
67 | */
68 | public static final int LOADING_PRIORITY = 0;
69 |
70 |
71 | private EventListener bufferedDurationListener;
72 | private Handler eventHandler;
73 |
74 | private static final int ABOVE_HIGH_WATERMARK = 0;
75 | private static final int BETWEEN_WATERMARKS = 1;
76 | private static final int BELOW_LOW_WATERMARK = 2;
77 |
78 | private final DefaultAllocator allocator;
79 |
80 | private final long minBufferUs;
81 | private final long maxBufferUs;
82 | private final long bufferForPlaybackUs;
83 | private final long bufferForPlaybackAfterRebufferUs;
84 | private final PriorityTaskManager priorityTaskManager;
85 |
86 | private int targetBufferSize;
87 | private boolean isBuffering;
88 |
89 | /**
90 | * Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class.
91 | */
92 | public CustomLoadControl() {
93 | this(new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE));
94 | }
95 |
96 | /**
97 | * Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class.
98 | */
99 | public CustomLoadControl(EventListener listener, Handler handler) {
100 | this(new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE));
101 | bufferedDurationListener = listener;
102 | eventHandler = handler;
103 | }
104 |
105 | /**
106 | * Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class.
107 | *
108 | * @param allocator The {@link DefaultAllocator} used by the loader.
109 | */
110 | public CustomLoadControl(DefaultAllocator allocator) {
111 | this(allocator, DEFAULT_MIN_BUFFER_MS, DEFAULT_MAX_BUFFER_MS, DEFAULT_BUFFER_FOR_PLAYBACK_MS,
112 | DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS);
113 | }
114 |
115 | /**
116 | * Constructs a new instance.
117 | *
118 | * @param allocator The {@link DefaultAllocator} used by the loader.
119 | * @param minBufferMs The minimum duration of media that the player will attempt to ensure is
120 | * buffered at all times, in milliseconds.
121 | * @param maxBufferMs The maximum duration of media that the player will attempt buffer, in
122 | * milliseconds.
123 | * @param bufferForPlaybackMs The duration of media that must be buffered for playback to start or
124 | * resume following a user action such as a seek, in milliseconds.
125 | * @param bufferForPlaybackAfterRebufferMs The default duration of media that must be buffered for
126 | * playback to resume after a rebuffer, in milliseconds. A rebuffer is defined to be caused by
127 | * buffer depletion rather than a user action.
128 | */
129 | public CustomLoadControl(DefaultAllocator allocator, int minBufferMs, int maxBufferMs,
130 | long bufferForPlaybackMs, long bufferForPlaybackAfterRebufferMs) {
131 | this(allocator, minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs,
132 | null);
133 | }
134 |
135 | /**
136 | * Constructs a new instance.
137 | *
138 | * @param allocator The {@link DefaultAllocator} used by the loader.
139 | * @param minBufferMs The minimum duration of media that the player will attempt to ensure is
140 | * buffered at all times, in milliseconds.
141 | * @param maxBufferMs The maximum duration of media that the player will attempt buffer, in
142 | * milliseconds.
143 | * @param bufferForPlaybackMs The duration of media that must be buffered for playback to start or
144 | * resume following a user action such as a seek, in milliseconds.
145 | * @param bufferForPlaybackAfterRebufferMs The default duration of media that must be buffered for
146 | * playback to resume after a rebuffer, in milliseconds. A rebuffer is defined to be caused by
147 | * buffer depletion rather than a user action.
148 | * @param priorityTaskManager If not null, registers itself as a task with priority
149 | * {@link #LOADING_PRIORITY} during loading periods, and unregisters itself during draining
150 | * periods.
151 | */
152 | public CustomLoadControl(DefaultAllocator allocator, int minBufferMs, int maxBufferMs,
153 | long bufferForPlaybackMs, long bufferForPlaybackAfterRebufferMs,
154 | PriorityTaskManager priorityTaskManager) {
155 | this.allocator = allocator;
156 | minBufferUs = VIDEO_BUFFER_SCALE_UP_FACTOR/*Added by Sri to control buffer size */ * minBufferMs * 1000L;
157 | maxBufferUs = VIDEO_BUFFER_SCALE_UP_FACTOR/*Added by Sri to control buffer size */ * maxBufferMs * 1000L;
158 | bufferForPlaybackUs = bufferForPlaybackMs * 1000L;
159 | bufferForPlaybackAfterRebufferUs = bufferForPlaybackAfterRebufferMs * 1000L;
160 | this.priorityTaskManager = priorityTaskManager;
161 | }
162 |
163 | @Override
164 | public void onPrepared() {
165 | reset(false);
166 | }
167 |
168 | @Override
169 | public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups,
170 | TrackSelectionArray trackSelections) {
171 | targetBufferSize = 0;
172 | for (int i = 0; i < renderers.length; i++) {
173 | if (trackSelections.get(i) != null) {
174 | targetBufferSize += Util.getDefaultBufferSize(renderers[i].getTrackType());
175 | if(renderers[i].getTrackType() == C.TRACK_TYPE_VIDEO)
176 | targetBufferSize *= VIDEO_BUFFER_SCALE_UP_FACTOR; /*Added by Sri to control buffer size */
177 | }
178 | }
179 | allocator.setTargetBufferSize(targetBufferSize);
180 | }
181 |
182 | @Override
183 | public void onStopped() {
184 | reset(true);
185 | }
186 |
187 | @Override
188 | public void onReleased() {
189 | reset(true);
190 | }
191 |
192 | @Override
193 | public Allocator getAllocator() {
194 | return allocator;
195 | }
196 |
197 | @Override
198 | public boolean shouldStartPlayback(long bufferedDurationUs, boolean rebuffering) {
199 | long minBufferDurationUs = rebuffering ? bufferForPlaybackAfterRebufferUs : bufferForPlaybackUs;
200 | return minBufferDurationUs <= 0 || bufferedDurationUs >= minBufferDurationUs;
201 | }
202 |
203 | @Override
204 | public boolean shouldContinueLoading(final long bufferedDurationUs) {
205 | int bufferTimeState = getBufferTimeState(bufferedDurationUs);
206 | boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize;
207 | boolean wasBuffering = isBuffering;
208 | isBuffering = bufferTimeState == BELOW_LOW_WATERMARK
209 | || (bufferTimeState == BETWEEN_WATERMARKS
210 | /*
211 | * commented below line to achieve drip-feeding method for better caching. once you are below maxBufferUs, do fetch immediately.
212 | * Added by Sri
213 | */
214 | /* && isBuffering */
215 | && !targetBufferSizeReached);
216 | if (priorityTaskManager != null && isBuffering != wasBuffering) {
217 | if (isBuffering) {
218 | priorityTaskManager.add(LOADING_PRIORITY);
219 | } else {
220 | priorityTaskManager.remove(LOADING_PRIORITY);
221 | }
222 | }
223 | if (null != bufferedDurationListener && null != eventHandler)
224 | eventHandler.post(new Runnable() {
225 | @Override
226 | public void run() {
227 | bufferedDurationListener.onBufferedDurationSample(bufferedDurationUs);
228 | }
229 | });
230 | // Log.e("DLC","current buff Dur: "+bufferedDurationUs+",max buff:" + maxBufferUs +" shouldContinueLoading: "+isBuffering);
231 | return isBuffering;
232 | }
233 |
234 | private int getBufferTimeState(long bufferedDurationUs) {
235 | return bufferedDurationUs > maxBufferUs ? ABOVE_HIGH_WATERMARK
236 | : (bufferedDurationUs < minBufferUs ? BELOW_LOW_WATERMARK : BETWEEN_WATERMARKS);
237 | }
238 |
239 | private void reset(boolean resetAllocator) {
240 | targetBufferSize = 0;
241 | if (priorityTaskManager != null && isBuffering) {
242 | priorityTaskManager.remove(LOADING_PRIORITY);
243 | }
244 | isBuffering = false;
245 | if (resetAllocator) {
246 | allocator.reset();
247 | }
248 | }
249 |
250 | public interface EventListener {
251 | void onBufferedDurationSample(long bufferedDurationUs);
252 | }
253 |
254 | }
255 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionHelper.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.google.android.exoplayer2.demo;
17 |
18 | import android.annotation.SuppressLint;
19 | import android.app.Activity;
20 | import android.app.AlertDialog;
21 | import android.content.Context;
22 | import android.content.DialogInterface;
23 | import android.content.res.TypedArray;
24 | import android.util.Pair;
25 | import android.view.LayoutInflater;
26 | import android.view.View;
27 | import android.view.ViewGroup;
28 | import android.widget.CheckedTextView;
29 | import com.google.android.exoplayer2.RendererCapabilities;
30 | import com.google.android.exoplayer2.source.TrackGroup;
31 | import com.google.android.exoplayer2.source.TrackGroupArray;
32 | import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
33 | import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
34 | import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
35 | import com.google.android.exoplayer2.trackselection.MappingTrackSelector.SelectionOverride;
36 | import com.google.android.exoplayer2.trackselection.RandomTrackSelection;
37 | import com.google.android.exoplayer2.trackselection.TrackSelection;
38 | import java.util.Arrays;
39 |
40 | /**
41 | * Helper class for displaying track selection dialogs.
42 | */
43 | /* package */ final class TrackSelectionHelper implements View.OnClickListener,
44 | DialogInterface.OnClickListener {
45 |
46 | private static final TrackSelection.Factory FIXED_FACTORY = new FixedTrackSelection.Factory();
47 | private static final TrackSelection.Factory RANDOM_FACTORY = new RandomTrackSelection.Factory();
48 |
49 | private final MappingTrackSelector selector;
50 | private final TrackSelection.Factory adaptiveTrackSelectionFactory;
51 |
52 | private MappedTrackInfo trackInfo;
53 | private int rendererIndex;
54 | private TrackGroupArray trackGroups;
55 | private boolean[] trackGroupsAdaptive;
56 | private boolean isDisabled;
57 | private SelectionOverride override;
58 |
59 | private CheckedTextView disableView;
60 | private CheckedTextView defaultView;
61 | private CheckedTextView enableRandomAdaptationView;
62 | private CheckedTextView[][] trackViews;
63 |
64 | /**
65 | * @param selector The track selector.
66 | * @param adaptiveTrackSelectionFactory A factory for adaptive {@link TrackSelection}s, or null
67 | * if the selection helper should not support adaptive tracks.
68 | */
69 | public TrackSelectionHelper(MappingTrackSelector selector,
70 | TrackSelection.Factory adaptiveTrackSelectionFactory) {
71 | this.selector = selector;
72 | this.adaptiveTrackSelectionFactory = adaptiveTrackSelectionFactory;
73 | }
74 |
75 | /**
76 | * Shows the selection dialog for a given renderer.
77 | *
78 | * @param activity The parent activity.
79 | * @param title The dialog's title.
80 | * @param trackInfo The current track information.
81 | * @param rendererIndex The index of the renderer.
82 | */
83 | public void showSelectionDialog(Activity activity, CharSequence title, MappedTrackInfo trackInfo,
84 | int rendererIndex) {
85 | this.trackInfo = trackInfo;
86 | this.rendererIndex = rendererIndex;
87 |
88 | trackGroups = trackInfo.getTrackGroups(rendererIndex);
89 | trackGroupsAdaptive = new boolean[trackGroups.length];
90 | for (int i = 0; i < trackGroups.length; i++) {
91 | trackGroupsAdaptive[i] = adaptiveTrackSelectionFactory != null
92 | && trackInfo.getAdaptiveSupport(rendererIndex, i, false)
93 | != RendererCapabilities.ADAPTIVE_NOT_SUPPORTED
94 | && trackGroups.get(i).length > 1;
95 | }
96 | isDisabled = selector.getRendererDisabled(rendererIndex);
97 | override = selector.getSelectionOverride(rendererIndex, trackGroups);
98 |
99 | AlertDialog.Builder builder = new AlertDialog.Builder(activity);
100 | builder.setTitle(title)
101 | .setView(buildView(builder.getContext()))
102 | .setPositiveButton(android.R.string.ok, this)
103 | .setNegativeButton(android.R.string.cancel, null)
104 | .create()
105 | .show();
106 | }
107 |
108 | @SuppressLint("InflateParams")
109 | private View buildView(Context context) {
110 | LayoutInflater inflater = LayoutInflater.from(context);
111 | View view = inflater.inflate(R.layout.track_selection_dialog, null);
112 | ViewGroup root = (ViewGroup) view.findViewById(R.id.root);
113 |
114 | TypedArray attributeArray = context.getTheme().obtainStyledAttributes(
115 | new int[] {android.R.attr.selectableItemBackground});
116 | int selectableItemBackgroundResourceId = attributeArray.getResourceId(0, 0);
117 | attributeArray.recycle();
118 |
119 | // View for disabling the renderer.
120 | disableView = (CheckedTextView) inflater.inflate(
121 | android.R.layout.simple_list_item_single_choice, root, false);
122 | disableView.setBackgroundResource(selectableItemBackgroundResourceId);
123 | disableView.setText(R.string.selection_disabled);
124 | disableView.setFocusable(true);
125 | disableView.setOnClickListener(this);
126 | root.addView(disableView);
127 |
128 | // View for clearing the override to allow the selector to use its default selection logic.
129 | defaultView = (CheckedTextView) inflater.inflate(
130 | android.R.layout.simple_list_item_single_choice, root, false);
131 | defaultView.setBackgroundResource(selectableItemBackgroundResourceId);
132 | defaultView.setText(R.string.selection_default);
133 | defaultView.setFocusable(true);
134 | defaultView.setOnClickListener(this);
135 | root.addView(inflater.inflate(R.layout.list_divider, root, false));
136 | root.addView(defaultView);
137 |
138 | // Per-track views.
139 | boolean haveSupportedTracks = false;
140 | boolean haveAdaptiveTracks = false;
141 | trackViews = new CheckedTextView[trackGroups.length][];
142 | for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
143 | TrackGroup group = trackGroups.get(groupIndex);
144 | boolean groupIsAdaptive = trackGroupsAdaptive[groupIndex];
145 | haveAdaptiveTracks |= groupIsAdaptive;
146 | trackViews[groupIndex] = new CheckedTextView[group.length];
147 | for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
148 | if (trackIndex == 0) {
149 | root.addView(inflater.inflate(R.layout.list_divider, root, false));
150 | }
151 | int trackViewLayoutId = groupIsAdaptive ? android.R.layout.simple_list_item_multiple_choice
152 | : android.R.layout.simple_list_item_single_choice;
153 | CheckedTextView trackView = (CheckedTextView) inflater.inflate(
154 | trackViewLayoutId, root, false);
155 | trackView.setBackgroundResource(selectableItemBackgroundResourceId);
156 | trackView.setText(DemoUtil.buildTrackName(group.getFormat(trackIndex)));
157 | if (trackInfo.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex)
158 | == RendererCapabilities.FORMAT_HANDLED) {
159 | trackView.setFocusable(true);
160 | trackView.setTag(Pair.create(groupIndex, trackIndex));
161 | trackView.setOnClickListener(this);
162 | haveSupportedTracks = true;
163 | } else {
164 | trackView.setFocusable(false);
165 | trackView.setEnabled(false);
166 | }
167 | trackViews[groupIndex][trackIndex] = trackView;
168 | root.addView(trackView);
169 | }
170 | }
171 |
172 | if (!haveSupportedTracks) {
173 | // Indicate that the default selection will be nothing.
174 | defaultView.setText(R.string.selection_default_none);
175 | } else if (haveAdaptiveTracks) {
176 | // View for using random adaptation.
177 | enableRandomAdaptationView = (CheckedTextView) inflater.inflate(
178 | android.R.layout.simple_list_item_multiple_choice, root, false);
179 | enableRandomAdaptationView.setBackgroundResource(selectableItemBackgroundResourceId);
180 | enableRandomAdaptationView.setText(R.string.enable_random_adaptation);
181 | enableRandomAdaptationView.setOnClickListener(this);
182 | root.addView(inflater.inflate(R.layout.list_divider, root, false));
183 | root.addView(enableRandomAdaptationView);
184 | }
185 |
186 | updateViews();
187 | return view;
188 | }
189 |
190 | private void updateViews() {
191 | disableView.setChecked(isDisabled);
192 | defaultView.setChecked(!isDisabled && override == null);
193 | for (int i = 0; i < trackViews.length; i++) {
194 | for (int j = 0; j < trackViews[i].length; j++) {
195 | trackViews[i][j].setChecked(override != null && override.groupIndex == i
196 | && override.containsTrack(j));
197 | }
198 | }
199 | if (enableRandomAdaptationView != null) {
200 | boolean enableView = !isDisabled && override != null && override.length > 1;
201 | enableRandomAdaptationView.setEnabled(enableView);
202 | enableRandomAdaptationView.setFocusable(enableView);
203 | if (enableView) {
204 | enableRandomAdaptationView.setChecked(!isDisabled
205 | && override.factory instanceof RandomTrackSelection.Factory);
206 | }
207 | }
208 | }
209 |
210 | // DialogInterface.OnClickListener
211 |
212 | @Override
213 | public void onClick(DialogInterface dialog, int which) {
214 | selector.setRendererDisabled(rendererIndex, isDisabled);
215 | if (override != null) {
216 | selector.setSelectionOverride(rendererIndex, trackGroups, override);
217 | } else {
218 | selector.clearSelectionOverrides(rendererIndex);
219 | }
220 | }
221 |
222 | // View.OnClickListener
223 |
224 | @Override
225 | public void onClick(View view) {
226 | if (view == disableView) {
227 | isDisabled = true;
228 | override = null;
229 | } else if (view == defaultView) {
230 | isDisabled = false;
231 | override = null;
232 | } else if (view == enableRandomAdaptationView) {
233 | setOverride(override.groupIndex, override.tracks, !enableRandomAdaptationView.isChecked());
234 | } else {
235 | isDisabled = false;
236 | @SuppressWarnings("unchecked")
237 | Pair tag = (Pair) view.getTag();
238 | int groupIndex = tag.first;
239 | int trackIndex = tag.second;
240 | if (!trackGroupsAdaptive[groupIndex] || override == null
241 | || override.groupIndex != groupIndex) {
242 | override = new SelectionOverride(FIXED_FACTORY, groupIndex, trackIndex);
243 | } else {
244 | // The group being modified is adaptive and we already have a non-null override.
245 | boolean isEnabled = ((CheckedTextView) view).isChecked();
246 | int overrideLength = override.length;
247 | if (isEnabled) {
248 | // Remove the track from the override.
249 | if (overrideLength == 1) {
250 | // The last track is being removed, so the override becomes empty.
251 | override = null;
252 | isDisabled = true;
253 | } else {
254 | setOverride(groupIndex, getTracksRemoving(override, trackIndex),
255 | enableRandomAdaptationView.isChecked());
256 | }
257 | } else {
258 | // Add the track to the override.
259 | setOverride(groupIndex, getTracksAdding(override, trackIndex),
260 | enableRandomAdaptationView.isChecked());
261 | }
262 | }
263 | }
264 | // Update the views with the new state.
265 | updateViews();
266 | }
267 |
268 | private void setOverride(int group, int[] tracks, boolean enableRandomAdaptation) {
269 | TrackSelection.Factory factory = tracks.length == 1 ? FIXED_FACTORY
270 | : (enableRandomAdaptation ? RANDOM_FACTORY : adaptiveTrackSelectionFactory);
271 | override = new SelectionOverride(factory, group, tracks);
272 | }
273 |
274 | // Track array manipulation.
275 |
276 | private static int[] getTracksAdding(SelectionOverride override, int addedTrack) {
277 | int[] tracks = override.tracks;
278 | tracks = Arrays.copyOf(tracks, tracks.length + 1);
279 | tracks[tracks.length - 1] = addedTrack;
280 | return tracks;
281 | }
282 |
283 | private static int[] getTracksRemoving(SelectionOverride override, int removedTrack) {
284 | int[] tracks = new int[override.length - 1];
285 | int trackCount = 0;
286 | for (int i = 0; i < tracks.length + 1; i++) {
287 | int track = override.tracks[i];
288 | if (track != removedTrack) {
289 | tracks[trackCount++] = track;
290 | }
291 | }
292 | return tracks;
293 | }
294 |
295 | }
296 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.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.google.android.exoplayer2.demo;
17 |
18 | import android.app.Activity;
19 | import android.content.Context;
20 | import android.content.Intent;
21 | import android.content.res.AssetManager;
22 | import android.net.Uri;
23 | import android.os.AsyncTask;
24 | import android.os.Bundle;
25 | import android.util.JsonReader;
26 | import android.util.Log;
27 | import android.view.LayoutInflater;
28 | import android.view.View;
29 | import android.view.ViewGroup;
30 | import android.widget.BaseExpandableListAdapter;
31 | import android.widget.ExpandableListView;
32 | import android.widget.ExpandableListView.OnChildClickListener;
33 | import android.widget.TextView;
34 | import android.widget.Toast;
35 | import com.google.android.exoplayer2.C;
36 | import com.google.android.exoplayer2.ParserException;
37 | import com.google.android.exoplayer2.upstream.DataSource;
38 | import com.google.android.exoplayer2.upstream.DataSourceInputStream;
39 | import com.google.android.exoplayer2.upstream.DataSpec;
40 | import com.google.android.exoplayer2.upstream.DefaultDataSource;
41 | import com.google.android.exoplayer2.util.Assertions;
42 | import com.google.android.exoplayer2.util.Util;
43 | import java.io.IOException;
44 | import java.io.InputStream;
45 | import java.io.InputStreamReader;
46 | import java.util.ArrayList;
47 | import java.util.Arrays;
48 | import java.util.List;
49 | import java.util.UUID;
50 |
51 | /**
52 | * An activity for selecting from a list of samples.
53 | */
54 | public class SampleChooserActivity extends Activity {
55 |
56 | private static final String TAG = "SampleChooserActivity";
57 |
58 | @Override
59 | public void onCreate(Bundle savedInstanceState) {
60 | super.onCreate(savedInstanceState);
61 | setContentView(R.layout.sample_chooser_activity);
62 | Intent intent = getIntent();
63 | String dataUri = intent.getDataString();
64 | String[] uris;
65 | if (dataUri != null) {
66 | uris = new String[] {dataUri};
67 | } else {
68 | ArrayList uriList = new ArrayList<>();
69 | AssetManager assetManager = getAssets();
70 | try {
71 | for (String asset : assetManager.list("")) {
72 | if (asset.endsWith(".exolist.json")) {
73 | uriList.add("asset:///" + asset);
74 | }
75 | }
76 | } catch (IOException e) {
77 | Toast.makeText(getApplicationContext(), R.string.sample_list_load_error, Toast.LENGTH_LONG)
78 | .show();
79 | }
80 | uris = new String[uriList.size()];
81 | uriList.toArray(uris);
82 | Arrays.sort(uris);
83 | }
84 | SampleListLoader loaderTask = new SampleListLoader();
85 | loaderTask.execute(uris);
86 | }
87 |
88 | private void onSampleGroups(final List groups, boolean sawError) {
89 | if (sawError) {
90 | Toast.makeText(getApplicationContext(), R.string.sample_list_load_error, Toast.LENGTH_LONG)
91 | .show();
92 | }
93 | ExpandableListView sampleList = (ExpandableListView) findViewById(R.id.sample_list);
94 | sampleList.setAdapter(new SampleAdapter(this, groups));
95 | sampleList.setOnChildClickListener(new OnChildClickListener() {
96 | @Override
97 | public boolean onChildClick(ExpandableListView parent, View view, int groupPosition,
98 | int childPosition, long id) {
99 | onSampleSelected(groups.get(groupPosition).samples.get(childPosition));
100 | return true;
101 | }
102 | });
103 | }
104 |
105 | private void onSampleSelected(Sample sample) {
106 | startActivity(sample.buildIntent(this));
107 | }
108 |
109 | private final class SampleListLoader extends AsyncTask> {
110 |
111 | private boolean sawError;
112 |
113 | @Override
114 | protected List doInBackground(String... uris) {
115 | List result = new ArrayList<>();
116 | Context context = getApplicationContext();
117 | String userAgent = Util.getUserAgent(context, "ExoPlayerDemo");
118 | DataSource dataSource = new DefaultDataSource(context, null, userAgent, false);
119 | for (String uri : uris) {
120 | DataSpec dataSpec = new DataSpec(Uri.parse(uri));
121 | InputStream inputStream = new DataSourceInputStream(dataSource, dataSpec);
122 | try {
123 | readSampleGroups(new JsonReader(new InputStreamReader(inputStream, "UTF-8")), result);
124 | } catch (Exception e) {
125 | Log.e(TAG, "Error loading sample list: " + uri, e);
126 | sawError = true;
127 | } finally {
128 | Util.closeQuietly(dataSource);
129 | }
130 | }
131 | return result;
132 | }
133 |
134 | @Override
135 | protected void onPostExecute(List result) {
136 | onSampleGroups(result, sawError);
137 | }
138 |
139 | private void readSampleGroups(JsonReader reader, List groups) throws IOException {
140 | reader.beginArray();
141 | while (reader.hasNext()) {
142 | readSampleGroup(reader, groups);
143 | }
144 | reader.endArray();
145 | }
146 |
147 | private void readSampleGroup(JsonReader reader, List groups) throws IOException {
148 | String groupName = "";
149 | ArrayList samples = new ArrayList<>();
150 |
151 | reader.beginObject();
152 | while (reader.hasNext()) {
153 | String name = reader.nextName();
154 | switch (name) {
155 | case "name":
156 | groupName = reader.nextString();
157 | break;
158 | case "samples":
159 | reader.beginArray();
160 | while (reader.hasNext()) {
161 | samples.add(readEntry(reader, false));
162 | }
163 | reader.endArray();
164 | break;
165 | case "_comment":
166 | reader.nextString(); // Ignore.
167 | break;
168 | default:
169 | throw new ParserException("Unsupported name: " + name);
170 | }
171 | }
172 | reader.endObject();
173 |
174 | SampleGroup group = getGroup(groupName, groups);
175 | group.samples.addAll(samples);
176 | }
177 |
178 | private Sample readEntry(JsonReader reader, boolean insidePlaylist) throws IOException {
179 | String sampleName = null;
180 | String uri = null;
181 | String extension = null;
182 | UUID drmUuid = null;
183 | String drmLicenseUrl = null;
184 | String[] drmKeyRequestProperties = null;
185 | boolean preferExtensionDecoders = false;
186 | ArrayList playlistSamples = null;
187 |
188 | reader.beginObject();
189 | while (reader.hasNext()) {
190 | String name = reader.nextName();
191 | switch (name) {
192 | case "name":
193 | sampleName = reader.nextString();
194 | break;
195 | case "uri":
196 | uri = reader.nextString();
197 | break;
198 | case "extension":
199 | extension = reader.nextString();
200 | break;
201 | case "drm_scheme":
202 | Assertions.checkState(!insidePlaylist, "Invalid attribute on nested item: drm_scheme");
203 | drmUuid = getDrmUuid(reader.nextString());
204 | break;
205 | case "drm_license_url":
206 | Assertions.checkState(!insidePlaylist,
207 | "Invalid attribute on nested item: drm_license_url");
208 | drmLicenseUrl = reader.nextString();
209 | break;
210 | case "drm_key_request_properties":
211 | Assertions.checkState(!insidePlaylist,
212 | "Invalid attribute on nested item: drm_key_request_properties");
213 | ArrayList drmKeyRequestPropertiesList = new ArrayList<>();
214 | reader.beginObject();
215 | while (reader.hasNext()) {
216 | drmKeyRequestPropertiesList.add(reader.nextName());
217 | drmKeyRequestPropertiesList.add(reader.nextString());
218 | }
219 | reader.endObject();
220 | drmKeyRequestProperties = drmKeyRequestPropertiesList.toArray(new String[0]);
221 | break;
222 | case "prefer_extension_decoders":
223 | Assertions.checkState(!insidePlaylist,
224 | "Invalid attribute on nested item: prefer_extension_decoders");
225 | preferExtensionDecoders = reader.nextBoolean();
226 | break;
227 | case "playlist":
228 | Assertions.checkState(!insidePlaylist, "Invalid nesting of playlists");
229 | playlistSamples = new ArrayList<>();
230 | reader.beginArray();
231 | while (reader.hasNext()) {
232 | playlistSamples.add((UriSample) readEntry(reader, true));
233 | }
234 | reader.endArray();
235 | break;
236 | default:
237 | throw new ParserException("Unsupported attribute name: " + name);
238 | }
239 | }
240 | reader.endObject();
241 |
242 | if (playlistSamples != null) {
243 | UriSample[] playlistSamplesArray = playlistSamples.toArray(
244 | new UriSample[playlistSamples.size()]);
245 | return new PlaylistSample(sampleName, drmUuid, drmLicenseUrl, drmKeyRequestProperties,
246 | preferExtensionDecoders, playlistSamplesArray);
247 | } else {
248 | return new UriSample(sampleName, drmUuid, drmLicenseUrl, drmKeyRequestProperties,
249 | preferExtensionDecoders, uri, extension);
250 | }
251 | }
252 |
253 | private SampleGroup getGroup(String groupName, List groups) {
254 | for (int i = 0; i < groups.size(); i++) {
255 | if (Util.areEqual(groupName, groups.get(i).title)) {
256 | return groups.get(i);
257 | }
258 | }
259 | SampleGroup group = new SampleGroup(groupName);
260 | groups.add(group);
261 | return group;
262 | }
263 |
264 | private UUID getDrmUuid(String typeString) throws ParserException {
265 | switch (Util.toLowerInvariant(typeString)) {
266 | case "widevine":
267 | return C.WIDEVINE_UUID;
268 | case "playready":
269 | return C.PLAYREADY_UUID;
270 | case "cenc":
271 | return C.CLEARKEY_UUID;
272 | default:
273 | try {
274 | return UUID.fromString(typeString);
275 | } catch (RuntimeException e) {
276 | throw new ParserException("Unsupported drm type: " + typeString);
277 | }
278 | }
279 | }
280 |
281 | }
282 |
283 | private static final class SampleAdapter extends BaseExpandableListAdapter {
284 |
285 | private final Context context;
286 | private final List sampleGroups;
287 |
288 | public SampleAdapter(Context context, List sampleGroups) {
289 | this.context = context;
290 | this.sampleGroups = sampleGroups;
291 | }
292 |
293 | @Override
294 | public Sample getChild(int groupPosition, int childPosition) {
295 | return getGroup(groupPosition).samples.get(childPosition);
296 | }
297 |
298 | @Override
299 | public long getChildId(int groupPosition, int childPosition) {
300 | return childPosition;
301 | }
302 |
303 | @Override
304 | public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
305 | View convertView, ViewGroup parent) {
306 | View view = convertView;
307 | if (view == null) {
308 | view = LayoutInflater.from(context).inflate(android.R.layout.simple_list_item_1, parent,
309 | false);
310 | }
311 | ((TextView) view).setText(getChild(groupPosition, childPosition).name);
312 | return view;
313 | }
314 |
315 | @Override
316 | public int getChildrenCount(int groupPosition) {
317 | return getGroup(groupPosition).samples.size();
318 | }
319 |
320 | @Override
321 | public SampleGroup getGroup(int groupPosition) {
322 | return sampleGroups.get(groupPosition);
323 | }
324 |
325 | @Override
326 | public long getGroupId(int groupPosition) {
327 | return groupPosition;
328 | }
329 |
330 | @Override
331 | public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
332 | ViewGroup parent) {
333 | View view = convertView;
334 | if (view == null) {
335 | view = LayoutInflater.from(context).inflate(android.R.layout.simple_expandable_list_item_1,
336 | parent, false);
337 | }
338 | ((TextView) view).setText(getGroup(groupPosition).title);
339 | return view;
340 | }
341 |
342 | @Override
343 | public int getGroupCount() {
344 | return sampleGroups.size();
345 | }
346 |
347 | @Override
348 | public boolean hasStableIds() {
349 | return false;
350 | }
351 |
352 | @Override
353 | public boolean isChildSelectable(int groupPosition, int childPosition) {
354 | return true;
355 | }
356 |
357 | }
358 |
359 | private static final class SampleGroup {
360 |
361 | public final String title;
362 | public final List samples;
363 |
364 | public SampleGroup(String title) {
365 | this.title = title;
366 | this.samples = new ArrayList<>();
367 | }
368 |
369 | }
370 |
371 | private abstract static class Sample {
372 |
373 | public final String name;
374 | public final boolean preferExtensionDecoders;
375 | public final UUID drmSchemeUuid;
376 | public final String drmLicenseUrl;
377 | public final String[] drmKeyRequestProperties;
378 |
379 | public Sample(String name, UUID drmSchemeUuid, String drmLicenseUrl,
380 | String[] drmKeyRequestProperties, boolean preferExtensionDecoders) {
381 | this.name = name;
382 | this.drmSchemeUuid = drmSchemeUuid;
383 | this.drmLicenseUrl = drmLicenseUrl;
384 | this.drmKeyRequestProperties = drmKeyRequestProperties;
385 | this.preferExtensionDecoders = preferExtensionDecoders;
386 | }
387 |
388 | public Intent buildIntent(Context context) {
389 | Intent intent = new Intent(context, PlayerActivity.class);
390 | intent.putExtra(PlayerActivity.PREFER_EXTENSION_DECODERS, preferExtensionDecoders);
391 | if (drmSchemeUuid != null) {
392 | intent.putExtra(PlayerActivity.DRM_SCHEME_UUID_EXTRA, drmSchemeUuid.toString());
393 | intent.putExtra(PlayerActivity.DRM_LICENSE_URL, drmLicenseUrl);
394 | intent.putExtra(PlayerActivity.DRM_KEY_REQUEST_PROPERTIES, drmKeyRequestProperties);
395 | }
396 | return intent;
397 | }
398 |
399 | }
400 |
401 | private static final class UriSample extends Sample {
402 |
403 | public final String uri;
404 | public final String extension;
405 |
406 | public UriSample(String name, UUID drmSchemeUuid, String drmLicenseUrl,
407 | String[] drmKeyRequestProperties, boolean preferExtensionDecoders, String uri,
408 | String extension) {
409 | super(name, drmSchemeUuid, drmLicenseUrl, drmKeyRequestProperties, preferExtensionDecoders);
410 | this.uri = uri;
411 | this.extension = extension;
412 | }
413 |
414 | @Override
415 | public Intent buildIntent(Context context) {
416 | return super.buildIntent(context)
417 | .setData(Uri.parse(uri))
418 | .putExtra(PlayerActivity.EXTENSION_EXTRA, extension)
419 | .setAction(PlayerActivity.ACTION_VIEW);
420 | }
421 |
422 | }
423 |
424 | private static final class PlaylistSample extends Sample {
425 |
426 | public final UriSample[] children;
427 |
428 | public PlaylistSample(String name, UUID drmSchemeUuid, String drmLicenseUrl,
429 | String[] drmKeyRequestProperties, boolean preferExtensionDecoders,
430 | UriSample... children) {
431 | super(name, drmSchemeUuid, drmLicenseUrl, drmKeyRequestProperties, preferExtensionDecoders);
432 | this.children = children;
433 | }
434 |
435 | @Override
436 | public Intent buildIntent(Context context) {
437 | String[] uris = new String[children.length];
438 | String[] extensions = new String[children.length];
439 | for (int i = 0; i < children.length; i++) {
440 | uris[i] = children[i].uri;
441 | extensions[i] = children[i].extension;
442 | }
443 | return super.buildIntent(context)
444 | .putExtra(PlayerActivity.URI_LIST_EXTRA, uris)
445 | .putExtra(PlayerActivity.EXTENSION_LIST_EXTRA, extensions)
446 | .setAction(PlayerActivity.ACTION_VIEW_LIST);
447 | }
448 |
449 | }
450 |
451 | }
452 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.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.google.android.exoplayer2.demo;
17 |
18 | import android.os.SystemClock;
19 | import android.util.Log;
20 | import android.view.Surface;
21 | import com.google.android.exoplayer2.C;
22 | import com.google.android.exoplayer2.ExoPlaybackException;
23 | import com.google.android.exoplayer2.ExoPlayer;
24 | import com.google.android.exoplayer2.Format;
25 | import com.google.android.exoplayer2.PlaybackParameters;
26 | import com.google.android.exoplayer2.RendererCapabilities;
27 | import com.google.android.exoplayer2.Timeline;
28 | import com.google.android.exoplayer2.audio.AudioRendererEventListener;
29 | import com.google.android.exoplayer2.decoder.DecoderCounters;
30 | import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
31 | import com.google.android.exoplayer2.metadata.Metadata;
32 | import com.google.android.exoplayer2.metadata.MetadataRenderer;
33 | import com.google.android.exoplayer2.metadata.emsg.EventMessage;
34 | import com.google.android.exoplayer2.metadata.id3.ApicFrame;
35 | import com.google.android.exoplayer2.metadata.id3.CommentFrame;
36 | import com.google.android.exoplayer2.metadata.id3.GeobFrame;
37 | import com.google.android.exoplayer2.metadata.id3.Id3Frame;
38 | import com.google.android.exoplayer2.metadata.id3.PrivFrame;
39 | import com.google.android.exoplayer2.metadata.id3.TextInformationFrame;
40 | import com.google.android.exoplayer2.metadata.id3.UrlLinkFrame;
41 | import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
42 | import com.google.android.exoplayer2.source.ExtractorMediaSource;
43 | import com.google.android.exoplayer2.source.TrackGroup;
44 | import com.google.android.exoplayer2.source.TrackGroupArray;
45 | import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
46 | import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
47 | import com.google.android.exoplayer2.trackselection.TrackSelection;
48 | import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
49 | import com.google.android.exoplayer2.upstream.DataSpec;
50 | import com.google.android.exoplayer2.video.VideoRendererEventListener;
51 | import java.io.IOException;
52 | import java.text.NumberFormat;
53 | import java.util.Locale;
54 |
55 | /**
56 | * Logs player events using {@link Log}.
57 | */
58 | /* package */ final class EventLogger implements ExoPlayer.EventListener,
59 | AudioRendererEventListener, VideoRendererEventListener, AdaptiveMediaSourceEventListener,
60 | ExtractorMediaSource.EventListener, DefaultDrmSessionManager.EventListener,
61 | MetadataRenderer.Output {
62 |
63 | private static final String TAG = "EventLogger";
64 | private static final int MAX_TIMELINE_ITEM_LINES = 3;
65 | private static final NumberFormat TIME_FORMAT;
66 | static {
67 | TIME_FORMAT = NumberFormat.getInstance(Locale.US);
68 | TIME_FORMAT.setMinimumFractionDigits(2);
69 | TIME_FORMAT.setMaximumFractionDigits(2);
70 | TIME_FORMAT.setGroupingUsed(false);
71 | }
72 |
73 | private final MappingTrackSelector trackSelector;
74 | private final Timeline.Window window;
75 | private final Timeline.Period period;
76 | private final long startTimeMs;
77 |
78 | public EventLogger(MappingTrackSelector trackSelector) {
79 | this.trackSelector = trackSelector;
80 | window = new Timeline.Window();
81 | period = new Timeline.Period();
82 | startTimeMs = SystemClock.elapsedRealtime();
83 | }
84 |
85 | // ExoPlayer.EventListener
86 |
87 | @Override
88 | public void onLoadingChanged(boolean isLoading) {
89 | Log.d(TAG, "loading [" + isLoading + "]");
90 | }
91 |
92 | @Override
93 | public void onPlayerStateChanged(boolean playWhenReady, int state) {
94 | Log.d(TAG, "state [" + getSessionTimeString() + ", " + playWhenReady + ", "
95 | + getStateString(state) + "]");
96 | }
97 |
98 | @Override
99 | public void onPositionDiscontinuity() {
100 | Log.d(TAG, "positionDiscontinuity");
101 | }
102 |
103 | @Override
104 | public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
105 | Log.d(TAG, "playbackParameters " + String.format(
106 | "[speed=%.2f, pitch=%.2f]", playbackParameters.speed, playbackParameters.pitch));
107 | }
108 |
109 | @Override
110 | public void onTimelineChanged(Timeline timeline, Object manifest) {
111 | int periodCount = timeline.getPeriodCount();
112 | int windowCount = timeline.getWindowCount();
113 | Log.d(TAG, "sourceInfo [periodCount=" + periodCount + ", windowCount=" + windowCount);
114 | for (int i = 0; i < Math.min(periodCount, MAX_TIMELINE_ITEM_LINES); i++) {
115 | timeline.getPeriod(i, period);
116 | Log.d(TAG, " " + "period [" + getTimeString(period.getDurationMs()) + "]");
117 | }
118 | if (periodCount > MAX_TIMELINE_ITEM_LINES) {
119 | Log.d(TAG, " ...");
120 | }
121 | for (int i = 0; i < Math.min(windowCount, MAX_TIMELINE_ITEM_LINES); i++) {
122 | timeline.getWindow(i, window);
123 | Log.d(TAG, " " + "window [" + getTimeString(window.getDurationMs()) + ", "
124 | + window.isSeekable + ", " + window.isDynamic + "]");
125 | }
126 | if (windowCount > MAX_TIMELINE_ITEM_LINES) {
127 | Log.d(TAG, " ...");
128 | }
129 | Log.d(TAG, "]");
130 | }
131 |
132 | @Override
133 | public void onPlayerError(ExoPlaybackException e) {
134 | Log.e(TAG, "playerFailed [" + getSessionTimeString() + "]", e);
135 | }
136 |
137 | @Override
138 | public void onTracksChanged(TrackGroupArray ignored, TrackSelectionArray trackSelections) {
139 | MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo();
140 | if (mappedTrackInfo == null) {
141 | Log.d(TAG, "Tracks []");
142 | return;
143 | }
144 | Log.d(TAG, "Tracks [");
145 | // Log tracks associated to renderers.
146 | for (int rendererIndex = 0; rendererIndex < mappedTrackInfo.length; rendererIndex++) {
147 | TrackGroupArray rendererTrackGroups = mappedTrackInfo.getTrackGroups(rendererIndex);
148 | TrackSelection trackSelection = trackSelections.get(rendererIndex);
149 | if (rendererTrackGroups.length > 0) {
150 | Log.d(TAG, " Renderer:" + rendererIndex + " [");
151 | for (int groupIndex = 0; groupIndex < rendererTrackGroups.length; groupIndex++) {
152 | TrackGroup trackGroup = rendererTrackGroups.get(groupIndex);
153 | String adaptiveSupport = getAdaptiveSupportString(trackGroup.length,
154 | mappedTrackInfo.getAdaptiveSupport(rendererIndex, groupIndex, false));
155 | Log.d(TAG, " Group:" + groupIndex + ", adaptive_supported=" + adaptiveSupport + " [");
156 | for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
157 | String status = getTrackStatusString(trackSelection, trackGroup, trackIndex);
158 | String formatSupport = getFormatSupportString(
159 | mappedTrackInfo.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex));
160 | Log.d(TAG, " " + status + " Track:" + trackIndex + ", "
161 | + Format.toLogString(trackGroup.getFormat(trackIndex))
162 | + ", supported=" + formatSupport);
163 | }
164 | Log.d(TAG, " ]");
165 | }
166 | // Log metadata for at most one of the tracks selected for the renderer.
167 | if (trackSelection != null) {
168 | for (int selectionIndex = 0; selectionIndex < trackSelection.length(); selectionIndex++) {
169 | Metadata metadata = trackSelection.getFormat(selectionIndex).metadata;
170 | if (metadata != null) {
171 | Log.d(TAG, " Metadata [");
172 | printMetadata(metadata, " ");
173 | Log.d(TAG, " ]");
174 | break;
175 | }
176 | }
177 | }
178 | Log.d(TAG, " ]");
179 | }
180 | }
181 | // Log tracks not associated with a renderer.
182 | TrackGroupArray unassociatedTrackGroups = mappedTrackInfo.getUnassociatedTrackGroups();
183 | if (unassociatedTrackGroups.length > 0) {
184 | Log.d(TAG, " Renderer:None [");
185 | for (int groupIndex = 0; groupIndex < unassociatedTrackGroups.length; groupIndex++) {
186 | Log.d(TAG, " Group:" + groupIndex + " [");
187 | TrackGroup trackGroup = unassociatedTrackGroups.get(groupIndex);
188 | for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
189 | String status = getTrackStatusString(false);
190 | String formatSupport = getFormatSupportString(
191 | RendererCapabilities.FORMAT_UNSUPPORTED_TYPE);
192 | Log.d(TAG, " " + status + " Track:" + trackIndex + ", "
193 | + Format.toLogString(trackGroup.getFormat(trackIndex))
194 | + ", supported=" + formatSupport);
195 | }
196 | Log.d(TAG, " ]");
197 | }
198 | Log.d(TAG, " ]");
199 | }
200 | Log.d(TAG, "]");
201 | }
202 |
203 | // MetadataRenderer.Output
204 |
205 | @Override
206 | public void onMetadata(Metadata metadata) {
207 | Log.d(TAG, "onMetadata [");
208 | printMetadata(metadata, " ");
209 | Log.d(TAG, "]");
210 | }
211 |
212 | // AudioRendererEventListener
213 |
214 | @Override
215 | public void onAudioEnabled(DecoderCounters counters) {
216 | Log.d(TAG, "audioEnabled [" + getSessionTimeString() + "]");
217 | }
218 |
219 | @Override
220 | public void onAudioSessionId(int audioSessionId) {
221 | Log.d(TAG, "audioSessionId [" + audioSessionId + "]");
222 | }
223 |
224 | @Override
225 | public void onAudioDecoderInitialized(String decoderName, long elapsedRealtimeMs,
226 | long initializationDurationMs) {
227 | Log.d(TAG, "audioDecoderInitialized [" + getSessionTimeString() + ", " + decoderName + "]");
228 | }
229 |
230 | @Override
231 | public void onAudioInputFormatChanged(Format format) {
232 | Log.d(TAG, "audioFormatChanged [" + getSessionTimeString() + ", " + Format.toLogString(format)
233 | + "]");
234 | }
235 |
236 | @Override
237 | public void onAudioDisabled(DecoderCounters counters) {
238 | Log.d(TAG, "audioDisabled [" + getSessionTimeString() + "]");
239 | }
240 |
241 | @Override
242 | public void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
243 | printInternalError("audioTrackUnderrun [" + bufferSize + ", " + bufferSizeMs + ", "
244 | + elapsedSinceLastFeedMs + "]", null);
245 | }
246 |
247 | // VideoRendererEventListener
248 |
249 | @Override
250 | public void onVideoEnabled(DecoderCounters counters) {
251 | Log.d(TAG, "videoEnabled [" + getSessionTimeString() + "]");
252 | }
253 |
254 | @Override
255 | public void onVideoDecoderInitialized(String decoderName, long elapsedRealtimeMs,
256 | long initializationDurationMs) {
257 | Log.d(TAG, "videoDecoderInitialized [" + getSessionTimeString() + ", " + decoderName + "]");
258 | }
259 |
260 | @Override
261 | public void onVideoInputFormatChanged(Format format) {
262 | Log.d(TAG, "videoFormatChanged [" + getSessionTimeString() + ", " + Format.toLogString(format)
263 | + "]");
264 | }
265 |
266 | @Override
267 | public void onVideoDisabled(DecoderCounters counters) {
268 | Log.d(TAG, "videoDisabled [" + getSessionTimeString() + "]");
269 | }
270 |
271 | @Override
272 | public void onDroppedFrames(int count, long elapsed) {
273 | Log.d(TAG, "droppedFrames [" + getSessionTimeString() + ", " + count + "]");
274 | }
275 |
276 | @Override
277 | public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
278 | float pixelWidthHeightRatio) {
279 | // Do nothing.
280 | }
281 |
282 | @Override
283 | public void onRenderedFirstFrame(Surface surface) {
284 | Log.d(TAG, "renderedFirstFrame [" + surface + "]");
285 | }
286 |
287 | // DefaultDrmSessionManager.EventListener
288 |
289 | @Override
290 | public void onDrmSessionManagerError(Exception e) {
291 | printInternalError("drmSessionManagerError", e);
292 | }
293 |
294 | @Override
295 | public void onDrmKeysRestored() {
296 | Log.d(TAG, "drmKeysRestored [" + getSessionTimeString() + "]");
297 | }
298 |
299 | @Override
300 | public void onDrmKeysRemoved() {
301 | Log.d(TAG, "drmKeysRemoved [" + getSessionTimeString() + "]");
302 | }
303 |
304 | @Override
305 | public void onDrmKeysLoaded() {
306 | Log.d(TAG, "drmKeysLoaded [" + getSessionTimeString() + "]");
307 | }
308 |
309 | // ExtractorMediaSource.EventListener
310 |
311 | @Override
312 | public void onLoadError(IOException error) {
313 | printInternalError("loadError", error);
314 | }
315 |
316 | // AdaptiveMediaSourceEventListener
317 |
318 | @Override
319 | public void onLoadStarted(DataSpec dataSpec, int dataType, int trackType, Format trackFormat,
320 | int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs,
321 | long mediaEndTimeMs, long elapsedRealtimeMs) {
322 | // Do nothing.
323 | }
324 |
325 | @Override
326 | public void onLoadError(DataSpec dataSpec, int dataType, int trackType, Format trackFormat,
327 | int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs,
328 | long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded,
329 | IOException error, boolean wasCanceled) {
330 | printInternalError("loadError", error);
331 | }
332 |
333 | @Override
334 | public void onLoadCanceled(DataSpec dataSpec, int dataType, int trackType, Format trackFormat,
335 | int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs,
336 | long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded) {
337 | // Do nothing.
338 | }
339 |
340 | @Override
341 | public void onLoadCompleted(DataSpec dataSpec, int dataType, int trackType, Format trackFormat,
342 | int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs,
343 | long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded) {
344 | // Do nothing.
345 | }
346 |
347 | @Override
348 | public void onUpstreamDiscarded(int trackType, long mediaStartTimeMs, long mediaEndTimeMs) {
349 | // Do nothing.
350 | }
351 |
352 | @Override
353 | public void onDownstreamFormatChanged(int trackType, Format trackFormat, int trackSelectionReason,
354 | Object trackSelectionData, long mediaTimeMs) {
355 | // Do nothing.
356 | }
357 |
358 | // Internal methods
359 |
360 | private void printInternalError(String type, Exception e) {
361 | Log.e(TAG, "internalError [" + getSessionTimeString() + ", " + type + "]", e);
362 | }
363 |
364 | private void printMetadata(Metadata metadata, String prefix) {
365 | for (int i = 0; i < metadata.length(); i++) {
366 | Metadata.Entry entry = metadata.get(i);
367 | if (entry instanceof TextInformationFrame) {
368 | TextInformationFrame textInformationFrame = (TextInformationFrame) entry;
369 | Log.d(TAG, prefix + String.format("%s: value=%s", textInformationFrame.id,
370 | textInformationFrame.value));
371 | } else if (entry instanceof UrlLinkFrame) {
372 | UrlLinkFrame urlLinkFrame = (UrlLinkFrame) entry;
373 | Log.d(TAG, prefix + String.format("%s: url=%s", urlLinkFrame.id, urlLinkFrame.url));
374 | } else if (entry instanceof PrivFrame) {
375 | PrivFrame privFrame = (PrivFrame) entry;
376 | Log.d(TAG, prefix + String.format("%s: owner=%s", privFrame.id, privFrame.owner));
377 | } else if (entry instanceof GeobFrame) {
378 | GeobFrame geobFrame = (GeobFrame) entry;
379 | Log.d(TAG, prefix + String.format("%s: mimeType=%s, filename=%s, description=%s",
380 | geobFrame.id, geobFrame.mimeType, geobFrame.filename, geobFrame.description));
381 | } else if (entry instanceof ApicFrame) {
382 | ApicFrame apicFrame = (ApicFrame) entry;
383 | Log.d(TAG, prefix + String.format("%s: mimeType=%s, description=%s",
384 | apicFrame.id, apicFrame.mimeType, apicFrame.description));
385 | } else if (entry instanceof CommentFrame) {
386 | CommentFrame commentFrame = (CommentFrame) entry;
387 | Log.d(TAG, prefix + String.format("%s: language=%s, description=%s", commentFrame.id,
388 | commentFrame.language, commentFrame.description));
389 | } else if (entry instanceof Id3Frame) {
390 | Id3Frame id3Frame = (Id3Frame) entry;
391 | Log.d(TAG, prefix + String.format("%s", id3Frame.id));
392 | } else if (entry instanceof EventMessage) {
393 | EventMessage eventMessage = (EventMessage) entry;
394 | Log.d(TAG, prefix + String.format("EMSG: scheme=%s, id=%d, value=%s",
395 | eventMessage.schemeIdUri, eventMessage.id, eventMessage.value));
396 | }
397 | }
398 | }
399 |
400 | private String getSessionTimeString() {
401 | return getTimeString(SystemClock.elapsedRealtime() - startTimeMs);
402 | }
403 |
404 | private static String getTimeString(long timeMs) {
405 | return timeMs == C.TIME_UNSET ? "?" : TIME_FORMAT.format((timeMs) / 1000f);
406 | }
407 |
408 | private static String getStateString(int state) {
409 | switch (state) {
410 | case ExoPlayer.STATE_BUFFERING:
411 | return "B";
412 | case ExoPlayer.STATE_ENDED:
413 | return "E";
414 | case ExoPlayer.STATE_IDLE:
415 | return "I";
416 | case ExoPlayer.STATE_READY:
417 | return "R";
418 | default:
419 | return "?";
420 | }
421 | }
422 |
423 | private static String getFormatSupportString(int formatSupport) {
424 | switch (formatSupport) {
425 | case RendererCapabilities.FORMAT_HANDLED:
426 | return "YES";
427 | case RendererCapabilities.FORMAT_EXCEEDS_CAPABILITIES:
428 | return "NO_EXCEEDS_CAPABILITIES";
429 | case RendererCapabilities.FORMAT_UNSUPPORTED_SUBTYPE:
430 | return "NO_UNSUPPORTED_TYPE";
431 | case RendererCapabilities.FORMAT_UNSUPPORTED_TYPE:
432 | return "NO";
433 | default:
434 | return "?";
435 | }
436 | }
437 |
438 | private static String getAdaptiveSupportString(int trackCount, int adaptiveSupport) {
439 | if (trackCount < 2) {
440 | return "N/A";
441 | }
442 | switch (adaptiveSupport) {
443 | case RendererCapabilities.ADAPTIVE_SEAMLESS:
444 | return "YES";
445 | case RendererCapabilities.ADAPTIVE_NOT_SEAMLESS:
446 | return "YES_NOT_SEAMLESS";
447 | case RendererCapabilities.ADAPTIVE_NOT_SUPPORTED:
448 | return "NO";
449 | default:
450 | return "?";
451 | }
452 | }
453 |
454 | private static String getTrackStatusString(TrackSelection selection, TrackGroup group,
455 | int trackIndex) {
456 | return getTrackStatusString(selection != null && selection.getTrackGroup() == group
457 | && selection.indexOf(trackIndex) != C.INDEX_UNSET);
458 | }
459 |
460 | private static String getTrackStatusString(boolean enabled) {
461 | return enabled ? "[X]" : "[ ]";
462 | }
463 |
464 | }
465 |
--------------------------------------------------------------------------------
/demo/src/main/assets/media.exolist.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "YouTube DASH",
4 | "samples": [
5 | {
6 | "name": "Google Glass (MP4,H264)",
7 | "uri": "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",
8 | "extension": "mpd"
9 | },
10 | {
11 | "name": "Google Play (MP4,H264)",
12 | "uri": "http://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",
13 | "extension": "mpd"
14 | },
15 | {
16 | "name": "Google Glass (WebM,VP9)",
17 | "uri": "http://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",
18 | "extension": "mpd"
19 | },
20 | {
21 | "name": "Google Play (WebM,VP9)",
22 | "uri": "http://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",
23 | "extension": "mpd"
24 | }
25 | ]
26 | },
27 | {
28 | "name": "Widevine DASH Policy Tests (GTS)",
29 | "samples": [
30 | {
31 | "name": "WV: HDCP not specified",
32 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
33 | "drm_scheme": "widevine",
34 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=d286538032258a1c&provider=widevine_test"
35 | },
36 | {
37 | "name": "WV: HDCP not required",
38 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
39 | "drm_scheme": "widevine",
40 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=48fcc369939ac96c&provider=widevine_test"
41 | },
42 | {
43 | "name": "WV: HDCP required",
44 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
45 | "drm_scheme": "widevine",
46 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=e06c39f1151da3df&provider=widevine_test"
47 | },
48 | {
49 | "name": "WV: Secure video path required (MP4,H264)",
50 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
51 | "drm_scheme": "widevine",
52 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=0894c7c8719b28a0&provider=widevine_test"
53 | },
54 | {
55 | "name": "WV: Secure video path required (WebM,VP9)",
56 | "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears.mpd",
57 | "drm_scheme": "widevine",
58 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=0894c7c8719b28a0&provider=widevine_test"
59 | },
60 | {
61 | "name": "WV: Secure video path required (MP4,H265)",
62 | "uri": "https://storage.googleapis.com/wvmedia/cenc/hevc/tears/tears.mpd",
63 | "drm_scheme": "widevine",
64 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=0894c7c8719b28a0&provider=widevine_test"
65 | },
66 | {
67 | "name": "WV: HDCP + secure video path required",
68 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
69 | "drm_scheme": "widevine",
70 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=efd045b1eb61888a&provider=widevine_test"
71 | },
72 | {
73 | "name": "WV: 30s license duration (fails at ~30s)",
74 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
75 | "drm_scheme": "widevine",
76 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=f9a34cab7b05881a&provider=widevine_test"
77 | }
78 | ]
79 | },
80 | {
81 | "name": "Widevine HDCP Capabilities Tests",
82 | "samples": [
83 | {
84 | "name": "WV: HDCP: None (not required)",
85 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
86 | "drm_scheme": "widevine",
87 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=HDCP_None&provider=widevine_test"
88 | },
89 | {
90 | "name": "WV: HDCP: 1.0 required",
91 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
92 | "drm_scheme": "widevine",
93 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=HDCP_V1&provider=widevine_test"
94 | },
95 | {
96 | "name": "WV: HDCP: 2.0 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=HDCP_V2&provider=widevine_test"
100 | },
101 | {
102 | "name": "WV: HDCP: 2.1 required",
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=HDCP_V2_1&provider=widevine_test"
106 | },
107 | {
108 | "name": "WV: HDCP: 2.2 required",
109 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
110 | "drm_scheme": "widevine",
111 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=HDCP_V2_2&provider=widevine_test"
112 | },
113 | {
114 | "name": "WV: HDCP: No digital output",
115 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
116 | "drm_scheme": "widevine",
117 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=HDCP_NO_DIGTAL_OUTPUT&provider=widevine_test"
118 | }
119 | ]
120 | },
121 | {
122 | "name": "Widevine DASH: MP4,H264",
123 | "samples": [
124 | {
125 | "name": "WV: Clear SD & HD (MP4,H264)",
126 | "uri": "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd"
127 | },
128 | {
129 | "name": "WV: Clear SD (MP4,H264)",
130 | "uri": "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears_sd.mpd"
131 | },
132 | {
133 | "name": "WV: Clear HD (MP4,H264)",
134 | "uri": "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears_hd.mpd"
135 | },
136 | {
137 | "name": "WV: Clear UHD (MP4,H264)",
138 | "uri": "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears_uhd.mpd"
139 | },
140 | {
141 | "name": "WV: Secure SD & HD (MP4,H264)",
142 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
143 | "drm_scheme": "widevine",
144 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
145 | },
146 | {
147 | "name": "WV: Secure SD (MP4,H264)",
148 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_sd.mpd",
149 | "drm_scheme": "widevine",
150 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
151 | },
152 | {
153 | "name": "WV: Secure HD (MP4,H264)",
154 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_hd.mpd",
155 | "drm_scheme": "widevine",
156 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
157 | },
158 | {
159 | "name": "WV: Secure UHD (MP4,H264)",
160 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_uhd.mpd",
161 | "drm_scheme": "widevine",
162 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
163 | }
164 | ]
165 | },
166 | {
167 | "name": "Widevine DASH: WebM,VP9",
168 | "samples": [
169 | {
170 | "name": "WV: Clear SD & HD (WebM,VP9)",
171 | "uri": "https://storage.googleapis.com/wvmedia/clear/vp9/tears/tears.mpd"
172 | },
173 | {
174 | "name": "WV: Clear SD (WebM,VP9)",
175 | "uri": "https://storage.googleapis.com/wvmedia/clear/vp9/tears/tears_sd.mpd"
176 | },
177 | {
178 | "name": "WV: Clear HD (WebM,VP9)",
179 | "uri": "https://storage.googleapis.com/wvmedia/clear/vp9/tears/tears_hd.mpd"
180 | },
181 | {
182 | "name": "WV: Clear UHD (WebM,VP9)",
183 | "uri": "https://storage.googleapis.com/wvmedia/clear/vp9/tears/tears_uhd.mpd"
184 | },
185 | {
186 | "name": "WV: Secure Fullsample SD & HD (WebM,VP9)",
187 | "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears.mpd",
188 | "drm_scheme": "widevine",
189 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
190 | },
191 | {
192 | "name": "WV: Secure Fullsample SD (WebM,VP9)",
193 | "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears_sd.mpd",
194 | "drm_scheme": "widevine",
195 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
196 | },
197 | {
198 | "name": "WV: Secure Fullsample HD (WebM,VP9)",
199 | "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears_hd.mpd",
200 | "drm_scheme": "widevine",
201 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
202 | },
203 | {
204 | "name": "WV: Secure Fullsample UHD (WebM,VP9)",
205 | "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears_uhd.mpd",
206 | "drm_scheme": "widevine",
207 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
208 | },
209 | {
210 | "name": "WV: Secure Subsample SD & HD (WebM,VP9)",
211 | "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/subsample/24fps/tears/tears.mpd",
212 | "drm_scheme": "widevine",
213 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
214 | },
215 | {
216 | "name": "WV: Secure Subsample SD (WebM,VP9)",
217 | "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/subsample/24fps/tears/tears_sd.mpd",
218 | "drm_scheme": "widevine",
219 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
220 | },
221 | {
222 | "name": "WV: Secure Subsample HD (WebM,VP9)",
223 | "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/subsample/24fps/tears/tears_hd.mpd",
224 | "drm_scheme": "widevine",
225 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
226 | },
227 | {
228 | "name": "WV: Secure Subsample UHD (WebM,VP9)",
229 | "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/subsample/24fps/tears/tears_uhd.mpd",
230 | "drm_scheme": "widevine",
231 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
232 | }
233 | ]
234 | },
235 | {
236 | "name": "Widevine DASH: MP4,H265",
237 | "samples": [
238 | {
239 | "name": "WV: Clear SD & HD (MP4,H265)",
240 | "uri": "https://storage.googleapis.com/wvmedia/clear/hevc/tears/tears.mpd"
241 | },
242 | {
243 | "name": "WV: Clear SD (MP4,H265)",
244 | "uri": "https://storage.googleapis.com/wvmedia/clear/hevc/tears/tears_sd.mpd"
245 | },
246 | {
247 | "name": "WV: Clear HD (MP4,H265)",
248 | "uri": "https://storage.googleapis.com/wvmedia/clear/hevc/tears/tears_hd.mpd"
249 | },
250 | {
251 | "name": "WV: Clear UHD (MP4,H265)",
252 | "uri": "https://storage.googleapis.com/wvmedia/clear/hevc/tears/tears_uhd.mpd"
253 | },
254 | {
255 | "name": "WV: Secure SD & HD (MP4,H265)",
256 | "uri": "https://storage.googleapis.com/wvmedia/cenc/hevc/tears/tears.mpd",
257 | "drm_scheme": "widevine",
258 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
259 | },
260 | {
261 | "name": "WV: Secure SD (MP4,H265)",
262 | "uri": "https://storage.googleapis.com/wvmedia/cenc/hevc/tears/tears_sd.mpd",
263 | "drm_scheme": "widevine",
264 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
265 | },
266 | {
267 | "name": "WV: Secure HD (MP4,H265)",
268 | "uri": "https://storage.googleapis.com/wvmedia/cenc/hevc/tears/tears_hd.mpd",
269 | "drm_scheme": "widevine",
270 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
271 | },
272 | {
273 | "name": "WV: Secure UHD (MP4,H265)",
274 | "uri": "https://storage.googleapis.com/wvmedia/cenc/hevc/tears/tears_uhd.mpd",
275 | "drm_scheme": "widevine",
276 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
277 | }
278 | ]
279 | },
280 | {
281 | "name": "ClearKey DASH",
282 | "samples": [
283 | {
284 | "name": "Big Buck Bunny (CENC ClearKey)",
285 | "uri": "http://html5.cablelabs.com:8100/cenc/ck/dash.mpd",
286 | "extension": "mpd",
287 | "drm_scheme": "cenc",
288 | "drm_license_url": "https://wasabeef.jp/demos/cenc-ck-dash.json"
289 | }
290 | ]
291 | },
292 | {
293 | "name": "SmoothStreaming",
294 | "samples": [
295 | {
296 | "name": "Super speed",
297 | "uri": "http://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism"
298 | },
299 | {
300 | "name": "Super speed (PlayReady)",
301 | "uri": "http://playready.directtaps.net/smoothstreaming/SSWSS720H264PR/SuperSpeedway_720.ism",
302 | "drm_scheme": "playready"
303 | }
304 | ]
305 | },
306 | {
307 | "name": "HLS",
308 | "samples": [
309 | {
310 | "name": "Apple 4x3 basic stream",
311 | "uri": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8"
312 | },
313 | {
314 | "name": "Apple 16x9 basic stream",
315 | "uri": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
316 | },
317 | {
318 | "name": "Apple master playlist advanced (TS)",
319 | "uri": "https://tungsten.aaplimg.com/VOD/bipbop_adv_example_v2/master.m3u8"
320 | },
321 | {
322 | "name": "Apple master playlist advanced (fMP4)",
323 | "uri": "https://tungsten.aaplimg.com/VOD/bipbop_adv_fmp4_example/master.m3u8"
324 | },
325 | {
326 | "name": "Apple TS media playlist",
327 | "uri": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear1/prog_index.m3u8"
328 | },
329 | {
330 | "name": "Apple AAC media playlist",
331 | "uri": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear0/prog_index.m3u8"
332 | },
333 | {
334 | "name": "Apple ID3 metadata",
335 | "uri": "http://devimages.apple.com/samplecode/adDemo/ad.m3u8"
336 | }
337 | ]
338 | },
339 | {
340 | "name": "Misc",
341 | "samples": [
342 | {
343 | "name": "Dizzy",
344 | "uri": "http://html5demos.com/assets/dizzy.mp4"
345 | },
346 | {
347 | "name": "Apple AAC 10s",
348 | "uri": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear0/fileSequence0.aac"
349 | },
350 | {
351 | "name": "Apple TS 10s",
352 | "uri": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear1/fileSequence0.ts"
353 | },
354 | {
355 | "name": "Android screens (Matroska)",
356 | "uri": "http://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv"
357 | },
358 | {
359 | "name": "Big Buck Bunny (MP4 Video)",
360 | "uri": "http://redirector.c.youtube.com/videoplayback?id=604ed5ce52eda7ee&itag=22&source=youtube&sparams=ip,ipbits,expire,source,id&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=513F28C7FDCBEC60A66C86C9A393556C99DC47FB.04C88036EEE12565A1ED864A875A58F15D8B5300&key=ik0"
361 | },
362 | {
363 | "name": "Screens 360P (WebM,VP9,No Audio)",
364 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-vp9-360.webm"
365 | },
366 | {
367 | "name": "Screens 480p (FMP4,H264,No Audio)",
368 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-avc-baseline-480.mp4"
369 | },
370 | {
371 | "name": "Screens 1080p (FMP4,H264, No Audio)",
372 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-137.mp4"
373 | },
374 | {
375 | "name": "Screens (FMP4,AAC Audio)",
376 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4"
377 | },
378 | {
379 | "name": "Google Play (MP3 Audio)",
380 | "uri": "http://storage.googleapis.com/exoplayer-test-media-0/play.mp3"
381 | },
382 | {
383 | "name": "Google Play (Ogg/Vorbis Audio)",
384 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/ogg/play.ogg"
385 | },
386 | {
387 | "name": "Google Glass (WebM Video with Vorbis Audio)",
388 | "uri": "http://demos.webmproject.org/exoplayer/glass_vp9_vorbis.webm"
389 | },
390 | {
391 | "name": "Google Glass (VP9 in MP4/ISO-BMFF)",
392 | "uri": "http://demos.webmproject.org/exoplayer/glass.mp4"
393 | },
394 | {
395 | "name": "Google Glass DASH - VP9 and Opus",
396 | "uri": "http://demos.webmproject.org/dash/201410/vp9_glass/manifest_vp9_opus.mpd"
397 | },
398 | {
399 | "name": "Big Buck Bunny (FLV Video)",
400 | "uri": "http://vod.leasewebcdn.com/bbb.flv?ri=1024&rs=150&start=0"
401 | }
402 | ]
403 | },
404 | {
405 | "name": "Playlists",
406 | "samples": [
407 | {
408 | "name": "Cats -> Dogs",
409 | "playlist": [
410 | {
411 | "uri": "http://html5demos.com/assets/dizzy.mp4"
412 | },
413 | {
414 | "uri": "http://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv"
415 | }
416 | ]
417 | },
418 | {
419 | "name": "Audio -> Video -> Audio",
420 | "playlist": [
421 | {
422 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4"
423 | },
424 | {
425 | "uri": "http://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv"
426 | },
427 | {
428 | "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4"
429 | }
430 | ]
431 | },
432 | {
433 | "name": "Clear -> Enc -> Clear -> Enc -> Enc",
434 | "drm_scheme": "widevine",
435 | "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test",
436 | "playlist": [
437 | {
438 | "uri": "http://html5demos.com/assets/dizzy.mp4"
439 | },
440 | {
441 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_sd.mpd"
442 | },
443 | {
444 | "uri": "http://html5demos.com/assets/dizzy.mp4"
445 | },
446 | {
447 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_sd.mpd"
448 | },
449 | {
450 | "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_sd.mpd"
451 | }
452 | ]
453 | }
454 | ]
455 | }
456 | ]
457 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/google/android/exoplayer2/demo/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.google.android.exoplayer2.demo;
17 |
18 | import android.app.Activity;
19 | import android.content.Intent;
20 | import android.content.pm.PackageManager;
21 | import android.graphics.Color;
22 | import android.net.Uri;
23 | import android.os.Bundle;
24 | import android.os.Handler;
25 | import android.os.Message;
26 | import android.support.annotation.NonNull;
27 | import android.text.TextUtils;
28 | import android.view.KeyEvent;
29 | import android.view.Surface;
30 | import android.view.View;
31 | import android.view.View.OnClickListener;
32 | import android.widget.Button;
33 | import android.widget.LinearLayout;
34 | import android.widget.ScrollView;
35 | import android.widget.TextView;
36 | import android.widget.Toast;
37 |
38 | import com.github.mikephil.charting.charts.LineChart;
39 | import com.github.mikephil.charting.components.XAxis;
40 | import com.github.mikephil.charting.components.YAxis;
41 | import com.github.mikephil.charting.data.Entry;
42 | import com.github.mikephil.charting.data.LineData;
43 | import com.github.mikephil.charting.data.LineDataSet;
44 | import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
45 | import com.github.mikephil.charting.utils.ColorTemplate;
46 | import com.google.android.exoplayer2.C;
47 | import com.google.android.exoplayer2.DefaultRenderersFactory;
48 | import com.google.android.exoplayer2.ExoPlaybackException;
49 | import com.google.android.exoplayer2.ExoPlayer;
50 | import com.google.android.exoplayer2.ExoPlayerFactory;
51 | import com.google.android.exoplayer2.Format;
52 | import com.google.android.exoplayer2.PlaybackParameters;
53 | import com.google.android.exoplayer2.SimpleExoPlayer;
54 | import com.google.android.exoplayer2.Timeline;
55 | import com.google.android.exoplayer2.decoder.DecoderCounters;
56 | import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
57 | import com.google.android.exoplayer2.drm.DrmSessionManager;
58 | import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
59 | import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
60 | import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
61 | import com.google.android.exoplayer2.drm.UnsupportedDrmException;
62 | import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
63 | import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.DecoderInitializationException;
64 | import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
65 | import com.google.android.exoplayer2.source.BehindLiveWindowException;
66 | import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
67 | import com.google.android.exoplayer2.source.ExtractorMediaSource;
68 | import com.google.android.exoplayer2.source.MediaSource;
69 | import com.google.android.exoplayer2.source.TrackGroupArray;
70 | import com.google.android.exoplayer2.source.dash.DashMediaSource;
71 | import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
72 | import com.google.android.exoplayer2.source.hls.HlsMediaSource;
73 | import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource;
74 | import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
75 | import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
76 | import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
77 | import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
78 | import com.google.android.exoplayer2.trackselection.TrackSelection;
79 | import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
80 | import com.google.android.exoplayer2.ui.DebugTextViewHelper;
81 | import com.google.android.exoplayer2.ui.PlaybackControlView;
82 | import com.google.android.exoplayer2.ui.SimpleExoPlayerView;
83 | import com.google.android.exoplayer2.upstream.BandwidthMeter;
84 | import com.google.android.exoplayer2.upstream.DataSource;
85 | import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
86 | import com.google.android.exoplayer2.upstream.HttpDataSource;
87 | import com.google.android.exoplayer2.util.Util;
88 | import com.google.android.exoplayer2.video.VideoRendererEventListener;
89 |
90 | import java.lang.ref.WeakReference;
91 | import java.net.CookieHandler;
92 | import java.net.CookieManager;
93 | import java.net.CookiePolicy;
94 | import java.util.UUID;
95 |
96 | /**
97 | * An activity that plays media using {@link SimpleExoPlayer}.
98 | */
99 | public class PlayerActivity extends Activity implements OnClickListener, ExoPlayer.EventListener,
100 | PlaybackControlView.VisibilityListener {
101 |
102 | public static final String DRM_SCHEME_UUID_EXTRA = "drm_scheme_uuid";
103 | public static final String DRM_LICENSE_URL = "drm_license_url";
104 | public static final String DRM_KEY_REQUEST_PROPERTIES = "drm_key_request_properties";
105 | public static final String PREFER_EXTENSION_DECODERS = "prefer_extension_decoders";
106 |
107 | public static final String ACTION_VIEW = "com.google.android.exoplayer.demo.action.VIEW";
108 | public static final String EXTENSION_EXTRA = "extension";
109 |
110 | public static final String ACTION_VIEW_LIST =
111 | "com.google.android.exoplayer.demo.action.VIEW_LIST";
112 | public static final String URI_LIST_EXTRA = "uri_list";
113 | public static final String EXTENSION_LIST_EXTRA = "extension_list";
114 |
115 | // private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter();
116 | private static final CookieManager DEFAULT_COOKIE_MANAGER;
117 |
118 | static {
119 | DEFAULT_COOKIE_MANAGER = new CookieManager();
120 | DEFAULT_COOKIE_MANAGER.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
121 | }
122 |
123 | private Handler mainHandler;
124 | private EventLogger eventLogger;
125 | private SimpleExoPlayerView simpleExoPlayerView;
126 | private LinearLayout debugRootView;
127 | private TextView debugTextView;
128 | private Button retryButton;
129 |
130 | private DataSource.Factory mediaDataSourceFactory;
131 | private SimpleExoPlayer player;
132 | private DefaultTrackSelector trackSelector;
133 | private TrackSelectionHelper trackSelectionHelper;
134 | private DebugTextViewHelper debugViewHelper;
135 | private boolean needRetrySource;
136 | private TrackGroupArray lastSeenTrackGroupArray;
137 |
138 | private boolean shouldAutoPlay;
139 | private int resumeWindow;
140 | private long resumePosition;
141 |
142 | private DefaultBandwidthMeter bandwidthMeter;
143 | private UiUpdateHandler mUiUpdateHandler;
144 | private long bufferedDurationMs;
145 | private long bitrateEstimate;
146 | private long bytesDownloaded;
147 | private int droppedFrames;
148 |
149 | private ScrollView player_stats_layout;
150 | private LineChart player_stats_speed_chart;
151 | private LineChart player_stats_health_chart;
152 | private LineChart player_stats_nw_chart;
153 | private TextView player_stats_size;
154 | private TextView player_stats_res;
155 | private TextView player_stats_dropframes;
156 | private Format currentVideoFormat;
157 |
158 | public static final int MSG_UPDATE_STATS = 10005;
159 | public static final int MSG_UPDATE_STATS_NW_ONLY = 10006;
160 |
161 | // Activity lifecycle
162 |
163 | @Override
164 | public void onCreate(Bundle savedInstanceState) {
165 | super.onCreate(savedInstanceState);
166 | mUiUpdateHandler = new UiUpdateHandler(this);
167 | bandwidthMeter = new DefaultBandwidthMeter(mUiUpdateHandler, new BandwidthMeter.EventListener() {
168 | @Override
169 | public void onBandwidthSample(int elapsedMs, long bytes, long bitrate) {
170 | bitrateEstimate = bitrate;
171 | bytesDownloaded = bytes;
172 | }
173 | });
174 |
175 | shouldAutoPlay = true;
176 | clearResumePosition();
177 | mediaDataSourceFactory = buildDataSourceFactory(true);
178 | mainHandler = new Handler();
179 | if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) {
180 | CookieHandler.setDefault(DEFAULT_COOKIE_MANAGER);
181 | }
182 |
183 | setContentView(R.layout.player_activity);
184 | View rootView = findViewById(R.id.root);
185 | rootView.setOnClickListener(this);
186 | debugRootView = (LinearLayout) findViewById(R.id.controls_root);
187 | debugTextView = (TextView) findViewById(R.id.debug_text_view);
188 | retryButton = (Button) findViewById(R.id.retry_button);
189 | retryButton.setOnClickListener(this);
190 |
191 | simpleExoPlayerView = (SimpleExoPlayerView) findViewById(R.id.player_view);
192 | simpleExoPlayerView.setControllerVisibilityListener(this);
193 | simpleExoPlayerView.requestFocus();
194 |
195 | player_stats_layout = (ScrollView) findViewById(R.id.player_stats_layout);
196 | player_stats_speed_chart = (LineChart) findViewById(R.id.player_stats_speed_chart);
197 | player_stats_health_chart = (LineChart) findViewById(R.id.player_stats_health_chart);
198 | player_stats_nw_chart = (LineChart) findViewById(R.id.player_stats_nw_chart);
199 |
200 | player_stats_res = (TextView) findViewById(R.id.player_stats_res);
201 | player_stats_size = (TextView) findViewById(R.id.player_stats_size);
202 | player_stats_dropframes = (TextView) findViewById(R.id.player_stats_dropframes);
203 |
204 | initStatChart(player_stats_speed_chart);
205 | initStatChart(player_stats_health_chart);
206 | initStatChart(player_stats_nw_chart);
207 | }
208 |
209 | @Override
210 | public void onNewIntent(Intent intent) {
211 | releasePlayer();
212 | shouldAutoPlay = true;
213 | clearResumePosition();
214 | setIntent(intent);
215 | }
216 |
217 | @Override
218 | public void onStart() {
219 | super.onStart();
220 | if (Util.SDK_INT > 23) {
221 | initializePlayer();
222 | }
223 | }
224 |
225 | @Override
226 | public void onResume() {
227 | super.onResume();
228 | if ((Util.SDK_INT <= 23 || player == null)) {
229 | initializePlayer();
230 | }
231 | }
232 |
233 | @Override
234 | public void onPause() {
235 | super.onPause();
236 | if (Util.SDK_INT <= 23) {
237 | releasePlayer();
238 | }
239 | }
240 |
241 | @Override
242 | public void onStop() {
243 | super.onStop();
244 | if (Util.SDK_INT > 23) {
245 | releasePlayer();
246 | }
247 | }
248 |
249 | @Override
250 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
251 | @NonNull int[] grantResults) {
252 | if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
253 | initializePlayer();
254 | } else {
255 | showToast(R.string.storage_permission_denied);
256 | finish();
257 | }
258 | }
259 |
260 | // Activity input
261 |
262 | @Override
263 | public boolean dispatchKeyEvent(KeyEvent event) {
264 | // Show the controls on any key event.
265 | simpleExoPlayerView.showController();
266 | // If the event was not handled then see if the player view can handle it as a media key event.
267 | return super.dispatchKeyEvent(event) || simpleExoPlayerView.dispatchMediaKeyEvent(event);
268 | }
269 |
270 | // OnClickListener methods
271 |
272 | @Override
273 | public void onClick(View view) {
274 | if (view == retryButton) {
275 | initializePlayer();
276 | } else if (view.getParent() == debugRootView) {
277 | if ("stats".equals(view.getTag())) {
278 | if (player_stats_layout.getVisibility() == View.VISIBLE) {
279 | ((TextView) view).setText("Stats: OFF");
280 | player_stats_layout.setVisibility(View.GONE);
281 | } else {
282 | player_stats_layout.setVisibility(View.VISIBLE);
283 | ((TextView) view).setText("Stats: ON");
284 | }
285 | } else {
286 | MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo();
287 | if (mappedTrackInfo != null) {
288 | trackSelectionHelper.showSelectionDialog(this, ((Button) view).getText(),
289 | trackSelector.getCurrentMappedTrackInfo(), (int) view.getTag());
290 | }
291 | }
292 | }
293 | }
294 |
295 | // PlaybackControlView.VisibilityListener implementation
296 |
297 | @Override
298 | public void onVisibilityChange(int visibility) {
299 | debugRootView.setVisibility(visibility);
300 | }
301 |
302 | // Internal methods
303 |
304 | private void initializePlayer() {
305 | Intent intent = getIntent();
306 | boolean needNewPlayer = player == null;
307 | if (needNewPlayer) {
308 | boolean preferExtensionDecoders = intent.getBooleanExtra(PREFER_EXTENSION_DECODERS, false);
309 | UUID drmSchemeUuid = intent.hasExtra(DRM_SCHEME_UUID_EXTRA)
310 | ? UUID.fromString(intent.getStringExtra(DRM_SCHEME_UUID_EXTRA)) : null;
311 | DrmSessionManager drmSessionManager = null;
312 | if (drmSchemeUuid != null) {
313 | String drmLicenseUrl = intent.getStringExtra(DRM_LICENSE_URL);
314 | String[] keyRequestPropertiesArray = intent.getStringArrayExtra(DRM_KEY_REQUEST_PROPERTIES);
315 | try {
316 | drmSessionManager = buildDrmSessionManager(drmSchemeUuid, drmLicenseUrl,
317 | keyRequestPropertiesArray);
318 | } catch (UnsupportedDrmException e) {
319 | int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported
320 | : (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
321 | ? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown);
322 | showToast(errorStringId);
323 | return;
324 | }
325 | }
326 |
327 | @DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode =
328 | ((DemoApplication) getApplication()).useExtensionRenderers()
329 | ? (preferExtensionDecoders ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER
330 | : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON)
331 | : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF;
332 | DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(this,
333 | drmSessionManager, extensionRendererMode);
334 |
335 | TrackSelection.Factory videoTrackSelectionFactory =
336 | new AdaptiveTrackSelection.Factory(bandwidthMeter);
337 | trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
338 | trackSelectionHelper = new TrackSelectionHelper(trackSelector, videoTrackSelectionFactory);
339 | lastSeenTrackGroupArray = null;
340 |
341 | player = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector, new CustomLoadControl(new CustomLoadControl.EventListener() {
342 | @Override
343 | public void onBufferedDurationSample(long bufferedDurationUs) {
344 | bufferedDurationMs = bufferedDurationUs;
345 | }
346 | }, mUiUpdateHandler));
347 | player.addListener(this);
348 |
349 | eventLogger = new EventLogger(trackSelector);
350 | player.addListener(eventLogger);
351 | player.setAudioDebugListener(eventLogger);
352 | player.setVideoDebugListener(new VideoRendererEventListener() {
353 | @Override
354 | public void onVideoEnabled(DecoderCounters counters) {
355 |
356 | }
357 |
358 | @Override
359 | public void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs, long initializationDurationMs) {
360 |
361 | }
362 |
363 | @Override
364 | public void onVideoInputFormatChanged(Format format) {
365 | currentVideoFormat = format;
366 | }
367 |
368 | @Override
369 | public void onDroppedFrames(int count, long elapsedMs) {
370 | droppedFrames += count;
371 | }
372 |
373 | @Override
374 | public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
375 |
376 | }
377 |
378 | @Override
379 | public void onRenderedFirstFrame(Surface surface) {
380 | startPlayerStats();
381 | }
382 |
383 | @Override
384 | public void onVideoDisabled(DecoderCounters counters) {
385 |
386 | }
387 | });
388 | player.setMetadataOutput(eventLogger);
389 |
390 | simpleExoPlayerView.setPlayer(player);
391 | player.setPlayWhenReady(shouldAutoPlay);
392 | debugViewHelper = new DebugTextViewHelper(player, debugTextView);
393 | debugViewHelper.start();
394 | }
395 | if (needNewPlayer || needRetrySource) {
396 | String action = intent.getAction();
397 | Uri[] uris;
398 | String[] extensions;
399 | if (ACTION_VIEW.equals(action)) {
400 | uris = new Uri[]{intent.getData()};
401 | extensions = new String[]{intent.getStringExtra(EXTENSION_EXTRA)};
402 | } else if (ACTION_VIEW_LIST.equals(action)) {
403 | String[] uriStrings = intent.getStringArrayExtra(URI_LIST_EXTRA);
404 | uris = new Uri[uriStrings.length];
405 | for (int i = 0; i < uriStrings.length; i++) {
406 | uris[i] = Uri.parse(uriStrings[i]);
407 | }
408 | extensions = intent.getStringArrayExtra(EXTENSION_LIST_EXTRA);
409 | if (extensions == null) {
410 | extensions = new String[uriStrings.length];
411 | }
412 | } else {
413 | showToast(getString(R.string.unexpected_intent_action, action));
414 | return;
415 | }
416 | if (Util.maybeRequestReadExternalStoragePermission(this, uris)) {
417 | // The player will be reinitialized if the permission is granted.
418 | return;
419 | }
420 | MediaSource[] mediaSources = new MediaSource[uris.length];
421 | for (int i = 0; i < uris.length; i++) {
422 | mediaSources[i] = buildMediaSource(uris[i], extensions[i]);
423 | }
424 | MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0]
425 | : new ConcatenatingMediaSource(mediaSources);
426 | boolean haveResumePosition = resumeWindow != C.INDEX_UNSET;
427 | if (haveResumePosition) {
428 | player.seekTo(resumeWindow, resumePosition);
429 | }
430 | player.prepare(mediaSource, !haveResumePosition, false);
431 | needRetrySource = false;
432 | updateButtonVisibilities();
433 | }
434 | }
435 |
436 | private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
437 | int type = TextUtils.isEmpty(overrideExtension) ? Util.inferContentType(uri)
438 | : Util.inferContentType("." + overrideExtension);
439 | switch (type) {
440 | case C.TYPE_SS:
441 | return new SsMediaSource(uri, buildDataSourceFactory(false),
442 | new DefaultSsChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger);
443 | case C.TYPE_DASH:
444 | return new DashMediaSource(uri, buildDataSourceFactory(false),
445 | new DefaultDashChunkSource.Factory(mediaDataSourceFactory), mainHandler, eventLogger);
446 | case C.TYPE_HLS:
447 | return new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, eventLogger);
448 | case C.TYPE_OTHER:
449 | return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(),
450 | mainHandler, eventLogger);
451 | default: {
452 | throw new IllegalStateException("Unsupported type: " + type);
453 | }
454 | }
455 | }
456 |
457 | private DrmSessionManager buildDrmSessionManager(UUID uuid,
458 | String licenseUrl, String[] keyRequestPropertiesArray) throws UnsupportedDrmException {
459 | if (Util.SDK_INT < 18) {
460 | return null;
461 | }
462 | HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl,
463 | buildHttpDataSourceFactory(false));
464 | if (keyRequestPropertiesArray != null) {
465 | for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) {
466 | drmCallback.setKeyRequestProperty(keyRequestPropertiesArray[i],
467 | keyRequestPropertiesArray[i + 1]);
468 | }
469 | }
470 | return new DefaultDrmSessionManager<>(uuid,
471 | FrameworkMediaDrm.newInstance(uuid), drmCallback, null, mainHandler, eventLogger);
472 | }
473 |
474 | private void releasePlayer() {
475 | if (player != null) {
476 | debugViewHelper.stop();
477 | debugViewHelper = null;
478 | shouldAutoPlay = player.getPlayWhenReady();
479 | updateResumePosition();
480 | player.release();
481 | player = null;
482 | trackSelector = null;
483 | trackSelectionHelper = null;
484 | eventLogger = null;
485 | }
486 | if (null != mUiUpdateHandler)
487 | mUiUpdateHandler.removeCallbacksAndMessages(null);
488 | }
489 |
490 | private void updateResumePosition() {
491 | resumeWindow = player.getCurrentWindowIndex();
492 | resumePosition = player.isCurrentWindowSeekable() ? Math.max(0, player.getCurrentPosition())
493 | : C.TIME_UNSET;
494 | }
495 |
496 | private void clearResumePosition() {
497 | resumeWindow = C.INDEX_UNSET;
498 | resumePosition = C.TIME_UNSET;
499 | }
500 |
501 | /**
502 | * Returns a new DataSource factory.
503 | *
504 | * @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener to the new
505 | * DataSource factory.
506 | * @return A new DataSource factory.
507 | */
508 | private DataSource.Factory buildDataSourceFactory(boolean useBandwidthMeter) {
509 | return ((DemoApplication) getApplication())
510 | .buildDataSourceFactory(useBandwidthMeter ? bandwidthMeter : null);
511 | }
512 |
513 | /**
514 | * Returns a new HttpDataSource factory.
515 | *
516 | * @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener to the new
517 | * DataSource factory.
518 | * @return A new HttpDataSource factory.
519 | */
520 | private HttpDataSource.Factory buildHttpDataSourceFactory(boolean useBandwidthMeter) {
521 | return ((DemoApplication) getApplication())
522 | .buildHttpDataSourceFactory(useBandwidthMeter ? bandwidthMeter : null);
523 | }
524 |
525 | // ExoPlayer.EventListener implementation
526 |
527 | @Override
528 | public void onLoadingChanged(boolean isLoading) {
529 | // Do nothing.
530 | }
531 |
532 | @Override
533 | public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
534 | if (playbackState == ExoPlayer.STATE_ENDED) {
535 | showControls();
536 | }
537 | updateButtonVisibilities();
538 | }
539 |
540 | @Override
541 | public void onPositionDiscontinuity() {
542 | if (needRetrySource) {
543 | // This will only occur if the user has performed a seek whilst in the error state. Update the
544 | // resume position so that if the user then retries, playback will resume from the position to
545 | // which they seeked.
546 | updateResumePosition();
547 | }
548 | }
549 |
550 | @Override
551 | public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
552 | // Do nothing.
553 | }
554 |
555 | @Override
556 | public void onTimelineChanged(Timeline timeline, Object manifest) {
557 | // Do nothing.
558 | }
559 |
560 | @Override
561 | public void onPlayerError(ExoPlaybackException e) {
562 | String errorString = null;
563 | if (e.type == ExoPlaybackException.TYPE_RENDERER) {
564 | Exception cause = e.getRendererException();
565 | if (cause instanceof DecoderInitializationException) {
566 | // Special case for decoder initialization failures.
567 | DecoderInitializationException decoderInitializationException =
568 | (DecoderInitializationException) cause;
569 | if (decoderInitializationException.decoderName == null) {
570 | if (decoderInitializationException.getCause() instanceof DecoderQueryException) {
571 | errorString = getString(R.string.error_querying_decoders);
572 | } else if (decoderInitializationException.secureDecoderRequired) {
573 | errorString = getString(R.string.error_no_secure_decoder,
574 | decoderInitializationException.mimeType);
575 | } else {
576 | errorString = getString(R.string.error_no_decoder,
577 | decoderInitializationException.mimeType);
578 | }
579 | } else {
580 | errorString = getString(R.string.error_instantiating_decoder,
581 | decoderInitializationException.decoderName);
582 | }
583 | }
584 | }
585 | if (errorString != null) {
586 | showToast(errorString);
587 | }
588 | needRetrySource = true;
589 | if (isBehindLiveWindow(e)) {
590 | clearResumePosition();
591 | initializePlayer();
592 | } else {
593 | updateResumePosition();
594 | updateButtonVisibilities();
595 | showControls();
596 | }
597 | }
598 |
599 | @Override
600 | @SuppressWarnings("ReferenceEquality")
601 | public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
602 | updateButtonVisibilities();
603 | if (trackGroups != lastSeenTrackGroupArray) {
604 | MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo();
605 | if (mappedTrackInfo != null) {
606 | if (mappedTrackInfo.getTrackTypeRendererSupport(C.TRACK_TYPE_VIDEO)
607 | == MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS) {
608 | showToast(R.string.error_unsupported_video);
609 | }
610 | if (mappedTrackInfo.getTrackTypeRendererSupport(C.TRACK_TYPE_AUDIO)
611 | == MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS) {
612 | showToast(R.string.error_unsupported_audio);
613 | }
614 | }
615 | lastSeenTrackGroupArray = trackGroups;
616 | }
617 | }
618 |
619 | // User controls
620 |
621 | private void updateButtonVisibilities() {
622 | debugRootView.removeAllViews();
623 |
624 | retryButton.setVisibility(needRetrySource ? View.VISIBLE : View.GONE);
625 | debugRootView.addView(retryButton);
626 |
627 | if (player == null) {
628 | return;
629 | }
630 |
631 | MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo();
632 | if (mappedTrackInfo == null) {
633 | return;
634 | }
635 | for (int i = 0; i < mappedTrackInfo.length; i++) {
636 | TrackGroupArray trackGroups = mappedTrackInfo.getTrackGroups(i);
637 | if (trackGroups.length != 0) {
638 | Button button = new Button(this);
639 | int label;
640 | switch (player.getRendererType(i)) {
641 | case C.TRACK_TYPE_AUDIO:
642 | label = R.string.audio;
643 | break;
644 | case C.TRACK_TYPE_VIDEO:
645 | label = R.string.video;
646 | break;
647 | case C.TRACK_TYPE_TEXT:
648 | label = R.string.text;
649 | break;
650 | default:
651 | continue;
652 | }
653 | button.setText(label);
654 | button.setTag(i);
655 | button.setOnClickListener(this);
656 | debugRootView.addView(button, debugRootView.getChildCount() - 1);
657 | }
658 | }
659 | Button statsButton = new Button(this);
660 | statsButton.setText("Stats-OFF");
661 | statsButton.setTag("stats");
662 | statsButton.setOnClickListener(this);
663 | debugRootView.addView(statsButton, debugRootView.getChildCount() - 1);
664 | }
665 |
666 | private void showControls() {
667 | debugRootView.setVisibility(View.VISIBLE);
668 | }
669 |
670 | private void showToast(int messageId) {
671 | showToast(getString(messageId));
672 | }
673 |
674 | private void showToast(String message) {
675 | Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
676 | }
677 |
678 | private static boolean isBehindLiveWindow(ExoPlaybackException e) {
679 | if (e.type != ExoPlaybackException.TYPE_SOURCE) {
680 | return false;
681 | }
682 | Throwable cause = e.getSourceException();
683 | while (cause != null) {
684 | if (cause instanceof BehindLiveWindowException) {
685 | return true;
686 | }
687 | cause = cause.getCause();
688 | }
689 | return false;
690 | }
691 |
692 | private void startPlayerStats() {
693 | mUiUpdateHandler.removeMessages(MSG_UPDATE_STATS);
694 | mUiUpdateHandler.removeMessages(MSG_UPDATE_STATS_NW_ONLY);
695 | depictPlayerStats();
696 | depictPlayerNWStats();
697 | }
698 |
699 | protected void depictPlayerStats() {
700 | if (!canShowStats())
701 | return;
702 | String buffer = DemoUtil.getFormattedDouble((bufferedDurationMs / Math.pow(10, 6)), 1);
703 | String brEstimate = DemoUtil.getFormattedDouble((bitrateEstimate / Math.pow(10, 3)), 1);
704 | updateStatChart(player_stats_health_chart, Float.parseFloat(buffer), ColorTemplate.getHoloBlue(), "Buffer Health: " + buffer + " s");
705 | updateStatChart(player_stats_speed_chart, Float.parseFloat(brEstimate), Color.LTGRAY, "Conn Speed: " + DemoUtil.humanReadableByteCount(
706 | bitrateEstimate, true, true) + "ps");
707 | player_stats_size.setText("Screen Dimensions: " + simpleExoPlayerView.getWidth() + " x " + simpleExoPlayerView.getHeight());
708 | player_stats_res.setText("Video Resolution: " + (null != currentVideoFormat ? (currentVideoFormat.width + " x " + currentVideoFormat.height) : "NA"));
709 | player_stats_dropframes.setText("Dropped Frames: " + droppedFrames);
710 | mUiUpdateHandler.sendEmptyMessageDelayed(MSG_UPDATE_STATS, 500);
711 | }
712 |
713 | protected void depictPlayerNWStats() {
714 | if (!canShowStats())
715 | return;
716 | updateStatChart(player_stats_nw_chart, (float) (bytesDownloaded / Math.pow(10, 3)), Color.CYAN, "Network Activity: " + DemoUtil.humanReadableByteCount(
717 | bytesDownloaded, true));
718 | bytesDownloaded = 0;
719 | mUiUpdateHandler.sendEmptyMessageDelayed(MSG_UPDATE_STATS_NW_ONLY, 1100);
720 | }
721 |
722 | private boolean canShowStats() {
723 | if (null == mUiUpdateHandler) {
724 | player_stats_layout.setVisibility(View.GONE);
725 | return false;
726 | }
727 | return true;
728 | }
729 |
730 | private void initStatChart(LineChart chart) {
731 | // disable description text
732 | chart.getDescription().setEnabled(false);
733 | chart.getLegend().setTextColor(Color.WHITE);
734 | // disable scaling and dragging
735 | chart.setTouchEnabled(false);
736 | chart.setDragEnabled(false);
737 | chart.setScaleEnabled(false);
738 | chart.setDrawGridBackground(false);
739 | // if disabled, scaling can be done on x- and y-axis separately
740 | chart.setPinchZoom(false);
741 | // set an alternative background color
742 | chart.setBackgroundColor(Color.TRANSPARENT);
743 | chart.getXAxis().setEnabled(false);
744 | XAxis speed_xl = chart.getXAxis();
745 | speed_xl.setDrawLabels(false);
746 | speed_xl.setDrawGridLines(false);
747 | speed_xl.setAvoidFirstLastClipping(true);
748 | speed_xl.setPosition(XAxis.XAxisPosition.BOTTOM);
749 | speed_xl.setEnabled(true);
750 | YAxis speed_yl = chart.getAxisRight();
751 | speed_yl.setDrawLabels(false);
752 | speed_yl.setDrawGridLines(false);
753 | speed_yl.setEnabled(true);
754 | YAxis speed_rightAxis = chart.getAxisLeft();
755 | speed_rightAxis.setEnabled(false);
756 | LineData speed_data = new LineData();
757 | // add empty data
758 | chart.setData(speed_data);
759 | }
760 |
761 | private void updateStatChart(LineChart chart, float value, int color, String formattedValue) {
762 |
763 | LineData data = chart.getData();
764 |
765 | if (data != null) {
766 |
767 | ILineDataSet set = data.getDataSetByIndex(0);
768 | // set.addEntry(...); // can be called as well
769 |
770 | if (set == null) {
771 | set = createDataSetForChart(color);
772 | data.addDataSet(set);
773 | }
774 | set.setLabel(formattedValue);
775 | data.addEntry(new Entry(set.getEntryCount(), value), 0);
776 | data.notifyDataChanged();
777 |
778 | // let the chart know it's data has changed
779 | chart.notifyDataSetChanged();
780 |
781 | // limit the number of visible entries
782 | chart.setVisibleXRangeMaximum(180);
783 | // mChart.setVisibleYRange(30, AxisDependency.LEFT);
784 |
785 | // move to the latest entry
786 | chart.moveViewToX(data.getEntryCount());
787 |
788 | // this automatically refreshes the chart (calls invalidate())
789 | // mChart.moveViewTo(data.getXValCount()-7, 55f,
790 | // AxisDependency.LEFT);
791 | }
792 | }
793 |
794 | private LineDataSet createDataSetForChart(int color) {
795 | LineDataSet set = new LineDataSet(null, "Dynamic Data");
796 | set.setAxisDependency(YAxis.AxisDependency.LEFT);
797 | set.setColor(color);
798 | set.setDrawFilled(true);
799 | set.setDrawCircles(false);
800 | set.setLineWidth(0);
801 | set.setFillAlpha(65);
802 | set.setFillColor(color);
803 | // set.setHighLightColor(Color.rgb(244, 117, 117));
804 | set.setDrawValues(false);
805 | return set;
806 | }
807 |
808 | private static class UiUpdateHandler extends Handler {
809 |
810 | WeakReference mPlayerActivity;
811 |
812 | UiUpdateHandler(PlayerActivity activity) {
813 | mPlayerActivity = new WeakReference<>(activity);
814 | }
815 |
816 | @Override
817 | public void handleMessage(Message msg) {
818 | PlayerActivity activity = mPlayerActivity.get();
819 | if (null == activity)
820 | return;
821 | switch (msg.what) {
822 | case MSG_UPDATE_STATS:
823 | activity.depictPlayerStats();
824 | break;
825 | case MSG_UPDATE_STATS_NW_ONLY:
826 | activity.depictPlayerNWStats();
827 | break;
828 | }
829 |
830 | }
831 | }
832 |
833 | }
834 |
--------------------------------------------------------------------------------