Commit 44293e8f by Oliver Woodman Committed by GitHub

Merge pull request #5833 from google/dev-v2-r2.10.0

r2.10.0
parents 4bc79c94 7d430423
Showing with 1367 additions and 735 deletions
...@@ -10,11 +10,11 @@ Before filing a bug: ...@@ -10,11 +10,11 @@ Before filing a bug:
----------------------- -----------------------
- Search existing issues, including issues that are closed. - Search existing issues, including issues that are closed.
- Consult our FAQs, supported devices and supported formats pages. These can be - Consult our FAQs, supported devices and supported formats pages. These can be
found at https://google.github.io/ExoPlayer/. found at https://exoplayer.dev/.
- Rule out issues in your own code. A good way to do this is to try and - Rule out issues in your own code. A good way to do this is to try and
reproduce the issue in the ExoPlayer demo app. Information about the ExoPlayer reproduce the issue in the ExoPlayer demo app. Information about the ExoPlayer
demo app can be found here: demo app can be found here:
http://google.github.io/ExoPlayer/demo-application.html. http://exoplayer.dev/demo-application.html.
When reporting a bug: When reporting a bug:
----------------------- -----------------------
......
...@@ -10,10 +10,10 @@ Before filing a content issue: ...@@ -10,10 +10,10 @@ Before filing a content issue:
------------------------------ ------------------------------
- Search existing issues, including issues that are closed. - Search existing issues, including issues that are closed.
- Consult our supported formats page, which can be found at - Consult our supported formats page, which can be found at
https://google.github.io/ExoPlayer/supported-formats.html. https://exoplayer.dev/supported-formats.html.
- Try playing your content in the ExoPlayer demo app. Information about the - Try playing your content in the ExoPlayer demo app. Information about the
ExoPlayer demo app can be found here: ExoPlayer demo app can be found here:
http://google.github.io/ExoPlayer/demo-application.html. http://exoplayer.dev/demo-application.html.
When reporting a content issue: When reporting a content issue:
----------------------------- -----------------------------
......
...@@ -13,7 +13,7 @@ Before filing a question: ...@@ -13,7 +13,7 @@ Before filing a question:
- Search existing issues, including issues that are closed. It’s often the - Search existing issues, including issues that are closed. It’s often the
quickest way to get an answer! quickest way to get an answer!
- Consult our FAQs, developer guide and the class reference of ExoPlayer. These - Consult our FAQs, developer guide and the class reference of ExoPlayer. These
can be found at https://google.github.io/ExoPlayer/. can be found at https://exoplayer.dev/.
When filing a question: When filing a question:
----------------------- -----------------------
......
...@@ -37,6 +37,12 @@ local.properties ...@@ -37,6 +37,12 @@ local.properties
proguard.cfg proguard.cfg
proguard-project.txt proguard-project.txt
# Bazel
bazel-bin
bazel-genfiles
bazel-out
bazel-testlogs
# Other # Other
.DS_Store .DS_Store
cmake-build-debug cmake-build-debug
...@@ -66,3 +72,6 @@ extensions/cronet/jniLibs/* ...@@ -66,3 +72,6 @@ extensions/cronet/jniLibs/*
extensions/cronet/libs/* extensions/cronet/libs/*
!extensions/cronet/libs/README.md !extensions/cronet/libs/README.md
# Cast receiver
cast_receiver_app/external-js
cast_receiver_app/bazel-cast_receiver_app
...@@ -44,6 +44,12 @@ local.properties ...@@ -44,6 +44,12 @@ local.properties
proguard.cfg proguard.cfg
proguard-project.txt proguard-project.txt
# Bazel
bazel-bin
bazel-genfiles
bazel-out
bazel-testlogs
# Other # Other
.DS_Store .DS_Store
cmake-build-debug cmake-build-debug
...@@ -69,3 +75,7 @@ extensions/cronet/jniLibs/* ...@@ -69,3 +75,7 @@ extensions/cronet/jniLibs/*
!extensions/cronet/jniLibs/README.md !extensions/cronet/jniLibs/README.md
extensions/cronet/libs/* extensions/cronet/libs/*
!extensions/cronet/libs/README.md !extensions/cronet/libs/README.md
# Cast receiver
cast_receiver_app/external-js
cast_receiver_app/bazel-cast_receiver_app
...@@ -15,8 +15,8 @@ and extend, and can be updated through Play Store application updates. ...@@ -15,8 +15,8 @@ and extend, and can be updated through Play Store application updates.
* Follow our [developer blog][] to keep up to date with the latest ExoPlayer * Follow our [developer blog][] to keep up to date with the latest ExoPlayer
developments! developments!
[developer guide]: https://google.github.io/ExoPlayer/guide.html [developer guide]: https://exoplayer.dev/guide.html
[class reference]: https://google.github.io/ExoPlayer/doc/reference [class reference]: https://exoplayer.dev/doc/reference
[release notes]: https://github.com/google/ExoPlayer/blob/release-v2/RELEASENOTES.md [release notes]: https://github.com/google/ExoPlayer/blob/release-v2/RELEASENOTES.md
[developer blog]: https://medium.com/google-exoplayer [developer blog]: https://medium.com/google-exoplayer
...@@ -95,20 +95,6 @@ compileOptions { ...@@ -95,20 +95,6 @@ compileOptions {
} }
``` ```
Note that if you want to use Java 8 features in your own code, the following
additional options need to be set:
```gradle
// For Java compilers:
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
}
// For Kotlin compilers:
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
}
```
### Locally ### ### Locally ###
Cloning the repository and depending on the modules locally is required when Cloning the repository and depending on the modules locally is required when
......
# Release notes # # Release notes #
### 2.10.0 ###
* Core library:
* Improve decoder re-use between playbacks
([#2826](https://github.com/google/ExoPlayer/issues/2826)). Read
[this blog post](https://medium.com/google-exoplayer/improved-decoder-reuse-in-exoplayer-ef4c6d99591d)
for more details.
* Rename `ExtractorMediaSource` to `ProgressiveMediaSource`.
* Fix issue where using `ProgressiveMediaSource.Factory` would mean that
`DefaultExtractorsFactory` would be kept by proguard. Custom
`ExtractorsFactory` instances must now be passed via the
`ProgressiveMediaSource.Factory` constructor, and `setExtractorsFactory` is
deprecated.
* Move `PriorityTaskManager` from `DefaultLoadControl` to `SimpleExoPlayer`.
* Add new `ExoPlaybackException` types for remote exceptions and out-of-memory
errors.
* Use full BCP 47 language tags in `Format`.
* Do not retry failed loads whose error is `FileNotFoundException`.
* Fix issue where not resetting the position for a new `MediaSource` in calls
to `ExoPlayer.prepare` causes an `IndexOutOfBoundsException`
([#5520](https://github.com/google/ExoPlayer/issues/5520)).
* Offline:
* Improve offline support. `DownloadManager` now tracks all offline content,
not just tasks in progress. Read
[this page](https://exoplayer.dev/downloading-media.html) for more details.
* Caching:
* Improve performance of `SimpleCache`
([#4253](https://github.com/google/ExoPlayer/issues/4253)).
* Cache data with unknown length by default. The previous flag to opt in to
this behavior (`DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH`) has been
replaced with an opt out flag
(`DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN`).
* Extractors:
* MP4/FMP4: Add support for Dolby Vision.
* MP4: Fix issue handling meta atoms in some streams
([#5698](https://github.com/google/ExoPlayer/issues/5698),
[#5694](https://github.com/google/ExoPlayer/issues/5694)).
* MP3: Add support for SHOUTcast ICY metadata
([#3735](https://github.com/google/ExoPlayer/issues/3735)).
* MP3: Fix ID3 frame unsychronization
([#5673](https://github.com/google/ExoPlayer/issues/5673)).
* MP3: Fix playback of badly clipped files
([#5772](https://github.com/google/ExoPlayer/issues/5772)).
* MPEG-TS: Enable HDMV DTS stream detection only if a flag is set. By default
(i.e. if the flag is not set), the 0x82 elementary stream type is now
treated as an SCTE subtitle track
([#5330](https://github.com/google/ExoPlayer/issues/5330)).
* Track selection:
* Add options for controlling audio track selections to `DefaultTrackSelector`
([#3314](https://github.com/google/ExoPlayer/issues/3314)).
* Update `TrackSelection.Factory` interface to support creating all track
selections together.
* Allow to specify a selection reason for a `SelectionOverride`.
* When no text language preference matches, only select forced text tracks
whose language matches the selected audio language.
* UI:
* Update `DefaultTimeBar` based on duration of media and add parameter to set
the minimum update interval to control the smoothness of the updates
([#5040](https://github.com/google/ExoPlayer/issues/5040)).
* Move creation of dialogs for `TrackSelectionView`s to
`TrackSelectionDialogBuilder` and add option to select multiple overrides.
* Change signature of `PlayerNotificationManager.NotificationListener` to
better fit service requirements.
* Add option to include navigation actions in the compact mode of
notifications created using `PlayerNotificationManager`.
* Fix issues with flickering notifications on KitKat when using
`PlayerNotificationManager` and `DownloadNotificationUtil`. For the latter,
applications should switch to using `DownloadNotificationHelper`.
* Fix accuracy of D-pad seeking in `DefaultTimeBar`
([#5767](https://github.com/google/ExoPlayer/issues/5767)).
* Audio:
* Allow `AudioProcessor`s to be drained of pending output after they are
reconfigured.
* Fix an issue that caused audio to be truncated at the end of a period
when switching to a new period where gapless playback information was newly
present or newly absent.
* Add support for reading AC-4 streams
([#5303](https://github.com/google/ExoPlayer/pull/5303)).
* Video:
* Remove `MediaCodecSelector.DEFAULT_WITH_FALLBACK`. Apps should instead
signal that fallback should be used by passing `true` as the
`enableDecoderFallback` parameter when instantiating the video renderer.
* Support video tunneling when the decoder is not listed first for the MIME
type ([#3100](https://github.com/google/ExoPlayer/issues/3100)).
* Query `MediaCodecList.ALL_CODECS` when selecting a tunneling decoder
([#5547](https://github.com/google/ExoPlayer/issues/5547)).
* DRM:
* Fix black flicker when keys rotate in DRM protected content
([#3561](https://github.com/google/ExoPlayer/issues/3561)).
* Work around lack of LA_URL attribute in PlayReady key request init data.
* CEA-608: Improved conformance to the specification
([#3860](https://github.com/google/ExoPlayer/issues/3860)).
* DASH:
* Parse role and accessibility descriptors into `Format.roleFlags`.
* Support multiple CEA-608 channels muxed into FMP4 representations
([#5656](https://github.com/google/ExoPlayer/issues/5656)).
* HLS:
* Prevent unnecessary reloads of initialization segments.
* Form an adaptive track group out of audio renditions with matching name.
* Support encrypted initialization segments
([#5441](https://github.com/google/ExoPlayer/issues/5441)).
* Parse `EXT-X-MEDIA` `CHARACTERISTICS` attribute into `Format.roleFlags`.
* Add metadata entry for HLS tracks to expose master playlist information.
* Prevent `IndexOutOfBoundsException` in some live HLS scenarios
([#5816](https://github.com/google/ExoPlayer/issues/5816)).
* Support for playing spherical videos on Daydream.
* Cast extension: Work around Cast framework returning a limited-size queue
items list ([#4964](https://github.com/google/ExoPlayer/issues/4964)).
* VP9 extension: Remove RGB output mode and libyuv dependency, and switch to
surface YUV output as the default. Remove constructor parameters `scaleToFit`
and `useSurfaceYuvOutput`.
* MediaSession extension:
* Let apps intercept media button events
([#5179](https://github.com/google/ExoPlayer/issues/5179)).
* Fix issue with `TimelineQueueNavigator` not publishing the queue in shuffled
order when in shuffle mode.
* Allow handling of custom commands via `registerCustomCommandReceiver`.
* Add ability to include an extras `Bundle` when reporting a custom error.
* LoadControl: Set minimum buffer for playbacks with video equal to maximum
buffer ([#2083](https://github.com/google/ExoPlayer/issues/2083)).
* Log warnings when extension native libraries can't be used, to help with
diagnosing playback failures
([#5788](https://github.com/google/ExoPlayer/issues/5788)).
### 2.9.6 ### ### 2.9.6 ###
* Remove `player` and `isTopLevelSource` parameters from `MediaSource.prepare`. * Remove `player` and `isTopLevelSource` parameters from `MediaSource.prepare`.
......
...@@ -17,9 +17,9 @@ buildscript { ...@@ -17,9 +17,9 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.1.4' classpath 'com.android.tools.build:gradle:3.4.0'
classpath 'com.novoda:bintray-release:0.8.1' classpath 'com.novoda:bintray-release:0.9'
classpath 'com.google.android.gms:strict-version-matcher-plugin:1.0.3' classpath 'com.google.android.gms:strict-version-matcher-plugin:1.1.0'
} }
// Workaround for the following test coverage issue. Remove when fixed: // Workaround for the following test coverage issue. Remove when fixed:
// https://code.google.com/p/android/issues/detail?id=226070 // https://code.google.com/p/android/issues/detail?id=226070
......
...@@ -13,26 +13,17 @@ ...@@ -13,26 +13,17 @@
// limitations under the License. // limitations under the License.
project.ext { project.ext {
// ExoPlayer version and version code. // ExoPlayer version and version code.
releaseVersion = '2.9.6' releaseVersion = '2.10.0'
releaseVersionCode = 2009006 releaseVersionCode = 2010000
// Important: ExoPlayer specifies a minSdkVersion of 14 because various minSdkVersion = 16
// components provided by the library may be of use on older devices.
// However, please note that the core media playback functionality provided
// by the library requires API level 16 or greater.
minSdkVersion = 14
targetSdkVersion = 28 targetSdkVersion = 28
compileSdkVersion = 28 compileSdkVersion = 28
buildToolsVersion = '28.0.2' dexmakerVersion = '2.21.0'
testSupportLibraryVersion = '0.5' mockitoVersion = '2.25.0'
supportLibraryVersion = '27.1.1' robolectricVersion = '4.2'
dexmakerVersion = '1.2'
mockitoVersion = '1.9.5'
junitVersion = '4.12'
truthVersion = '0.39'
robolectricVersion = '3.7.1'
autoValueVersion = '1.6' autoValueVersion = '1.6'
checkerframeworkVersion = '2.5.0' checkerframeworkVersion = '2.5.0'
testRunnerVersion = '1.1.0-alpha3' androidXTestVersion = '1.1.0'
modulePrefix = ':' modulePrefix = ':'
if (gradle.ext.has('exoplayerModulePrefix')) { if (gradle.ext.has('exoplayerModulePrefix')) {
modulePrefix += gradle.ext.exoplayerModulePrefix modulePrefix += gradle.ext.exoplayerModulePrefix
......
...@@ -16,7 +16,6 @@ apply plugin: 'com.android.application' ...@@ -16,7 +16,6 @@ apply plugin: 'com.android.application'
android { android {
compileSdkVersion project.ext.compileSdkVersion compileSdkVersion project.ext.compileSdkVersion
buildToolsVersion project.ext.buildToolsVersion
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
...@@ -26,7 +25,7 @@ android { ...@@ -26,7 +25,7 @@ android {
defaultConfig { defaultConfig {
versionName project.ext.releaseVersion versionName project.ext.releaseVersion
versionCode project.ext.releaseVersionCode versionCode project.ext.releaseVersionCode
minSdkVersion 16 minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion targetSdkVersion project.ext.targetSdkVersion
} }
...@@ -45,8 +44,18 @@ android { ...@@ -45,8 +44,18 @@ android {
} }
lintOptions { lintOptions {
// The demo app does not have translations. // The demo app isn't indexed and doesn't have translations.
disable 'MissingTranslation' disable 'GoogleAppIndexingWarning','MissingTranslation'
}
flavorDimensions "receiver"
productFlavors {
defaultCast {
dimension "receiver"
manifestPlaceholders =
[castOptionsProvider: "com.google.android.exoplayer2.ext.cast.DefaultCastOptionsProvider"]
}
} }
} }
...@@ -58,9 +67,10 @@ dependencies { ...@@ -58,9 +67,10 @@ dependencies {
implementation project(modulePrefix + 'library-smoothstreaming') implementation project(modulePrefix + 'library-smoothstreaming')
implementation project(modulePrefix + 'library-ui') implementation project(modulePrefix + 'library-ui')
implementation project(modulePrefix + 'extension-cast') implementation project(modulePrefix + 'extension-cast')
implementation 'com.android.support:support-v4:' + supportLibraryVersion implementation 'com.google.android.material:material:1.0.0'
implementation 'com.android.support:appcompat-v7:' + supportLibraryVersion implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.android.support:recyclerview-v7:' + supportLibraryVersion implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
} }
apply plugin: 'com.google.android.gms.strict-version-matcher-plugin' apply plugin: 'com.google.android.gms.strict-version-matcher-plugin'
# Proguard rules specific to the Cast demo app. # Proguard rules specific to the Cast demo app.
# Accessed via menu.xml # Accessed via menu.xml
-keep class android.support.v7.app.MediaRouteActionProvider { -keep class androidx.mediarouter.app.MediaRouteActionProvider {
*; *;
} }
...@@ -17,13 +17,15 @@ ...@@ -17,13 +17,15 @@
package="com.google.android.exoplayer2.castdemo"> package="com.google.android.exoplayer2.castdemo">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-sdk/> <uses-sdk/>
<application android:label="@string/application_name" android:icon="@mipmap/ic_launcher" <application android:label="@string/application_name" android:icon="@mipmap/ic_launcher"
android:largeHeap="true" android:allowBackup="false"> android:largeHeap="true" android:allowBackup="false">
<meta-data android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME" <meta-data android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.google.android.exoplayer2.ext.cast.DefaultCastOptionsProvider" /> android:value="${castOptionsProvider}" />
<activity android:name="com.google.android.exoplayer2.castdemo.MainActivity" <activity android:name="com.google.android.exoplayer2.castdemo.MainActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
......
...@@ -15,44 +15,37 @@ ...@@ -15,44 +15,37 @@
*/ */
package com.google.android.exoplayer2.castdemo; package com.google.android.exoplayer2.castdemo;
import android.net.Uri;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.gms.cast.MediaInfo;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID;
/** /** Utility methods and constants for the Cast demo application. */
* Utility methods and constants for the Cast demo application.
*/
/* package */ final class DemoUtil { /* package */ final class DemoUtil {
public static final String MIME_TYPE_DASH = MimeTypes.APPLICATION_MPD; /** Represents a media sample. */
public static final String MIME_TYPE_HLS = MimeTypes.APPLICATION_M3U8;
public static final String MIME_TYPE_SS = MimeTypes.APPLICATION_SS;
public static final String MIME_TYPE_VIDEO_MP4 = MimeTypes.VIDEO_MP4;
/**
* The list of samples available in the cast demo app.
*/
public static final List<Sample> SAMPLES;
/**
* Represents a media sample.
*/
public static final class Sample { public static final class Sample {
/** /** The uri of the media content. */
* The uri from which the media sample is obtained.
*/
public final String uri; public final String uri;
/** The name of the sample. */
public final String name;
/** The mime type of the sample media content. */
public final String mimeType;
/** /**
* A descriptive name for the sample. * The {@link UUID} of the DRM scheme that protects the content, or null if the content is not
* DRM-protected.
*/ */
public final String name; @Nullable public final UUID drmSchemeUuid;
/** /**
* The mime type of the media sample, as required by {@link MediaInfo#setContentType}. * The url from which players should obtain DRM licenses, or null if the content is not
* DRM-protected.
*/ */
public final String mimeType; @Nullable public final Uri licenseServerUri;
/** /**
* @param uri See {@link #uri}. * @param uri See {@link #uri}.
...@@ -60,31 +53,53 @@ import java.util.List; ...@@ -60,31 +53,53 @@ import java.util.List;
* @param mimeType See {@link #mimeType}. * @param mimeType See {@link #mimeType}.
*/ */
public Sample(String uri, String name, String mimeType) { public Sample(String uri, String name, String mimeType) {
this(uri, name, mimeType, /* drmSchemeUuid= */ null, /* licenseServerUriString= */ null);
}
public Sample(
String uri,
String name,
String mimeType,
@Nullable UUID drmSchemeUuid,
@Nullable String licenseServerUriString) {
this.uri = uri; this.uri = uri;
this.name = name; this.name = name;
this.mimeType = mimeType; this.mimeType = mimeType;
this.drmSchemeUuid = drmSchemeUuid;
this.licenseServerUri =
licenseServerUriString != null ? Uri.parse(licenseServerUriString) : null;
} }
@Override @Override
public String toString() { public String toString() {
return name; return name;
} }
} }
public static final String MIME_TYPE_DASH = MimeTypes.APPLICATION_MPD;
public static final String MIME_TYPE_HLS = MimeTypes.APPLICATION_M3U8;
public static final String MIME_TYPE_SS = MimeTypes.APPLICATION_SS;
public static final String MIME_TYPE_VIDEO_MP4 = MimeTypes.VIDEO_MP4;
/** The list of samples available in the cast demo app. */
public static final List<Sample> SAMPLES;
static { static {
// App samples. // App samples.
ArrayList<Sample> samples = new ArrayList<>(); ArrayList<Sample> samples = new ArrayList<>();
samples.add(new Sample("https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd",
"DASH (clear,MP4,H264)", MIME_TYPE_DASH));
samples.add(new Sample("https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/"
+ "hls/TearsOfSteel.m3u8", "Tears of Steel (HLS)", MIME_TYPE_HLS));
samples.add(new Sample("https://html5demos.com/assets/dizzy.mp4", "Dizzy (MP4)",
MIME_TYPE_VIDEO_MP4));
SAMPLES = Collections.unmodifiableList(samples);
// Clear content.
samples.add(
new Sample(
"https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd",
"Clear DASH: Tears",
MIME_TYPE_DASH));
samples.add(
new Sample(
"https://html5demos.com/assets/dizzy.mp4", "Clear MP4: Dizzy", MIME_TYPE_VIDEO_MP4));
SAMPLES = Collections.unmodifiableList(samples);
} }
private DemoUtil() {} private DemoUtil() {}
} }
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24.0dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24.0dp" >
<path
android:fillColor="#FFFFFFFF"
android:pathData="M18,13h-5v5c0,0.55 -0.45,1 -1,1h0c-0.55,0 -1,-0.45 -1,-1v-5H6c-0.55,0 -1,-0.45 -1,-1v0c0,-0.55 0.45,-1 1,-1h5V6c0,-0.55 0.45,-1 1,-1h0c0.55,0 1,0.45 1,1v5h5c0.55,0 1,0.45 1,1v0C19,12.55 18.55,13 18,13z"/>
</vector>
...@@ -13,17 +13,10 @@ ...@@ -13,17 +13,10 @@
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView" android:id="@+id/textView"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1"
android:textSize="20sp"
android:gravity="center" android:gravity="center"
android:textSize="20sp"
android:text="@string/cast_context_error"/> android:text="@string/cast_context_error"/>
</LinearLayout>
...@@ -19,34 +19,42 @@ ...@@ -19,34 +19,42 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:keepScreenOn="true"> android:keepScreenOn="true">
<com.google.android.exoplayer2.ui.PlayerView android:id="@+id/local_player_view" <com.google.android.exoplayer2.ui.PlayerView android:id="@+id/local_player_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="12" android:layout_weight="1"
android:background="@android:color/black"
app:repeat_toggle_modes="all|one"/> app:repeat_toggle_modes="all|one"/>
<RelativeLayout android:layout_width="match_parent" <RelativeLayout android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="12"> android:layout_weight="1">
<android.support.v7.widget.RecyclerView android:id="@+id/sample_list"
<androidx.recyclerview.widget.RecyclerView android:id="@+id/sample_list"
android:choiceMode="singleChoice" android:choiceMode="singleChoice"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:scrollbars="vertical" android:scrollbars="vertical"
android:fadeScrollbars="false"/> android:fadeScrollbars="false"/>
<ImageButton android:id="@+id/add_sample_button"
android:background="@drawable/ic_add_circle_white_24dp" <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/add_sample_button"
android:src="@drawable/ic_plus"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"
android:padding="30dp"/> android:layout_margin="16dp"
android:contentDescription="@string/add_samples"/>
</RelativeLayout> </RelativeLayout>
<com.google.android.exoplayer2.ui.PlayerControlView android:id="@+id/cast_control_view" <com.google.android.exoplayer2.ui.PlayerControlView android:id="@+id/cast_control_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="wrap_content"
android:layout_weight="2"
android:visibility="gone" android:visibility="gone"
app:repeat_toggle_modes="all|one" app:repeat_toggle_modes="all|one"
app:show_timeout="-1"/> app:show_timeout="-1"/>
</LinearLayout> </LinearLayout>
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
limitations under the License. limitations under the License.
--> -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<ListView android:id="@+id/sample_list" <ListView android:id="@+id/sample_list"
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
<item <item
android:id="@+id/media_route_menu_item" android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title" android:title="@string/media_route_menu_title"
app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider" app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always" /> app:showAsAction="always" />
</menu> </menu>
...@@ -20,8 +20,10 @@ ...@@ -20,8 +20,10 @@
<string name="media_route_menu_title">Cast</string> <string name="media_route_menu_title">Cast</string>
<string name="sample_list_dialog_title">Add samples</string> <string name="add_samples">Add samples</string>
<string name="cast_context_error">Failed to get Cast context. Try updating Google Play Services and restart the app.</string> <string name="cast_context_error">Failed to get Cast context. Try updating Google Play Services and restart the app.</string>
<string name="player_error_msg">Player error encountered. Select a queue item to reprepare. Check the logcat and receiver app\'s console for more info.</string>
</resources> </resources>
...@@ -16,7 +16,6 @@ apply plugin: 'com.android.application' ...@@ -16,7 +16,6 @@ apply plugin: 'com.android.application'
android { android {
compileSdkVersion project.ext.compileSdkVersion compileSdkVersion project.ext.compileSdkVersion
buildToolsVersion project.ext.buildToolsVersion
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
...@@ -26,7 +25,7 @@ android { ...@@ -26,7 +25,7 @@ android {
defaultConfig { defaultConfig {
versionName project.ext.releaseVersion versionName project.ext.releaseVersion
versionCode project.ext.releaseVersionCode versionCode project.ext.releaseVersionCode
minSdkVersion 16 minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion targetSdkVersion project.ext.targetSdkVersion
} }
...@@ -42,8 +41,8 @@ android { ...@@ -42,8 +41,8 @@ android {
} }
lintOptions { lintOptions {
// The demo app does not have translations. // The demo app isn't indexed and doesn't have translations.
disable 'MissingTranslation' disable 'GoogleAppIndexingWarning','MissingTranslation'
} }
} }
...@@ -54,7 +53,7 @@ dependencies { ...@@ -54,7 +53,7 @@ dependencies {
implementation project(modulePrefix + 'library-hls') implementation project(modulePrefix + 'library-hls')
implementation project(modulePrefix + 'library-smoothstreaming') implementation project(modulePrefix + 'library-smoothstreaming')
implementation project(modulePrefix + 'extension-ima') implementation project(modulePrefix + 'extension-ima')
implementation 'com.android.support:support-annotations:' + supportLibraryVersion implementation 'androidx.annotation:annotation:1.0.2'
} }
apply plugin: 'com.google.android.gms.strict-version-matcher-plugin' apply plugin: 'com.google.android.gms.strict-version-matcher-plugin'
...@@ -23,8 +23,8 @@ import com.google.android.exoplayer2.ExoPlayer; ...@@ -23,8 +23,8 @@ import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.ext.ima.ImaAdsLoader; import com.google.android.exoplayer2.ext.ima.ImaAdsLoader;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.ads.AdsMediaSource; import com.google.android.exoplayer2.source.ads.AdsMediaSource;
import com.google.android.exoplayer2.source.dash.DashMediaSource; import com.google.android.exoplayer2.source.dash.DashMediaSource;
import com.google.android.exoplayer2.source.hls.HlsMediaSource; import com.google.android.exoplayer2.source.hls.HlsMediaSource;
...@@ -114,7 +114,7 @@ import com.google.android.exoplayer2.util.Util; ...@@ -114,7 +114,7 @@ import com.google.android.exoplayer2.util.Util;
case C.TYPE_HLS: case C.TYPE_HLS:
return new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(uri); return new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
case C.TYPE_OTHER: case C.TYPE_OTHER:
return new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri); return new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
default: default:
throw new IllegalStateException("Unsupported type: " + type); throw new IllegalStateException("Unsupported type: " + type);
} }
......
...@@ -16,7 +16,6 @@ apply plugin: 'com.android.application' ...@@ -16,7 +16,6 @@ apply plugin: 'com.android.application'
android { android {
compileSdkVersion project.ext.compileSdkVersion compileSdkVersion project.ext.compileSdkVersion
buildToolsVersion project.ext.buildToolsVersion
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
...@@ -26,7 +25,7 @@ android { ...@@ -26,7 +25,7 @@ android {
defaultConfig { defaultConfig {
versionName project.ext.releaseVersion versionName project.ext.releaseVersion
versionCode project.ext.releaseVersionCode versionCode project.ext.releaseVersionCode
minSdkVersion 16 minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion targetSdkVersion project.ext.targetSdkVersion
} }
...@@ -45,8 +44,9 @@ android { ...@@ -45,8 +44,9 @@ android {
} }
lintOptions { lintOptions {
// The demo app does not have translations. // The demo app isn't indexed, doesn't have translations, and has a
disable 'MissingTranslation' // banner for AndroidTV that's only in xhdpi density.
disable 'GoogleAppIndexingWarning','MissingTranslation','IconDensities'
} }
flavorDimensions "extensions" flavorDimensions "extensions"
...@@ -62,7 +62,10 @@ android { ...@@ -62,7 +62,10 @@ android {
} }
dependencies { dependencies {
implementation 'com.android.support:support-annotations:' + supportLibraryVersion implementation 'androidx.annotation:annotation:1.0.2'
implementation 'androidx.legacy:legacy-support-core-ui:1.0.0'
implementation 'androidx.fragment:fragment:1.0.0'
implementation 'com.google.android.material:material:1.0.0'
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
implementation project(modulePrefix + 'library-dash') implementation project(modulePrefix + 'library-dash')
implementation project(modulePrefix + 'library-hls') implementation project(modulePrefix + 'library-hls')
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
--> -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer2.demo"> package="com.google.android.exoplayer2.demo">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
...@@ -33,11 +34,13 @@ ...@@ -33,11 +34,13 @@
android:banner="@drawable/ic_banner" android:banner="@drawable/ic_banner"
android:largeHeap="true" android:largeHeap="true"
android:allowBackup="false" android:allowBackup="false"
android:name="com.google.android.exoplayer2.demo.DemoApplication"> android:name="com.google.android.exoplayer2.demo.DemoApplication"
tools:ignore="UnusedAttribute">
<activity android:name="com.google.android.exoplayer2.demo.SampleChooserActivity" <activity android:name="com.google.android.exoplayer2.demo.SampleChooserActivity"
android:configChanges="keyboardHidden" android:configChanges="keyboardHidden"
android:label="@string/application_name"> android:label="@string/application_name"
android:theme="@style/Theme.AppCompat">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
......
...@@ -330,11 +330,11 @@ ...@@ -330,11 +330,11 @@
"samples": [ "samples": [
{ {
"name": "Super speed", "name": "Super speed",
"uri": "https://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism" "uri": "https://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism/Manifest"
}, },
{ {
"name": "Super speed (PlayReady)", "name": "Super speed (PlayReady)",
"uri": "https://playready.directtaps.net/smoothstreaming/SSWSS720H264PR/SuperSpeedway_720.ism", "uri": "https://playready.directtaps.net/smoothstreaming/SSWSS720H264PR/SuperSpeedway_720.ism/Manifest",
"drm_scheme": "playready" "drm_scheme": "playready"
} }
] ]
......
...@@ -16,6 +16,13 @@ ...@@ -16,6 +16,13 @@
package com.google.android.exoplayer2.demo; package com.google.android.exoplayer2.demo;
import android.app.Application; import android.app.Application;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.database.DatabaseProvider;
import com.google.android.exoplayer2.database.ExoDatabaseProvider;
import com.google.android.exoplayer2.offline.ActionFileUpgradeUtil;
import com.google.android.exoplayer2.offline.DefaultDownloadIndex;
import com.google.android.exoplayer2.offline.DefaultDownloaderFactory;
import com.google.android.exoplayer2.offline.DownloadManager; import com.google.android.exoplayer2.offline.DownloadManager;
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
...@@ -28,21 +35,24 @@ import com.google.android.exoplayer2.upstream.cache.CacheDataSource; ...@@ -28,21 +35,24 @@ import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory; import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor; import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor;
import com.google.android.exoplayer2.upstream.cache.SimpleCache; import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.File; import java.io.File;
import java.io.IOException;
/** /**
* Placeholder application to facilitate overriding Application methods for debugging and testing. * Placeholder application to facilitate overriding Application methods for debugging and testing.
*/ */
public class DemoApplication extends Application { public class DemoApplication extends Application {
private static final String TAG = "DemoApplication";
private static final String DOWNLOAD_ACTION_FILE = "actions"; private static final String DOWNLOAD_ACTION_FILE = "actions";
private static final String DOWNLOAD_TRACKER_ACTION_FILE = "tracked_actions"; private static final String DOWNLOAD_TRACKER_ACTION_FILE = "tracked_actions";
private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads"; private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
private static final int MAX_SIMULTANEOUS_DOWNLOADS = 2;
protected String userAgent; protected String userAgent;
private DatabaseProvider databaseProvider;
private File downloadDirectory; private File downloadDirectory;
private Cache downloadCache; private Cache downloadCache;
private DownloadManager downloadManager; private DownloadManager downloadManager;
...@@ -71,6 +81,18 @@ public class DemoApplication extends Application { ...@@ -71,6 +81,18 @@ public class DemoApplication extends Application {
return "withExtensions".equals(BuildConfig.FLAVOR); return "withExtensions".equals(BuildConfig.FLAVOR);
} }
public RenderersFactory buildRenderersFactory(boolean preferExtensionRenderer) {
@DefaultRenderersFactory.ExtensionRendererMode
int extensionRendererMode =
useExtensionRenderers()
? (preferExtensionRenderer
? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER
: DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON)
: DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF;
return new DefaultRenderersFactory(/* context= */ this)
.setExtensionRendererMode(extensionRendererMode);
}
public DownloadManager getDownloadManager() { public DownloadManager getDownloadManager() {
initDownloadManager(); initDownloadManager();
return downloadManager; return downloadManager;
...@@ -81,31 +103,51 @@ public class DemoApplication extends Application { ...@@ -81,31 +103,51 @@ public class DemoApplication extends Application {
return downloadTracker; return downloadTracker;
} }
protected synchronized Cache getDownloadCache() {
if (downloadCache == null) {
File downloadContentDirectory = new File(getDownloadDirectory(), DOWNLOAD_CONTENT_DIRECTORY);
downloadCache =
new SimpleCache(downloadContentDirectory, new NoOpCacheEvictor(), getDatabaseProvider());
}
return downloadCache;
}
private synchronized void initDownloadManager() { private synchronized void initDownloadManager() {
if (downloadManager == null) { if (downloadManager == null) {
DefaultDownloadIndex downloadIndex = new DefaultDownloadIndex(getDatabaseProvider());
upgradeActionFile(
DOWNLOAD_ACTION_FILE, downloadIndex, /* addNewDownloadsAsCompleted= */ false);
upgradeActionFile(
DOWNLOAD_TRACKER_ACTION_FILE, downloadIndex, /* addNewDownloadsAsCompleted= */ true);
DownloaderConstructorHelper downloaderConstructorHelper = DownloaderConstructorHelper downloaderConstructorHelper =
new DownloaderConstructorHelper(getDownloadCache(), buildHttpDataSourceFactory()); new DownloaderConstructorHelper(getDownloadCache(), buildHttpDataSourceFactory());
downloadManager = downloadManager =
new DownloadManager( new DownloadManager(
downloaderConstructorHelper, this, downloadIndex, new DefaultDownloaderFactory(downloaderConstructorHelper));
MAX_SIMULTANEOUS_DOWNLOADS,
DownloadManager.DEFAULT_MIN_RETRY_COUNT,
new File(getDownloadDirectory(), DOWNLOAD_ACTION_FILE));
downloadTracker = downloadTracker =
new DownloadTracker( new DownloadTracker(/* context= */ this, buildDataSourceFactory(), downloadManager);
/* context= */ this,
buildDataSourceFactory(),
new File(getDownloadDirectory(), DOWNLOAD_TRACKER_ACTION_FILE));
downloadManager.addListener(downloadTracker);
} }
} }
private synchronized Cache getDownloadCache() { private void upgradeActionFile(
if (downloadCache == null) { String fileName, DefaultDownloadIndex downloadIndex, boolean addNewDownloadsAsCompleted) {
File downloadContentDirectory = new File(getDownloadDirectory(), DOWNLOAD_CONTENT_DIRECTORY); try {
downloadCache = new SimpleCache(downloadContentDirectory, new NoOpCacheEvictor()); ActionFileUpgradeUtil.upgradeAndDelete(
new File(getDownloadDirectory(), fileName),
/* downloadIdProvider= */ null,
downloadIndex,
/* deleteOnFailure= */ true,
addNewDownloadsAsCompleted);
} catch (IOException e) {
Log.e(TAG, "Failed to upgrade action file: " + fileName, e);
} }
return downloadCache; }
private DatabaseProvider getDatabaseProvider() {
if (databaseProvider == null) {
databaseProvider = new ExoDatabaseProvider(this);
}
return databaseProvider;
} }
private File getDownloadDirectory() { private File getDownloadDirectory() {
...@@ -118,8 +160,8 @@ public class DemoApplication extends Application { ...@@ -118,8 +160,8 @@ public class DemoApplication extends Application {
return downloadDirectory; return downloadDirectory;
} }
private static CacheDataSourceFactory buildReadOnlyCacheDataSource( protected static CacheDataSourceFactory buildReadOnlyCacheDataSource(
DefaultDataSourceFactory upstreamFactory, Cache cache) { DataSource.Factory upstreamFactory, Cache cache) {
return new CacheDataSourceFactory( return new CacheDataSourceFactory(
cache, cache,
upstreamFactory, upstreamFactory,
......
...@@ -16,13 +16,14 @@ ...@@ -16,13 +16,14 @@
package com.google.android.exoplayer2.demo; package com.google.android.exoplayer2.demo;
import android.app.Notification; import android.app.Notification;
import com.google.android.exoplayer2.offline.Download;
import com.google.android.exoplayer2.offline.DownloadManager; import com.google.android.exoplayer2.offline.DownloadManager;
import com.google.android.exoplayer2.offline.DownloadManager.TaskState;
import com.google.android.exoplayer2.offline.DownloadService; import com.google.android.exoplayer2.offline.DownloadService;
import com.google.android.exoplayer2.scheduler.PlatformScheduler; import com.google.android.exoplayer2.scheduler.PlatformScheduler;
import com.google.android.exoplayer2.ui.DownloadNotificationUtil; import com.google.android.exoplayer2.ui.DownloadNotificationHelper;
import com.google.android.exoplayer2.util.NotificationUtil; import com.google.android.exoplayer2.util.NotificationUtil;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.util.List;
/** A service for downloading media. */ /** A service for downloading media. */
public class DemoDownloadService extends DownloadService { public class DemoDownloadService extends DownloadService {
...@@ -31,12 +32,23 @@ public class DemoDownloadService extends DownloadService { ...@@ -31,12 +32,23 @@ public class DemoDownloadService extends DownloadService {
private static final int JOB_ID = 1; private static final int JOB_ID = 1;
private static final int FOREGROUND_NOTIFICATION_ID = 1; private static final int FOREGROUND_NOTIFICATION_ID = 1;
private static int nextNotificationId = FOREGROUND_NOTIFICATION_ID + 1;
private DownloadNotificationHelper notificationHelper;
public DemoDownloadService() { public DemoDownloadService() {
super( super(
FOREGROUND_NOTIFICATION_ID, FOREGROUND_NOTIFICATION_ID,
DEFAULT_FOREGROUND_NOTIFICATION_UPDATE_INTERVAL, DEFAULT_FOREGROUND_NOTIFICATION_UPDATE_INTERVAL,
CHANNEL_ID, CHANNEL_ID,
R.string.exo_download_notification_channel_name); R.string.exo_download_notification_channel_name);
nextNotificationId = FOREGROUND_NOTIFICATION_ID + 1;
}
@Override
public void onCreate() {
super.onCreate();
notificationHelper = new DownloadNotificationHelper(this, CHANNEL_ID);
} }
@Override @Override
...@@ -50,40 +62,29 @@ public class DemoDownloadService extends DownloadService { ...@@ -50,40 +62,29 @@ public class DemoDownloadService extends DownloadService {
} }
@Override @Override
protected Notification getForegroundNotification(TaskState[] taskStates) { protected Notification getForegroundNotification(List<Download> downloads) {
return DownloadNotificationUtil.buildProgressNotification( return notificationHelper.buildProgressNotification(
/* context= */ this, R.drawable.ic_download, /* contentIntent= */ null, /* message= */ null, downloads);
R.drawable.exo_controls_play,
CHANNEL_ID,
/* contentIntent= */ null,
/* message= */ null,
taskStates);
} }
@Override @Override
protected void onTaskStateChanged(TaskState taskState) { protected void onDownloadChanged(Download download) {
if (taskState.action.isRemoveAction) { Notification notification;
return; if (download.state == Download.STATE_COMPLETED) {
}
Notification notification = null;
if (taskState.state == TaskState.STATE_COMPLETED) {
notification = notification =
DownloadNotificationUtil.buildDownloadCompletedNotification( notificationHelper.buildDownloadCompletedNotification(
/* context= */ this, R.drawable.ic_download_done,
R.drawable.exo_controls_play,
CHANNEL_ID,
/* contentIntent= */ null, /* contentIntent= */ null,
Util.fromUtf8Bytes(taskState.action.data)); Util.fromUtf8Bytes(download.request.data));
} else if (taskState.state == TaskState.STATE_FAILED) { } else if (download.state == Download.STATE_FAILED) {
notification = notification =
DownloadNotificationUtil.buildDownloadFailedNotification( notificationHelper.buildDownloadFailedNotification(
/* context= */ this, R.drawable.ic_download_done,
R.drawable.exo_controls_play,
CHANNEL_ID,
/* contentIntent= */ null, /* contentIntent= */ null,
Util.fromUtf8Bytes(taskState.action.data)); Util.fromUtf8Bytes(download.request.data));
} else {
return;
} }
int notificationId = FOREGROUND_NOTIFICATION_ID + 1 + taskState.taskId; NotificationUtil.setNotification(this, nextNotificationId++, notification);
NotificationUtil.setNotification(this, notificationId, notification);
} }
} }
...@@ -15,14 +15,14 @@ ...@@ -15,14 +15,14 @@
*/ */
package com.google.android.exoplayer2.demo; package com.google.android.exoplayer2.demo;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import android.util.JsonReader; import android.util.JsonReader;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
...@@ -37,6 +37,7 @@ import android.widget.ImageButton; ...@@ -37,6 +37,7 @@ import android.widget.ImageButton;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.offline.DownloadService; import com.google.android.exoplayer2.offline.DownloadService;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSourceInputStream; import com.google.android.exoplayer2.upstream.DataSourceInputStream;
...@@ -54,7 +55,7 @@ import java.util.Collections; ...@@ -54,7 +55,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
/** An activity for selecting from a list of media samples. */ /** An activity for selecting from a list of media samples. */
public class SampleChooserActivity extends Activity public class SampleChooserActivity extends AppCompatActivity
implements DownloadTracker.Listener, OnChildClickListener { implements DownloadTracker.Listener, OnChildClickListener {
private static final String TAG = "SampleChooserActivity"; private static final String TAG = "SampleChooserActivity";
...@@ -177,7 +178,15 @@ public class SampleChooserActivity extends Activity ...@@ -177,7 +178,15 @@ public class SampleChooserActivity extends Activity
.show(); .show();
} else { } else {
UriSample uriSample = (UriSample) sample; UriSample uriSample = (UriSample) sample;
downloadTracker.toggleDownload(this, sample.name, uriSample.uri, uriSample.extension); RenderersFactory renderersFactory =
((DemoApplication) getApplication())
.buildRenderersFactory(isNonNullAndChecked(preferExtensionDecodersMenuItem));
downloadTracker.toggleDownload(
getSupportFragmentManager(),
sample.name,
uriSample.uri,
uriSample.extension,
renderersFactory);
} }
} }
...@@ -350,8 +359,7 @@ public class SampleChooserActivity extends Activity ...@@ -350,8 +359,7 @@ public class SampleChooserActivity extends Activity
? null ? null
: new DrmInfo(drmScheme, drmLicenseUrl, drmKeyRequestProperties, drmMultiSession); : new DrmInfo(drmScheme, drmLicenseUrl, drmKeyRequestProperties, drmMultiSession);
if (playlistSamples != null) { if (playlistSamples != null) {
UriSample[] playlistSamplesArray = playlistSamples.toArray( UriSample[] playlistSamplesArray = playlistSamples.toArray(new UriSample[0]);
new UriSample[playlistSamples.size()]);
return new PlaylistSample(sampleName, drmInfo, playlistSamplesArray); return new PlaylistSample(sampleName, drmInfo, playlistSamplesArray);
} else { } else {
return new UriSample( return new UriSample(
......
...@@ -42,7 +42,15 @@ ...@@ -42,7 +42,15 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:visibility="gone"/> android:visibility="gone">
<Button android:id="@+id/select_tracks_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/track_selection_title"
android:enabled="false"/>
</LinearLayout>
</LinearLayout> </LinearLayout>
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager.widget.ViewPager
android:id="@+id/track_selection_dialog_view_pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<com.google.android.material.tabs.TabLayout
android:id="@+id/track_selection_dialog_tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabGravity="fill"
app:tabMode="fixed"/>
</androidx.viewpager.widget.ViewPager>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end">
<Button
android:id="@+id/track_selection_dialog_cancel_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@android:string/cancel"
style="?android:attr/borderlessButtonStyle"/>
<Button
android:id="@+id/track_selection_dialog_ok_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@android:string/ok"
style="?android:attr/borderlessButtonStyle"/>
</LinearLayout>
</LinearLayout>
...@@ -13,13 +13,14 @@ ...@@ -13,13 +13,14 @@
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/prefer_extension_decoders" <item android:id="@+id/prefer_extension_decoders"
android:title="@string/prefer_extension_decoders" android:title="@string/prefer_extension_decoders"
android:showAsAction="never" android:checkable="true"
android:checkable="true"/> app:showAsAction="never"/>
<item android:id="@+id/random_abr" <item android:id="@+id/random_abr"
android:title="@string/random_abr" android:title="@string/random_abr"
android:showAsAction="never" android:checkable="true"
android:checkable="true"/> app:showAsAction="never"/>
</menu> </menu>
...@@ -17,6 +17,8 @@ ...@@ -17,6 +17,8 @@
<string name="application_name">ExoPlayer</string> <string name="application_name">ExoPlayer</string>
<string name="track_selection_title">Select tracks</string>
<string name="unexpected_intent_action">Unexpected intent action: <xliff:g id="action">%1$s</xliff:g></string> <string name="unexpected_intent_action">Unexpected intent action: <xliff:g id="action">%1$s</xliff:g></string>
<string name="error_cleartext_not_permitted">Cleartext traffic not permitted</string> <string name="error_cleartext_not_permitted">Cleartext traffic not permitted</string>
......
...@@ -15,8 +15,11 @@ ...@@ -15,8 +15,11 @@
--> -->
<resources xmlns:android="http://schemas.android.com/apk/res/android"> <resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="PlayerTheme" parent="android:Theme.Holo"> <style name="TrackSelectionDialogThemeOverlay" parent="ThemeOverlay.AppCompat.Dialog.Alert">
<item name="android:windowNoTitle">true</item> <item name="windowNoTitle">false</item>
</style>
<style name="PlayerTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowBackground">@android:color/black</item> <item name="android:windowBackground">@android:color/black</item>
</style> </style>
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
The cast extension is a [Player][] implementation that controls playback on a The cast extension is a [Player][] implementation that controls playback on a
Cast receiver app. Cast receiver app.
[Player]: https://google.github.io/ExoPlayer/doc/reference/index.html?com/google/android/exoplayer2/Player.html [Player]: https://exoplayer.dev/doc/reference/index.html?com/google/android/exoplayer2/Player.html
## Getting the extension ## ## Getting the extension ##
......
...@@ -16,7 +16,6 @@ apply plugin: 'com.android.library' ...@@ -16,7 +16,6 @@ apply plugin: 'com.android.library'
android { android {
compileSdkVersion project.ext.compileSdkVersion compileSdkVersion project.ext.compileSdkVersion
buildToolsVersion project.ext.buildToolsVersion
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
...@@ -24,32 +23,21 @@ android { ...@@ -24,32 +23,21 @@ android {
} }
defaultConfig { defaultConfig {
minSdkVersion 14 minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion targetSdkVersion project.ext.targetSdkVersion
consumerProguardFiles 'proguard-rules.txt'
} }
testOptions.unitTests.includeAndroidResources = true
} }
dependencies { dependencies {
api 'com.google.android.gms:play-services-cast-framework:16.1.2' api 'com.google.android.gms:play-services-cast-framework:16.1.2'
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion implementation 'androidx.annotation:annotation:1.0.2'
compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkVersion
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
implementation project(modulePrefix + 'library-ui') implementation project(modulePrefix + 'library-ui')
testImplementation project(modulePrefix + 'testutils') compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
testImplementation 'junit:junit:' + junitVersion compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkVersion
testImplementation 'org.mockito:mockito-core:' + mockitoVersion
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
testImplementation project(modulePrefix + 'testutils-robolectric') testImplementation project(modulePrefix + 'testutils-robolectric')
// These dependencies are necessary to force the supportLibraryVersion of
// com.android.support:support-v4, com.android.support:appcompat-v7 and
// com.android.support:mediarouter-v7 to be used. Else older versions are
// used, for example via:
// com.google.android.gms:play-services-cast-framework:15.0.1
// |-- com.android.support:mediarouter-v7:26.1.0
api 'com.android.support:support-v4:' + supportLibraryVersion
api 'com.android.support:mediarouter-v7:' + supportLibraryVersion
api 'com.android.support:recyclerview-v7:' + supportLibraryVersion
} }
ext { ext {
......
# Proguard rules specific to the Cast extension.
# DefaultCastOptionsProvider is commonly referred to only by the app's manifest.
-keep class com.google.android.exoplayer2.ext.cast.DefaultCastOptionsProvider
...@@ -16,8 +16,8 @@ ...@@ -16,8 +16,8 @@
package com.google.android.exoplayer2.ext.cast; package com.google.android.exoplayer2.ext.cast;
import android.os.Looper; import android.os.Looper;
import android.support.annotation.NonNull; import androidx.annotation.NonNull;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.BasePlayer; import com.google.android.exoplayer2.BasePlayer;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
...@@ -52,35 +52,18 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -52,35 +52,18 @@ import java.util.concurrent.CopyOnWriteArraySet;
* {@link Player} implementation that communicates with a Cast receiver app. * {@link Player} implementation that communicates with a Cast receiver app.
* *
* <p>The behavior of this class depends on the underlying Cast session, which is obtained from the * <p>The behavior of this class depends on the underlying Cast session, which is obtained from the
* Cast context passed to {@link #CastPlayer}. To keep track of the session, * Cast context passed to {@link #CastPlayer}. To keep track of the session, {@link
* {@link #isCastSessionAvailable()} can be queried and {@link SessionAvailabilityListener} can be * #isCastSessionAvailable()} can be queried and {@link SessionAvailabilityListener} can be
* implemented and attached to the player.</p> * implemented and attached to the player.
* *
* <p> If no session is available, the player state will remain unchanged and calls to methods that * <p>If no session is available, the player state will remain unchanged and calls to methods that
* alter it will be ignored. Querying the player state is possible even when no session is * alter it will be ignored. Querying the player state is possible even when no session is
* available, in which case, the last observed receiver app state is reported.</p> * available, in which case, the last observed receiver app state is reported.
* *
* <p>Methods should be called on the application's main thread.</p> * <p>Methods should be called on the application's main thread.
*/ */
public final class CastPlayer extends BasePlayer { public final class CastPlayer extends BasePlayer {
/**
* Listener of changes in the cast session availability.
*/
public interface SessionAvailabilityListener {
/**
* Called when a cast session becomes available to the player.
*/
void onCastSessionAvailable();
/**
* Called when the cast session becomes unavailable.
*/
void onCastSessionUnavailable();
}
private static final String TAG = "CastPlayer"; private static final String TAG = "CastPlayer";
private static final int RENDERER_COUNT = 3; private static final int RENDERER_COUNT = 3;
...@@ -591,7 +574,9 @@ public final class CastPlayer extends BasePlayer { ...@@ -591,7 +574,9 @@ public final class CastPlayer extends BasePlayer {
CastTimeline oldTimeline = currentTimeline; CastTimeline oldTimeline = currentTimeline;
MediaStatus status = getMediaStatus(); MediaStatus status = getMediaStatus();
currentTimeline = currentTimeline =
status != null ? timelineTracker.getCastTimeline(status) : CastTimeline.EMPTY_CAST_TIMELINE; status != null
? timelineTracker.getCastTimeline(remoteMediaClient)
: CastTimeline.EMPTY_CAST_TIMELINE;
return !oldTimeline.equals(currentTimeline); return !oldTimeline.equals(currentTimeline);
} }
......
...@@ -15,24 +15,66 @@ ...@@ -15,24 +15,66 @@
*/ */
package com.google.android.exoplayer2.ext.cast; package com.google.android.exoplayer2.ext.cast;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import android.util.SparseArray;
import android.util.SparseIntArray; import android.util.SparseIntArray;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaQueueItem;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/** /**
* A {@link Timeline} for Cast media queues. * A {@link Timeline} for Cast media queues.
*/ */
/* package */ final class CastTimeline extends Timeline { /* package */ final class CastTimeline extends Timeline {
/** Holds {@link Timeline} related data for a Cast media item. */
public static final class ItemData {
/** Holds no media information. */
public static final ItemData EMPTY = new ItemData();
/** The duration of the item in microseconds, or {@link C#TIME_UNSET} if unknown. */
public final long durationUs;
/**
* The default start position of the item in microseconds, or {@link C#TIME_UNSET} if unknown.
*/
public final long defaultPositionUs;
private ItemData() {
this(/* durationUs= */ C.TIME_UNSET, /* defaultPositionUs */ C.TIME_UNSET);
}
/**
* Creates an instance.
*
* @param durationUs See {@link #durationsUs}.
* @param defaultPositionUs See {@link #defaultPositionUs}.
*/
public ItemData(long durationUs, long defaultPositionUs) {
this.durationUs = durationUs;
this.defaultPositionUs = defaultPositionUs;
}
/** Returns an instance with the given {@link #durationsUs}. */
public ItemData copyWithDurationUs(long durationUs) {
if (durationUs == this.durationUs) {
return this;
}
return new ItemData(durationUs, defaultPositionUs);
}
/** Returns an instance with the given {@link #defaultPositionsUs}. */
public ItemData copyWithDefaultPositionUs(long defaultPositionUs) {
if (defaultPositionUs == this.defaultPositionUs) {
return this;
}
return new ItemData(durationUs, defaultPositionUs);
}
}
/** {@link Timeline} for a cast queue that has no items. */
public static final CastTimeline EMPTY_CAST_TIMELINE = public static final CastTimeline EMPTY_CAST_TIMELINE =
new CastTimeline(Collections.emptyList(), Collections.emptyMap()); new CastTimeline(new int[0], new SparseArray<>());
private final SparseIntArray idsToIndex; private final SparseIntArray idsToIndex;
private final int[] ids; private final int[] ids;
...@@ -40,28 +82,23 @@ import java.util.Map; ...@@ -40,28 +82,23 @@ import java.util.Map;
private final long[] defaultPositionsUs; private final long[] defaultPositionsUs;
/** /**
* @param items A list of cast media queue items to represent. * Creates a Cast timeline from the given data.
* @param contentIdToDurationUsMap A map of content id to duration in microseconds. *
* @param itemIds The ids of the items in the timeline.
* @param itemIdToData Maps item ids to {@link ItemData}.
*/ */
public CastTimeline(List<MediaQueueItem> items, Map<String, Long> contentIdToDurationUsMap) { public CastTimeline(int[] itemIds, SparseArray<ItemData> itemIdToData) {
int itemCount = items.size(); int itemCount = itemIds.length;
int index = 0;
idsToIndex = new SparseIntArray(itemCount); idsToIndex = new SparseIntArray(itemCount);
ids = new int[itemCount]; ids = Arrays.copyOf(itemIds, itemCount);
durationsUs = new long[itemCount]; durationsUs = new long[itemCount];
defaultPositionsUs = new long[itemCount]; defaultPositionsUs = new long[itemCount];
for (MediaQueueItem item : items) { for (int i = 0; i < ids.length; i++) {
int itemId = item.getItemId(); int id = ids[i];
ids[index] = itemId; idsToIndex.put(id, i);
idsToIndex.put(itemId, index); ItemData data = itemIdToData.get(id, ItemData.EMPTY);
MediaInfo mediaInfo = item.getMedia(); durationsUs[i] = data.durationUs;
String contentId = mediaInfo.getContentId(); defaultPositionsUs[i] = data.defaultPositionUs;
durationsUs[index] =
contentIdToDurationUsMap.containsKey(contentId)
? contentIdToDurationUsMap.get(contentId)
: CastUtils.getStreamDurationUs(mediaInfo);
defaultPositionsUs[index] = (long) (item.getStartTime() * C.MICROS_PER_SECOND);
index++;
} }
} }
...@@ -108,7 +145,7 @@ import java.util.Map; ...@@ -108,7 +145,7 @@ import java.util.Map;
} }
@Override @Override
public Object getUidOfPeriod(int periodIndex) { public Integer getUidOfPeriod(int periodIndex) {
return ids[periodIndex]; return ids[periodIndex];
} }
......
...@@ -15,53 +15,84 @@ ...@@ -15,53 +15,84 @@
*/ */
package com.google.android.exoplayer2.ext.cast; package com.google.android.exoplayer2.ext.cast;
import com.google.android.gms.cast.MediaInfo; import android.util.SparseArray;
import com.google.android.exoplayer2.C;
import com.google.android.gms.cast.MediaQueueItem; import com.google.android.gms.cast.MediaQueueItem;
import com.google.android.gms.cast.MediaStatus; import com.google.android.gms.cast.MediaStatus;
import java.util.HashMap; import com.google.android.gms.cast.framework.media.RemoteMediaClient;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
/** /**
* Creates {@link CastTimeline}s from cast receiver app media status. * Creates {@link CastTimeline CastTimelines} from cast receiver app status updates.
* *
* <p>This class keeps track of the duration reported by the current item to fill any missing * <p>This class keeps track of the duration reported by the current item to fill any missing
* durations in the media queue items [See internal: b/65152553]. * durations in the media queue items [See internal: b/65152553].
*/ */
/* package */ final class CastTimelineTracker { /* package */ final class CastTimelineTracker {
private final HashMap<String, Long> contentIdToDurationUsMap; private final SparseArray<CastTimeline.ItemData> itemIdToData;
private final HashSet<String> scratchContentIdSet;
public CastTimelineTracker() { public CastTimelineTracker() {
contentIdToDurationUsMap = new HashMap<>(); itemIdToData = new SparseArray<>();
scratchContentIdSet = new HashSet<>();
} }
/** /**
* Returns a {@link CastTimeline} that represent the given {@code status}. * Returns a {@link CastTimeline} that represents the state of the given {@code
* remoteMediaClient}.
* *
* @param status The Cast media status. * <p>Returned timelines may contain values obtained from {@code remoteMediaClient} in previous
* @return A {@link CastTimeline} that represent the given {@code status}. * invocations of this method.
*
* @param remoteMediaClient The Cast media client.
* @return A {@link CastTimeline} that represents the given {@code remoteMediaClient} status.
*/ */
public CastTimeline getCastTimeline(MediaStatus status) { public CastTimeline getCastTimeline(RemoteMediaClient remoteMediaClient) {
MediaInfo mediaInfo = status.getMediaInfo(); int[] itemIds = remoteMediaClient.getMediaQueue().getItemIds();
List<MediaQueueItem> items = status.getQueueItems(); if (itemIds.length > 0) {
removeUnusedDurationEntries(items); // Only remove unused items when there is something in the queue to avoid removing all entries
// if the remote media client clears the queue temporarily. See [Internal ref: b/128825216].
removeUnusedItemDataEntries(itemIds);
}
if (mediaInfo != null) { // TODO: Reset state when the app instance changes [Internal ref: b/129672468].
String contentId = mediaInfo.getContentId(); MediaStatus mediaStatus = remoteMediaClient.getMediaStatus();
long durationUs = CastUtils.getStreamDurationUs(mediaInfo); if (mediaStatus == null) {
contentIdToDurationUsMap.put(contentId, durationUs); return CastTimeline.EMPTY_CAST_TIMELINE;
} }
return new CastTimeline(items, contentIdToDurationUsMap);
int currentItemId = mediaStatus.getCurrentItemId();
long durationUs = CastUtils.getStreamDurationUs(mediaStatus.getMediaInfo());
itemIdToData.put(
currentItemId,
itemIdToData
.get(currentItemId, CastTimeline.ItemData.EMPTY)
.copyWithDurationUs(durationUs));
for (MediaQueueItem item : mediaStatus.getQueueItems()) {
int itemId = item.getItemId();
itemIdToData.put(
itemId,
itemIdToData
.get(itemId, CastTimeline.ItemData.EMPTY)
.copyWithDefaultPositionUs((long) (item.getStartTime() * C.MICROS_PER_SECOND)));
} }
private void removeUnusedDurationEntries(List<MediaQueueItem> items) { return new CastTimeline(itemIds, itemIdToData);
scratchContentIdSet.clear(); }
for (MediaQueueItem item : items) {
scratchContentIdSet.add(item.getMedia().getContentId()); private void removeUnusedItemDataEntries(int[] itemIds) {
HashSet<Integer> scratchItemIds = new HashSet<>(/* initialCapacity= */ itemIds.length * 2);
for (int id : itemIds) {
scratchItemIds.add(id);
}
int index = 0;
while (index < itemIdToData.size()) {
if (!scratchItemIds.contains(itemIdToData.keyAt(index))) {
itemIdToData.removeAt(index);
} else {
index++;
}
} }
contentIdToDurationUsMap.keySet().retainAll(scratchContentIdSet);
} }
} }
...@@ -31,11 +31,13 @@ import com.google.android.gms.cast.MediaTrack; ...@@ -31,11 +31,13 @@ import com.google.android.gms.cast.MediaTrack;
* unknown or not applicable. * unknown or not applicable.
* *
* @param mediaInfo The media info to get the duration from. * @param mediaInfo The media info to get the duration from.
* @return The duration in microseconds. * @return The duration in microseconds, or {@link C#TIME_UNSET} if unknown or not applicable.
*/ */
public static long getStreamDurationUs(MediaInfo mediaInfo) { public static long getStreamDurationUs(MediaInfo mediaInfo) {
long durationMs = if (mediaInfo == null) {
mediaInfo != null ? mediaInfo.getStreamDuration() : MediaInfo.UNKNOWN_DURATION; return C.TIME_UNSET;
}
long durationMs = mediaInfo.getStreamDuration();
return durationMs != MediaInfo.UNKNOWN_DURATION ? C.msToUs(durationMs) : C.TIME_UNSET; return durationMs != MediaInfo.UNKNOWN_DURATION ? C.msToUs(durationMs) : C.TIME_UNSET;
} }
...@@ -109,6 +111,7 @@ import com.google.android.gms.cast.MediaTrack; ...@@ -109,6 +111,7 @@ import com.google.android.gms.cast.MediaTrack;
/* codecs= */ null, /* codecs= */ null,
/* bitrate= */ Format.NO_VALUE, /* bitrate= */ Format.NO_VALUE,
/* selectionFlags= */ 0, /* selectionFlags= */ 0,
/* roleFlags= */ 0,
mediaTrack.getLanguage()); mediaTrack.getLanguage());
} }
......
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ext.cast;
/** Represents a sequence of {@link MediaItem MediaItems}. */
public interface MediaItemQueue {
/**
* Returns the item at the given index.
*
* @param index The index of the item to retrieve.
* @return The item at the given index.
* @throws IndexOutOfBoundsException If {@code index < 0 || index >= getSize()}.
*/
MediaItem get(int index);
/** Returns the number of items in this queue. */
int getSize();
/**
* Appends the given sequence of items to the queue.
*
* @param items The sequence of items to append.
*/
void add(MediaItem... items);
/**
* Adds the given sequence of items to the queue at the given position, so that the first of
* {@code items} is placed at the given index.
*
* @param index The index at which {@code items} will be inserted.
* @param items The sequence of items to append.
* @throws IndexOutOfBoundsException If {@code index < 0 || index > getSize()}.
*/
void add(int index, MediaItem... items);
/**
* Moves an existing item within the playlist.
*
* <p>Calling this method is equivalent to removing the item at position {@code indexFrom} and
* immediately inserting it at position {@code indexTo}. If the moved item is being played at the
* moment of the invocation, playback will stick with the moved item.
*
* @param indexFrom The index of the item to move.
* @param indexTo The index at which the item will be placed after this operation.
* @throws IndexOutOfBoundsException If for either index, {@code index < 0 || index >= getSize()}.
*/
void move(int indexFrom, int indexTo);
/**
* Removes an item from the queue.
*
* @param index The index of the item to remove from the queue.
* @throws IndexOutOfBoundsException If {@code index < 0 || index >= getSize()}.
*/
void remove(int index);
/**
* Removes a range of items from the queue.
*
* <p>Does nothing if an empty range ({@code from == exclusiveTo}) is passed.
*
* @param from The inclusive index at which the range to remove starts.
* @param exclusiveTo The exclusive index at which the range to remove ends.
* @throws IndexOutOfBoundsException If {@code from < 0 || exclusiveTo > getSize() || from >
* exclusiveTo}.
*/
void removeRange(int from, int exclusiveTo);
/** Removes all items in the queue. */
void clear();
}
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ext.cast;
/** Listener of changes in the cast session availability. */
public interface SessionAvailabilityListener {
/** Called when a cast session becomes available to the player. */
void onCastSessionAvailable();
/** Called when the cast session becomes unavailable. */
void onCastSessionUnavailable();
}
...@@ -15,23 +15,23 @@ ...@@ -15,23 +15,23 @@
*/ */
package com.google.android.exoplayer2.ext.cast; package com.google.android.exoplayer2.ext.cast;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.testutil.TimelineAsserts; import com.google.android.exoplayer2.testutil.TimelineAsserts;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.gms.cast.MediaInfo; import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaQueueItem;
import com.google.android.gms.cast.MediaStatus; import com.google.android.gms.cast.MediaStatus;
import java.util.ArrayList; import com.google.android.gms.cast.framework.media.MediaQueue;
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
import java.util.Collections;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.robolectric.RobolectricTestRunner;
/** Tests for {@link CastTimelineTracker}. */ /** Tests for {@link CastTimelineTracker}. */
@RunWith(RobolectricTestRunner.class) @RunWith(AndroidJUnit4.class)
public class CastTimelineTrackerTest { public class CastTimelineTrackerTest {
private static final long DURATION_1_MS = 1000;
private static final long DURATION_2_MS = 2000; private static final long DURATION_2_MS = 2000;
private static final long DURATION_3_MS = 3000; private static final long DURATION_3_MS = 3000;
private static final long DURATION_4_MS = 4000; private static final long DURATION_4_MS = 4000;
...@@ -39,91 +39,89 @@ public class CastTimelineTrackerTest { ...@@ -39,91 +39,89 @@ public class CastTimelineTrackerTest {
/** Tests that duration of the current media info is correctly propagated to the timeline. */ /** Tests that duration of the current media info is correctly propagated to the timeline. */
@Test @Test
public void testGetCastTimeline() { public void testGetCastTimelinePersistsDuration() {
MediaInfo mediaInfo;
MediaStatus status =
mockMediaStatus(
new int[] {1, 2, 3},
new String[] {"contentId1", "contentId2", "contentId3"},
new long[] {DURATION_1_MS, MediaInfo.UNKNOWN_DURATION, MediaInfo.UNKNOWN_DURATION});
CastTimelineTracker tracker = new CastTimelineTracker(); CastTimelineTracker tracker = new CastTimelineTracker();
mediaInfo = getMediaInfo("contentId1", DURATION_1_MS);
Mockito.when(status.getMediaInfo()).thenReturn(mediaInfo);
TimelineAsserts.assertPeriodDurations(
tracker.getCastTimeline(status), C.msToUs(DURATION_1_MS), C.TIME_UNSET, C.TIME_UNSET);
mediaInfo = getMediaInfo("contentId3", DURATION_3_MS); RemoteMediaClient remoteMediaClient =
Mockito.when(status.getMediaInfo()).thenReturn(mediaInfo); mockRemoteMediaClient(
/* itemIds= */ new int[] {1, 2, 3, 4, 5},
/* currentItemId= */ 2,
/* currentDurationMs= */ DURATION_2_MS);
TimelineAsserts.assertPeriodDurations( TimelineAsserts.assertPeriodDurations(
tracker.getCastTimeline(status), tracker.getCastTimeline(remoteMediaClient),
C.msToUs(DURATION_1_MS),
C.TIME_UNSET, C.TIME_UNSET,
C.msToUs(DURATION_3_MS)); C.msToUs(DURATION_2_MS),
C.TIME_UNSET,
C.TIME_UNSET,
C.TIME_UNSET);
mediaInfo = getMediaInfo("contentId2", DURATION_2_MS); remoteMediaClient =
Mockito.when(status.getMediaInfo()).thenReturn(mediaInfo); mockRemoteMediaClient(
/* itemIds= */ new int[] {1, 2, 3},
/* currentItemId= */ 3,
/* currentDurationMs= */ DURATION_3_MS);
TimelineAsserts.assertPeriodDurations( TimelineAsserts.assertPeriodDurations(
tracker.getCastTimeline(status), tracker.getCastTimeline(remoteMediaClient),
C.msToUs(DURATION_1_MS), C.TIME_UNSET,
C.msToUs(DURATION_2_MS), C.msToUs(DURATION_2_MS),
C.msToUs(DURATION_3_MS)); C.msToUs(DURATION_3_MS));
MediaStatus newStatus = remoteMediaClient =
mockMediaStatus( mockRemoteMediaClient(
new int[] {4, 1, 5, 3}, /* itemIds= */ new int[] {1, 3},
new String[] {"contentId4", "contentId1", "contentId5", "contentId3"}, /* currentItemId= */ 3,
new long[] { /* currentDurationMs= */ DURATION_3_MS);
MediaInfo.UNKNOWN_DURATION,
MediaInfo.UNKNOWN_DURATION,
DURATION_5_MS,
MediaInfo.UNKNOWN_DURATION
});
mediaInfo = getMediaInfo("contentId5", DURATION_5_MS);
Mockito.when(newStatus.getMediaInfo()).thenReturn(mediaInfo);
TimelineAsserts.assertPeriodDurations( TimelineAsserts.assertPeriodDurations(
tracker.getCastTimeline(newStatus), tracker.getCastTimeline(remoteMediaClient), C.TIME_UNSET, C.msToUs(DURATION_3_MS));
C.TIME_UNSET,
C.msToUs(DURATION_1_MS),
C.msToUs(DURATION_5_MS),
C.msToUs(DURATION_3_MS));
mediaInfo = getMediaInfo("contentId3", DURATION_3_MS); remoteMediaClient =
Mockito.when(newStatus.getMediaInfo()).thenReturn(mediaInfo); mockRemoteMediaClient(
/* itemIds= */ new int[] {1, 2, 3, 4, 5},
/* currentItemId= */ 4,
/* currentDurationMs= */ DURATION_4_MS);
TimelineAsserts.assertPeriodDurations( TimelineAsserts.assertPeriodDurations(
tracker.getCastTimeline(newStatus), tracker.getCastTimeline(remoteMediaClient),
C.TIME_UNSET, C.TIME_UNSET,
C.msToUs(DURATION_1_MS), C.TIME_UNSET,
C.msToUs(DURATION_5_MS), C.msToUs(DURATION_3_MS),
C.msToUs(DURATION_3_MS)); C.msToUs(DURATION_4_MS),
C.TIME_UNSET);
mediaInfo = getMediaInfo("contentId4", DURATION_4_MS); remoteMediaClient =
Mockito.when(newStatus.getMediaInfo()).thenReturn(mediaInfo); mockRemoteMediaClient(
/* itemIds= */ new int[] {1, 2, 3, 4, 5},
/* currentItemId= */ 5,
/* currentDurationMs= */ DURATION_5_MS);
TimelineAsserts.assertPeriodDurations( TimelineAsserts.assertPeriodDurations(
tracker.getCastTimeline(newStatus), tracker.getCastTimeline(remoteMediaClient),
C.TIME_UNSET,
C.TIME_UNSET,
C.msToUs(DURATION_3_MS),
C.msToUs(DURATION_4_MS), C.msToUs(DURATION_4_MS),
C.msToUs(DURATION_1_MS), C.msToUs(DURATION_5_MS));
C.msToUs(DURATION_5_MS),
C.msToUs(DURATION_3_MS));
} }
private static MediaStatus mockMediaStatus( private static RemoteMediaClient mockRemoteMediaClient(
int[] itemIds, String[] contentIds, long[] durationsMs) { int[] itemIds, int currentItemId, long currentDurationMs) {
ArrayList<MediaQueueItem> items = new ArrayList<>(); RemoteMediaClient remoteMediaClient = Mockito.mock(RemoteMediaClient.class);
for (int i = 0; i < contentIds.length; i++) {
MediaInfo mediaInfo = getMediaInfo(contentIds[i], durationsMs[i]);
MediaQueueItem item = Mockito.mock(MediaQueueItem.class);
Mockito.when(item.getMedia()).thenReturn(mediaInfo);
Mockito.when(item.getItemId()).thenReturn(itemIds[i]);
items.add(item);
}
MediaStatus status = Mockito.mock(MediaStatus.class); MediaStatus status = Mockito.mock(MediaStatus.class);
Mockito.when(status.getQueueItems()).thenReturn(items); Mockito.when(status.getQueueItems()).thenReturn(Collections.emptyList());
return status; Mockito.when(remoteMediaClient.getMediaStatus()).thenReturn(status);
Mockito.when(status.getMediaInfo()).thenReturn(getMediaInfo(currentDurationMs));
Mockito.when(status.getCurrentItemId()).thenReturn(currentItemId);
MediaQueue mediaQueue = mockMediaQueue(itemIds);
Mockito.when(remoteMediaClient.getMediaQueue()).thenReturn(mediaQueue);
return remoteMediaClient;
}
private static MediaQueue mockMediaQueue(int[] itemIds) {
MediaQueue mediaQueue = Mockito.mock(MediaQueue.class);
Mockito.when(mediaQueue.getItemIds()).thenReturn(itemIds);
return mediaQueue;
} }
private static MediaInfo getMediaInfo(String contentId, long durationMs) { private static MediaInfo getMediaInfo(long durationMs) {
return new MediaInfo.Builder(contentId) return new MediaInfo.Builder(/*contentId= */ "")
.setStreamDuration(durationMs) .setStreamDuration(durationMs)
.setContentType(MimeTypes.APPLICATION_MP4) .setContentType(MimeTypes.APPLICATION_MP4)
.setStreamType(MediaInfo.STREAM_TYPE_NONE) .setStreamType(MediaInfo.STREAM_TYPE_NONE)
......
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ext.cast;
import static com.google.common.truth.Truth.assertThat;
import android.net.Uri;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Test for {@link MediaItem}. */
@RunWith(AndroidJUnit4.class)
public class MediaItemTest {
@Test
public void buildMediaItem_resetsUuid() {
MediaItem.Builder builder = new MediaItem.Builder();
UUID uuid = new UUID(1, 1);
MediaItem item1 = builder.setUuid(uuid).build();
MediaItem item2 = builder.build();
MediaItem item3 = builder.build();
assertThat(item1.uuid).isEqualTo(uuid);
assertThat(item2.uuid).isNotEqualTo(uuid);
assertThat(item3.uuid).isNotEqualTo(item2.uuid);
assertThat(item3.uuid).isNotEqualTo(uuid);
}
@Test
public void buildMediaItem_doesNotChangeState() {
MediaItem.Builder builder = new MediaItem.Builder();
MediaItem item1 =
builder
.setUuid(new UUID(0, 1))
.setMedia("http://example.com")
.setTitle("title")
.setMimeType(MimeTypes.AUDIO_MP4)
.setStartPositionUs(3)
.setEndPositionUs(4)
.build();
MediaItem item2 = builder.setUuid(new UUID(0, 1)).build();
assertThat(item1).isEqualTo(item2);
}
@Test
public void buildMediaItem_assertDefaultValues() {
assertDefaultValues(new MediaItem.Builder().build());
}
@Test
public void buildAndClear_assertDefaultValues() {
MediaItem.Builder builder = new MediaItem.Builder();
builder
.setMedia("http://example.com")
.setTitle("title")
.setMimeType(MimeTypes.AUDIO_MP4)
.setStartPositionUs(3)
.setEndPositionUs(4)
.buildAndClear();
assertDefaultValues(builder.build());
}
@Test
public void equals_withEqualDrmSchemes_returnsTrue() {
MediaItem.Builder builder = new MediaItem.Builder();
MediaItem mediaItem1 =
builder
.setUuid(new UUID(0, 1))
.setMedia("www.google.com")
.setDrmSchemes(createDummyDrmSchemes(1))
.buildAndClear();
MediaItem mediaItem2 =
builder
.setUuid(new UUID(0, 1))
.setMedia("www.google.com")
.setDrmSchemes(createDummyDrmSchemes(1))
.buildAndClear();
assertThat(mediaItem1).isEqualTo(mediaItem2);
}
@Test
public void equals_withDifferentDrmRequestHeaders_returnsFalse() {
MediaItem.Builder builder = new MediaItem.Builder();
MediaItem mediaItem1 =
builder
.setUuid(new UUID(0, 1))
.setMedia("www.google.com")
.setDrmSchemes(createDummyDrmSchemes(1))
.buildAndClear();
MediaItem mediaItem2 =
builder
.setUuid(new UUID(0, 1))
.setMedia("www.google.com")
.setDrmSchemes(createDummyDrmSchemes(2))
.buildAndClear();
assertThat(mediaItem1).isNotEqualTo(mediaItem2);
}
private static void assertDefaultValues(MediaItem item) {
assertThat(item.title).isEmpty();
assertThat(item.description).isEmpty();
assertThat(item.media.uri).isEqualTo(Uri.EMPTY);
assertThat(item.attachment).isNull();
assertThat(item.drmSchemes).isEmpty();
assertThat(item.startPositionUs).isEqualTo(C.TIME_UNSET);
assertThat(item.endPositionUs).isEqualTo(C.TIME_UNSET);
assertThat(item.mimeType).isEmpty();
}
private static List<MediaItem.DrmScheme> createDummyDrmSchemes(int seed) {
HashMap<String, String> requestHeaders1 = new HashMap<>();
requestHeaders1.put("key1", "value1");
requestHeaders1.put("key2", "value1");
MediaItem.UriBundle uriBundle1 =
new MediaItem.UriBundle(Uri.parse("www.uri1.com"), requestHeaders1);
MediaItem.DrmScheme drmScheme1 = new MediaItem.DrmScheme(C.WIDEVINE_UUID, uriBundle1);
HashMap<String, String> requestHeaders2 = new HashMap<>();
requestHeaders2.put("key3", "value3");
requestHeaders2.put("key4", "valueWithSeed" + seed);
MediaItem.UriBundle uriBundle2 =
new MediaItem.UriBundle(Uri.parse("www.uri2.com"), requestHeaders2);
MediaItem.DrmScheme drmScheme2 = new MediaItem.DrmScheme(C.PLAYREADY_UUID, uriBundle2);
return Arrays.asList(drmScheme1, drmScheme2);
}
}
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
The Cronet extension is an [HttpDataSource][] implementation using [Cronet][]. The Cronet extension is an [HttpDataSource][] implementation using [Cronet][].
[HttpDataSource]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/upstream/HttpDataSource.html [HttpDataSource]: https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/upstream/HttpDataSource.html
[Cronet]: https://chromium.googlesource.com/chromium/src/+/master/components/cronet?autodive=0%2F%2F [Cronet]: https://chromium.googlesource.com/chromium/src/+/master/components/cronet?autodive=0%2F%2F
## Getting the extension ## ## Getting the extension ##
...@@ -52,4 +52,4 @@ respectively. ...@@ -52,4 +52,4 @@ respectively.
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.cronet.*` * [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.cronet.*`
belong to this module. belong to this module.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html [Javadoc]: https://exoplayer.dev/doc/reference/index.html
...@@ -16,10 +16,9 @@ apply plugin: 'com.android.library' ...@@ -16,10 +16,9 @@ apply plugin: 'com.android.library'
android { android {
compileSdkVersion project.ext.compileSdkVersion compileSdkVersion project.ext.compileSdkVersion
buildToolsVersion project.ext.buildToolsVersion
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion targetSdkVersion project.ext.targetSdkVersion
} }
...@@ -27,12 +26,14 @@ android { ...@@ -27,12 +26,14 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
testOptions.unitTests.includeAndroidResources = true
} }
dependencies { dependencies {
api 'org.chromium.net:cronet-embedded:71.3578.98' api 'org.chromium.net:cronet-embedded:73.3683.76'
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
implementation 'com.android.support:support-annotations:' + supportLibraryVersion implementation 'androidx.annotation:annotation:1.0.2'
testImplementation project(modulePrefix + 'library') testImplementation project(modulePrefix + 'library')
testImplementation project(modulePrefix + 'testutils-robolectric') testImplementation project(modulePrefix + 'testutils-robolectric')
} }
......
...@@ -16,10 +16,11 @@ ...@@ -16,10 +16,11 @@
package com.google.android.exoplayer2.ext.cronet; package com.google.android.exoplayer2.ext.cronet;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import android.text.TextUtils; import android.text.TextUtils;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.metadata.icy.IcyHeaders;
import com.google.android.exoplayer2.upstream.BaseDataSource; import com.google.android.exoplayer2.upstream.BaseDataSource;
import com.google.android.exoplayer2.upstream.DataSourceException; import com.google.android.exoplayer2.upstream.DataSourceException;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
...@@ -493,6 +494,11 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { ...@@ -493,6 +494,11 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
if (dataSpec.httpBody != null && !isContentTypeHeaderSet) { if (dataSpec.httpBody != null && !isContentTypeHeaderSet) {
throw new IOException("HTTP request with non-empty body must set Content-Type"); throw new IOException("HTTP request with non-empty body must set Content-Type");
} }
if (dataSpec.isFlagSet(DataSpec.FLAG_ALLOW_ICY_METADATA)) {
requestBuilder.addHeader(
IcyHeaders.REQUEST_HEADER_ENABLE_METADATA_NAME,
IcyHeaders.REQUEST_HEADER_ENABLE_METADATA_VALUE);
}
// Set the Range header. // Set the Range header.
if (dataSpec.position != 0 || dataSpec.length != C.LENGTH_UNSET) { if (dataSpec.position != 0 || dataSpec.length != C.LENGTH_UNSET) {
StringBuilder rangeValue = new StringBuilder(); StringBuilder rangeValue = new StringBuilder();
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
*/ */
package com.google.android.exoplayer2.ext.cronet; package com.google.android.exoplayer2.ext.cronet;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource.BaseFactory; import com.google.android.exoplayer2.upstream.HttpDataSource.BaseFactory;
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
package com.google.android.exoplayer2.ext.cronet; package com.google.android.exoplayer2.ext.cronet;
import android.content.Context; import android.content.Context;
import android.support.annotation.IntDef; import androidx.annotation.IntDef;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
......
...@@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertThat; ...@@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Arrays; import java.util.Arrays;
...@@ -28,10 +29,9 @@ import org.junit.Test; ...@@ -28,10 +29,9 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
/** Tests for {@link ByteArrayUploadDataProvider}. */ /** Tests for {@link ByteArrayUploadDataProvider}. */
@RunWith(RobolectricTestRunner.class) @RunWith(AndroidJUnit4.class)
public final class ByteArrayUploadDataProviderTest { public final class ByteArrayUploadDataProviderTest {
private static final byte[] TEST_DATA = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; private static final byte[] TEST_DATA = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
......
...@@ -31,6 +31,7 @@ import static org.mockito.Mockito.when; ...@@ -31,6 +31,7 @@ import static org.mockito.Mockito.when;
import android.net.Uri; import android.net.Uri;
import android.os.ConditionVariable; import android.os.ConditionVariable;
import android.os.SystemClock; import android.os.SystemClock;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource;
...@@ -62,10 +63,9 @@ import org.junit.Test; ...@@ -62,10 +63,9 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
/** Tests for {@link CronetDataSource}. */ /** Tests for {@link CronetDataSource}. */
@RunWith(RobolectricTestRunner.class) @RunWith(AndroidJUnit4.class)
public final class CronetDataSourceTest { public final class CronetDataSourceTest {
private static final int TEST_CONNECT_TIMEOUT_MS = 100; private static final int TEST_CONNECT_TIMEOUT_MS = 100;
......
...@@ -147,11 +147,11 @@ then implement your own logic to use the renderer for a given track. ...@@ -147,11 +147,11 @@ then implement your own logic to use the renderer for a given track.
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md [top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
[Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html [Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
[#2781]: https://github.com/google/ExoPlayer/issues/2781 [#2781]: https://github.com/google/ExoPlayer/issues/2781
[Supported formats]: https://google.github.io/ExoPlayer/supported-formats.html#ffmpeg-extension [Supported formats]: https://exoplayer.dev/supported-formats.html#ffmpeg-extension
## Links ## ## Links ##
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.ffmpeg.*` * [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.ffmpeg.*`
belong to this module. belong to this module.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html [Javadoc]: https://exoplayer.dev/doc/reference/index.html
...@@ -16,7 +16,6 @@ apply plugin: 'com.android.library' ...@@ -16,7 +16,6 @@ apply plugin: 'com.android.library'
android { android {
compileSdkVersion project.ext.compileSdkVersion compileSdkVersion project.ext.compileSdkVersion
buildToolsVersion project.ext.buildToolsVersion
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
...@@ -33,12 +32,15 @@ android { ...@@ -33,12 +32,15 @@ android {
jniLibs.srcDir 'src/main/libs' jniLibs.srcDir 'src/main/libs'
jni.srcDirs = [] // Disable the automatic ndk-build call by Android Studio. jni.srcDirs = [] // Disable the automatic ndk-build call by Android Studio.
} }
testOptions.unitTests.includeAndroidResources = true
} }
dependencies { dependencies {
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
implementation 'com.android.support:support-annotations:' + supportLibraryVersion implementation 'androidx.annotation:annotation:1.0.2'
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
testImplementation project(modulePrefix + 'testutils-robolectric')
} }
ext { ext {
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
package com.google.android.exoplayer2.ext.ffmpeg; package com.google.android.exoplayer2.ext.ffmpeg;
import android.os.Handler; import android.os.Handler;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
*/ */
package com.google.android.exoplayer2.ext.ffmpeg; package com.google.android.exoplayer2.ext.ffmpeg;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
......
...@@ -15,10 +15,11 @@ ...@@ -15,10 +15,11 @@
*/ */
package com.google.android.exoplayer2.ext.ffmpeg; package com.google.android.exoplayer2.ext.ffmpeg;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.util.LibraryLoader; import com.google.android.exoplayer2.util.LibraryLoader;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
/** /**
...@@ -30,6 +31,8 @@ public final class FfmpegLibrary { ...@@ -30,6 +31,8 @@ public final class FfmpegLibrary {
ExoPlayerLibraryInfo.registerModule("goog.exo.ffmpeg"); ExoPlayerLibraryInfo.registerModule("goog.exo.ffmpeg");
} }
private static final String TAG = "FfmpegLibrary";
private static final LibraryLoader LOADER = private static final LibraryLoader LOADER =
new LibraryLoader("avutil", "avresample", "avcodec", "ffmpeg"); new LibraryLoader("avutil", "avresample", "avcodec", "ffmpeg");
...@@ -69,7 +72,14 @@ public final class FfmpegLibrary { ...@@ -69,7 +72,14 @@ public final class FfmpegLibrary {
return false; return false;
} }
String codecName = getCodecName(mimeType, encoding); String codecName = getCodecName(mimeType, encoding);
return codecName != null && ffmpegHasDecoder(codecName); if (codecName == null) {
return false;
}
if (!ffmpegHasDecoder(codecName)) {
Log.w(TAG, "No " + codecName + " decoder available. Check the FFmpeg build configuration.");
return false;
}
return true;
} }
/** /**
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest package="com.google.android.exoplayer2.ext.ffmpeg"/>
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ext.ffmpeg;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.testutil.DefaultRenderersFactoryAsserts;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit test for {@link DefaultRenderersFactoryTest} with {@link FfmpegAudioRenderer}. */
@RunWith(AndroidJUnit4.class)
public final class DefaultRenderersFactoryTest {
@Test
public void createRenderers_instantiatesVpxRenderer() {
DefaultRenderersFactoryAsserts.assertExtensionRendererCreated(
FfmpegAudioRenderer.class, C.TRACK_TYPE_AUDIO);
}
}
...@@ -95,4 +95,4 @@ player, then implement your own logic to use the renderer for a given track. ...@@ -95,4 +95,4 @@ player, then implement your own logic to use the renderer for a given track.
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.flac.*` * [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.flac.*`
belong to this module. belong to this module.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html [Javadoc]: https://exoplayer.dev/doc/reference/index.html
...@@ -16,7 +16,6 @@ apply plugin: 'com.android.library' ...@@ -16,7 +16,6 @@ apply plugin: 'com.android.library'
android { android {
compileSdkVersion project.ext.compileSdkVersion compileSdkVersion project.ext.compileSdkVersion
buildToolsVersion project.ext.buildToolsVersion
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
...@@ -34,13 +33,15 @@ android { ...@@ -34,13 +33,15 @@ android {
jniLibs.srcDir 'src/main/libs' jniLibs.srcDir 'src/main/libs'
jni.srcDirs = [] // Disable the automatic ndk-build call by Android Studio. jni.srcDirs = [] // Disable the automatic ndk-build call by Android Studio.
} }
testOptions.unitTests.includeAndroidResources = true
} }
dependencies { dependencies {
implementation 'com.android.support:support-annotations:' + supportLibraryVersion
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
androidTestImplementation 'androidx.test:runner:' + testRunnerVersion implementation 'androidx.annotation:annotation:1.0.2'
androidTestImplementation project(modulePrefix + 'testutils') androidTestImplementation project(modulePrefix + 'testutils')
androidTestImplementation 'androidx.test:runner:' + androidXTestVersion
testImplementation project(modulePrefix + 'testutils-robolectric') testImplementation project(modulePrefix + 'testutils-robolectric')
} }
......
...@@ -18,6 +18,9 @@ ...@@ -18,6 +18,9 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer2.ext.flac.test"> package="com.google.android.exoplayer2.ext.flac.test">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-sdk/>
<application android:debuggable="true" <application android:debuggable="true"
android:allowBackup="false" android:allowBackup="false"
tools:ignore="MissingApplicationIcon,HardcodedDebugMode"> tools:ignore="MissingApplicationIcon,HardcodedDebugMode">
......
...@@ -16,22 +16,26 @@ ...@@ -16,22 +16,26 @@
package com.google.android.exoplayer2.ext.flac; package com.google.android.exoplayer2.ext.flac;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import android.test.InstrumentationTestCase; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.testutil.FakeExtractorInput; import com.google.android.exoplayer2.testutil.FakeExtractorInput;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
import java.io.IOException; import java.io.IOException;
import org.junit.Before;
import org.junit.runner.RunWith;
/** Unit test for {@link FlacBinarySearchSeeker}. */ /** Unit test for {@link FlacBinarySearchSeeker}. */
public final class FlacBinarySearchSeekerTest extends InstrumentationTestCase { @RunWith(AndroidJUnit4.class)
public final class FlacBinarySearchSeekerTest {
private static final String NOSEEKTABLE_FLAC = "bear_no_seek.flac"; private static final String NOSEEKTABLE_FLAC = "bear_no_seek.flac";
private static final int DURATION_US = 2_741_000; private static final int DURATION_US = 2_741_000;
@Override @Before
protected void setUp() throws Exception { public void setUp() {
super.setUp();
if (!FlacLibrary.isAvailable()) { if (!FlacLibrary.isAvailable()) {
fail("Flac library not available."); fail("Flac library not available.");
} }
...@@ -39,7 +43,8 @@ public final class FlacBinarySearchSeekerTest extends InstrumentationTestCase { ...@@ -39,7 +43,8 @@ public final class FlacBinarySearchSeekerTest extends InstrumentationTestCase {
public void testGetSeekMap_returnsSeekMapWithCorrectDuration() public void testGetSeekMap_returnsSeekMapWithCorrectDuration()
throws IOException, FlacDecoderException, InterruptedException { throws IOException, FlacDecoderException, InterruptedException {
byte[] data = TestUtil.getByteArray(getInstrumentation().getContext(), NOSEEKTABLE_FLAC); byte[] data =
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), NOSEEKTABLE_FLAC);
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data).build(); FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data).build();
FlacDecoderJni decoderJni = new FlacDecoderJni(); FlacDecoderJni decoderJni = new FlacDecoderJni();
...@@ -57,7 +62,8 @@ public final class FlacBinarySearchSeekerTest extends InstrumentationTestCase { ...@@ -57,7 +62,8 @@ public final class FlacBinarySearchSeekerTest extends InstrumentationTestCase {
public void testSetSeekTargetUs_returnsSeekPending() public void testSetSeekTargetUs_returnsSeekPending()
throws IOException, FlacDecoderException, InterruptedException { throws IOException, FlacDecoderException, InterruptedException {
byte[] data = TestUtil.getByteArray(getInstrumentation().getContext(), NOSEEKTABLE_FLAC); byte[] data =
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), NOSEEKTABLE_FLAC);
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data).build(); FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data).build();
FlacDecoderJni decoderJni = new FlacDecoderJni(); FlacDecoderJni decoderJni = new FlacDecoderJni();
......
...@@ -16,11 +16,13 @@ ...@@ -16,11 +16,13 @@
package com.google.android.exoplayer2.ext.flac; package com.google.android.exoplayer2.ext.flac;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import android.test.InstrumentationTestCase; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.DefaultExtractorInput; import com.google.android.exoplayer2.extractor.DefaultExtractorInput;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
...@@ -38,9 +40,12 @@ import com.google.android.exoplayer2.util.Util; ...@@ -38,9 +40,12 @@ import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import org.junit.Before;
import org.junit.runner.RunWith;
/** Seeking tests for {@link FlacExtractor} when the FLAC stream does not have a SEEKTABLE. */ /** Seeking tests for {@link FlacExtractor} when the FLAC stream does not have a SEEKTABLE. */
public final class FlacExtractorSeekTest extends InstrumentationTestCase { @RunWith(AndroidJUnit4.class)
public final class FlacExtractorSeekTest {
private static final String NO_SEEKTABLE_FLAC = "bear_no_seek.flac"; private static final String NO_SEEKTABLE_FLAC = "bear_no_seek.flac";
private static final int DURATION_US = 2_741_000; private static final int DURATION_US = 2_741_000;
...@@ -54,18 +59,18 @@ public final class FlacExtractorSeekTest extends InstrumentationTestCase { ...@@ -54,18 +59,18 @@ public final class FlacExtractorSeekTest extends InstrumentationTestCase {
private PositionHolder positionHolder; private PositionHolder positionHolder;
private long totalInputLength; private long totalInputLength;
@Override @Before
protected void setUp() throws Exception { public void setUp() throws Exception {
super.setUp();
if (!FlacLibrary.isAvailable()) { if (!FlacLibrary.isAvailable()) {
fail("Flac library not available."); fail("Flac library not available.");
} }
expectedOutput = new FakeExtractorOutput(); expectedOutput = new FakeExtractorOutput();
extractAllSamplesFromFileToExpectedOutput(getInstrumentation().getContext(), NO_SEEKTABLE_FLAC); extractAllSamplesFromFileToExpectedOutput(
ApplicationProvider.getApplicationContext(), NO_SEEKTABLE_FLAC);
expectedTrackOutput = expectedOutput.trackOutputs.get(0); expectedTrackOutput = expectedOutput.trackOutputs.get(0);
dataSource = dataSource =
new DefaultDataSourceFactory(getInstrumentation().getContext(), "UserAgent") new DefaultDataSourceFactory(ApplicationProvider.getApplicationContext(), "UserAgent")
.createDataSource(); .createDataSource();
totalInputLength = readInputLength(); totalInputLength = readInputLength();
positionHolder = new PositionHolder(); positionHolder = new PositionHolder();
......
...@@ -15,17 +15,20 @@ ...@@ -15,17 +15,20 @@
*/ */
package com.google.android.exoplayer2.ext.flac; package com.google.android.exoplayer2.ext.flac;
import android.test.InstrumentationTestCase; import static org.junit.Assert.fail;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.testutil.ExtractorAsserts; import com.google.android.exoplayer2.testutil.ExtractorAsserts;
import org.junit.Before;
import org.junit.runner.RunWith;
/** /** Unit test for {@link FlacExtractor}. */
* Unit test for {@link FlacExtractor}. @RunWith(AndroidJUnit4.class)
*/ public class FlacExtractorTest {
public class FlacExtractorTest extends InstrumentationTestCase {
@Override @Before
protected void setUp() throws Exception { public void setUp() throws Exception {
super.setUp();
if (!FlacLibrary.isAvailable()) { if (!FlacLibrary.isAvailable()) {
fail("Flac library not available."); fail("Flac library not available.");
} }
...@@ -33,11 +36,11 @@ public class FlacExtractorTest extends InstrumentationTestCase { ...@@ -33,11 +36,11 @@ public class FlacExtractorTest extends InstrumentationTestCase {
public void testExtractFlacSample() throws Exception { public void testExtractFlacSample() throws Exception {
ExtractorAsserts.assertBehavior( ExtractorAsserts.assertBehavior(
FlacExtractor::new, "bear.flac", getInstrumentation().getContext()); FlacExtractor::new, "bear.flac", ApplicationProvider.getApplicationContext());
} }
public void testExtractFlacSampleWithId3Header() throws Exception { public void testExtractFlacSampleWithId3Header() throws Exception {
ExtractorAsserts.assertBehavior( ExtractorAsserts.assertBehavior(
FlacExtractor::new, "bear_with_id3.flac", getInstrumentation().getContext()); FlacExtractor::new, "bear_with_id3.flac", ApplicationProvider.getApplicationContext());
} }
} }
...@@ -15,21 +15,21 @@ ...@@ -15,21 +15,21 @@
*/ */
package com.google.android.exoplayer2.ext.flac; package com.google.android.exoplayer2.ext.flac;
import static androidx.test.InstrumentationRegistry.getContext;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.os.Looper; import android.os.Looper;
import androidx.test.runner.AndroidJUnit4; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import org.junit.Before; import org.junit.Before;
...@@ -56,7 +56,7 @@ public class FlacPlaybackTest { ...@@ -56,7 +56,7 @@ public class FlacPlaybackTest {
private void playUri(String uri) throws Exception { private void playUri(String uri) throws Exception {
TestPlaybackRunnable testPlaybackRunnable = TestPlaybackRunnable testPlaybackRunnable =
new TestPlaybackRunnable(Uri.parse(uri), getContext()); new TestPlaybackRunnable(Uri.parse(uri), ApplicationProvider.getApplicationContext());
Thread thread = new Thread(testPlaybackRunnable); Thread thread = new Thread(testPlaybackRunnable);
thread.start(); thread.start();
thread.join(); thread.join();
...@@ -83,12 +83,12 @@ public class FlacPlaybackTest { ...@@ -83,12 +83,12 @@ public class FlacPlaybackTest {
Looper.prepare(); Looper.prepare();
LibflacAudioRenderer audioRenderer = new LibflacAudioRenderer(); LibflacAudioRenderer audioRenderer = new LibflacAudioRenderer();
DefaultTrackSelector trackSelector = new DefaultTrackSelector(); DefaultTrackSelector trackSelector = new DefaultTrackSelector();
player = ExoPlayerFactory.newInstance(new Renderer[] {audioRenderer}, trackSelector); player = ExoPlayerFactory.newInstance(context, new Renderer[] {audioRenderer}, trackSelector);
player.addListener(this); player.addListener(this);
MediaSource mediaSource = MediaSource mediaSource =
new ExtractorMediaSource.Factory( new ProgressiveMediaSource.Factory(
new DefaultDataSourceFactory(context, "ExoPlayerExtFlacTest")) new DefaultDataSourceFactory(context, "ExoPlayerExtFlacTest"),
.setExtractorsFactory(MatroskaExtractor.FACTORY) MatroskaExtractor.FACTORY)
.createMediaSource(uri); .createMediaSource(uri);
player.prepare(mediaSource); player.prepare(mediaSource);
player.setPlayWhenReady(true); player.setPlayWhenReady(true);
......
...@@ -17,8 +17,8 @@ package com.google.android.exoplayer2.ext.flac; ...@@ -17,8 +17,8 @@ package com.google.android.exoplayer2.ext.flac;
import static com.google.android.exoplayer2.util.Util.getPcmEncoding; import static com.google.android.exoplayer2.util.Util.getPcmEncoding;
import android.support.annotation.IntDef; import androidx.annotation.IntDef;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.BinarySearchSeeker; import com.google.android.exoplayer2.extractor.BinarySearchSeeker;
...@@ -94,7 +94,7 @@ public final class FlacExtractor implements Extractor { ...@@ -94,7 +94,7 @@ public final class FlacExtractor implements Extractor {
/** Constructs an instance with flags = 0. */ /** Constructs an instance with flags = 0. */
public FlacExtractor() { public FlacExtractor() {
this(0); this(/* flags= */ 0);
} }
/** /**
......
...@@ -42,7 +42,9 @@ public class LibflacAudioRenderer extends SimpleDecoderAudioRenderer { ...@@ -42,7 +42,9 @@ public class LibflacAudioRenderer extends SimpleDecoderAudioRenderer {
* @param eventListener A listener of events. May be null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required.
* @param audioProcessors Optional {@link AudioProcessor}s that will process audio before output. * @param audioProcessors Optional {@link AudioProcessor}s that will process audio before output.
*/ */
public LibflacAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener, public LibflacAudioRenderer(
Handler eventHandler,
AudioRendererEventListener eventListener,
AudioProcessor... audioProcessors) { AudioProcessor... audioProcessors) {
super(eventHandler, eventListener, audioProcessors); super(eventHandler, eventListener, audioProcessors);
} }
......
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.flac; ...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.flac;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.amr.AmrExtractor; import com.google.android.exoplayer2.extractor.amr.AmrExtractor;
...@@ -27,6 +28,7 @@ import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor; ...@@ -27,6 +28,7 @@ import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor;
import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor; import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor;
import com.google.android.exoplayer2.extractor.ogg.OggExtractor; import com.google.android.exoplayer2.extractor.ogg.OggExtractor;
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor; import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
import com.google.android.exoplayer2.extractor.ts.Ac4Extractor;
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor; import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
import com.google.android.exoplayer2.extractor.ts.PsExtractor; import com.google.android.exoplayer2.extractor.ts.PsExtractor;
import com.google.android.exoplayer2.extractor.ts.TsExtractor; import com.google.android.exoplayer2.extractor.ts.TsExtractor;
...@@ -35,10 +37,9 @@ import java.util.ArrayList; ...@@ -35,10 +37,9 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
/** Unit test for {@link DefaultExtractorsFactory}. */ /** Unit test for {@link DefaultExtractorsFactory}. */
@RunWith(RobolectricTestRunner.class) @RunWith(AndroidJUnit4.class)
public final class DefaultExtractorsFactoryTest { public final class DefaultExtractorsFactoryTest {
@Test @Test
...@@ -59,6 +60,7 @@ public final class DefaultExtractorsFactoryTest { ...@@ -59,6 +60,7 @@ public final class DefaultExtractorsFactoryTest {
Mp3Extractor.class, Mp3Extractor.class,
AdtsExtractor.class, AdtsExtractor.class,
Ac3Extractor.class, Ac3Extractor.class,
Ac4Extractor.class,
TsExtractor.class, TsExtractor.class,
FlvExtractor.class, FlvExtractor.class,
OggExtractor.class, OggExtractor.class,
......
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ext.flac;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.testutil.DefaultRenderersFactoryAsserts;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit test for {@link DefaultRenderersFactoryTest} with {@link LibflacAudioRenderer}. */
@RunWith(AndroidJUnit4.class)
public final class DefaultRenderersFactoryTest {
@Test
public void createRenderers_instantiatesVpxRenderer() {
DefaultRenderersFactoryAsserts.assertExtensionRendererCreated(
LibflacAudioRenderer.class, C.TRACK_TYPE_AUDIO);
}
}
...@@ -37,4 +37,4 @@ locally. Instructions for doing this can be found in ExoPlayer's ...@@ -37,4 +37,4 @@ locally. Instructions for doing this can be found in ExoPlayer's
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.gvr.*` * [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.gvr.*`
belong to this module. belong to this module.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html [Javadoc]: https://exoplayer.dev/doc/reference/index.html
...@@ -16,7 +16,6 @@ apply plugin: 'com.android.library' ...@@ -16,7 +16,6 @@ apply plugin: 'com.android.library'
android { android {
compileSdkVersion project.ext.compileSdkVersion compileSdkVersion project.ext.compileSdkVersion
buildToolsVersion project.ext.buildToolsVersion
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
...@@ -27,11 +26,14 @@ android { ...@@ -27,11 +26,14 @@ android {
minSdkVersion 19 minSdkVersion 19
targetSdkVersion project.ext.targetSdkVersion targetSdkVersion project.ext.targetSdkVersion
} }
testOptions.unitTests.includeAndroidResources = true
} }
dependencies { dependencies {
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
implementation 'com.android.support:support-annotations:' + supportLibraryVersion implementation project(modulePrefix + 'library-ui')
implementation 'androidx.annotation:annotation:1.0.2'
api 'com.google.vr:sdk-base:1.190.0' api 'com.google.vr:sdk-base:1.190.0'
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
} }
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
*/ */
package com.google.android.exoplayer2.ext.gvr; package com.google.android.exoplayer2.ext.gvr;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
...@@ -38,9 +38,11 @@ public final class GvrAudioProcessor implements AudioProcessor { ...@@ -38,9 +38,11 @@ public final class GvrAudioProcessor implements AudioProcessor {
private static final int FRAMES_PER_OUTPUT_BUFFER = 1024; private static final int FRAMES_PER_OUTPUT_BUFFER = 1024;
private static final int OUTPUT_CHANNEL_COUNT = 2; private static final int OUTPUT_CHANNEL_COUNT = 2;
private static final int OUTPUT_FRAME_SIZE = OUTPUT_CHANNEL_COUNT * 2; // 16-bit stereo output. private static final int OUTPUT_FRAME_SIZE = OUTPUT_CHANNEL_COUNT * 2; // 16-bit stereo output.
private static final int NO_SURROUND_FORMAT = GvrAudioSurround.SurroundFormat.INVALID;
private int sampleRateHz; private int sampleRateHz;
private int channelCount; private int channelCount;
private int pendingGvrAudioSurroundFormat;
@Nullable private GvrAudioSurround gvrAudioSurround; @Nullable private GvrAudioSurround gvrAudioSurround;
private ByteBuffer buffer; private ByteBuffer buffer;
private boolean inputEnded; private boolean inputEnded;
...@@ -57,6 +59,7 @@ public final class GvrAudioProcessor implements AudioProcessor { ...@@ -57,6 +59,7 @@ public final class GvrAudioProcessor implements AudioProcessor {
sampleRateHz = Format.NO_VALUE; sampleRateHz = Format.NO_VALUE;
channelCount = Format.NO_VALUE; channelCount = Format.NO_VALUE;
buffer = EMPTY_BUFFER; buffer = EMPTY_BUFFER;
pendingGvrAudioSurroundFormat = NO_SURROUND_FORMAT;
} }
/** /**
...@@ -92,33 +95,28 @@ public final class GvrAudioProcessor implements AudioProcessor { ...@@ -92,33 +95,28 @@ public final class GvrAudioProcessor implements AudioProcessor {
} }
this.sampleRateHz = sampleRateHz; this.sampleRateHz = sampleRateHz;
this.channelCount = channelCount; this.channelCount = channelCount;
maybeReleaseGvrAudioSurround();
int surroundFormat;
switch (channelCount) { switch (channelCount) {
case 1: case 1:
surroundFormat = GvrAudioSurround.SurroundFormat.SURROUND_MONO; pendingGvrAudioSurroundFormat = GvrAudioSurround.SurroundFormat.SURROUND_MONO;
break; break;
case 2: case 2:
surroundFormat = GvrAudioSurround.SurroundFormat.SURROUND_STEREO; pendingGvrAudioSurroundFormat = GvrAudioSurround.SurroundFormat.SURROUND_STEREO;
break; break;
case 4: case 4:
surroundFormat = GvrAudioSurround.SurroundFormat.FIRST_ORDER_AMBISONICS; pendingGvrAudioSurroundFormat = GvrAudioSurround.SurroundFormat.FIRST_ORDER_AMBISONICS;
break; break;
case 6: case 6:
surroundFormat = GvrAudioSurround.SurroundFormat.SURROUND_FIVE_DOT_ONE; pendingGvrAudioSurroundFormat = GvrAudioSurround.SurroundFormat.SURROUND_FIVE_DOT_ONE;
break; break;
case 9: case 9:
surroundFormat = GvrAudioSurround.SurroundFormat.SECOND_ORDER_AMBISONICS; pendingGvrAudioSurroundFormat = GvrAudioSurround.SurroundFormat.SECOND_ORDER_AMBISONICS;
break; break;
case 16: case 16:
surroundFormat = GvrAudioSurround.SurroundFormat.THIRD_ORDER_AMBISONICS; pendingGvrAudioSurroundFormat = GvrAudioSurround.SurroundFormat.THIRD_ORDER_AMBISONICS;
break; break;
default: default:
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
} }
gvrAudioSurround = new GvrAudioSurround(surroundFormat, sampleRateHz, channelCount,
FRAMES_PER_OUTPUT_BUFFER);
gvrAudioSurround.updateNativeOrientation(w, x, y, z);
if (buffer == EMPTY_BUFFER) { if (buffer == EMPTY_BUFFER) {
buffer = ByteBuffer.allocateDirect(FRAMES_PER_OUTPUT_BUFFER * OUTPUT_FRAME_SIZE) buffer = ByteBuffer.allocateDirect(FRAMES_PER_OUTPUT_BUFFER * OUTPUT_FRAME_SIZE)
.order(ByteOrder.nativeOrder()); .order(ByteOrder.nativeOrder());
...@@ -128,7 +126,7 @@ public final class GvrAudioProcessor implements AudioProcessor { ...@@ -128,7 +126,7 @@ public final class GvrAudioProcessor implements AudioProcessor {
@Override @Override
public boolean isActive() { public boolean isActive() {
return gvrAudioSurround != null; return pendingGvrAudioSurroundFormat != NO_SURROUND_FORMAT || gvrAudioSurround != null;
} }
@Override @Override
...@@ -156,14 +154,17 @@ public final class GvrAudioProcessor implements AudioProcessor { ...@@ -156,14 +154,17 @@ public final class GvrAudioProcessor implements AudioProcessor {
@Override @Override
public void queueEndOfStream() { public void queueEndOfStream() {
Assertions.checkNotNull(gvrAudioSurround); if (gvrAudioSurround != null) {
inputEnded = true;
gvrAudioSurround.triggerProcessing(); gvrAudioSurround.triggerProcessing();
} }
inputEnded = true;
}
@Override @Override
public ByteBuffer getOutput() { public ByteBuffer getOutput() {
Assertions.checkNotNull(gvrAudioSurround); if (gvrAudioSurround == null) {
return EMPTY_BUFFER;
}
int writtenBytes = gvrAudioSurround.getOutput(buffer, 0, buffer.capacity()); int writtenBytes = gvrAudioSurround.getOutput(buffer, 0, buffer.capacity());
buffer.position(0).limit(writtenBytes); buffer.position(0).limit(writtenBytes);
return buffer; return buffer;
...@@ -171,13 +172,20 @@ public final class GvrAudioProcessor implements AudioProcessor { ...@@ -171,13 +172,20 @@ public final class GvrAudioProcessor implements AudioProcessor {
@Override @Override
public boolean isEnded() { public boolean isEnded() {
Assertions.checkNotNull(gvrAudioSurround); return inputEnded
return inputEnded && gvrAudioSurround.getAvailableOutputSize() == 0; && (gvrAudioSurround == null || gvrAudioSurround.getAvailableOutputSize() == 0);
} }
@Override @Override
public void flush() { public void flush() {
if (gvrAudioSurround != null) { if (pendingGvrAudioSurroundFormat != NO_SURROUND_FORMAT) {
maybeReleaseGvrAudioSurround();
gvrAudioSurround =
new GvrAudioSurround(
pendingGvrAudioSurroundFormat, sampleRateHz, channelCount, FRAMES_PER_OUTPUT_BUFFER);
gvrAudioSurround.updateNativeOrientation(w, x, y, z);
pendingGvrAudioSurroundFormat = NO_SURROUND_FORMAT;
} else if (gvrAudioSurround != null) {
gvrAudioSurround.flush(); gvrAudioSurround.flush();
} }
inputEnded = false; inputEnded = false;
...@@ -191,13 +199,13 @@ public final class GvrAudioProcessor implements AudioProcessor { ...@@ -191,13 +199,13 @@ public final class GvrAudioProcessor implements AudioProcessor {
sampleRateHz = Format.NO_VALUE; sampleRateHz = Format.NO_VALUE;
channelCount = Format.NO_VALUE; channelCount = Format.NO_VALUE;
buffer = EMPTY_BUFFER; buffer = EMPTY_BUFFER;
pendingGvrAudioSurroundFormat = NO_SURROUND_FORMAT;
} }
private void maybeReleaseGvrAudioSurround() { private void maybeReleaseGvrAudioSurround() {
if (this.gvrAudioSurround != null) { if (gvrAudioSurround != null) {
GvrAudioSurround gvrAudioSurround = this.gvrAudioSurround;
this.gvrAudioSurround = null;
gvrAudioSurround.release(); gvrAudioSurround.release();
gvrAudioSurround = null;
} }
} }
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/video_ui_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/black"
android:orientation="horizontal"
tools:ignore="Overdraw">
<com.google.android.exoplayer2.ui.PlayerControlView
android:id="@+id/controller"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</merge>
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<ListView xmlns:android="http://schemas.android.com/apk/res/android" <resources>
android:id="@+id/representation_list" <style name="VrTheme" parent="android:Theme.Material"/>
android:layout_width="match_parent" </resources>
android:layout_height="match_parent"/>
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<style name="VrTheme" parent="android:Theme.Holo"/>
</resources>
...@@ -5,7 +5,7 @@ The IMA extension is an [AdsLoader][] implementation wrapping the ...@@ -5,7 +5,7 @@ The IMA extension is an [AdsLoader][] implementation wrapping the
alongside content. alongside content.
[IMA]: https://developers.google.com/interactive-media-ads/docs/sdks/android/ [IMA]: https://developers.google.com/interactive-media-ads/docs/sdks/android/
[AdsLoader]: https://google.github.io/ExoPlayer/doc/reference/index.html?com/google/android/exoplayer2/source/ads/AdsLoader.html [AdsLoader]: https://exoplayer.dev/doc/reference/index.html?com/google/android/exoplayer2/source/ads/AdsLoader.html
## Getting the extension ## ## Getting the extension ##
...@@ -61,4 +61,4 @@ playback. ...@@ -61,4 +61,4 @@ playback.
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.ima.*` * [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.ima.*`
belong to this module. belong to this module.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html [Javadoc]: https://exoplayer.dev/doc/reference/index.html
...@@ -16,7 +16,6 @@ apply plugin: 'com.android.library' ...@@ -16,7 +16,6 @@ apply plugin: 'com.android.library'
android { android {
compileSdkVersion project.ext.compileSdkVersion compileSdkVersion project.ext.compileSdkVersion
buildToolsVersion project.ext.buildToolsVersion
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
...@@ -28,23 +27,14 @@ android { ...@@ -28,23 +27,14 @@ android {
targetSdkVersion project.ext.targetSdkVersion targetSdkVersion project.ext.targetSdkVersion
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
} }
testOptions.unitTests.includeAndroidResources = true
} }
dependencies { dependencies {
api 'com.google.ads.interactivemedia.v3:interactivemedia:3.10.6' api 'com.google.ads.interactivemedia.v3:interactivemedia:3.11.2'
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
implementation 'com.google.android.gms:play-services-ads:17.1.2' implementation 'com.google.android.gms:play-services-ads-identifier:16.0.0'
// These dependencies are necessary to force the supportLibraryVersion of
// com.android.support:support-v4 and com.android.support:customtabs to be
// used. Else older versions are used, for example via:
// com.google.android.gms:play-services-ads:17.1.2
// |-- com.android.support:customtabs:26.1.0
implementation 'com.android.support:support-v4:' + supportLibraryVersion
implementation 'com.android.support:customtabs:' + supportLibraryVersion
testImplementation 'com.google.truth:truth:' + truthVersion
testImplementation 'junit:junit:' + junitVersion
testImplementation 'org.mockito:mockito-core:' + mockitoVersion
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
testImplementation project(modulePrefix + 'testutils-robolectric') testImplementation project(modulePrefix + 'testutils-robolectric')
} }
......
...@@ -19,8 +19,9 @@ import android.content.Context; ...@@ -19,8 +19,9 @@ import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.os.Looper; import android.os.Looper;
import android.os.SystemClock; import android.os.SystemClock;
import android.support.annotation.IntDef; import androidx.annotation.IntDef;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.google.ads.interactivemedia.v3.api.Ad; import com.google.ads.interactivemedia.v3.api.Ad;
...@@ -216,7 +217,7 @@ public final class ImaAdsLoader ...@@ -216,7 +217,7 @@ public final class ImaAdsLoader
return this; return this;
} }
// @VisibleForTesting @VisibleForTesting
/* package */ Builder setImaFactory(ImaFactory imaFactory) { /* package */ Builder setImaFactory(ImaFactory imaFactory) {
this.imaFactory = Assertions.checkNotNull(imaFactory); this.imaFactory = Assertions.checkNotNull(imaFactory);
return this; return this;
...@@ -755,7 +756,8 @@ public final class ImaAdsLoader ...@@ -755,7 +756,8 @@ public final class ImaAdsLoader
// until MAXIMUM_PRELOAD_DURATION_MS before the ad so that an ad group load error delivered // until MAXIMUM_PRELOAD_DURATION_MS before the ad so that an ad group load error delivered
// just after an ad group isn't incorrectly attributed to the next ad group. // just after an ad group isn't incorrectly attributed to the next ad group.
int nextAdGroupIndex = int nextAdGroupIndex =
adPlaybackState.getAdGroupIndexAfterPositionUs(C.msToUs(contentPositionMs)); adPlaybackState.getAdGroupIndexAfterPositionUs(
C.msToUs(contentPositionMs), C.msToUs(contentDurationMs));
if (nextAdGroupIndex != expectedAdGroupIndex && nextAdGroupIndex != C.INDEX_UNSET) { if (nextAdGroupIndex != expectedAdGroupIndex && nextAdGroupIndex != C.INDEX_UNSET) {
long nextAdGroupTimeMs = C.usToMs(adPlaybackState.adGroupTimesUs[nextAdGroupIndex]); long nextAdGroupTimeMs = C.usToMs(adPlaybackState.adGroupTimesUs[nextAdGroupIndex]);
if (nextAdGroupTimeMs == C.TIME_END_OF_SOURCE) { if (nextAdGroupTimeMs == C.TIME_END_OF_SOURCE) {
...@@ -1389,7 +1391,7 @@ public final class ImaAdsLoader ...@@ -1389,7 +1391,7 @@ public final class ImaAdsLoader
} }
/** Factory for objects provided by the IMA SDK. */ /** Factory for objects provided by the IMA SDK. */
// @VisibleForTesting @VisibleForTesting
/* package */ interface ImaFactory { /* package */ interface ImaFactory {
/** @see ImaSdkSettings */ /** @see ImaSdkSettings */
ImaSdkSettings createImaSdkSettings(); ImaSdkSettings createImaSdkSettings();
......
...@@ -22,10 +22,12 @@ import static org.mockito.Mockito.verify; ...@@ -22,10 +22,12 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.ads.interactivemedia.v3.api.Ad; import com.google.ads.interactivemedia.v3.api.Ad;
import com.google.ads.interactivemedia.v3.api.AdDisplayContainer; import com.google.ads.interactivemedia.v3.api.AdDisplayContainer;
import com.google.ads.interactivemedia.v3.api.AdEvent; import com.google.ads.interactivemedia.v3.api.AdEvent;
...@@ -54,11 +56,9 @@ import org.junit.runner.RunWith; ...@@ -54,11 +56,9 @@ import org.junit.runner.RunWith;
import org.mockito.InOrder; import org.mockito.InOrder;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
/** Test for {@link ImaAdsLoader}. */ /** Test for {@link ImaAdsLoader}. */
@RunWith(RobolectricTestRunner.class) @RunWith(AndroidJUnit4.class)
public class ImaAdsLoaderTest { public class ImaAdsLoaderTest {
private static final long CONTENT_DURATION_US = 10 * C.MICROS_PER_SECOND; private static final long CONTENT_DURATION_US = 10 * C.MICROS_PER_SECOND;
...@@ -95,8 +95,8 @@ public class ImaAdsLoaderTest { ...@@ -95,8 +95,8 @@ public class ImaAdsLoaderTest {
adDisplayContainer, adDisplayContainer,
fakeAdsRequest, fakeAdsRequest,
fakeAdsLoader); fakeAdsLoader);
adViewGroup = new FrameLayout(RuntimeEnvironment.application); adViewGroup = new FrameLayout(ApplicationProvider.getApplicationContext());
adOverlayView = new View(RuntimeEnvironment.application); adOverlayView = new View(ApplicationProvider.getApplicationContext());
adViewProvider = adViewProvider =
new AdsLoader.AdViewProvider() { new AdsLoader.AdViewProvider() {
@Override @Override
...@@ -237,7 +237,7 @@ public class ImaAdsLoaderTest { ...@@ -237,7 +237,7 @@ public class ImaAdsLoaderTest {
adsLoaderListener = new TestAdsLoaderListener(fakeExoPlayer, contentTimeline, adDurationsUs); adsLoaderListener = new TestAdsLoaderListener(fakeExoPlayer, contentTimeline, adDurationsUs);
when(adsManager.getAdCuePoints()).thenReturn(Arrays.asList(cuePoints)); when(adsManager.getAdCuePoints()).thenReturn(Arrays.asList(cuePoints));
imaAdsLoader = imaAdsLoader =
new ImaAdsLoader.Builder(RuntimeEnvironment.application) new ImaAdsLoader.Builder(ApplicationProvider.getApplicationContext())
.setImaFactory(testImaFactory) .setImaFactory(testImaFactory)
.setImaSdkSettings(imaSdkSettings) .setImaSdkSettings(imaSdkSettings)
.buildForAdTag(TEST_URI); .buildForAdTag(TEST_URI);
......
...@@ -18,7 +18,6 @@ apply plugin: 'com.android.library' ...@@ -18,7 +18,6 @@ apply plugin: 'com.android.library'
android { android {
compileSdkVersion project.ext.compileSdkVersion compileSdkVersion project.ext.compileSdkVersion
buildToolsVersion project.ext.buildToolsVersion
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
...@@ -29,6 +28,8 @@ android { ...@@ -29,6 +28,8 @@ android {
minSdkVersion project.ext.minSdkVersion minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion targetSdkVersion project.ext.targetSdkVersion
} }
testOptions.unitTests.includeAndroidResources = true
} }
dependencies { dependencies {
......
...@@ -57,6 +57,7 @@ import com.google.android.exoplayer2.util.Util; ...@@ -57,6 +57,7 @@ import com.google.android.exoplayer2.util.Util;
*/ */
public final class JobDispatcherScheduler implements Scheduler { public final class JobDispatcherScheduler implements Scheduler {
private static final boolean DEBUG = false;
private static final String TAG = "JobDispatcherScheduler"; private static final String TAG = "JobDispatcherScheduler";
private static final String KEY_SERVICE_ACTION = "service_action"; private static final String KEY_SERVICE_ACTION = "service_action";
private static final String KEY_SERVICE_PACKAGE = "service_package"; private static final String KEY_SERVICE_PACKAGE = "service_package";
...@@ -78,8 +79,8 @@ public final class JobDispatcherScheduler implements Scheduler { ...@@ -78,8 +79,8 @@ public final class JobDispatcherScheduler implements Scheduler {
} }
@Override @Override
public boolean schedule(Requirements requirements, String serviceAction, String servicePackage) { public boolean schedule(Requirements requirements, String servicePackage, String serviceAction) {
Job job = buildJob(jobDispatcher, requirements, jobTag, serviceAction, servicePackage); Job job = buildJob(jobDispatcher, requirements, jobTag, servicePackage, serviceAction);
int result = jobDispatcher.schedule(job); int result = jobDispatcher.schedule(job);
logd("Scheduling job: " + jobTag + " result: " + result); logd("Scheduling job: " + jobTag + " result: " + result);
return result == FirebaseJobDispatcher.SCHEDULE_RESULT_SUCCESS; return result == FirebaseJobDispatcher.SCHEDULE_RESULT_SUCCESS;
...@@ -96,26 +97,18 @@ public final class JobDispatcherScheduler implements Scheduler { ...@@ -96,26 +97,18 @@ public final class JobDispatcherScheduler implements Scheduler {
FirebaseJobDispatcher dispatcher, FirebaseJobDispatcher dispatcher,
Requirements requirements, Requirements requirements,
String tag, String tag,
String serviceAction, String servicePackage,
String servicePackage) { String serviceAction) {
Job.Builder builder = Job.Builder builder =
dispatcher dispatcher
.newJobBuilder() .newJobBuilder()
.setService(JobDispatcherSchedulerService.class) // the JobService that will be called .setService(JobDispatcherSchedulerService.class) // the JobService that will be called
.setTag(tag); .setTag(tag);
switch (requirements.getRequiredNetworkType()) { if (requirements.isUnmeteredNetworkRequired()) {
case Requirements.NETWORK_TYPE_NONE:
// do nothing.
break;
case Requirements.NETWORK_TYPE_ANY:
builder.addConstraint(Constraint.ON_ANY_NETWORK);
break;
case Requirements.NETWORK_TYPE_UNMETERED:
builder.addConstraint(Constraint.ON_UNMETERED_NETWORK); builder.addConstraint(Constraint.ON_UNMETERED_NETWORK);
break; } else if (requirements.isNetworkRequired()) {
default: builder.addConstraint(Constraint.ON_ANY_NETWORK);
throw new UnsupportedOperationException();
} }
if (requirements.isIdleRequired()) { if (requirements.isIdleRequired()) {
...@@ -129,7 +122,7 @@ public final class JobDispatcherScheduler implements Scheduler { ...@@ -129,7 +122,7 @@ public final class JobDispatcherScheduler implements Scheduler {
Bundle extras = new Bundle(); Bundle extras = new Bundle();
extras.putString(KEY_SERVICE_ACTION, serviceAction); extras.putString(KEY_SERVICE_ACTION, serviceAction);
extras.putString(KEY_SERVICE_PACKAGE, servicePackage); extras.putString(KEY_SERVICE_PACKAGE, servicePackage);
extras.putInt(KEY_REQUIREMENTS, requirements.getRequirementsData()); extras.putInt(KEY_REQUIREMENTS, requirements.getRequirements());
builder.setExtras(extras); builder.setExtras(extras);
return builder.build(); return builder.build();
......
...@@ -28,4 +28,4 @@ locally. Instructions for doing this can be found in ExoPlayer's ...@@ -28,4 +28,4 @@ locally. Instructions for doing this can be found in ExoPlayer's
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.leanback.*` * [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.leanback.*`
belong to this module. belong to this module.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html [Javadoc]: https://exoplayer.dev/doc/reference/index.html
...@@ -16,7 +16,6 @@ apply plugin: 'com.android.library' ...@@ -16,7 +16,6 @@ apply plugin: 'com.android.library'
android { android {
compileSdkVersion project.ext.compileSdkVersion compileSdkVersion project.ext.compileSdkVersion
buildToolsVersion project.ext.buildToolsVersion
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
...@@ -27,11 +26,14 @@ android { ...@@ -27,11 +26,14 @@ android {
minSdkVersion 17 minSdkVersion 17
targetSdkVersion project.ext.targetSdkVersion targetSdkVersion project.ext.targetSdkVersion
} }
testOptions.unitTests.includeAndroidResources = true
} }
dependencies { dependencies {
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
implementation('com.android.support:leanback-v17:' + supportLibraryVersion) implementation 'androidx.annotation:annotation:1.0.2'
implementation 'androidx.leanback:leanback:1.0.0'
} }
ext { ext {
......
...@@ -17,11 +17,11 @@ package com.google.android.exoplayer2.ext.leanback; ...@@ -17,11 +17,11 @@ package com.google.android.exoplayer2.ext.leanback;
import android.content.Context; import android.content.Context;
import android.os.Handler; import android.os.Handler;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import android.support.v17.leanback.R; import androidx.leanback.R;
import android.support.v17.leanback.media.PlaybackGlueHost; import androidx.leanback.media.PlaybackGlueHost;
import android.support.v17.leanback.media.PlayerAdapter; import androidx.leanback.media.PlayerAdapter;
import android.support.v17.leanback.media.SurfaceHolderGlueHost; import androidx.leanback.media.SurfaceHolderGlueHost;
import android.util.Pair; import android.util.Pair;
import android.view.Surface; import android.view.Surface;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
......
...@@ -29,4 +29,4 @@ locally. Instructions for doing this can be found in ExoPlayer's ...@@ -29,4 +29,4 @@ locally. Instructions for doing this can be found in ExoPlayer's
* [Javadoc][]: Classes matching * [Javadoc][]: Classes matching
`com.google.android.exoplayer2.ext.mediasession.*` belong to this module. `com.google.android.exoplayer2.ext.mediasession.*` belong to this module.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html [Javadoc]: https://exoplayer.dev/doc/reference/index.html
...@@ -16,7 +16,6 @@ apply plugin: 'com.android.library' ...@@ -16,7 +16,6 @@ apply plugin: 'com.android.library'
android { android {
compileSdkVersion project.ext.compileSdkVersion compileSdkVersion project.ext.compileSdkVersion
buildToolsVersion project.ext.buildToolsVersion
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
...@@ -27,11 +26,13 @@ android { ...@@ -27,11 +26,13 @@ android {
minSdkVersion project.ext.minSdkVersion minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion targetSdkVersion project.ext.targetSdkVersion
} }
testOptions.unitTests.includeAndroidResources = true
} }
dependencies { dependencies {
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
api 'com.android.support:support-media-compat:' + supportLibraryVersion api 'androidx.media:media:1.0.1'
} }
ext { ext {
......
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ext.mediasession;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.support.v4.media.session.PlaybackStateCompat;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.util.RepeatModeUtil;
/**
* A default implementation of {@link MediaSessionConnector.PlaybackController}.
* <p>
* Methods can be safely overridden by subclasses to intercept calls for given actions.
*/
public class DefaultPlaybackController implements MediaSessionConnector.PlaybackController {
/**
* The default fast forward increment, in milliseconds.
*/
public static final int DEFAULT_FAST_FORWARD_MS = 15000;
/**
* The default rewind increment, in milliseconds.
*/
public static final int DEFAULT_REWIND_MS = 5000;
private static final long BASE_ACTIONS = PlaybackStateCompat.ACTION_PLAY_PAUSE
| PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE
| PlaybackStateCompat.ACTION_STOP | PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE
| PlaybackStateCompat.ACTION_SET_REPEAT_MODE;
protected final long rewindIncrementMs;
protected final long fastForwardIncrementMs;
protected final int repeatToggleModes;
/**
* Creates a new instance.
* <p>
* Equivalent to {@code DefaultPlaybackController(DefaultPlaybackController.DEFAULT_REWIND_MS,
* DefaultPlaybackController.DEFAULT_FAST_FORWARD_MS,
* MediaSessionConnector.DEFAULT_REPEAT_TOGGLE_MODES)}.
*/
public DefaultPlaybackController() {
this(DEFAULT_REWIND_MS, DEFAULT_FAST_FORWARD_MS,
MediaSessionConnector.DEFAULT_REPEAT_TOGGLE_MODES);
}
/**
* Creates a new instance with the given fast forward and rewind increments.
* @param rewindIncrementMs The rewind increment in milliseconds. A zero or negative value will
* cause the rewind action to be disabled.
* @param fastForwardIncrementMs The fast forward increment in milliseconds. A zero or negative
* @param repeatToggleModes The available repeatToggleModes.
*/
public DefaultPlaybackController(long rewindIncrementMs, long fastForwardIncrementMs,
@RepeatModeUtil.RepeatToggleModes int repeatToggleModes) {
this.rewindIncrementMs = rewindIncrementMs;
this.fastForwardIncrementMs = fastForwardIncrementMs;
this.repeatToggleModes = repeatToggleModes;
}
@Override
public long getSupportedPlaybackActions(Player player) {
if (player == null || player.getCurrentTimeline().isEmpty()) {
return 0;
} else if (!player.isCurrentWindowSeekable()) {
return BASE_ACTIONS;
}
long actions = BASE_ACTIONS | PlaybackStateCompat.ACTION_SEEK_TO;
if (fastForwardIncrementMs > 0) {
actions |= PlaybackStateCompat.ACTION_FAST_FORWARD;
}
if (rewindIncrementMs > 0) {
actions |= PlaybackStateCompat.ACTION_REWIND;
}
return actions;
}
@Override
public void onPlay(Player player) {
player.setPlayWhenReady(true);
}
@Override
public void onPause(Player player) {
player.setPlayWhenReady(false);
}
@Override
public void onSeekTo(Player player, long position) {
long duration = player.getDuration();
if (duration != C.TIME_UNSET) {
position = Math.min(position, duration);
}
player.seekTo(Math.max(position, 0));
}
@Override
public void onFastForward(Player player) {
if (fastForwardIncrementMs <= 0) {
return;
}
onSeekTo(player, player.getCurrentPosition() + fastForwardIncrementMs);
}
@Override
public void onRewind(Player player) {
if (rewindIncrementMs <= 0) {
return;
}
onSeekTo(player, player.getCurrentPosition() - rewindIncrementMs);
}
@Override
public void onStop(Player player) {
player.stop(true);
}
@Override
public void onSetShuffleMode(Player player, int shuffleMode) {
player.setShuffleModeEnabled(shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_ALL
|| shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_GROUP);
}
@Override
public void onSetRepeatMode(Player player, int repeatMode) {
int selectedExoPlayerRepeatMode = player.getRepeatMode();
switch (repeatMode) {
case PlaybackStateCompat.REPEAT_MODE_ALL:
case PlaybackStateCompat.REPEAT_MODE_GROUP:
if ((repeatToggleModes & RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL) != 0) {
selectedExoPlayerRepeatMode = Player.REPEAT_MODE_ALL;
}
break;
case PlaybackStateCompat.REPEAT_MODE_ONE:
if ((repeatToggleModes & RepeatModeUtil.REPEAT_TOGGLE_MODE_ONE) != 0) {
selectedExoPlayerRepeatMode = Player.REPEAT_MODE_ONE;
}
break;
default:
selectedExoPlayerRepeatMode = Player.REPEAT_MODE_OFF;
break;
}
player.setRepeatMode(selectedExoPlayerRepeatMode);
}
// CommandReceiver implementation.
@Override
public String[] getCommands() {
return null;
}
@Override
public void onCommand(Player player, String command, Bundle extras, ResultReceiver cb) {
// Do nothing.
}
}
...@@ -18,17 +18,20 @@ package com.google.android.exoplayer2.ext.mediasession; ...@@ -18,17 +18,20 @@ package com.google.android.exoplayer2.ext.mediasession;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.media.session.PlaybackStateCompat; import android.support.v4.media.session.PlaybackStateCompat;
import com.google.android.exoplayer2.ControlDispatcher;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.util.RepeatModeUtil; import com.google.android.exoplayer2.util.RepeatModeUtil;
/** /** Provides a custom action for toggling repeat modes. */
* Provides a custom action for toggling repeat modes.
*/
public final class RepeatModeActionProvider implements MediaSessionConnector.CustomActionProvider { public final class RepeatModeActionProvider implements MediaSessionConnector.CustomActionProvider {
/** The default repeat toggle modes. */
@RepeatModeUtil.RepeatToggleModes
public static final int DEFAULT_REPEAT_TOGGLE_MODES =
RepeatModeUtil.REPEAT_TOGGLE_MODE_ONE | RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL;
private static final String ACTION_REPEAT_MODE = "ACTION_EXO_REPEAT_MODE"; private static final String ACTION_REPEAT_MODE = "ACTION_EXO_REPEAT_MODE";
private final Player player;
@RepeatModeUtil.RepeatToggleModes @RepeatModeUtil.RepeatToggleModes
private final int repeatToggleModes; private final int repeatToggleModes;
private final CharSequence repeatAllDescription; private final CharSequence repeatAllDescription;
...@@ -37,27 +40,23 @@ public final class RepeatModeActionProvider implements MediaSessionConnector.Cus ...@@ -37,27 +40,23 @@ public final class RepeatModeActionProvider implements MediaSessionConnector.Cus
/** /**
* Creates a new instance. * Creates a new instance.
* <p> *
* Equivalent to {@code RepeatModeActionProvider(context, player, * <p>Equivalent to {@code RepeatModeActionProvider(context, DEFAULT_REPEAT_TOGGLE_MODES)}.
* MediaSessionConnector.DEFAULT_REPEAT_TOGGLE_MODES)}.
* *
* @param context The context. * @param context The context.
* @param player The player on which to toggle the repeat mode.
*/ */
public RepeatModeActionProvider(Context context, Player player) { public RepeatModeActionProvider(Context context) {
this(context, player, MediaSessionConnector.DEFAULT_REPEAT_TOGGLE_MODES); this(context, DEFAULT_REPEAT_TOGGLE_MODES);
} }
/** /**
* Creates a new instance enabling the given repeat toggle modes. * Creates a new instance enabling the given repeat toggle modes.
* *
* @param context The context. * @param context The context.
* @param player The player on which to toggle the repeat mode.
* @param repeatToggleModes The toggle modes to enable. * @param repeatToggleModes The toggle modes to enable.
*/ */
public RepeatModeActionProvider(Context context, Player player, public RepeatModeActionProvider(
@RepeatModeUtil.RepeatToggleModes int repeatToggleModes) { Context context, @RepeatModeUtil.RepeatToggleModes int repeatToggleModes) {
this.player = player;
this.repeatToggleModes = repeatToggleModes; this.repeatToggleModes = repeatToggleModes;
repeatAllDescription = context.getString(R.string.exo_media_action_repeat_all_description); repeatAllDescription = context.getString(R.string.exo_media_action_repeat_all_description);
repeatOneDescription = context.getString(R.string.exo_media_action_repeat_one_description); repeatOneDescription = context.getString(R.string.exo_media_action_repeat_one_description);
...@@ -65,16 +64,17 @@ public final class RepeatModeActionProvider implements MediaSessionConnector.Cus ...@@ -65,16 +64,17 @@ public final class RepeatModeActionProvider implements MediaSessionConnector.Cus
} }
@Override @Override
public void onCustomAction(String action, Bundle extras) { public void onCustomAction(
Player player, ControlDispatcher controlDispatcher, String action, Bundle extras) {
int mode = player.getRepeatMode(); int mode = player.getRepeatMode();
int proposedMode = RepeatModeUtil.getNextRepeatMode(mode, repeatToggleModes); int proposedMode = RepeatModeUtil.getNextRepeatMode(mode, repeatToggleModes);
if (mode != proposedMode) { if (mode != proposedMode) {
player.setRepeatMode(proposedMode); controlDispatcher.dispatchSetRepeatMode(player, proposedMode);
} }
} }
@Override @Override
public PlaybackStateCompat.CustomAction getCustomAction() { public PlaybackStateCompat.CustomAction getCustomAction(Player player) {
CharSequence actionLabel; CharSequence actionLabel;
int iconResourceId; int iconResourceId;
switch (player.getRepeatMode()) { switch (player.getRepeatMode()) {
......
No preview for this file type
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment