Commit 0ecbe5dc by ojw28 Committed by GitHub

Merge branch 'dev-v2' into rtmp_client

parents 4343ce60 a0466337
Showing with 1773 additions and 409 deletions
...@@ -20,6 +20,11 @@ and extend, and can be updated through Play Store application updates. ...@@ -20,6 +20,11 @@ and extend, and can be updated through Play Store application updates.
## Using ExoPlayer ## ## Using ExoPlayer ##
ExoPlayer modules can be obtained via jCenter. It's also possible to clone the
repository and depend on the modules locally.
### Via jCenter ###
The easiest way to get started using ExoPlayer is to add it as a gradle The easiest way to get started using ExoPlayer is to add it as a gradle
dependency. You need to make sure you have the jcenter repository included in dependency. You need to make sure you have the jcenter repository included in
the `build.gradle` file in the root of your project: the `build.gradle` file in the root of your project:
...@@ -64,6 +69,39 @@ latest versions, see the [Release notes][]. ...@@ -64,6 +69,39 @@ latest versions, see the [Release notes][].
[Bintray]: https://bintray.com/google/exoplayer [Bintray]: https://bintray.com/google/exoplayer
[Release notes]: https://github.com/google/ExoPlayer/blob/release-v2/RELEASENOTES.md [Release notes]: https://github.com/google/ExoPlayer/blob/release-v2/RELEASENOTES.md
### Locally ###
Cloning the repository and depending on the modules locally is required when
using some ExoPlayer extension modules. It's also a suitable approach if you
want to make local changes to ExoPlayer, or if you want to use a development
branch.
First, clone the repository into a local directory and checkout the desired
branch:
```sh
git clone https://github.com/google/ExoPlayer.git
git checkout release-v2
```
Next, add the following to your project's `settings.gradle` file, replacing
`path/to/exoplayer` with the path to your local copy:
```gradle
gradle.ext.exoplayerRoot = 'path/to/exoplayer'
gradle.ext.exoplayerModulePrefix = 'exoplayer-'
apply from: new File(gradle.ext.exoplayerRoot, 'core_settings.gradle')
```
You should now see the ExoPlayer modules appear as part of your project. You can
depend on them as you would on any other local module, for example:
```gradle
compile project(':exoplayer-library-core')
compile project(':exoplayer-library-dash')
compile project(':exoplayer-library-ui)
```
## Developing ExoPlayer ## ## Developing ExoPlayer ##
#### Project branches #### #### Project branches ####
......
# Release notes # # Release notes #
### r2.4.3 ###
* Audio: Workaround custom audio decoders misreporting their maximum supported
channel counts ([#2940](https://github.com/google/ExoPlayer/issues/2940)).
* Audio: Workaround for broken MediaTek raw decoder on some devices
([#2873](https://github.com/google/ExoPlayer/issues/2873)).
* Captions: Fix TTML captions appearing at the top of the screen
([#2953](https://github.com/google/ExoPlayer/issues/2953)).
* Captions: Fix handling of some DVB subtitles
([#2957](https://github.com/google/ExoPlayer/issues/2957)).
* Track selection: Fix setSelectionOverride(index, tracks, null)
([#2988](https://github.com/google/ExoPlayer/issues/2988)).
* GVR extension: Add support for mono input
([#2710](https://github.com/google/ExoPlayer/issues/2710)).
* FLAC extension: Fix failing build
([#2977](https://github.com/google/ExoPlayer/pull/2977)).
* Misc bugfixes.
### r2.4.2 ###
* Stability: Work around Nexus 10 reboot when playing certain content
([#2806](https://github.com/google/ExoPlayer/issues/2806)).
* MP3: Correctly treat MP3s with INFO headers as constant bitrate
([#2895](https://github.com/google/ExoPlayer/issues/2895)).
* HLS: Use average rather than peak bandwidth when available
([#2863](https://github.com/google/ExoPlayer/issues/2863)).
* SmoothStreaming: Fix timeline for live streams
([#2760](https://github.com/google/ExoPlayer/issues/2760)).
* UI: Fix DefaultTimeBar invalidation
([#2871](https://github.com/google/ExoPlayer/issues/2871)).
* Misc bugfixes.
### r2.4.1 ###
* Stability: Avoid OutOfMemoryError in extractors when parsing malformed media
([#2780](https://github.com/google/ExoPlayer/issues/2780)).
* Stability: Avoid native crash on Galaxy Nexus. Avoid unnecessarily large codec
input buffer allocations on all devices
([#2607](https://github.com/google/ExoPlayer/issues/2607)).
* Variable speed playback: Fix interpolation for rate/pitch adjustment
([#2774](https://github.com/google/ExoPlayer/issues/2774)).
* HLS: Include EXT-X-DATERANGE tags in HlsMediaPlaylist.
* HLS: Don't expose CEA-608 track if CLOSED-CAPTIONS=NONE
([#2743](https://github.com/google/ExoPlayer/issues/2743)).
* HLS: Correctly propagate errors loading the media playlist
([#2623](https://github.com/google/ExoPlayer/issues/2623)).
* UI: DefaultTimeBar enhancements and bug fixes
([#2740](https://github.com/google/ExoPlayer/issues/2740)).
* Ogg: Fix failure to play some Ogg files
([#2782](https://github.com/google/ExoPlayer/issues/2782)).
* Captions: Don't select text tack with no language by default.
* Captions: TTML positioning fixes
([#2824](https://github.com/google/ExoPlayer/issues/2824)).
* Misc bugfixes.
### r2.4.0 ### ### r2.4.0 ###
* New modular library structure. You can read more about depending on individual * New modular library structure. You can read more about depending on individual
......
...@@ -16,7 +16,7 @@ buildscript { ...@@ -16,7 +16,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:2.3.0' classpath 'com.android.tools.build:gradle:2.3.1'
classpath 'com.novoda:bintray-release:0.4.0' classpath 'com.novoda:bintray-release:0.4.0'
} }
// Workaround for the following test coverage issue. Remove when fixed: // Workaround for the following test coverage issue. Remove when fixed:
...@@ -33,23 +33,7 @@ allprojects { ...@@ -33,23 +33,7 @@ allprojects {
jcenter() jcenter()
} }
project.ext { project.ext {
// Important: ExoPlayer specifies a minSdkVersion of 9 because various exoplayerPublishEnabled = true
// 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 = 9
compileSdkVersion = 25
targetSdkVersion = 25
buildToolsVersion = '25'
testSupportLibraryVersion = '0.5'
supportLibraryVersion = '25.3.1'
dexmakerVersion = '1.2'
mockitoVersion = '1.9.5'
releaseRepoName = getBintrayRepo()
releaseUserOrg = 'google'
releaseGroupId = 'com.google.android.exoplayer'
releaseVersion = 'r2.4.0'
releaseWebsite = 'https://github.com/google/ExoPlayer'
} }
if (it.hasProperty('externalBuildDir')) { if (it.hasProperty('externalBuildDir')) {
if (!new File(externalBuildDir).isAbsolute()) { if (!new File(externalBuildDir).isAbsolute()) {
...@@ -59,10 +43,4 @@ allprojects { ...@@ -59,10 +43,4 @@ allprojects {
} }
} }
def getBintrayRepo() {
boolean publicRepo = hasProperty('publicRepo') &&
property('publicRepo').toBoolean()
return publicRepo ? 'exoplayer' : 'exoplayer-test'
}
apply from: 'javadoc_combined.gradle' apply from: 'javadoc_combined.gradle'
// 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.
project.ext {
// Important: ExoPlayer specifies a minSdkVersion of 9 because various
// 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 = 9
compileSdkVersion = 25
targetSdkVersion = 25
buildToolsVersion = '25'
testSupportLibraryVersion = '0.5'
supportLibraryVersion = '25.3.1'
dexmakerVersion = '1.2'
mockitoVersion = '1.9.5'
releaseVersion = 'r2.4.3'
modulePrefix = ':';
if (gradle.ext.has('exoplayerModulePrefix')) {
modulePrefix += gradle.ext.exoplayerModulePrefix
}
}
// 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.
def rootDir = gradle.ext.exoplayerRoot
def modulePrefix = ':'
if (gradle.ext.has('exoplayerModulePrefix')) {
modulePrefix += gradle.ext.exoplayerModulePrefix
}
include modulePrefix + 'library'
include modulePrefix + 'library-core'
include modulePrefix + 'library-dash'
include modulePrefix + 'library-hls'
include modulePrefix + 'library-smoothstreaming'
include modulePrefix + 'library-ui'
include modulePrefix + 'testutils'
include modulePrefix + 'extension-ffmpeg'
include modulePrefix + 'extension-flac'
include modulePrefix + 'extension-gvr'
include modulePrefix + 'extension-ima'
include modulePrefix + 'extension-okhttp'
include modulePrefix + 'extension-opus'
include modulePrefix + 'extension-vp9'
project(modulePrefix + 'library').projectDir = new File(rootDir, 'library/all')
project(modulePrefix + 'library-core').projectDir = new File(rootDir, 'library/core')
project(modulePrefix + 'library-dash').projectDir = new File(rootDir, 'library/dash')
project(modulePrefix + 'library-hls').projectDir = new File(rootDir, 'library/hls')
project(modulePrefix + 'library-smoothstreaming').projectDir = new File(rootDir, 'library/smoothstreaming')
project(modulePrefix + 'library-ui').projectDir = new File(rootDir, 'library/ui')
project(modulePrefix + 'testutils').projectDir = new File(rootDir, 'testutils')
project(modulePrefix + 'extension-ffmpeg').projectDir = new File(rootDir, 'extensions/ffmpeg')
project(modulePrefix + 'extension-flac').projectDir = new File(rootDir, 'extensions/flac')
project(modulePrefix + 'extension-gvr').projectDir = new File(rootDir, 'extensions/gvr')
project(modulePrefix + 'extension-ima').projectDir = new File(rootDir, 'extensions/ima')
project(modulePrefix + 'extension-okhttp').projectDir = new File(rootDir, 'extensions/okhttp')
project(modulePrefix + 'extension-opus').projectDir = new File(rootDir, 'extensions/opus')
project(modulePrefix + 'extension-vp9').projectDir = new File(rootDir, 'extensions/vp9')
if (gradle.ext.has('exoplayerIncludeCronetExtension')
&& gradle.ext.exoplayerIncludeCronetExtension) {
include modulePrefix + 'extension-cronet'
project(modulePrefix + 'extension-cronet').projectDir = new File(rootDir, 'extensions/cronet')
}
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// 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.
apply from: '../constants.gradle'
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
...@@ -45,13 +46,14 @@ android { ...@@ -45,13 +46,14 @@ android {
} }
dependencies { dependencies {
compile project(':library-core') compile project(modulePrefix + 'library-core')
compile project(':library-dash') compile project(modulePrefix + 'library-dash')
compile project(':library-hls') compile project(modulePrefix + 'library-hls')
compile project(':library-smoothstreaming') compile project(modulePrefix + 'library-smoothstreaming')
compile project(':library-ui') compile project(modulePrefix + 'library-ui')
withExtensionsCompile project(path: ':extension-ffmpeg') withExtensionsCompile project(path: modulePrefix + 'extension-ffmpeg')
withExtensionsCompile project(path: ':extension-flac') withExtensionsCompile project(path: modulePrefix + 'extension-flac')
withExtensionsCompile project(path: ':extension-opus') withExtensionsCompile project(path: modulePrefix + 'extension-ima')
withExtensionsCompile project(path: ':extension-vp9') withExtensionsCompile project(path: modulePrefix + 'extension-opus')
withExtensionsCompile project(path: modulePrefix + 'extension-vp9')
} }
...@@ -16,8 +16,8 @@ ...@@ -16,8 +16,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.exoplayer2.demo" package="com.google.android.exoplayer2.demo"
android:versionCode="2400" android:versionCode="2403"
android:versionName="2.4.0"> android:versionName="2.4.3">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
......
...@@ -96,6 +96,11 @@ import java.util.Locale; ...@@ -96,6 +96,11 @@ import java.util.Locale;
} }
@Override @Override
public void onRepeatModeChanged(@ExoPlayer.RepeatMode int repeatMode) {
Log.d(TAG, "repeatMode [" + getRepeatModeString(repeatMode) + "]");
}
@Override
public void onPositionDiscontinuity() { public void onPositionDiscontinuity() {
Log.d(TAG, "positionDiscontinuity"); Log.d(TAG, "positionDiscontinuity");
} }
...@@ -276,7 +281,7 @@ import java.util.Locale; ...@@ -276,7 +281,7 @@ import java.util.Locale;
@Override @Override
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
float pixelWidthHeightRatio) { float pixelWidthHeightRatio) {
// Do nothing. Log.d(TAG, "videoSizeChanged [" + width + ", " + height + "]");
} }
@Override @Override
...@@ -461,4 +466,16 @@ import java.util.Locale; ...@@ -461,4 +466,16 @@ import java.util.Locale;
return enabled ? "[X]" : "[ ]"; return enabled ? "[X]" : "[ ]";
} }
private static String getRepeatModeString(@ExoPlayer.RepeatMode int repeatMode) {
switch (repeatMode) {
case ExoPlayer.REPEAT_MODE_OFF:
return "OFF";
case ExoPlayer.REPEAT_MODE_ONE:
return "ONE";
case ExoPlayer.REPEAT_MODE_ALL:
return "ALL";
default:
return "?";
}
}
} }
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.demo; package com.google.android.exoplayer2.demo;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
...@@ -26,7 +27,9 @@ import android.text.TextUtils; ...@@ -26,7 +27,9 @@ import android.text.TextUtils;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
...@@ -69,6 +72,7 @@ import com.google.android.exoplayer2.upstream.DataSource; ...@@ -69,6 +72,7 @@ import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.lang.reflect.Constructor;
import java.net.CookieHandler; import java.net.CookieHandler;
import java.net.CookieManager; import java.net.CookieManager;
import java.net.CookiePolicy; import java.net.CookiePolicy;
...@@ -92,6 +96,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay ...@@ -92,6 +96,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
"com.google.android.exoplayer.demo.action.VIEW_LIST"; "com.google.android.exoplayer.demo.action.VIEW_LIST";
public static final String URI_LIST_EXTRA = "uri_list"; public static final String URI_LIST_EXTRA = "uri_list";
public static final String EXTENSION_LIST_EXTRA = "extension_list"; public static final String EXTENSION_LIST_EXTRA = "extension_list";
public static final String AD_TAG_URI_EXTRA = "ad_tag_uri";
private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter(); private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter();
private static final CookieManager DEFAULT_COOKIE_MANAGER; private static final CookieManager DEFAULT_COOKIE_MANAGER;
...@@ -200,10 +205,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay ...@@ -200,10 +205,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
@Override @Override
public boolean dispatchKeyEvent(KeyEvent event) { public boolean dispatchKeyEvent(KeyEvent event) {
// Show the controls on any key event. // If the event was not handled then see if the player view can handle it.
simpleExoPlayerView.showController(); return super.dispatchKeyEvent(event) || simpleExoPlayerView.dispatchKeyEvent(event);
// If the event was not handled then see if the player view can handle it as a media key event.
return super.dispatchKeyEvent(event) || simpleExoPlayerView.dispatchMediaKeyEvent(event);
} }
// OnClickListener methods // OnClickListener methods
...@@ -234,7 +237,13 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay ...@@ -234,7 +237,13 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
Intent intent = getIntent(); Intent intent = getIntent();
boolean needNewPlayer = player == null; boolean needNewPlayer = player == null;
if (needNewPlayer) { if (needNewPlayer) {
boolean preferExtensionDecoders = intent.getBooleanExtra(PREFER_EXTENSION_DECODERS, false); TrackSelection.Factory adaptiveTrackSelectionFactory =
new AdaptiveTrackSelection.Factory(BANDWIDTH_METER);
trackSelector = new DefaultTrackSelector(adaptiveTrackSelectionFactory);
trackSelectionHelper = new TrackSelectionHelper(trackSelector, adaptiveTrackSelectionFactory);
lastSeenTrackGroupArray = null;
eventLogger = new EventLogger(trackSelector);
UUID drmSchemeUuid = intent.hasExtra(DRM_SCHEME_UUID_EXTRA) UUID drmSchemeUuid = intent.hasExtra(DRM_SCHEME_UUID_EXTRA)
? UUID.fromString(intent.getStringExtra(DRM_SCHEME_UUID_EXTRA)) : null; ? UUID.fromString(intent.getStringExtra(DRM_SCHEME_UUID_EXTRA)) : null;
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager = null; DrmSessionManager<FrameworkMediaCrypto> drmSessionManager = null;
...@@ -253,6 +262,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay ...@@ -253,6 +262,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
} }
} }
boolean preferExtensionDecoders = intent.getBooleanExtra(PREFER_EXTENSION_DECODERS, false);
@DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode = @DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode =
((DemoApplication) getApplication()).useExtensionRenderers() ((DemoApplication) getApplication()).useExtensionRenderers()
? (preferExtensionDecoders ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER ? (preferExtensionDecoders ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER
...@@ -261,16 +271,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay ...@@ -261,16 +271,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(this, DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(this,
drmSessionManager, extensionRendererMode); drmSessionManager, extensionRendererMode);
TrackSelection.Factory videoTrackSelectionFactory =
new AdaptiveTrackSelection.Factory(BANDWIDTH_METER);
trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
trackSelectionHelper = new TrackSelectionHelper(trackSelector, videoTrackSelectionFactory);
lastSeenTrackGroupArray = null;
player = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector); player = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector);
player.addListener(this); player.addListener(this);
eventLogger = new EventLogger(trackSelector);
player.addListener(eventLogger); player.addListener(eventLogger);
player.setAudioDebugListener(eventLogger); player.setAudioDebugListener(eventLogger);
player.setVideoDebugListener(eventLogger); player.setVideoDebugListener(eventLogger);
...@@ -312,6 +314,26 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay ...@@ -312,6 +314,26 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
} }
MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0] MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0]
: new ConcatenatingMediaSource(mediaSources); : new ConcatenatingMediaSource(mediaSources);
String adTagUriString = intent.getStringExtra(AD_TAG_URI_EXTRA);
if (adTagUriString != null) {
Uri adTagUri = Uri.parse(adTagUriString);
ViewGroup adOverlayViewGroup = new FrameLayout(this);
// Load the extension source using reflection so that demo app doesn't have to depend on it.
try {
Class<?> clazz = Class.forName("com.google.android.exoplayer2.ext.ima.ImaAdsMediaSource");
Constructor<?> constructor = clazz.getConstructor(MediaSource.class,
DataSource.Factory.class, Context.class, Uri.class, ViewGroup.class);
mediaSource = (MediaSource) constructor.newInstance(mediaSource,
mediaDataSourceFactory, this, adTagUri, adOverlayViewGroup);
// The demo app has a non-null overlay frame layout.
simpleExoPlayerView.getOverlayFrameLayout().addView(adOverlayViewGroup);
// Show a multi-window time bar, which will include ad position markers.
simpleExoPlayerView.setShowMultiWindowTimeBar(true);
} catch (Exception e) {
// Throw if the media source class was not found, or there was an error instantiating it.
showToast(R.string.ima_not_loaded);
}
}
boolean haveResumePosition = resumeWindow != C.INDEX_UNSET; boolean haveResumePosition = resumeWindow != C.INDEX_UNSET;
if (haveResumePosition) { if (haveResumePosition) {
player.seekTo(resumeWindow, resumePosition); player.seekTo(resumeWindow, resumePosition);
...@@ -425,6 +447,11 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay ...@@ -425,6 +447,11 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
} }
@Override @Override
public void onRepeatModeChanged(int repeatMode) {
// Do nothing.
}
@Override
public void onPositionDiscontinuity() { public void onPositionDiscontinuity() {
if (needRetrySource) { if (needRetrySource) {
// This will only occur if the user has performed a seek whilst in the error state. Update the // This will only occur if the user has performed a seek whilst in the error state. Update the
......
...@@ -184,6 +184,7 @@ public class SampleChooserActivity extends Activity { ...@@ -184,6 +184,7 @@ public class SampleChooserActivity extends Activity {
String[] drmKeyRequestProperties = null; String[] drmKeyRequestProperties = null;
boolean preferExtensionDecoders = false; boolean preferExtensionDecoders = false;
ArrayList<UriSample> playlistSamples = null; ArrayList<UriSample> playlistSamples = null;
String adTagUri = null;
reader.beginObject(); reader.beginObject();
while (reader.hasNext()) { while (reader.hasNext()) {
...@@ -233,6 +234,9 @@ public class SampleChooserActivity extends Activity { ...@@ -233,6 +234,9 @@ public class SampleChooserActivity extends Activity {
} }
reader.endArray(); reader.endArray();
break; break;
case "ad_tag_uri":
adTagUri = reader.nextString();
break;
default: default:
throw new ParserException("Unsupported attribute name: " + name); throw new ParserException("Unsupported attribute name: " + name);
} }
...@@ -246,7 +250,7 @@ public class SampleChooserActivity extends Activity { ...@@ -246,7 +250,7 @@ public class SampleChooserActivity extends Activity {
preferExtensionDecoders, playlistSamplesArray); preferExtensionDecoders, playlistSamplesArray);
} else { } else {
return new UriSample(sampleName, drmUuid, drmLicenseUrl, drmKeyRequestProperties, return new UriSample(sampleName, drmUuid, drmLicenseUrl, drmKeyRequestProperties,
preferExtensionDecoders, uri, extension); preferExtensionDecoders, uri, extension, adTagUri);
} }
} }
...@@ -402,13 +406,15 @@ public class SampleChooserActivity extends Activity { ...@@ -402,13 +406,15 @@ public class SampleChooserActivity extends Activity {
public final String uri; public final String uri;
public final String extension; public final String extension;
public final String adTagUri;
public UriSample(String name, UUID drmSchemeUuid, String drmLicenseUrl, public UriSample(String name, UUID drmSchemeUuid, String drmLicenseUrl,
String[] drmKeyRequestProperties, boolean preferExtensionDecoders, String uri, String[] drmKeyRequestProperties, boolean preferExtensionDecoders, String uri,
String extension) { String extension, String adTagUri) {
super(name, drmSchemeUuid, drmLicenseUrl, drmKeyRequestProperties, preferExtensionDecoders); super(name, drmSchemeUuid, drmLicenseUrl, drmKeyRequestProperties, preferExtensionDecoders);
this.uri = uri; this.uri = uri;
this.extension = extension; this.extension = extension;
this.adTagUri = adTagUri;
} }
@Override @Override
...@@ -416,6 +422,7 @@ public class SampleChooserActivity extends Activity { ...@@ -416,6 +422,7 @@ public class SampleChooserActivity extends Activity {
return super.buildIntent(context) return super.buildIntent(context)
.setData(Uri.parse(uri)) .setData(Uri.parse(uri))
.putExtra(PlayerActivity.EXTENSION_EXTRA, extension) .putExtra(PlayerActivity.EXTENSION_EXTRA, extension)
.putExtra(PlayerActivity.AD_TAG_URI_EXTRA, adTagUri)
.setAction(PlayerActivity.ACTION_VIEW); .setAction(PlayerActivity.ACTION_VIEW);
} }
......
...@@ -58,4 +58,6 @@ ...@@ -58,4 +58,6 @@
<string name="sample_list_load_error">One or more sample lists failed to load</string> <string name="sample_list_load_error">One or more sample lists failed to load</string>
<string name="ima_not_loaded">Playing sample without ads, as the IMA extension was not loaded</string>
</resources> </resources>
...@@ -11,13 +11,10 @@ The Cronet Extension is an [HttpDataSource][] implementation using [Cronet][]. ...@@ -11,13 +11,10 @@ The Cronet Extension is an [HttpDataSource][] implementation using [Cronet][].
## Build Instructions ## ## Build Instructions ##
* Checkout ExoPlayer along with Extensions: To use this extension you need to clone the ExoPlayer repository and depend on
its modules locally. Instructions for doing this can be found in ExoPlayer's
``` [top level README][]. In addition, it's necessary to get the Cronet libraries
git clone https://github.com/google/ExoPlayer.git and enable the extension:
```
* Get the Cronet libraries:
1. Find the latest Cronet release [here][] and navigate to its `Release/cronet` 1. Find the latest Cronet release [here][] and navigate to its `Release/cronet`
directory directory
...@@ -27,6 +24,12 @@ git clone https://github.com/google/ExoPlayer.git ...@@ -27,6 +24,12 @@ git clone https://github.com/google/ExoPlayer.git
1. Copy the content of the downloaded `libs` directory into the `jniLibs` 1. Copy the content of the downloaded `libs` directory into the `jniLibs`
directory of this extension directory of this extension
* In ExoPlayer's `settings.gradle` file, uncomment the Cronet extension * In your `settings.gradle` file, add the following line before the line that
applies `core_settings.gradle`:
```gradle
gradle.ext.exoplayerIncludeCronetExtension = true;
```
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
[here]: https://console.cloud.google.com/storage/browser/chromium-cronet/android [here]: https://console.cloud.google.com/storage/browser/chromium-cronet/android
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// 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.
apply from: '../../constants.gradle'
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
android { android {
...@@ -29,11 +30,11 @@ android { ...@@ -29,11 +30,11 @@ android {
} }
dependencies { dependencies {
compile project(':library-core') compile project(modulePrefix + 'library-core')
compile files('libs/cronet_api.jar') compile files('libs/cronet_api.jar')
compile files('libs/cronet_impl_common_java.jar') compile files('libs/cronet_impl_common_java.jar')
compile files('libs/cronet_impl_native_java.jar') compile files('libs/cronet_impl_native_java.jar')
androidTestCompile project(':library') androidTestCompile project(modulePrefix + 'library')
androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion
androidTestCompile 'org.mockito:mockito-core:' + mockitoVersion androidTestCompile 'org.mockito:mockito-core:' + mockitoVersion
......
...@@ -28,7 +28,6 @@ ...@@ -28,7 +28,6 @@
<instrumentation <instrumentation
android:name="android.test.InstrumentationTestRunner" android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.google.android.exoplayer.ext.cronet" android:targetPackage="com.google.android.exoplayer.ext.cronet"/>
tools:replace="android:targetPackage"/>
</manifest> </manifest>
...@@ -16,9 +16,11 @@ ...@@ -16,9 +16,11 @@
package com.google.android.exoplayer2.ext.cronet; package com.google.android.exoplayer2.ext.cronet;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
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;
import com.google.android.exoplayer2.upstream.HttpDataSource.Factory; import com.google.android.exoplayer2.upstream.HttpDataSource.Factory;
import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidContentTypeException;
import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Predicate; import com.google.android.exoplayer2.util.Predicate;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
...@@ -34,43 +36,143 @@ public final class CronetDataSourceFactory extends BaseFactory { ...@@ -34,43 +36,143 @@ public final class CronetDataSourceFactory extends BaseFactory {
*/ */
public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS =
CronetDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS; CronetDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS;
/** /**
* The default read timeout, in milliseconds. * The default read timeout, in milliseconds.
*/ */
public static final int DEFAULT_READ_TIMEOUT_MILLIS = public static final int DEFAULT_READ_TIMEOUT_MILLIS =
CronetDataSource.DEFAULT_READ_TIMEOUT_MILLIS; CronetDataSource.DEFAULT_READ_TIMEOUT_MILLIS;
private final CronetEngine cronetEngine; private final CronetEngineWrapper cronetEngineWrapper;
private final Executor executor; private final Executor executor;
private final Predicate<String> contentTypePredicate; private final Predicate<String> contentTypePredicate;
private final TransferListener<? super DataSource> transferListener; private final TransferListener<? super DataSource> transferListener;
private final int connectTimeoutMs; private final int connectTimeoutMs;
private final int readTimeoutMs; private final int readTimeoutMs;
private final boolean resetTimeoutOnRedirects; private final boolean resetTimeoutOnRedirects;
private final HttpDataSource.Factory fallbackFactory;
/**
* Constructs a CronetDataSourceFactory.
* <p>
* If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, the provided
* fallback {@link HttpDataSource.Factory} will be used instead.
*
* Sets {@link CronetDataSource#DEFAULT_CONNECT_TIMEOUT_MILLIS} as the connection timeout, {@link
* CronetDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout and disables
* cross-protocol redirects.
*
* @param cronetEngineWrapper A {@link CronetEngineWrapper}.
* @param executor The {@link java.util.concurrent.Executor} that will perform the requests.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then an {@link InvalidContentTypeException} is thrown from
* {@link CronetDataSource#open}.
* @param transferListener An optional listener.
* @param fallbackFactory A {@link HttpDataSource.Factory} which is used as a fallback in case
* no suitable CronetEngine can be build.
*/
public CronetDataSourceFactory(CronetEngineWrapper cronetEngineWrapper,
Executor executor, Predicate<String> contentTypePredicate,
TransferListener<? super DataSource> transferListener,
HttpDataSource.Factory fallbackFactory) {
this(cronetEngineWrapper, executor, contentTypePredicate, transferListener,
DEFAULT_CONNECT_TIMEOUT_MILLIS, DEFAULT_READ_TIMEOUT_MILLIS, false, fallbackFactory);
}
/**
* Constructs a CronetDataSourceFactory.
* <p>
* If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, a
* {@link DefaultHttpDataSourceFactory} will be used instead.
*
* Sets {@link CronetDataSource#DEFAULT_CONNECT_TIMEOUT_MILLIS} as the connection timeout, {@link
* CronetDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout and disables
* cross-protocol redirects.
*
* @param cronetEngineWrapper A {@link CronetEngineWrapper}.
* @param executor The {@link java.util.concurrent.Executor} that will perform the requests.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then an {@link InvalidContentTypeException} is thrown from
* {@link CronetDataSource#open}.
* @param transferListener An optional listener.
* @param userAgent A user agent used to create a fallback HttpDataSource if needed.
*/
public CronetDataSourceFactory(CronetEngineWrapper cronetEngineWrapper,
Executor executor, Predicate<String> contentTypePredicate,
TransferListener<? super DataSource> transferListener, String userAgent) {
this(cronetEngineWrapper, executor, contentTypePredicate, transferListener,
DEFAULT_CONNECT_TIMEOUT_MILLIS, DEFAULT_READ_TIMEOUT_MILLIS, false,
new DefaultHttpDataSourceFactory(userAgent, transferListener,
DEFAULT_CONNECT_TIMEOUT_MILLIS, DEFAULT_READ_TIMEOUT_MILLIS, false));
}
public CronetDataSourceFactory(CronetEngine cronetEngine, /**
* Constructs a CronetDataSourceFactory.
* <p>
* If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, a
* {@link DefaultHttpDataSourceFactory} will be used instead.
*
* @param cronetEngineWrapper A {@link CronetEngineWrapper}.
* @param executor The {@link java.util.concurrent.Executor} that will perform the requests.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then an {@link InvalidContentTypeException} is thrown from
* {@link CronetDataSource#open}.
* @param transferListener An optional listener.
* @param connectTimeoutMs The connection timeout, in milliseconds.
* @param readTimeoutMs The read timeout, in milliseconds.
* @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs.
* @param userAgent A user agent used to create a fallback HttpDataSource if needed.
*/
public CronetDataSourceFactory(CronetEngineWrapper cronetEngineWrapper,
Executor executor, Predicate<String> contentTypePredicate, Executor executor, Predicate<String> contentTypePredicate,
TransferListener<? super DataSource> transferListener) { TransferListener<? super DataSource> transferListener, int connectTimeoutMs,
this(cronetEngine, executor, contentTypePredicate, transferListener, int readTimeoutMs, boolean resetTimeoutOnRedirects, String userAgent) {
DEFAULT_CONNECT_TIMEOUT_MILLIS, DEFAULT_READ_TIMEOUT_MILLIS, false); this(cronetEngineWrapper, executor, contentTypePredicate, transferListener,
DEFAULT_CONNECT_TIMEOUT_MILLIS, DEFAULT_READ_TIMEOUT_MILLIS, resetTimeoutOnRedirects,
new DefaultHttpDataSourceFactory(userAgent, transferListener, connectTimeoutMs,
readTimeoutMs, resetTimeoutOnRedirects));
} }
public CronetDataSourceFactory(CronetEngine cronetEngine, /**
* Constructs a CronetDataSourceFactory.
* <p>
* If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, the provided
* fallback {@link HttpDataSource.Factory} will be used instead.
*
* @param cronetEngineWrapper A {@link CronetEngineWrapper}.
* @param executor The {@link java.util.concurrent.Executor} that will perform the requests.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then an {@link InvalidContentTypeException} is thrown from
* {@link CronetDataSource#open}.
* @param transferListener An optional listener.
* @param connectTimeoutMs The connection timeout, in milliseconds.
* @param readTimeoutMs The read timeout, in milliseconds.
* @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs.
* @param fallbackFactory A {@link HttpDataSource.Factory} which is used as a fallback in case
* no suitable CronetEngine can be build.
*/
public CronetDataSourceFactory(CronetEngineWrapper cronetEngineWrapper,
Executor executor, Predicate<String> contentTypePredicate, Executor executor, Predicate<String> contentTypePredicate,
TransferListener<? super DataSource> transferListener, int connectTimeoutMs, TransferListener<? super DataSource> transferListener, int connectTimeoutMs,
int readTimeoutMs, boolean resetTimeoutOnRedirects) { int readTimeoutMs, boolean resetTimeoutOnRedirects,
this.cronetEngine = cronetEngine; HttpDataSource.Factory fallbackFactory) {
this.cronetEngineWrapper = cronetEngineWrapper;
this.executor = executor; this.executor = executor;
this.contentTypePredicate = contentTypePredicate; this.contentTypePredicate = contentTypePredicate;
this.transferListener = transferListener; this.transferListener = transferListener;
this.connectTimeoutMs = connectTimeoutMs; this.connectTimeoutMs = connectTimeoutMs;
this.readTimeoutMs = readTimeoutMs; this.readTimeoutMs = readTimeoutMs;
this.resetTimeoutOnRedirects = resetTimeoutOnRedirects; this.resetTimeoutOnRedirects = resetTimeoutOnRedirects;
this.fallbackFactory = fallbackFactory;
} }
@Override @Override
protected CronetDataSource createDataSourceInternal(HttpDataSource.RequestProperties protected HttpDataSource createDataSourceInternal(HttpDataSource.RequestProperties
defaultRequestProperties) { defaultRequestProperties) {
CronetEngine cronetEngine = cronetEngineWrapper.getCronetEngine();
if (cronetEngine == null) {
return fallbackFactory.createDataSource();
}
return new CronetDataSource(cronetEngine, executor, contentTypePredicate, transferListener, return new CronetDataSource(cronetEngine, executor, contentTypePredicate, transferListener,
connectTimeoutMs, readTimeoutMs, resetTimeoutOnRedirects, defaultRequestProperties); connectTimeoutMs, readTimeoutMs, resetTimeoutOnRedirects, defaultRequestProperties);
} }
......
/*
* 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.cronet;
import android.content.Context;
import android.support.annotation.IntDef;
import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.chromium.net.CronetEngine;
import org.chromium.net.CronetProvider;
/**
* A wrapper class for a {@link CronetEngine}.
*/
public final class CronetEngineWrapper {
private static final String TAG = "CronetEngineWrapper";
private final CronetEngine cronetEngine;
private final @CronetEngineSource int cronetEngineSource;
/**
* Source of {@link CronetEngine}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({SOURCE_NATIVE, SOURCE_GMS, SOURCE_UNKNOWN, SOURCE_USER_PROVIDED, SOURCE_UNAVAILABLE})
public @interface CronetEngineSource {}
/**
* Natively bundled Cronet implementation.
*/
public static final int SOURCE_NATIVE = 0;
/**
* Cronet implementation from GMSCore.
*/
public static final int SOURCE_GMS = 1;
/**
* Other (unknown) Cronet implementation.
*/
public static final int SOURCE_UNKNOWN = 2;
/**
* User-provided Cronet engine.
*/
public static final int SOURCE_USER_PROVIDED = 3;
/**
* No Cronet implementation available. Fallback Http provider is used if possible.
*/
public static final int SOURCE_UNAVAILABLE = 4;
/**
* Creates a wrapper for a {@link CronetEngine} which automatically selects the most suitable
* {@link CronetProvider}. Sets wrapper to prefer natively bundled Cronet over GMSCore Cronet
* if both are available.
*
* @param context A context.
*/
public CronetEngineWrapper(Context context) {
this(context, false);
}
/**
* Creates a wrapper for a {@link CronetEngine} which automatically selects the most suitable
* {@link CronetProvider} based on user preference.
*
* @param context A context.
* @param preferGMSCoreCronet Whether Cronet from GMSCore should be preferred over natively
* bundled Cronet if both are available.
*/
public CronetEngineWrapper(Context context, boolean preferGMSCoreCronet) {
CronetEngine cronetEngine = null;
@CronetEngineSource int cronetEngineSource = SOURCE_UNAVAILABLE;
List<CronetProvider> cronetProviders = CronetProvider.getAllProviders(context);
// Remove disabled and fallback Cronet providers from list
for (int i = cronetProviders.size() - 1; i >= 0; i--) {
if (!cronetProviders.get(i).isEnabled()
|| CronetProvider.PROVIDER_NAME_FALLBACK.equals(cronetProviders.get(i).getName())) {
cronetProviders.remove(i);
}
}
// Sort remaining providers by type and version.
CronetProviderComparator providerComparator = new CronetProviderComparator(preferGMSCoreCronet);
Collections.sort(cronetProviders, providerComparator);
for (int i = 0; i < cronetProviders.size() && cronetEngine == null; i++) {
String providerName = cronetProviders.get(i).getName();
try {
cronetEngine = cronetProviders.get(i).createBuilder().build();
if (providerComparator.isNativeProvider(providerName)) {
cronetEngineSource = SOURCE_NATIVE;
} else if (providerComparator.isGMSCoreProvider(providerName)) {
cronetEngineSource = SOURCE_GMS;
} else {
cronetEngineSource = SOURCE_UNKNOWN;
}
Log.d(TAG, "CronetEngine built using " + providerName);
} catch (SecurityException e) {
Log.w(TAG, "Failed to build CronetEngine. Please check if current process has "
+ "android.permission.ACCESS_NETWORK_STATE.");
} catch (UnsatisfiedLinkError e) {
Log.w(TAG, "Failed to link Cronet binaries. Please check if native Cronet binaries are "
+ "bundled into your app.");
}
}
if (cronetEngine == null) {
Log.w(TAG, "Cronet not available. Using fallback provider.");
}
this.cronetEngine = cronetEngine;
this.cronetEngineSource = cronetEngineSource;
}
/**
* Creates a wrapper for an existing CronetEngine.
*
* @param cronetEngine An existing CronetEngine.
*/
public CronetEngineWrapper(CronetEngine cronetEngine) {
this.cronetEngine = cronetEngine;
this.cronetEngineSource = SOURCE_USER_PROVIDED;
}
/**
* Returns the source of the wrapped {@link CronetEngine}.
*
* @return A {@link CronetEngineSource} value.
*/
public @CronetEngineSource int getCronetEngineSource() {
return cronetEngineSource;
}
/**
* Returns the wrapped {@link CronetEngine}.
*
* @return The CronetEngine, or null if no CronetEngine is available.
*/
/* package */ CronetEngine getCronetEngine() {
return cronetEngine;
}
private static class CronetProviderComparator implements Comparator<CronetProvider> {
private final String gmsCoreCronetName;
private final boolean preferGMSCoreCronet;
public CronetProviderComparator(boolean preferGMSCoreCronet) {
// GMSCore CronetProvider classes are only available in some configurations.
// Thus, we use reflection to copy static name.
String gmsCoreVersionString = null;
try {
Class<?> cronetProviderInstallerClass =
Class.forName("com.google.android.gms.net.CronetProviderInstaller");
Field providerNameField = cronetProviderInstallerClass.getDeclaredField("PROVIDER_NAME");
gmsCoreVersionString = (String) providerNameField.get(null);
} catch (ClassNotFoundException e) {
// GMSCore CronetProvider not available.
} catch (NoSuchFieldException e) {
// GMSCore CronetProvider not available.
} catch (IllegalAccessException e) {
// GMSCore CronetProvider not available.
}
gmsCoreCronetName = gmsCoreVersionString;
this.preferGMSCoreCronet = preferGMSCoreCronet;
}
@Override
public int compare(CronetProvider providerLeft, CronetProvider providerRight) {
int typePreferenceLeft = evaluateCronetProviderType(providerLeft.getName());
int typePreferenceRight = evaluateCronetProviderType(providerRight.getName());
if (typePreferenceLeft != typePreferenceRight) {
return typePreferenceLeft - typePreferenceRight;
}
return -compareVersionStrings(providerLeft.getVersion(), providerRight.getVersion());
}
public boolean isNativeProvider(String providerName) {
return CronetProvider.PROVIDER_NAME_APP_PACKAGED.equals(providerName);
}
public boolean isGMSCoreProvider(String providerName) {
return gmsCoreCronetName != null && gmsCoreCronetName.equals(providerName);
}
/**
* Convert Cronet provider name into a sortable preference value.
* Smaller values are preferred.
*/
private int evaluateCronetProviderType(String providerName) {
if (isNativeProvider(providerName)) {
return 1;
}
if (isGMSCoreProvider(providerName)) {
return preferGMSCoreCronet ? 0 : 2;
}
// Unknown provider type.
return -1;
}
/**
* Compares version strings of format "12.123.35.23".
*/
private static int compareVersionStrings(String versionLeft, String versionRight) {
if (versionLeft == null || versionRight == null) {
return 0;
}
String[] versionStringsLeft = versionLeft.split("\\.");
String[] versionStringsRight = versionRight.split("\\.");
int minLength = Math.min(versionStringsLeft.length, versionStringsRight.length);
for (int i = 0; i < minLength; i++) {
if (!versionStringsLeft[i].equals(versionStringsRight[i])) {
try {
int versionIntLeft = Integer.parseInt(versionStringsLeft[i]);
int versionIntRight = Integer.parseInt(versionStringsRight[i]);
return versionIntLeft - versionIntRight;
} catch (NumberFormatException e) {
return 0;
}
}
}
return 0;
}
}
}
...@@ -9,11 +9,10 @@ audio. ...@@ -9,11 +9,10 @@ audio.
## Build instructions ## ## Build instructions ##
* Checkout ExoPlayer along with Extensions To use this extension you need to clone the ExoPlayer repository and depend on
its modules locally. Instructions for doing this can be found in ExoPlayer's
``` [top level README][]. In addition, it's necessary to build the extension's
git clone https://github.com/google/ExoPlayer.git native components as follows:
```
* Set the following environment variables: * Set the following environment variables:
...@@ -25,8 +24,6 @@ FFMPEG_EXT_PATH="${EXOPLAYER_ROOT}/extensions/ffmpeg/src/main" ...@@ -25,8 +24,6 @@ FFMPEG_EXT_PATH="${EXOPLAYER_ROOT}/extensions/ffmpeg/src/main"
* Download the [Android NDK][] and set its location in an environment variable: * Download the [Android NDK][] and set its location in an environment variable:
[Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
``` ```
NDK_PATH="<path to Android NDK>" NDK_PATH="<path to Android NDK>"
``` ```
...@@ -106,20 +103,5 @@ cd "${FFMPEG_EXT_PATH}"/jni && \ ...@@ -106,20 +103,5 @@ cd "${FFMPEG_EXT_PATH}"/jni && \
${NDK_PATH}/ndk-build APP_ABI="armeabi-v7a arm64-v8a x86" -j4 ${NDK_PATH}/ndk-build APP_ABI="armeabi-v7a arm64-v8a x86" -j4
``` ```
* In your project, you can add a dependency on the extension by using a rule [top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
like this: [Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
```
// in settings.gradle
include ':..:ExoPlayer:library'
include ':..:ExoPlayer:extension-ffmpeg'
// in build.gradle
dependencies {
compile project(':..:ExoPlayer:library')
compile project(':..:ExoPlayer:extension-ffmpeg')
}
```
* Now, when you build your app, the extension will be built and the native
libraries will be packaged along with the APK.
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// 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.
apply from: '../../constants.gradle'
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
android { android {
...@@ -30,7 +31,7 @@ android { ...@@ -30,7 +31,7 @@ android {
} }
dependencies { dependencies {
compile project(':library-core') compile project(modulePrefix + 'library-core')
} }
ext { ext {
......
...@@ -10,11 +10,10 @@ ExoPlayer to play Flac audio on Android devices. ...@@ -10,11 +10,10 @@ ExoPlayer to play Flac audio on Android devices.
## Build Instructions ## ## Build Instructions ##
* Checkout ExoPlayer along with Extensions: To use this extension you need to clone the ExoPlayer repository and depend on
its modules locally. Instructions for doing this can be found in ExoPlayer's
``` [top level README][]. In addition, it's necessary to build the extension's
git clone https://github.com/google/ExoPlayer.git native components as follows:
```
* Set the following environment variables: * Set the following environment variables:
...@@ -26,8 +25,6 @@ FLAC_EXT_PATH="${EXOPLAYER_ROOT}/extensions/flac/src/main" ...@@ -26,8 +25,6 @@ FLAC_EXT_PATH="${EXOPLAYER_ROOT}/extensions/flac/src/main"
* Download the [Android NDK][] and set its location in an environment variable: * Download the [Android NDK][] and set its location in an environment variable:
[Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
``` ```
NDK_PATH="<path to Android NDK>" NDK_PATH="<path to Android NDK>"
``` ```
...@@ -47,20 +44,5 @@ cd "${FLAC_EXT_PATH}"/jni && \ ...@@ -47,20 +44,5 @@ cd "${FLAC_EXT_PATH}"/jni && \
${NDK_PATH}/ndk-build APP_ABI=all -j4 ${NDK_PATH}/ndk-build APP_ABI=all -j4
``` ```
* In your project, you can add a dependency to the Flac Extension by using a [top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
rule like this: [Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
```
// in settings.gradle
include ':..:ExoPlayer:library'
include ':..:ExoPlayer:extension-flac'
// in build.gradle
dependencies {
compile project(':..:ExoPlayer:library')
compile project(':..:ExoPlayer:extension-flac')
}
```
* Now, when you build your app, the Flac extension will be built and the native
libraries will be packaged along with the APK.
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// 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.
apply from: '../../constants.gradle'
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
android { android {
...@@ -30,8 +31,8 @@ android { ...@@ -30,8 +31,8 @@ android {
} }
dependencies { dependencies {
compile project(':library-core') compile project(modulePrefix + 'library-core')
androidTestCompile project(':testutils') androidTestCompile project(modulePrefix + 'testutils')
} }
ext { ext {
......
...@@ -28,7 +28,6 @@ ...@@ -28,7 +28,6 @@
<instrumentation <instrumentation
android:targetPackage="com.google.android.exoplayer2.ext.flac.test" android:targetPackage="com.google.android.exoplayer2.ext.flac.test"
android:name="android.test.InstrumentationTestRunner" android:name="android.test.InstrumentationTestRunner"/>
tools:replace="android:targetPackage"/>
</manifest> </manifest>
...@@ -17,7 +17,8 @@ package com.google.android.exoplayer2.ext.flac; ...@@ -17,7 +17,8 @@ package com.google.android.exoplayer2.ext.flac;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.ExtractorAsserts;
import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory;
/** /**
* Unit test for {@link FlacExtractor}. * Unit test for {@link FlacExtractor}.
...@@ -25,7 +26,7 @@ import com.google.android.exoplayer2.testutil.TestUtil; ...@@ -25,7 +26,7 @@ import com.google.android.exoplayer2.testutil.TestUtil;
public class FlacExtractorTest extends InstrumentationTestCase { public class FlacExtractorTest extends InstrumentationTestCase {
public void testSample() throws Exception { public void testSample() throws Exception {
TestUtil.assertOutput(new TestUtil.ExtractorFactory() { ExtractorAsserts.assertOutput(new ExtractorFactory() {
@Override @Override
public Extractor create() { public Extractor create() {
return new FlacExtractor(); return new FlacExtractor();
......
...@@ -126,6 +126,11 @@ public class FlacPlaybackTest extends InstrumentationTestCase { ...@@ -126,6 +126,11 @@ public class FlacPlaybackTest extends InstrumentationTestCase {
} }
} }
@Override
public void onRepeatModeChanged(int repeatMode) {
// Do nothing.
}
private void releasePlayerAndQuitLooper() { private void releasePlayerAndQuitLooper() {
player.release(); player.release();
Looper.myLooper().quit(); Looper.myLooper().quit();
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include <cassert> #include <cassert>
#include <cstdlib> #include <cstdlib>
#include <cstring>
#define LOG_TAG "FLACParser" #define LOG_TAG "FLACParser"
#define ALOGE(...) \ #define ALOGE(...) \
......
...@@ -6,7 +6,10 @@ The GVR extension wraps the [Google VR SDK for Android][]. It provides a ...@@ -6,7 +6,10 @@ The GVR extension wraps the [Google VR SDK for Android][]. It provides a
GvrAudioProcessor, which uses [GvrAudioSurround][] to provide binaural rendering GvrAudioProcessor, which uses [GvrAudioSurround][] to provide binaural rendering
of surround sound and ambisonic soundfields. of surround sound and ambisonic soundfields.
## Using the extension ## [Google VR SDK for Android]: https://developers.google.com/vr/android/
[GvrAudioSurround]: https://developers.google.com/vr/android/reference/com/google/vr/sdk/audio/GvrAudioSurround
## Getting the extension ##
The easiest way to use the extension is to add it as a gradle dependency. You The easiest way to use the extension is to add it as a gradle dependency. You
need to make sure you have the jcenter repository included in the `build.gradle` need to make sure you have the jcenter repository included in the `build.gradle`
...@@ -27,12 +30,15 @@ compile 'com.google.android.exoplayer:extension-gvr:rX.X.X' ...@@ -27,12 +30,15 @@ compile 'com.google.android.exoplayer:extension-gvr:rX.X.X'
where `rX.X.X` is the version, which must match the version of the ExoPlayer where `rX.X.X` is the version, which must match the version of the ExoPlayer
library being used. library being used.
## Using GvrAudioProcessor ## Alternatively, you can clone the ExoPlayer repository and depend on the module
locally. Instructions for doing this can be found in ExoPlayer's
[top level README][].
## Using the extension ##
* If using SimpleExoPlayer, override SimpleExoPlayer.buildAudioProcessors to * If using SimpleExoPlayer, override SimpleExoPlayer.buildAudioProcessors to
return a GvrAudioProcessor. return a GvrAudioProcessor.
* If constructing renderers directly, pass a GvrAudioProcessor to * If constructing renderers directly, pass a GvrAudioProcessor to
MediaCodecAudioRenderer's constructor. MediaCodecAudioRenderer's constructor.
[Google VR SDK for Android]: https://developers.google.com/vr/android/ [top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
[GvrAudioSurround]: https://developers.google.com/vr/android/reference/com/google/vr/sdk/audio/GvrAudioSurround
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// 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.
apply from: '../../constants.gradle'
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
android { android {
...@@ -24,8 +25,8 @@ android { ...@@ -24,8 +25,8 @@ android {
} }
dependencies { dependencies {
compile project(':library-core') compile project(modulePrefix + 'library-core')
compile 'com.google.vr:sdk-audio:1.30.0' compile 'com.google.vr:sdk-audio:1.60.1'
} }
ext { ext {
......
...@@ -82,6 +82,9 @@ public final class GvrAudioProcessor implements AudioProcessor { ...@@ -82,6 +82,9 @@ public final class GvrAudioProcessor implements AudioProcessor {
maybeReleaseGvrAudioSurround(); maybeReleaseGvrAudioSurround();
int surroundFormat; int surroundFormat;
switch (channelCount) { switch (channelCount) {
case 1:
surroundFormat = GvrAudioSurround.SurroundFormat.SURROUND_MONO;
break;
case 2: case 2:
surroundFormat = GvrAudioSurround.SurroundFormat.SURROUND_STEREO; surroundFormat = GvrAudioSurround.SurroundFormat.SURROUND_STEREO;
break; break;
......
# ExoPlayer IMA extension #
## Description ##
The IMA extension is a [MediaSource][] implementation wrapping the
[Interactive Media Ads SDK for Android][IMA]. You can use it to insert ads
alongside content.
[IMA]: https://developers.google.com/interactive-media-ads/docs/sdks/android/
[MediaSource]: https://github.com/google/ExoPlayer/blob/release-v2/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java
## Getting the extension ##
To use this extension you need to clone the ExoPlayer repository and depend on
its modules locally. Instructions for doing this can be found in ExoPlayer's
[top level README][].
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
## Using the extension ##
Pass a single-window content `MediaSource` to `ImaAdsMediaSource`'s constructor,
along with a `ViewGroup` that is on top of the player and the ad tag URI to
show. The IMA documentation includes some [sample ad tags][] for testing. Then
pass the `ImaAdsMediaSource` to `ExoPlayer.prepare`.
You can try the IMA extension in the ExoPlayer demo app. To do this you must
select and build one of the `withExtensions` build variants of the demo app in
Android Studio. You can find IMA test content in the "IMA sample ad tags"
section of the app.
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
[sample ad tags]: https://developers.google.com/interactive-media-ads/docs/sdks/android/tags
## Known issues ##
This is a preview version with some known issues:
* Tapping the 'More info' button on an ad in the demo app will pause the
activity, which destroys the ImaAdsMediaSource. Played ad breaks will be
shown to the user again if the demo app returns to the foreground.
* Ad loading timeouts are currently propagated as player errors, rather than
being silently handled by resuming content.
apply from: '../../constants.gradle'
apply plugin: 'com.android.library'
android {
compileSdkVersion project.ext.compileSdkVersion
buildToolsVersion project.ext.buildToolsVersion
defaultConfig {
minSdkVersion 14
targetSdkVersion project.ext.targetSdkVersion
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
dependencies {
compile project(modulePrefix + 'library-core')
compile 'com.android.support:support-annotations:' + supportLibraryVersion
compile 'com.google.ads.interactivemedia.v3:interactivemedia:3.7.4'
compile 'com.google.android.gms:play-services-ads:11.0.1'
androidTestCompile project(modulePrefix + 'library')
androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion
androidTestCompile 'org.mockito:mockito-core:' + mockitoVersion
androidTestCompile 'com.android.support.test:runner:' + testSupportLibraryVersion
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.exoplayer2.ext.ima">
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version"/>
</manifest>
/*
* 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.ima;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.util.Assertions;
/**
* A {@link Timeline} for sources that have ads.
*/
public final class SinglePeriodAdTimeline extends Timeline {
private final Timeline contentTimeline;
private final long[] adGroupTimesUs;
private final boolean[] hasPlayedAdGroup;
private final int[] adCounts;
private final boolean[][] isAdAvailable;
private final long[][] adDurationsUs;
/**
* Creates a new timeline with a single period containing the specified ads.
*
* @param contentTimeline The timeline of the content alongside which ads will be played. It must
* have one window and one period.
* @param adGroupTimesUs The times of ad groups relative to the start of the period, in
* microseconds. A final element with the value {@link C#TIME_END_OF_SOURCE} indicates that
* the period has a postroll ad.
* @param hasPlayedAdGroup Whether each ad group has been played.
* @param adCounts The number of ads in each ad group. An element may be {@link C#LENGTH_UNSET}
* if the number of ads is not yet known.
* @param isAdAvailable Whether each ad in each ad group is available.
* @param adDurationsUs The duration of each ad in each ad group, in microseconds. An element
* may be {@link C#TIME_UNSET} if the duration is not yet known.
*/
public SinglePeriodAdTimeline(Timeline contentTimeline, long[] adGroupTimesUs,
boolean[] hasPlayedAdGroup, int[] adCounts, boolean[][] isAdAvailable,
long[][] adDurationsUs) {
Assertions.checkState(contentTimeline.getPeriodCount() == 1);
Assertions.checkState(contentTimeline.getWindowCount() == 1);
this.contentTimeline = contentTimeline;
this.adGroupTimesUs = adGroupTimesUs;
this.hasPlayedAdGroup = hasPlayedAdGroup;
this.adCounts = adCounts;
this.isAdAvailable = isAdAvailable;
this.adDurationsUs = adDurationsUs;
}
@Override
public int getWindowCount() {
return 1;
}
@Override
public Window getWindow(int windowIndex, Window window, boolean setIds,
long defaultPositionProjectionUs) {
return contentTimeline.getWindow(windowIndex, window, setIds, defaultPositionProjectionUs);
}
@Override
public int getPeriodCount() {
return 1;
}
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
contentTimeline.getPeriod(periodIndex, period, setIds);
period.set(period.id, period.uid, period.windowIndex, period.durationUs,
period.getPositionInWindowUs(), adGroupTimesUs, hasPlayedAdGroup, adCounts,
isAdAvailable, adDurationsUs);
return period;
}
@Override
public int getIndexOfPeriod(Object uid) {
return contentTimeline.getIndexOfPeriod(uid);
}
}
...@@ -5,19 +5,12 @@ ...@@ -5,19 +5,12 @@
The OkHttp Extension is an [HttpDataSource][] implementation using Square's The OkHttp Extension is an [HttpDataSource][] implementation using Square's
[OkHttp][]. [OkHttp][].
## Using the extension ## [HttpDataSource]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/upstream/HttpDataSource.html
[OkHttp]: https://square.github.io/okhttp/
The easiest way to use the extension is to add it as a gradle dependency. You
need to make sure you have the jcenter repository included in the `build.gradle`
file in the root of your project:
```gradle ## Getting the extension ##
repositories {
jcenter()
}
```
Next, include the following in your module's `build.gradle` file: The easiest way to use the extension is to add it as a gradle dependency:
```gradle ```gradle
compile 'com.google.android.exoplayer:extension-okhttp:rX.X.X' compile 'com.google.android.exoplayer:extension-okhttp:rX.X.X'
...@@ -26,5 +19,8 @@ compile 'com.google.android.exoplayer:extension-okhttp:rX.X.X' ...@@ -26,5 +19,8 @@ compile 'com.google.android.exoplayer:extension-okhttp:rX.X.X'
where `rX.X.X` is the version, which must match the version of the ExoPlayer where `rX.X.X` is the version, which must match the version of the ExoPlayer
library being used. library being used.
[HttpDataSource]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/upstream/HttpDataSource.html Alternatively, you can clone the ExoPlayer repository and depend on the module
[OkHttp]: https://square.github.io/okhttp/ locally. Instructions for doing this can be found in ExoPlayer's
[top level README][].
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// 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.
apply from: '../../constants.gradle'
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
android { android {
...@@ -29,7 +30,7 @@ android { ...@@ -29,7 +30,7 @@ android {
} }
dependencies { dependencies {
compile project(':library-core') compile project(modulePrefix + 'library-core')
compile('com.squareup.okhttp3:okhttp:3.6.0') { compile('com.squareup.okhttp3:okhttp:3.6.0') {
exclude group: 'org.json' exclude group: 'org.json'
} }
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package com.google.android.exoplayer2.ext.okhttp; package com.google.android.exoplayer2.ext.okhttp;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
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;
...@@ -45,13 +47,14 @@ public class OkHttpDataSource implements HttpDataSource { ...@@ -45,13 +47,14 @@ public class OkHttpDataSource implements HttpDataSource {
private static final AtomicReference<byte[]> skipBufferReference = new AtomicReference<>(); private static final AtomicReference<byte[]> skipBufferReference = new AtomicReference<>();
private final Call.Factory callFactory; @NonNull private final Call.Factory callFactory;
private final String userAgent; @NonNull private final RequestProperties requestProperties;
private final Predicate<String> contentTypePredicate;
private final TransferListener<? super OkHttpDataSource> listener; @Nullable private final String userAgent;
private final CacheControl cacheControl; @Nullable private final Predicate<String> contentTypePredicate;
private final RequestProperties defaultRequestProperties; @Nullable private final TransferListener<? super OkHttpDataSource> listener;
private final RequestProperties requestProperties; @Nullable private final CacheControl cacheControl;
@Nullable private final RequestProperties defaultRequestProperties;
private DataSpec dataSpec; private DataSpec dataSpec;
private Response response; private Response response;
...@@ -67,33 +70,34 @@ public class OkHttpDataSource implements HttpDataSource { ...@@ -67,33 +70,34 @@ public class OkHttpDataSource implements HttpDataSource {
/** /**
* @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use * @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use
* by the source. * by the source.
* @param userAgent The User-Agent string that should be used. * @param userAgent An optional User-Agent string.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then a InvalidContentTypeException} is thrown from {@link #open(DataSpec)}. * predicate then a InvalidContentTypeException} is thrown from {@link #open(DataSpec)}.
*/ */
public OkHttpDataSource(Call.Factory callFactory, String userAgent, public OkHttpDataSource(@NonNull Call.Factory callFactory, @Nullable String userAgent,
Predicate<String> contentTypePredicate) { @Nullable Predicate<String> contentTypePredicate) {
this(callFactory, userAgent, contentTypePredicate, null); this(callFactory, userAgent, contentTypePredicate, null);
} }
/** /**
* @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use * @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use
* by the source. * by the source.
* @param userAgent The User-Agent string that should be used. * @param userAgent An optional User-Agent string.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then a {@link InvalidContentTypeException} is thrown from * predicate then a {@link InvalidContentTypeException} is thrown from
* {@link #open(DataSpec)}. * {@link #open(DataSpec)}.
* @param listener An optional listener. * @param listener An optional listener.
*/ */
public OkHttpDataSource(Call.Factory callFactory, String userAgent, public OkHttpDataSource(@NonNull Call.Factory callFactory, @Nullable String userAgent,
Predicate<String> contentTypePredicate, TransferListener<? super OkHttpDataSource> listener) { @Nullable Predicate<String> contentTypePredicate,
@Nullable TransferListener<? super OkHttpDataSource> listener) {
this(callFactory, userAgent, contentTypePredicate, listener, null, null); this(callFactory, userAgent, contentTypePredicate, listener, null, null);
} }
/** /**
* @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use * @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use
* by the source. * by the source.
* @param userAgent The User-Agent string that should be used. * @param userAgent An optional User-Agent string.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then a {@link InvalidContentTypeException} is thrown from * predicate then a {@link InvalidContentTypeException} is thrown from
* {@link #open(DataSpec)}. * {@link #open(DataSpec)}.
...@@ -102,11 +106,12 @@ public class OkHttpDataSource implements HttpDataSource { ...@@ -102,11 +106,12 @@ public class OkHttpDataSource implements HttpDataSource {
* @param defaultRequestProperties The optional default {@link RequestProperties} to be sent to * @param defaultRequestProperties The optional default {@link RequestProperties} to be sent to
* the server as HTTP headers on every request. * the server as HTTP headers on every request.
*/ */
public OkHttpDataSource(Call.Factory callFactory, String userAgent, public OkHttpDataSource(@NonNull Call.Factory callFactory, @Nullable String userAgent,
Predicate<String> contentTypePredicate, TransferListener<? super OkHttpDataSource> listener, @Nullable Predicate<String> contentTypePredicate,
CacheControl cacheControl, RequestProperties defaultRequestProperties) { @Nullable TransferListener<? super OkHttpDataSource> listener,
@Nullable CacheControl cacheControl, @Nullable RequestProperties defaultRequestProperties) {
this.callFactory = Assertions.checkNotNull(callFactory); this.callFactory = Assertions.checkNotNull(callFactory);
this.userAgent = Assertions.checkNotEmpty(userAgent); this.userAgent = userAgent;
this.contentTypePredicate = contentTypePredicate; this.contentTypePredicate = contentTypePredicate;
this.listener = listener; this.listener = listener;
this.cacheControl = cacheControl; this.cacheControl = cacheControl;
...@@ -280,7 +285,10 @@ public class OkHttpDataSource implements HttpDataSource { ...@@ -280,7 +285,10 @@ public class OkHttpDataSource implements HttpDataSource {
} }
builder.addHeader("Range", rangeRequest); builder.addHeader("Range", rangeRequest);
} }
builder.addHeader("User-Agent", userAgent); if (userAgent != null) {
builder.addHeader("User-Agent", userAgent);
}
if (!allowGzip) { if (!allowGzip) {
builder.addHeader("Accept-Encoding", "identity"); builder.addHeader("Accept-Encoding", "identity");
} }
......
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
*/ */
package com.google.android.exoplayer2.ext.okhttp; package com.google.android.exoplayer2.ext.okhttp;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
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;
...@@ -28,31 +30,32 @@ import okhttp3.Call; ...@@ -28,31 +30,32 @@ import okhttp3.Call;
*/ */
public final class OkHttpDataSourceFactory extends BaseFactory { public final class OkHttpDataSourceFactory extends BaseFactory {
private final Call.Factory callFactory; @NonNull private final Call.Factory callFactory;
private final String userAgent; @Nullable private final String userAgent;
private final TransferListener<? super DataSource> listener; @Nullable private final TransferListener<? super DataSource> listener;
private final CacheControl cacheControl; @Nullable private final CacheControl cacheControl;
/** /**
* @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use * @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use
* by the sources created by the factory. * by the sources created by the factory.
* @param userAgent The User-Agent string that should be used. * @param userAgent An optional User-Agent string.
* @param listener An optional listener. * @param listener An optional listener.
*/ */
public OkHttpDataSourceFactory(Call.Factory callFactory, String userAgent, public OkHttpDataSourceFactory(@NonNull Call.Factory callFactory, @Nullable String userAgent,
TransferListener<? super DataSource> listener) { @Nullable TransferListener<? super DataSource> listener) {
this(callFactory, userAgent, listener, null); this(callFactory, userAgent, listener, null);
} }
/** /**
* @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use * @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use
* by the sources created by the factory. * by the sources created by the factory.
* @param userAgent The User-Agent string that should be used. * @param userAgent An optional User-Agent string.
* @param listener An optional listener. * @param listener An optional listener.
* @param cacheControl An optional {@link CacheControl} for setting the Cache-Control header. * @param cacheControl An optional {@link CacheControl} for setting the Cache-Control header.
*/ */
public OkHttpDataSourceFactory(Call.Factory callFactory, String userAgent, public OkHttpDataSourceFactory(@NonNull Call.Factory callFactory, @Nullable String userAgent,
TransferListener<? super DataSource> listener, CacheControl cacheControl) { @Nullable TransferListener<? super DataSource> listener,
@Nullable CacheControl cacheControl) {
this.callFactory = callFactory; this.callFactory = callFactory;
this.userAgent = userAgent; this.userAgent = userAgent;
this.listener = listener; this.listener = listener;
......
...@@ -10,11 +10,10 @@ ExoPlayer to play Opus audio on Android devices. ...@@ -10,11 +10,10 @@ ExoPlayer to play Opus audio on Android devices.
## Build Instructions ## ## Build Instructions ##
* Checkout ExoPlayer along with Extensions: To use this extension you need to clone the ExoPlayer repository and depend on
its modules locally. Instructions for doing this can be found in ExoPlayer's
``` [top level README][]. In addition, it's necessary to build the extension's
git clone https://github.com/google/ExoPlayer.git native components as follows:
```
* Set the following environment variables: * Set the following environment variables:
...@@ -26,8 +25,6 @@ OPUS_EXT_PATH="${EXOPLAYER_ROOT}/extensions/opus/src/main" ...@@ -26,8 +25,6 @@ OPUS_EXT_PATH="${EXOPLAYER_ROOT}/extensions/opus/src/main"
* Download the [Android NDK][] and set its location in an environment variable: * Download the [Android NDK][] and set its location in an environment variable:
[Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
``` ```
NDK_PATH="<path to Android NDK>" NDK_PATH="<path to Android NDK>"
``` ```
...@@ -52,23 +49,8 @@ cd "${OPUS_EXT_PATH}"/jni && \ ...@@ -52,23 +49,8 @@ cd "${OPUS_EXT_PATH}"/jni && \
${NDK_PATH}/ndk-build APP_ABI=all -j4 ${NDK_PATH}/ndk-build APP_ABI=all -j4
``` ```
* In your project, you can add a dependency to the Opus Extension by using a [top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
rule like this: [Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
```
// in settings.gradle
include ':..:ExoPlayer:library'
include ':..:ExoPlayer:extension-opus'
// in build.gradle
dependencies {
compile project(':..:ExoPlayer:library')
compile project(':..:ExoPlayer:extension-opus')
}
```
* Now, when you build your app, the Opus extension will be built and the native
libraries will be packaged along with the APK.
## Notes ## ## Notes ##
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// 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.
apply from: '../../constants.gradle'
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
android { android {
...@@ -30,7 +31,7 @@ android { ...@@ -30,7 +31,7 @@ android {
} }
dependencies { dependencies {
compile project(':library-core') compile project(modulePrefix + 'library-core')
} }
ext { ext {
......
...@@ -28,7 +28,6 @@ ...@@ -28,7 +28,6 @@
<instrumentation <instrumentation
android:targetPackage="com.google.android.exoplayer2.ext.opus.test" android:targetPackage="com.google.android.exoplayer2.ext.opus.test"
android:name="android.test.InstrumentationTestRunner" android:name="android.test.InstrumentationTestRunner"/>
tools:replace="android:targetPackage"/>
</manifest> </manifest>
...@@ -126,6 +126,11 @@ public class OpusPlaybackTest extends InstrumentationTestCase { ...@@ -126,6 +126,11 @@ public class OpusPlaybackTest extends InstrumentationTestCase {
} }
} }
@Override
public void onRepeatModeChanged(int repeatMode) {
// Do nothing.
}
private void releasePlayerAndQuitLooper() { private void releasePlayerAndQuitLooper() {
player.release(); player.release();
Looper.myLooper().quit(); Looper.myLooper().quit();
......
...@@ -10,11 +10,10 @@ VP9 video on Android devices. ...@@ -10,11 +10,10 @@ VP9 video on Android devices.
## Build Instructions ## ## Build Instructions ##
* Checkout ExoPlayer along with Extensions: To use this extension you need to clone the ExoPlayer repository and depend on
its modules locally. Instructions for doing this can be found in ExoPlayer's
``` [top level README][]. In addition, it's necessary to build the extension's
git clone https://github.com/google/ExoPlayer.git native components as follows:
```
* Set the following environment variables: * Set the following environment variables:
...@@ -26,8 +25,6 @@ VP9_EXT_PATH="${EXOPLAYER_ROOT}/extensions/vp9/src/main" ...@@ -26,8 +25,6 @@ VP9_EXT_PATH="${EXOPLAYER_ROOT}/extensions/vp9/src/main"
* Download the [Android NDK][] and set its location in an environment variable: * Download the [Android NDK][] and set its location in an environment variable:
[Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
``` ```
NDK_PATH="<path to Android NDK>" NDK_PATH="<path to Android NDK>"
``` ```
...@@ -66,23 +63,8 @@ cd "${VP9_EXT_PATH}"/jni && \ ...@@ -66,23 +63,8 @@ cd "${VP9_EXT_PATH}"/jni && \
${NDK_PATH}/ndk-build APP_ABI=all -j4 ${NDK_PATH}/ndk-build APP_ABI=all -j4
``` ```
* In your project, you can add a dependency to the VP9 Extension by using a the [top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
following rule: [Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
```
// in settings.gradle
include ':..:ExoPlayer:library'
include ':..:ExoPlayer:extension-vp9'
// in build.gradle
dependencies {
compile project(':..:ExoPlayer:library')
compile project(':..:ExoPlayer:extension-vp9')
}
```
* Now, when you build your app, the VP9 extension will be built and the native
libraries will be packaged along with the APK.
## Notes ## ## Notes ##
...@@ -94,4 +76,3 @@ dependencies { ...@@ -94,4 +76,3 @@ dependencies {
`${VP9_EXT_PATH}/jni/libvpx` or `${VP9_EXT_PATH}/jni/libyuv` respectively. But `${VP9_EXT_PATH}/jni/libvpx` or `${VP9_EXT_PATH}/jni/libyuv` respectively. But
please note that `generate_libvpx_android_configs.sh` and the makefiles need please note that `generate_libvpx_android_configs.sh` and the makefiles need
to be modified to work with arbitrary versions of libvpx and libyuv. to be modified to work with arbitrary versions of libvpx and libyuv.
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// 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.
apply from: '../../constants.gradle'
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
android { android {
...@@ -30,7 +31,7 @@ android { ...@@ -30,7 +31,7 @@ android {
} }
dependencies { dependencies {
compile project(':library-core') compile project(modulePrefix + 'library-core')
} }
ext { ext {
......
...@@ -28,7 +28,6 @@ ...@@ -28,7 +28,6 @@
<instrumentation <instrumentation
android:targetPackage="com.google.android.exoplayer2.ext.vp9.test" android:targetPackage="com.google.android.exoplayer2.ext.vp9.test"
android:name="android.test.InstrumentationTestRunner" android:name="android.test.InstrumentationTestRunner"/>
tools:replace="android:targetPackage"/>
</manifest> </manifest>
...@@ -158,6 +158,11 @@ public class VpxPlaybackTest extends InstrumentationTestCase { ...@@ -158,6 +158,11 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
} }
} }
@Override
public void onRepeatModeChanged(int repeatMode) {
// Do nothing.
}
private void releasePlayerAndQuitLooper() { private void releasePlayerAndQuitLooper() {
player.release(); player.release();
Looper.myLooper().quit(); Looper.myLooper().quit();
......
...@@ -17,7 +17,6 @@ package com.google.android.exoplayer2.ext.vp9; ...@@ -17,7 +17,6 @@ package com.google.android.exoplayer2.ext.vp9;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.decoder.CryptoInfo; import com.google.android.exoplayer2.decoder.CryptoInfo;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.decoder.SimpleDecoder; import com.google.android.exoplayer2.decoder.SimpleDecoder;
import com.google.android.exoplayer2.drm.DecryptionException; import com.google.android.exoplayer2.drm.DecryptionException;
import com.google.android.exoplayer2.drm.ExoMediaCrypto; import com.google.android.exoplayer2.drm.ExoMediaCrypto;
...@@ -27,7 +26,7 @@ import java.nio.ByteBuffer; ...@@ -27,7 +26,7 @@ import java.nio.ByteBuffer;
* Vpx decoder. * Vpx decoder.
*/ */
/* package */ final class VpxDecoder extends /* package */ final class VpxDecoder extends
SimpleDecoder<DecoderInputBuffer, VpxOutputBuffer, VpxDecoderException> { SimpleDecoder<VpxInputBuffer, VpxOutputBuffer, VpxDecoderException> {
public static final int OUTPUT_MODE_NONE = -1; public static final int OUTPUT_MODE_NONE = -1;
public static final int OUTPUT_MODE_YUV = 0; public static final int OUTPUT_MODE_YUV = 0;
...@@ -54,7 +53,7 @@ import java.nio.ByteBuffer; ...@@ -54,7 +53,7 @@ import java.nio.ByteBuffer;
*/ */
public VpxDecoder(int numInputBuffers, int numOutputBuffers, int initialInputBufferSize, public VpxDecoder(int numInputBuffers, int numOutputBuffers, int initialInputBufferSize,
ExoMediaCrypto exoMediaCrypto) throws VpxDecoderException { ExoMediaCrypto exoMediaCrypto) throws VpxDecoderException {
super(new DecoderInputBuffer[numInputBuffers], new VpxOutputBuffer[numOutputBuffers]); super(new VpxInputBuffer[numInputBuffers], new VpxOutputBuffer[numOutputBuffers]);
if (!VpxLibrary.isAvailable()) { if (!VpxLibrary.isAvailable()) {
throw new VpxDecoderException("Failed to load decoder native libraries."); throw new VpxDecoderException("Failed to load decoder native libraries.");
} }
...@@ -85,8 +84,8 @@ import java.nio.ByteBuffer; ...@@ -85,8 +84,8 @@ import java.nio.ByteBuffer;
} }
@Override @Override
protected DecoderInputBuffer createInputBuffer() { protected VpxInputBuffer createInputBuffer() {
return new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT); return new VpxInputBuffer();
} }
@Override @Override
...@@ -100,7 +99,7 @@ import java.nio.ByteBuffer; ...@@ -100,7 +99,7 @@ import java.nio.ByteBuffer;
} }
@Override @Override
protected VpxDecoderException decode(DecoderInputBuffer inputBuffer, VpxOutputBuffer outputBuffer, protected VpxDecoderException decode(VpxInputBuffer inputBuffer, VpxOutputBuffer outputBuffer,
boolean reset) { boolean reset) {
ByteBuffer inputData = inputBuffer.data; ByteBuffer inputData = inputBuffer.data;
int inputSize = inputData.limit(); int inputSize = inputData.limit();
...@@ -128,6 +127,7 @@ import java.nio.ByteBuffer; ...@@ -128,6 +127,7 @@ import java.nio.ByteBuffer;
} else if (getFrameResult == -1) { } else if (getFrameResult == -1) {
return new VpxDecoderException("Buffer initialization failed."); return new VpxDecoderException("Buffer initialization failed.");
} }
outputBuffer.colorInfo = inputBuffer.colorInfo;
return null; return null;
} }
......
/*
* 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.vp9;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.video.ColorInfo;
/**
* Input buffer to a {@link VpxDecoder}.
*/
/* package */ final class VpxInputBuffer extends DecoderInputBuffer {
public ColorInfo colorInfo;
public VpxInputBuffer() {
super(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);
}
}
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.ext.vp9; package com.google.android.exoplayer2.ext.vp9;
import com.google.android.exoplayer2.decoder.OutputBuffer; import com.google.android.exoplayer2.decoder.OutputBuffer;
import com.google.android.exoplayer2.video.ColorInfo;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** /**
...@@ -37,6 +38,8 @@ import java.nio.ByteBuffer; ...@@ -37,6 +38,8 @@ import java.nio.ByteBuffer;
public ByteBuffer data; public ByteBuffer data;
public int width; public int width;
public int height; public int height;
public ColorInfo colorInfo;
/** /**
* YUV planes for YUV mode. * YUV planes for YUV mode.
*/ */
......
...@@ -51,8 +51,7 @@ config[3]+=" --disable-sse3 --disable-ssse3 --disable-sse4_1 --disable-avx" ...@@ -51,8 +51,7 @@ config[3]+=" --disable-sse3 --disable-ssse3 --disable-sse4_1 --disable-avx"
config[3]+=" --disable-avx2 --enable-pic" config[3]+=" --disable-avx2 --enable-pic"
arch[4]="arm64-v8a" arch[4]="arm64-v8a"
config[4]="--force-target=armv8-android-gcc --sdk-path=$ndk --disable-neon" config[4]="--force-target=armv8-android-gcc --sdk-path=$ndk --enable-neon"
config[4]+=" --disable-neon-asm"
arch[5]="x86_64" arch[5]="x86_64"
config[5]="--force-target=x86_64-android-gcc --sdk-path=$ndk --disable-sse2" config[5]="--force-target=x86_64-android-gcc --sdk-path=$ndk --disable-sse2"
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// 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.
apply from: '../../constants.gradle'
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
android { android {
...@@ -24,11 +25,11 @@ android { ...@@ -24,11 +25,11 @@ android {
} }
dependencies { dependencies {
compile project(':library-core') compile project(modulePrefix + 'library-core')
compile project(':library-dash') compile project(modulePrefix + 'library-dash')
compile project(':library-hls') compile project(modulePrefix + 'library-hls')
compile project(':library-smoothstreaming') compile project(modulePrefix + 'library-smoothstreaming')
compile project(':library-ui') compile project(modulePrefix + 'library-ui')
} }
ext { ext {
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
// 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.
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
apply from: '../../constants.gradle'
android { android {
compileSdkVersion project.ext.compileSdkVersion compileSdkVersion project.ext.compileSdkVersion
...@@ -22,6 +23,7 @@ android { ...@@ -22,6 +23,7 @@ android {
targetSdkVersion project.ext.targetSdkVersion targetSdkVersion project.ext.targetSdkVersion
} }
// Workaround to prevent circular dependency on project :testutils.
sourceSets { sourceSets {
androidTest { androidTest {
java.srcDirs += "../../testutils/src/main/java/" java.srcDirs += "../../testutils/src/main/java/"
......
...@@ -24,11 +24,13 @@ ...@@ -24,11 +24,13 @@
android:allowBackup="false" android:allowBackup="false"
tools:ignore="MissingApplicationIcon,HardcodedDebugMode"> tools:ignore="MissingApplicationIcon,HardcodedDebugMode">
<uses-library android:name="android.test.runner"/> <uses-library android:name="android.test.runner"/>
<provider
android:authorities="com.google.android.exoplayer2.core.test"
android:name="com.google.android.exoplayer2.upstream.ContentDataSourceTest$TestContentProvider"/>
</application> </application>
<instrumentation <instrumentation
android:targetPackage="com.google.android.exoplayer2.core.test" android:targetPackage="com.google.android.exoplayer2.core.test"
android:name="android.test.InstrumentationTestRunner" android:name="android.test.InstrumentationTestRunner"/>
tools:replace="android:targetPackage"/>
</manifest> </manifest>
...@@ -30,5 +30,6 @@ track 1: ...@@ -30,5 +30,6 @@ track 1:
time = 0 time = 0
flags = 1073741824 flags = 1073741824
data = length 39, hash B7FE77F4 data = length 39, hash B7FE77F4
crypto mode = 1
encryption key = length 16, hash 4CE944CF encryption key = length 16, hash 4CE944CF
tracksEnded = true tracksEnded = true
...@@ -30,5 +30,6 @@ track 1: ...@@ -30,5 +30,6 @@ track 1:
time = 0 time = 0
flags = 1073741824 flags = 1073741824
data = length 24, hash E58668B1 data = length 24, hash E58668B1
crypto mode = 1
encryption key = length 16, hash 4CE944CF encryption key = length 16, hash 4CE944CF
tracksEnded = true tracksEnded = true
...@@ -8,7 +8,7 @@ track 0: ...@@ -8,7 +8,7 @@ track 0:
bitrate = -1 bitrate = -1
id = null id = null
containerMimeType = null containerMimeType = null
sampleMimeType = audio/x-flac sampleMimeType = audio/flac
maxInputSize = 768000 maxInputSize = 768000
width = -1 width = -1
height = -1 height = -1
......
...@@ -8,7 +8,7 @@ track 0: ...@@ -8,7 +8,7 @@ track 0:
bitrate = -1 bitrate = -1
id = null id = null
containerMimeType = null containerMimeType = null
sampleMimeType = audio/x-flac sampleMimeType = audio/flac
maxInputSize = 768000 maxInputSize = 768000
width = -1 width = -1
height = -1 height = -1
......
...@@ -8,7 +8,7 @@ track 0: ...@@ -8,7 +8,7 @@ track 0:
bitrate = -1 bitrate = -1
id = null id = null
containerMimeType = null containerMimeType = null
sampleMimeType = audio/x-flac sampleMimeType = audio/flac
maxInputSize = 768000 maxInputSize = 768000
width = -1 width = -1
height = -1 height = -1
......
...@@ -8,7 +8,7 @@ track 0: ...@@ -8,7 +8,7 @@ track 0:
bitrate = -1 bitrate = -1
id = null id = null
containerMimeType = null containerMimeType = null
sampleMimeType = audio/x-flac sampleMimeType = audio/flac
maxInputSize = 768000 maxInputSize = 768000
width = -1 width = -1
height = -1 height = -1
......
...@@ -8,7 +8,7 @@ track 0: ...@@ -8,7 +8,7 @@ track 0:
bitrate = -1 bitrate = -1
id = null id = null
containerMimeType = null containerMimeType = null
sampleMimeType = audio/x-flac sampleMimeType = audio/flac
maxInputSize = 768000 maxInputSize = 768000
width = -1 width = -1
height = -1 height = -1
......
...@@ -8,7 +8,7 @@ track 0: ...@@ -8,7 +8,7 @@ track 0:
bitrate = -1 bitrate = -1
id = null id = null
containerMimeType = null containerMimeType = null
sampleMimeType = audio/x-flac sampleMimeType = audio/flac
maxInputSize = 768000 maxInputSize = 768000
width = -1 width = -1
height = -1 height = -1
......
...@@ -8,7 +8,7 @@ track 0: ...@@ -8,7 +8,7 @@ track 0:
bitrate = -1 bitrate = -1
id = null id = null
containerMimeType = null containerMimeType = null
sampleMimeType = audio/x-flac sampleMimeType = audio/flac
maxInputSize = 768000 maxInputSize = 768000
width = -1 width = -1
height = -1 height = -1
......
...@@ -8,7 +8,7 @@ track 0: ...@@ -8,7 +8,7 @@ track 0:
bitrate = -1 bitrate = -1
id = null id = null
containerMimeType = null containerMimeType = null
sampleMimeType = audio/x-flac sampleMimeType = audio/flac
maxInputSize = 768000 maxInputSize = 768000
width = -1 width = -1
height = -1 height = -1
......
...@@ -8,7 +8,7 @@ track 0: ...@@ -8,7 +8,7 @@ track 0:
bitrate = -1 bitrate = -1
id = null id = null
containerMimeType = null containerMimeType = null
sampleMimeType = audio/x-flac sampleMimeType = audio/flac
maxInputSize = 768000 maxInputSize = 768000
width = -1 width = -1
height = -1 height = -1
......
...@@ -8,7 +8,7 @@ track 0: ...@@ -8,7 +8,7 @@ track 0:
bitrate = -1 bitrate = -1
id = null id = null
containerMimeType = null containerMimeType = null
sampleMimeType = audio/x-flac sampleMimeType = audio/flac
maxInputSize = 768000 maxInputSize = 768000
width = -1 width = -1
height = -1 height = -1
......
...@@ -9,7 +9,7 @@ track 0: ...@@ -9,7 +9,7 @@ track 0:
id = null id = null
containerMimeType = null containerMimeType = null
sampleMimeType = audio/vorbis sampleMimeType = audio/vorbis
maxInputSize = 65025 maxInputSize = -1
width = -1 width = -1
height = -1 height = -1
frameRate = -1.0 frameRate = -1.0
......
...@@ -9,7 +9,7 @@ track 0: ...@@ -9,7 +9,7 @@ track 0:
id = null id = null
containerMimeType = null containerMimeType = null
sampleMimeType = audio/vorbis sampleMimeType = audio/vorbis
maxInputSize = 65025 maxInputSize = -1
width = -1 width = -1
height = -1 height = -1
frameRate = -1.0 frameRate = -1.0
......
...@@ -9,7 +9,7 @@ track 0: ...@@ -9,7 +9,7 @@ track 0:
id = null id = null
containerMimeType = null containerMimeType = null
sampleMimeType = audio/vorbis sampleMimeType = audio/vorbis
maxInputSize = 65025 maxInputSize = -1
width = -1 width = -1
height = -1 height = -1
frameRate = -1.0 frameRate = -1.0
......
...@@ -9,7 +9,7 @@ track 0: ...@@ -9,7 +9,7 @@ track 0:
id = null id = null
containerMimeType = null containerMimeType = null
sampleMimeType = audio/vorbis sampleMimeType = audio/vorbis
maxInputSize = 65025 maxInputSize = -1
width = -1 width = -1
height = -1 height = -1
frameRate = -1.0 frameRate = -1.0
......
...@@ -9,7 +9,7 @@ track 0: ...@@ -9,7 +9,7 @@ track 0:
id = null id = null
containerMimeType = null containerMimeType = null
sampleMimeType = audio/vorbis sampleMimeType = audio/vorbis
maxInputSize = 65025 maxInputSize = -1
width = -1 width = -1
height = -1 height = -1
frameRate = -1.0 frameRate = -1.0
......
...@@ -53,9 +53,9 @@ public final class FormatTest extends TestCase { ...@@ -53,9 +53,9 @@ public final class FormatTest extends TestCase {
} }
public void testParcelable() { public void testParcelable() {
DrmInitData.SchemeData DRM_DATA_1 = new DrmInitData.SchemeData(WIDEVINE_UUID, VIDEO_MP4, DrmInitData.SchemeData DRM_DATA_1 = new DrmInitData.SchemeData(WIDEVINE_UUID, "cenc", VIDEO_MP4,
TestUtil.buildTestData(128, 1 /* data seed */)); TestUtil.buildTestData(128, 1 /* data seed */));
DrmInitData.SchemeData DRM_DATA_2 = new DrmInitData.SchemeData(C.UUID_NIL, VIDEO_WEBM, DrmInitData.SchemeData DRM_DATA_2 = new DrmInitData.SchemeData(C.UUID_NIL, null, VIDEO_WEBM,
TestUtil.buildTestData(128, 1 /* data seed */)); TestUtil.buildTestData(128, 1 /* data seed */));
DrmInitData drmInitData = new DrmInitData(DRM_DATA_1, DRM_DATA_2); DrmInitData drmInitData = new DrmInitData(DRM_DATA_1, DRM_DATA_2);
byte[] projectionData = new byte[] {1, 2, 3}; byte[] projectionData = new byte[] {1, 2, 3};
......
/*
* 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;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.testutil.TimelineAsserts;
import junit.framework.TestCase;
/**
* Unit test for {@link Timeline}.
*/
public class TimelineTest extends TestCase {
public void testEmptyTimeline() {
TimelineAsserts.assertEmpty(Timeline.EMPTY);
}
public void testSinglePeriodTimeline() {
Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(1, 111));
TimelineAsserts.assertWindowIds(timeline, 111);
TimelineAsserts.assertPeriodCounts(timeline, 1);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_OFF, C.INDEX_UNSET);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ONE, 0);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ALL, 0);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_OFF, C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ONE, 0);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ALL, 0);
}
public void testMultiPeriodTimeline() {
Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(5, 111));
TimelineAsserts.assertWindowIds(timeline, 111);
TimelineAsserts.assertPeriodCounts(timeline, 5);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_OFF, C.INDEX_UNSET);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ONE, 0);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ALL, 0);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_OFF, C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ONE, 0);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ALL, 0);
}
}
...@@ -31,16 +31,16 @@ import junit.framework.TestCase; ...@@ -31,16 +31,16 @@ import junit.framework.TestCase;
*/ */
public class DrmInitDataTest extends TestCase { public class DrmInitDataTest extends TestCase {
private static final SchemeData DATA_1 = private static final SchemeData DATA_1 = new SchemeData(WIDEVINE_UUID, "cbc1", VIDEO_MP4,
new SchemeData(WIDEVINE_UUID, VIDEO_MP4, TestUtil.buildTestData(128, 1 /* data seed */)); TestUtil.buildTestData(128, 1 /* data seed */));
private static final SchemeData DATA_2 = private static final SchemeData DATA_2 = new SchemeData(PLAYREADY_UUID, null, VIDEO_MP4,
new SchemeData(PLAYREADY_UUID, VIDEO_MP4, TestUtil.buildTestData(128, 2 /* data seed */)); TestUtil.buildTestData(128, 2 /* data seed */));
private static final SchemeData DATA_1B = private static final SchemeData DATA_1B = new SchemeData(WIDEVINE_UUID, "cbc1", VIDEO_MP4,
new SchemeData(WIDEVINE_UUID, VIDEO_MP4, TestUtil.buildTestData(128, 1 /* data seed */)); TestUtil.buildTestData(128, 1 /* data seed */));
private static final SchemeData DATA_2B = private static final SchemeData DATA_2B = new SchemeData(PLAYREADY_UUID, null, VIDEO_MP4,
new SchemeData(PLAYREADY_UUID, VIDEO_MP4, TestUtil.buildTestData(128, 2 /* data seed */)); TestUtil.buildTestData(128, 2 /* data seed */));
private static final SchemeData DATA_UNIVERSAL = private static final SchemeData DATA_UNIVERSAL = new SchemeData(C.UUID_NIL, null, VIDEO_MP4,
new SchemeData(C.UUID_NIL, VIDEO_MP4, TestUtil.buildTestData(128, 3 /* data seed */)); TestUtil.buildTestData(128, 3 /* data seed */));
public void testParcelable() { public void testParcelable() {
DrmInitData drmInitDataToParcel = new DrmInitData(DATA_1, DATA_2); DrmInitData drmInitDataToParcel = new DrmInitData(DATA_1, DATA_2);
......
...@@ -154,7 +154,7 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase { ...@@ -154,7 +154,7 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase {
} }
private static DrmInitData newDrmInitData() { private static DrmInitData newDrmInitData() {
return new DrmInitData(new SchemeData(C.WIDEVINE_UUID, "mimeType", return new DrmInitData(new SchemeData(C.WIDEVINE_UUID, "cenc", "mimeType",
new byte[] {1, 4, 7, 0, 3, 6})); new byte[] {1, 4, 7, 0, 3, 6}));
} }
......
...@@ -17,7 +17,8 @@ package com.google.android.exoplayer2.extractor.flv; ...@@ -17,7 +17,8 @@ package com.google.android.exoplayer2.extractor.flv;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.ExtractorAsserts;
import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory;
/** /**
* Unit test for {@link FlvExtractor}. * Unit test for {@link FlvExtractor}.
...@@ -25,7 +26,7 @@ import com.google.android.exoplayer2.testutil.TestUtil; ...@@ -25,7 +26,7 @@ import com.google.android.exoplayer2.testutil.TestUtil;
public final class FlvExtractorTest extends InstrumentationTestCase { public final class FlvExtractorTest extends InstrumentationTestCase {
public void testSample() throws Exception { public void testSample() throws Exception {
TestUtil.assertOutput(new TestUtil.ExtractorFactory() { ExtractorAsserts.assertOutput(new ExtractorFactory() {
@Override @Override
public Extractor create() { public Extractor create() {
return new FlvExtractor(); return new FlvExtractor();
......
...@@ -17,7 +17,8 @@ package com.google.android.exoplayer2.extractor.mkv; ...@@ -17,7 +17,8 @@ package com.google.android.exoplayer2.extractor.mkv;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.ExtractorAsserts;
import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory;
/** /**
* Tests for {@link MatroskaExtractor}. * Tests for {@link MatroskaExtractor}.
...@@ -25,7 +26,7 @@ import com.google.android.exoplayer2.testutil.TestUtil; ...@@ -25,7 +26,7 @@ import com.google.android.exoplayer2.testutil.TestUtil;
public final class MatroskaExtractorTest extends InstrumentationTestCase { public final class MatroskaExtractorTest extends InstrumentationTestCase {
public void testMkvSample() throws Exception { public void testMkvSample() throws Exception {
TestUtil.assertOutput(new TestUtil.ExtractorFactory() { ExtractorAsserts.assertOutput(new ExtractorFactory() {
@Override @Override
public Extractor create() { public Extractor create() {
return new MatroskaExtractor(); return new MatroskaExtractor();
...@@ -34,7 +35,7 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { ...@@ -34,7 +35,7 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase {
} }
public void testWebmSubsampleEncryption() throws Exception { public void testWebmSubsampleEncryption() throws Exception {
TestUtil.assertOutput(new TestUtil.ExtractorFactory() { ExtractorAsserts.assertOutput(new ExtractorFactory() {
@Override @Override
public Extractor create() { public Extractor create() {
return new MatroskaExtractor(); return new MatroskaExtractor();
...@@ -43,7 +44,7 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { ...@@ -43,7 +44,7 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase {
} }
public void testWebmSubsampleEncryptionWithAltrefFrames() throws Exception { public void testWebmSubsampleEncryptionWithAltrefFrames() throws Exception {
TestUtil.assertOutput(new TestUtil.ExtractorFactory() { ExtractorAsserts.assertOutput(new ExtractorFactory() {
@Override @Override
public Extractor create() { public Extractor create() {
return new MatroskaExtractor(); return new MatroskaExtractor();
......
...@@ -17,7 +17,8 @@ package com.google.android.exoplayer2.extractor.mp3; ...@@ -17,7 +17,8 @@ package com.google.android.exoplayer2.extractor.mp3;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.ExtractorAsserts;
import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory;
/** /**
* Unit test for {@link Mp3Extractor}. * Unit test for {@link Mp3Extractor}.
...@@ -25,7 +26,7 @@ import com.google.android.exoplayer2.testutil.TestUtil; ...@@ -25,7 +26,7 @@ import com.google.android.exoplayer2.testutil.TestUtil;
public final class Mp3ExtractorTest extends InstrumentationTestCase { public final class Mp3ExtractorTest extends InstrumentationTestCase {
public void testMp3Sample() throws Exception { public void testMp3Sample() throws Exception {
TestUtil.assertOutput(new TestUtil.ExtractorFactory() { ExtractorAsserts.assertOutput(new ExtractorFactory() {
@Override @Override
public Extractor create() { public Extractor create() {
return new Mp3Extractor(); return new Mp3Extractor();
...@@ -34,7 +35,7 @@ public final class Mp3ExtractorTest extends InstrumentationTestCase { ...@@ -34,7 +35,7 @@ public final class Mp3ExtractorTest extends InstrumentationTestCase {
} }
public void testTrimmedMp3Sample() throws Exception { public void testTrimmedMp3Sample() throws Exception {
TestUtil.assertOutput(new TestUtil.ExtractorFactory() { ExtractorAsserts.assertOutput(new ExtractorFactory() {
@Override @Override
public Extractor create() { public Extractor create() {
return new Mp3Extractor(); return new Mp3Extractor();
......
...@@ -18,7 +18,8 @@ package com.google.android.exoplayer2.extractor.mp4; ...@@ -18,7 +18,8 @@ package com.google.android.exoplayer2.extractor.mp4;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.ExtractorAsserts;
import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory;
/** /**
* Unit test for {@link FragmentedMp4Extractor}. * Unit test for {@link FragmentedMp4Extractor}.
...@@ -26,26 +27,28 @@ import com.google.android.exoplayer2.testutil.TestUtil; ...@@ -26,26 +27,28 @@ import com.google.android.exoplayer2.testutil.TestUtil;
public final class FragmentedMp4ExtractorTest extends InstrumentationTestCase { public final class FragmentedMp4ExtractorTest extends InstrumentationTestCase {
public void testSample() throws Exception { public void testSample() throws Exception {
TestUtil.assertOutput(getExtractorFactory(), "mp4/sample_fragmented.mp4", getInstrumentation()); ExtractorAsserts
.assertOutput(getExtractorFactory(), "mp4/sample_fragmented.mp4", getInstrumentation());
} }
public void testSampleWithSeiPayloadParsing() throws Exception { public void testSampleWithSeiPayloadParsing() throws Exception {
// Enabling the CEA-608 track enables SEI payload parsing. // Enabling the CEA-608 track enables SEI payload parsing.
TestUtil.assertOutput(getExtractorFactory(FragmentedMp4Extractor.FLAG_ENABLE_CEA608_TRACK), ExtractorAsserts.assertOutput(
getExtractorFactory(FragmentedMp4Extractor.FLAG_ENABLE_CEA608_TRACK),
"mp4/sample_fragmented_sei.mp4", getInstrumentation()); "mp4/sample_fragmented_sei.mp4", getInstrumentation());
} }
public void testAtomWithZeroSize() throws Exception { public void testAtomWithZeroSize() throws Exception {
TestUtil.assertThrows(getExtractorFactory(), "mp4/sample_fragmented_zero_size_atom.mp4", ExtractorAsserts.assertThrows(getExtractorFactory(), "mp4/sample_fragmented_zero_size_atom.mp4",
getInstrumentation(), ParserException.class); getInstrumentation(), ParserException.class);
} }
private static TestUtil.ExtractorFactory getExtractorFactory() { private static ExtractorFactory getExtractorFactory() {
return getExtractorFactory(0); return getExtractorFactory(0);
} }
private static TestUtil.ExtractorFactory getExtractorFactory(final int flags) { private static ExtractorFactory getExtractorFactory(final int flags) {
return new TestUtil.ExtractorFactory() { return new ExtractorFactory() {
@Override @Override
public Extractor create() { public Extractor create() {
return new FragmentedMp4Extractor(flags, null); return new FragmentedMp4Extractor(flags, null);
......
...@@ -18,7 +18,8 @@ package com.google.android.exoplayer2.extractor.mp4; ...@@ -18,7 +18,8 @@ package com.google.android.exoplayer2.extractor.mp4;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.ExtractorAsserts;
import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory;
/** /**
* Tests for {@link Mp4Extractor}. * Tests for {@link Mp4Extractor}.
...@@ -27,7 +28,7 @@ import com.google.android.exoplayer2.testutil.TestUtil; ...@@ -27,7 +28,7 @@ import com.google.android.exoplayer2.testutil.TestUtil;
public final class Mp4ExtractorTest extends InstrumentationTestCase { public final class Mp4ExtractorTest extends InstrumentationTestCase {
public void testMp4Sample() throws Exception { public void testMp4Sample() throws Exception {
TestUtil.assertOutput(new TestUtil.ExtractorFactory() { ExtractorAsserts.assertOutput(new ExtractorFactory() {
@Override @Override
public Extractor create() { public Extractor create() {
return new Mp4Extractor(); return new Mp4Extractor();
......
...@@ -17,9 +17,10 @@ package com.google.android.exoplayer2.extractor.ogg; ...@@ -17,9 +17,10 @@ package com.google.android.exoplayer2.extractor.ogg;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.testutil.ExtractorAsserts;
import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory;
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 com.google.android.exoplayer2.testutil.TestUtil.ExtractorFactory;
import java.io.IOException; import java.io.IOException;
/** /**
...@@ -35,20 +36,21 @@ public final class OggExtractorTest extends InstrumentationTestCase { ...@@ -35,20 +36,21 @@ public final class OggExtractorTest extends InstrumentationTestCase {
}; };
public void testOpus() throws Exception { public void testOpus() throws Exception {
TestUtil.assertOutput(OGG_EXTRACTOR_FACTORY, "ogg/bear.opus", getInstrumentation()); ExtractorAsserts.assertOutput(OGG_EXTRACTOR_FACTORY, "ogg/bear.opus", getInstrumentation());
} }
public void testFlac() throws Exception { public void testFlac() throws Exception {
TestUtil.assertOutput(OGG_EXTRACTOR_FACTORY, "ogg/bear_flac.ogg", getInstrumentation()); ExtractorAsserts.assertOutput(OGG_EXTRACTOR_FACTORY, "ogg/bear_flac.ogg", getInstrumentation());
} }
public void testFlacNoSeektable() throws Exception { public void testFlacNoSeektable() throws Exception {
TestUtil.assertOutput(OGG_EXTRACTOR_FACTORY, "ogg/bear_flac_noseektable.ogg", ExtractorAsserts.assertOutput(OGG_EXTRACTOR_FACTORY, "ogg/bear_flac_noseektable.ogg",
getInstrumentation()); getInstrumentation());
} }
public void testVorbis() throws Exception { public void testVorbis() throws Exception {
TestUtil.assertOutput(OGG_EXTRACTOR_FACTORY, "ogg/bear_vorbis.ogg", getInstrumentation()); ExtractorAsserts.assertOutput(OGG_EXTRACTOR_FACTORY, "ogg/bear_vorbis.ogg",
getInstrumentation());
} }
public void testSniffVorbis() throws Exception { public void testSniffVorbis() throws Exception {
......
...@@ -19,7 +19,8 @@ import android.annotation.TargetApi; ...@@ -19,7 +19,8 @@ import android.annotation.TargetApi;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.ExtractorAsserts;
import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
/** /**
...@@ -29,8 +30,8 @@ import com.google.android.exoplayer2.util.MimeTypes; ...@@ -29,8 +30,8 @@ import com.google.android.exoplayer2.util.MimeTypes;
public final class RawCcExtractorTest extends InstrumentationTestCase { public final class RawCcExtractorTest extends InstrumentationTestCase {
public void testRawCcSample() throws Exception { public void testRawCcSample() throws Exception {
TestUtil.assertOutput( ExtractorAsserts.assertOutput(
new TestUtil.ExtractorFactory() { new ExtractorFactory() {
@Override @Override
public Extractor create() { public Extractor create() {
return new RawCcExtractor( return new RawCcExtractor(
......
...@@ -17,7 +17,8 @@ package com.google.android.exoplayer2.extractor.ts; ...@@ -17,7 +17,8 @@ package com.google.android.exoplayer2.extractor.ts;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.ExtractorAsserts;
import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory;
/** /**
* Unit test for {@link Ac3Extractor}. * Unit test for {@link Ac3Extractor}.
...@@ -25,7 +26,7 @@ import com.google.android.exoplayer2.testutil.TestUtil; ...@@ -25,7 +26,7 @@ import com.google.android.exoplayer2.testutil.TestUtil;
public final class Ac3ExtractorTest extends InstrumentationTestCase { public final class Ac3ExtractorTest extends InstrumentationTestCase {
public void testSample() throws Exception { public void testSample() throws Exception {
TestUtil.assertOutput(new TestUtil.ExtractorFactory() { ExtractorAsserts.assertOutput(new ExtractorFactory() {
@Override @Override
public Extractor create() { public Extractor create() {
return new Ac3Extractor(); return new Ac3Extractor();
......
...@@ -17,7 +17,8 @@ package com.google.android.exoplayer2.extractor.ts; ...@@ -17,7 +17,8 @@ package com.google.android.exoplayer2.extractor.ts;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.ExtractorAsserts;
import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory;
/** /**
* Unit test for {@link AdtsExtractor}. * Unit test for {@link AdtsExtractor}.
...@@ -25,7 +26,7 @@ import com.google.android.exoplayer2.testutil.TestUtil; ...@@ -25,7 +26,7 @@ import com.google.android.exoplayer2.testutil.TestUtil;
public final class AdtsExtractorTest extends InstrumentationTestCase { public final class AdtsExtractorTest extends InstrumentationTestCase {
public void testSample() throws Exception { public void testSample() throws Exception {
TestUtil.assertOutput(new TestUtil.ExtractorFactory() { ExtractorAsserts.assertOutput(new ExtractorFactory() {
@Override @Override
public Extractor create() { public Extractor create() {
return new AdtsExtractor(); return new AdtsExtractor();
......
...@@ -17,7 +17,8 @@ package com.google.android.exoplayer2.extractor.ts; ...@@ -17,7 +17,8 @@ package com.google.android.exoplayer2.extractor.ts;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.ExtractorAsserts;
import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory;
/** /**
* Unit test for {@link PsExtractor}. * Unit test for {@link PsExtractor}.
...@@ -25,7 +26,7 @@ import com.google.android.exoplayer2.testutil.TestUtil; ...@@ -25,7 +26,7 @@ import com.google.android.exoplayer2.testutil.TestUtil;
public final class PsExtractorTest extends InstrumentationTestCase { public final class PsExtractorTest extends InstrumentationTestCase {
public void testSample() throws Exception { public void testSample() throws Exception {
TestUtil.assertOutput(new TestUtil.ExtractorFactory() { ExtractorAsserts.assertOutput(new ExtractorFactory() {
@Override @Override
public Extractor create() { public Extractor create() {
return new PsExtractor(); return new PsExtractor();
......
...@@ -25,6 +25,8 @@ import com.google.android.exoplayer2.extractor.PositionHolder; ...@@ -25,6 +25,8 @@ import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.testutil.ExtractorAsserts;
import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory;
import com.google.android.exoplayer2.testutil.FakeExtractorInput; import com.google.android.exoplayer2.testutil.FakeExtractorInput;
import com.google.android.exoplayer2.testutil.FakeExtractorOutput; import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
import com.google.android.exoplayer2.testutil.FakeTrackOutput; import com.google.android.exoplayer2.testutil.FakeTrackOutput;
...@@ -43,7 +45,7 @@ public final class TsExtractorTest extends InstrumentationTestCase { ...@@ -43,7 +45,7 @@ public final class TsExtractorTest extends InstrumentationTestCase {
private static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet. private static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet.
public void testSample() throws Exception { public void testSample() throws Exception {
TestUtil.assertOutput(new TestUtil.ExtractorFactory() { ExtractorAsserts.assertOutput(new ExtractorFactory() {
@Override @Override
public Extractor create() { public Extractor create() {
return new TsExtractor(); return new TsExtractor();
...@@ -65,7 +67,7 @@ public final class TsExtractorTest extends InstrumentationTestCase { ...@@ -65,7 +67,7 @@ public final class TsExtractorTest extends InstrumentationTestCase {
writeJunkData(out, random.nextInt(TS_PACKET_SIZE - 1) + 1); writeJunkData(out, random.nextInt(TS_PACKET_SIZE - 1) + 1);
fileData = out.toByteArray(); fileData = out.toByteArray();
TestUtil.assertOutput(new TestUtil.ExtractorFactory() { ExtractorAsserts.assertOutput(new ExtractorFactory() {
@Override @Override
public Extractor create() { public Extractor create() {
return new TsExtractor(); return new TsExtractor();
...@@ -75,7 +77,7 @@ public final class TsExtractorTest extends InstrumentationTestCase { ...@@ -75,7 +77,7 @@ public final class TsExtractorTest extends InstrumentationTestCase {
public void testCustomPesReader() throws Exception { public void testCustomPesReader() throws Exception {
CustomTsPayloadReaderFactory factory = new CustomTsPayloadReaderFactory(true, false); CustomTsPayloadReaderFactory factory = new CustomTsPayloadReaderFactory(true, false);
TsExtractor tsExtractor = new TsExtractor(TsExtractor.MODE_NORMAL, new TimestampAdjuster(0), TsExtractor tsExtractor = new TsExtractor(TsExtractor.MODE_MULTI_PMT, new TimestampAdjuster(0),
factory); factory);
FakeExtractorInput input = new FakeExtractorInput.Builder() FakeExtractorInput input = new FakeExtractorInput.Builder()
.setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample.ts")) .setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample.ts"))
...@@ -100,7 +102,7 @@ public final class TsExtractorTest extends InstrumentationTestCase { ...@@ -100,7 +102,7 @@ public final class TsExtractorTest extends InstrumentationTestCase {
public void testCustomInitialSectionReader() throws Exception { public void testCustomInitialSectionReader() throws Exception {
CustomTsPayloadReaderFactory factory = new CustomTsPayloadReaderFactory(false, true); CustomTsPayloadReaderFactory factory = new CustomTsPayloadReaderFactory(false, true);
TsExtractor tsExtractor = new TsExtractor(TsExtractor.MODE_NORMAL, new TimestampAdjuster(0), TsExtractor tsExtractor = new TsExtractor(TsExtractor.MODE_MULTI_PMT, new TimestampAdjuster(0),
factory); factory);
FakeExtractorInput input = new FakeExtractorInput.Builder() FakeExtractorInput input = new FakeExtractorInput.Builder()
.setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample_with_sdt.ts")) .setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample_with_sdt.ts"))
......
...@@ -17,7 +17,8 @@ package com.google.android.exoplayer2.extractor.wav; ...@@ -17,7 +17,8 @@ package com.google.android.exoplayer2.extractor.wav;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.ExtractorAsserts;
import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory;
/** /**
* Unit test for {@link WavExtractor}. * Unit test for {@link WavExtractor}.
...@@ -25,7 +26,7 @@ import com.google.android.exoplayer2.testutil.TestUtil; ...@@ -25,7 +26,7 @@ import com.google.android.exoplayer2.testutil.TestUtil;
public final class WavExtractorTest extends InstrumentationTestCase { public final class WavExtractorTest extends InstrumentationTestCase {
public void testSample() throws Exception { public void testSample() throws Exception {
TestUtil.assertOutput(new TestUtil.ExtractorFactory() { ExtractorAsserts.assertOutput(new ExtractorFactory() {
@Override @Override
public Extractor create() { public Extractor create() {
return new WavExtractor(); return new WavExtractor();
......
...@@ -15,20 +15,17 @@ ...@@ -15,20 +15,17 @@
*/ */
package com.google.android.exoplayer2.source; package com.google.android.exoplayer2.source;
import static org.mockito.Mockito.doAnswer;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Timeline.Period; import com.google.android.exoplayer2.Timeline.Period;
import com.google.android.exoplayer2.Timeline.Window; import com.google.android.exoplayer2.Timeline.Window;
import com.google.android.exoplayer2.source.MediaSource.Listener; import com.google.android.exoplayer2.testutil.FakeMediaSource;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
import org.mockito.Mock; import com.google.android.exoplayer2.testutil.TimelineAsserts;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
/** /**
* Unit tests for {@link ClippingMediaSource}. * Unit tests for {@link ClippingMediaSource}.
...@@ -38,15 +35,11 @@ public final class ClippingMediaSourceTest extends InstrumentationTestCase { ...@@ -38,15 +35,11 @@ public final class ClippingMediaSourceTest extends InstrumentationTestCase {
private static final long TEST_PERIOD_DURATION_US = 1000000; private static final long TEST_PERIOD_DURATION_US = 1000000;
private static final long TEST_CLIP_AMOUNT_US = 300000; private static final long TEST_CLIP_AMOUNT_US = 300000;
@Mock
private MediaSource mockMediaSource;
private Timeline clippedTimeline;
private Window window; private Window window;
private Period period; private Period period;
@Override @Override
protected void setUp() throws Exception { protected void setUp() throws Exception {
TestUtil.setUpMockito(this);
window = new Timeline.Window(); window = new Timeline.Window();
period = new Timeline.Period(); period = new Timeline.Period();
} }
...@@ -109,35 +102,30 @@ public final class ClippingMediaSourceTest extends InstrumentationTestCase { ...@@ -109,35 +102,30 @@ public final class ClippingMediaSourceTest extends InstrumentationTestCase {
clippedTimeline.getPeriod(0, period).getDurationUs()); clippedTimeline.getPeriod(0, period).getDurationUs());
} }
/** public void testWindowAndPeriodIndices() {
* Wraps the specified timeline in a {@link ClippingMediaSource} and returns the clipped timeline. Timeline timeline = new FakeTimeline(
*/ new TimelineWindowDefinition(1, 111, true, false, TEST_PERIOD_DURATION_US));
private Timeline getClippedTimeline(Timeline timeline, long startMs, long endMs) { Timeline clippedTimeline = getClippedTimeline(timeline, TEST_CLIP_AMOUNT_US,
mockMediaSourceSourceWithTimeline(timeline); TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US);
new ClippingMediaSource(mockMediaSource, startMs, endMs).prepareSource(null, true, TimelineAsserts.assertWindowIds(clippedTimeline, 111);
new Listener() { TimelineAsserts.assertPeriodCounts(clippedTimeline, 1);
@Override TimelineAsserts.assertPreviousWindowIndices(clippedTimeline, ExoPlayer.REPEAT_MODE_OFF,
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { C.INDEX_UNSET);
clippedTimeline = timeline; TimelineAsserts.assertPreviousWindowIndices(clippedTimeline, ExoPlayer.REPEAT_MODE_ONE, 0);
} TimelineAsserts.assertPreviousWindowIndices(clippedTimeline, ExoPlayer.REPEAT_MODE_ALL, 0);
}); TimelineAsserts.assertNextWindowIndices(clippedTimeline, ExoPlayer.REPEAT_MODE_OFF,
return clippedTimeline; C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(clippedTimeline, ExoPlayer.REPEAT_MODE_ONE, 0);
TimelineAsserts.assertNextWindowIndices(clippedTimeline, ExoPlayer.REPEAT_MODE_ALL, 0);
} }
/** /**
* Returns a mock {@link MediaSource} with the specified {@link Timeline} in its source info. * Wraps the specified timeline in a {@link ClippingMediaSource} and returns the clipped timeline.
*/ */
private MediaSource mockMediaSourceSourceWithTimeline(final Timeline timeline) { private static Timeline getClippedTimeline(Timeline timeline, long startMs, long endMs) {
doAnswer(new Answer<Void>() { MediaSource mediaSource = new FakeMediaSource(timeline, null);
@Override return TestUtil.extractTimelineFromMediaSource(
public Void answer(InvocationOnMock invocation) throws Throwable { new ClippingMediaSource(mediaSource, startMs, endMs));
MediaSource.Listener listener = (MediaSource.Listener) invocation.getArguments()[2];
listener.onSourceInfoRefreshed(timeline, null);
return null;
}
}).when(mockMediaSource).prepareSource(Mockito.any(ExoPlayer.class), Mockito.anyBoolean(),
Mockito.any(MediaSource.Listener.class));
return mockMediaSource;
} }
} }
/*
* Copyright (C) 2016 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.source;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.testutil.FakeMediaSource;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.testutil.TimelineAsserts;
import junit.framework.TestCase;
/**
* Unit tests for {@link ConcatenatingMediaSource}.
*/
public final class ConcatenatingMediaSourceTest extends TestCase {
public void testSingleMediaSource() {
Timeline timeline = getConcatenatedTimeline(false, createFakeTimeline(3, 111));
TimelineAsserts.assertWindowIds(timeline, 111);
TimelineAsserts.assertPeriodCounts(timeline, 3);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_OFF, C.INDEX_UNSET);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ONE, 0);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ALL, 0);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_OFF, C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ONE, 0);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ALL, 0);
timeline = getConcatenatedTimeline(true, createFakeTimeline(3, 111));
TimelineAsserts.assertWindowIds(timeline, 111);
TimelineAsserts.assertPeriodCounts(timeline, 3);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_OFF, C.INDEX_UNSET);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ONE, 0);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ALL, 0);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_OFF, C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ONE, 0);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ALL, 0);
}
public void testMultipleMediaSources() {
Timeline[] timelines = { createFakeTimeline(3, 111), createFakeTimeline(1, 222),
createFakeTimeline(3, 333) };
Timeline timeline = getConcatenatedTimeline(false, timelines);
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333);
TimelineAsserts.assertPeriodCounts(timeline, 3, 1, 3);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_OFF, C.INDEX_UNSET,
0, 1);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ONE, 0, 1, 2);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ALL, 2, 0, 1);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_OFF,
1, 2, C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ONE, 0, 1, 2);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ALL, 1, 2, 0);
timeline = getConcatenatedTimeline(true, timelines);
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333);
TimelineAsserts.assertPeriodCounts(timeline, 3, 1, 3);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_OFF,
C.INDEX_UNSET, 0, 1);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ONE, 2, 0, 1);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ALL, 2, 0, 1);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_OFF,
1, 2, C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ONE, 1, 2, 0);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ALL, 1, 2, 0);
}
public void testNestedMediaSources() {
Timeline timeline = getConcatenatedTimeline(false,
getConcatenatedTimeline(false, createFakeTimeline(1, 111), createFakeTimeline(1, 222)),
getConcatenatedTimeline(true, createFakeTimeline(1, 333), createFakeTimeline(1, 444)));
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333, 444);
TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1, 1);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_OFF,
C.INDEX_UNSET, 0, 1, 2);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ONE, 0, 1, 3, 2);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ALL, 3, 0, 1, 2);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_OFF,
1, 2, 3, C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ONE, 0, 1, 3, 2);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ALL, 1, 2, 3, 0);
}
/**
* Wraps the specified timelines in a {@link ConcatenatingMediaSource} and returns
* the concatenated timeline.
*/
private static Timeline getConcatenatedTimeline(boolean isRepeatOneAtomic,
Timeline... timelines) {
MediaSource[] mediaSources = new MediaSource[timelines.length];
for (int i = 0; i < timelines.length; i++) {
mediaSources[i] = new FakeMediaSource(timelines[i], null);
}
return TestUtil.extractTimelineFromMediaSource(
new ConcatenatingMediaSource(isRepeatOneAtomic, mediaSources));
}
private static FakeTimeline createFakeTimeline(int periodCount, int windowId) {
return new FakeTimeline(new TimelineWindowDefinition(periodCount, windowId));
}
}
/*
* 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.source;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.testutil.FakeMediaSource;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.testutil.TimelineAsserts;
import junit.framework.TestCase;
/**
* Unit tests for {@link LoopingMediaSource}.
*/
public class LoopingMediaSourceTest extends TestCase {
private final Timeline multiWindowTimeline;
public LoopingMediaSourceTest() {
multiWindowTimeline = TestUtil.extractTimelineFromMediaSource(new FakeMediaSource(
new FakeTimeline(new TimelineWindowDefinition(1, 111),
new TimelineWindowDefinition(1, 222), new TimelineWindowDefinition(1, 333)), null));
}
public void testSingleLoop() {
Timeline timeline = getLoopingTimeline(multiWindowTimeline, 1);
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333);
TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_OFF,
C.INDEX_UNSET, 0, 1);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ONE, 0, 1, 2);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ALL, 2, 0, 1);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_OFF,
1, 2, C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ONE, 0, 1, 2);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ALL, 1, 2, 0);
}
public void testMultiLoop() {
Timeline timeline = getLoopingTimeline(multiWindowTimeline, 3);
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333, 111, 222, 333, 111, 222, 333);
TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1, 1, 1, 1, 1, 1, 1);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_OFF,
C.INDEX_UNSET, 0, 1, 2, 3, 4, 5, 6, 7, 8);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ONE,
0, 1, 2, 3, 4, 5, 6, 7, 8);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ALL,
8, 0, 1, 2, 3, 4, 5, 6, 7);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_OFF,
1, 2, 3, 4, 5, 6, 7, 8, C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ONE,
0, 1, 2, 3, 4, 5, 6, 7, 8);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ALL,
1, 2, 3, 4, 5, 6, 7, 8, 0);
}
public void testInfiniteLoop() {
Timeline timeline = getLoopingTimeline(multiWindowTimeline, Integer.MAX_VALUE);
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333);
TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_OFF, 2, 0, 1);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ONE, 0, 1, 2);
TimelineAsserts.assertPreviousWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ALL, 2, 0, 1);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_OFF, 1, 2, 0);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ONE, 0, 1, 2);
TimelineAsserts.assertNextWindowIndices(timeline, ExoPlayer.REPEAT_MODE_ALL, 1, 2, 0);
}
/**
* Wraps the specified timeline in a {@link LoopingMediaSource} and returns
* the looping timeline.
*/
private static Timeline getLoopingTimeline(Timeline timeline, int loopCount) {
MediaSource mediaSource = new FakeMediaSource(timeline, null);
return TestUtil.extractTimelineFromMediaSource(
new LoopingMediaSource(mediaSource, loopCount));
}
}
...@@ -157,39 +157,43 @@ public final class TtmlDecoderTest extends InstrumentationTestCase { ...@@ -157,39 +157,43 @@ public final class TtmlDecoderTest extends InstrumentationTestCase {
assertEquals(2, output.size()); assertEquals(2, output.size());
Cue ttmlCue = output.get(0); Cue ttmlCue = output.get(0);
assertEquals("lorem", ttmlCue.text.toString()); assertEquals("lorem", ttmlCue.text.toString());
assertEquals(10.f / 100.f, ttmlCue.position); assertEquals(10f / 100f, ttmlCue.position);
assertEquals(10.f / 100.f, ttmlCue.line); assertEquals(10f / 100f, ttmlCue.line);
assertEquals(20.f / 100.f, ttmlCue.size); assertEquals(20f / 100f, ttmlCue.size);
ttmlCue = output.get(1); ttmlCue = output.get(1);
assertEquals("amet", ttmlCue.text.toString()); assertEquals("amet", ttmlCue.text.toString());
assertEquals(60.f / 100.f, ttmlCue.position); assertEquals(60f / 100f, ttmlCue.position);
assertEquals(10.f / 100.f, ttmlCue.line); assertEquals(10f / 100f, ttmlCue.line);
assertEquals(20.f / 100.f, ttmlCue.size); assertEquals(20f / 100f, ttmlCue.size);
output = subtitle.getCues(5000000); output = subtitle.getCues(5000000);
assertEquals(1, output.size()); assertEquals(1, output.size());
ttmlCue = output.get(0); ttmlCue = output.get(0);
assertEquals("ipsum", ttmlCue.text.toString()); assertEquals("ipsum", ttmlCue.text.toString());
assertEquals(40.f / 100.f, ttmlCue.position); assertEquals(40f / 100f, ttmlCue.position);
assertEquals(40.f / 100.f, ttmlCue.line); assertEquals(40f / 100f, ttmlCue.line);
assertEquals(20.f / 100.f, ttmlCue.size); assertEquals(20f / 100f, ttmlCue.size);
output = subtitle.getCues(9000000); output = subtitle.getCues(9000000);
assertEquals(1, output.size()); assertEquals(1, output.size());
ttmlCue = output.get(0); ttmlCue = output.get(0);
assertEquals("dolor", ttmlCue.text.toString()); assertEquals("dolor", ttmlCue.text.toString());
assertEquals(10.f / 100.f, ttmlCue.position); assertEquals(Cue.DIMEN_UNSET, ttmlCue.position);
assertEquals(80.f / 100.f, ttmlCue.line); assertEquals(Cue.DIMEN_UNSET, ttmlCue.line);
assertEquals(Cue.DIMEN_UNSET, ttmlCue.size); assertEquals(Cue.DIMEN_UNSET, ttmlCue.size);
// TODO: Should be as below, once https://github.com/google/ExoPlayer/issues/2953 is fixed.
// assertEquals(10f / 100f, ttmlCue.position);
// assertEquals(80f / 100f, ttmlCue.line);
// assertEquals(1f, ttmlCue.size);
output = subtitle.getCues(21000000); output = subtitle.getCues(21000000);
assertEquals(1, output.size()); assertEquals(1, output.size());
ttmlCue = output.get(0); ttmlCue = output.get(0);
assertEquals("She first said this", ttmlCue.text.toString()); assertEquals("She first said this", ttmlCue.text.toString());
assertEquals(45.f / 100.f, ttmlCue.position); assertEquals(45f / 100f, ttmlCue.position);
assertEquals(45.f / 100.f, ttmlCue.line); assertEquals(45f / 100f, ttmlCue.line);
assertEquals(35.f / 100.f, ttmlCue.size); assertEquals(35f / 100f, ttmlCue.size);
output = subtitle.getCues(25000000); output = subtitle.getCues(25000000);
ttmlCue = output.get(0); ttmlCue = output.get(0);
assertEquals("She first said this\nThen this", ttmlCue.text.toString()); assertEquals("She first said this\nThen this", ttmlCue.text.toString());
...@@ -197,8 +201,8 @@ public final class TtmlDecoderTest extends InstrumentationTestCase { ...@@ -197,8 +201,8 @@ public final class TtmlDecoderTest extends InstrumentationTestCase {
assertEquals(1, output.size()); assertEquals(1, output.size());
ttmlCue = output.get(0); ttmlCue = output.get(0);
assertEquals("She first said this\nThen this\nFinally this", ttmlCue.text.toString()); assertEquals("She first said this\nThen this\nFinally this", ttmlCue.text.toString());
assertEquals(45.f / 100.f, ttmlCue.position); assertEquals(45f / 100f, ttmlCue.position);
assertEquals(45.f / 100.f, ttmlCue.line); assertEquals(45f / 100f, ttmlCue.line);
} }
public void testEmptyStyleAttribute() throws IOException, SubtitleDecoderException { public void testEmptyStyleAttribute() throws IOException, SubtitleDecoderException {
......
...@@ -125,25 +125,25 @@ public final class CssParserTest extends InstrumentationTestCase { ...@@ -125,25 +125,25 @@ public final class CssParserTest extends InstrumentationTestCase {
String stringInput = " lorem:ipsum\n{dolor}#sit,amet;lorem:ipsum\r\t\f\ndolor(())\n"; String stringInput = " lorem:ipsum\n{dolor}#sit,amet;lorem:ipsum\r\t\f\ndolor(())\n";
ParsableByteArray input = new ParsableByteArray(Util.getUtf8Bytes(stringInput)); ParsableByteArray input = new ParsableByteArray(Util.getUtf8Bytes(stringInput));
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
assertEquals(CssParser.parseNextToken(input, builder), "lorem"); assertEquals("lorem", CssParser.parseNextToken(input, builder));
assertEquals(CssParser.parseNextToken(input, builder), ":"); assertEquals(":", CssParser.parseNextToken(input, builder));
assertEquals(CssParser.parseNextToken(input, builder), "ipsum"); assertEquals("ipsum", CssParser.parseNextToken(input, builder));
assertEquals(CssParser.parseNextToken(input, builder), "{"); assertEquals("{", CssParser.parseNextToken(input, builder));
assertEquals(CssParser.parseNextToken(input, builder), "dolor"); assertEquals("dolor", CssParser.parseNextToken(input, builder));
assertEquals(CssParser.parseNextToken(input, builder), "}"); assertEquals("}", CssParser.parseNextToken(input, builder));
assertEquals(CssParser.parseNextToken(input, builder), "#sit"); assertEquals("#sit", CssParser.parseNextToken(input, builder));
assertEquals(CssParser.parseNextToken(input, builder), ","); assertEquals(",", CssParser.parseNextToken(input, builder));
assertEquals(CssParser.parseNextToken(input, builder), "amet"); assertEquals("amet", CssParser.parseNextToken(input, builder));
assertEquals(CssParser.parseNextToken(input, builder), ";"); assertEquals(";", CssParser.parseNextToken(input, builder));
assertEquals(CssParser.parseNextToken(input, builder), "lorem"); assertEquals("lorem", CssParser.parseNextToken(input, builder));
assertEquals(CssParser.parseNextToken(input, builder), ":"); assertEquals(":", CssParser.parseNextToken(input, builder));
assertEquals(CssParser.parseNextToken(input, builder), "ipsum"); assertEquals("ipsum", CssParser.parseNextToken(input, builder));
assertEquals(CssParser.parseNextToken(input, builder), "dolor"); assertEquals("dolor", CssParser.parseNextToken(input, builder));
assertEquals(CssParser.parseNextToken(input, builder), "("); assertEquals("(", CssParser.parseNextToken(input, builder));
assertEquals(CssParser.parseNextToken(input, builder), "("); assertEquals("(", CssParser.parseNextToken(input, builder));
assertEquals(CssParser.parseNextToken(input, builder), ")"); assertEquals(")", CssParser.parseNextToken(input, builder));
assertEquals(CssParser.parseNextToken(input, builder), ")"); assertEquals(")", CssParser.parseNextToken(input, builder));
assertEquals(CssParser.parseNextToken(input, builder), null); assertEquals(null, CssParser.parseNextToken(input, builder));
} }
public void testStyleScoreSystem() { public void testStyleScoreSystem() {
......
/*
* Copyright (C) 2016 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.trackselection;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.util.MimeTypes;
import junit.framework.TestCase;
/**
* Unit tests for {@link MappingTrackSelector}.
*/
public final class MappingTrackSelectorTest extends TestCase {
private static final RendererCapabilities VIDEO_CAPABILITIES =
new FakeRendererCapabilities(C.TRACK_TYPE_VIDEO);
private static final RendererCapabilities AUDIO_CAPABILITIES =
new FakeRendererCapabilities(C.TRACK_TYPE_AUDIO);
private static final RendererCapabilities[] RENDERER_CAPABILITIES = new RendererCapabilities[] {
VIDEO_CAPABILITIES, AUDIO_CAPABILITIES
};
private static final TrackGroup VIDEO_TRACK_GROUP = new TrackGroup(
Format.createVideoSampleFormat("video", MimeTypes.VIDEO_H264, null, Format.NO_VALUE,
Format.NO_VALUE, 1024, 768, Format.NO_VALUE, null, null));
private static final TrackGroup AUDIO_TRACK_GROUP = new TrackGroup(
Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
Format.NO_VALUE, 2, 44100, null, null, 0, null));
private static final TrackGroupArray TRACK_GROUPS = new TrackGroupArray(
VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP);
private static final TrackSelection[] TRACK_SELECTIONS = new TrackSelection[] {
new FixedTrackSelection(VIDEO_TRACK_GROUP, 0),
new FixedTrackSelection(AUDIO_TRACK_GROUP, 0)
};
/**
* Tests that the video and audio track groups are mapped onto the correct renderers.
*/
public void testMapping() throws ExoPlaybackException {
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector();
trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS);
trackSelector.assertMappedTrackGroups(0, VIDEO_TRACK_GROUP);
trackSelector.assertMappedTrackGroups(1, AUDIO_TRACK_GROUP);
}
/**
* Tests that the video and audio track groups are mapped onto the correct renderers when the
* renderer ordering is reversed.
*/
public void testMappingReverseOrder() throws ExoPlaybackException {
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector();
RendererCapabilities[] reverseOrderRendererCapabilities = new RendererCapabilities[] {
AUDIO_CAPABILITIES, VIDEO_CAPABILITIES};
trackSelector.selectTracks(reverseOrderRendererCapabilities, TRACK_GROUPS);
trackSelector.assertMappedTrackGroups(0, AUDIO_TRACK_GROUP);
trackSelector.assertMappedTrackGroups(1, VIDEO_TRACK_GROUP);
}
/**
* Tests video and audio track groups are mapped onto the correct renderers when there are
* multiple track groups of the same type.
*/
public void testMappingMulti() throws ExoPlaybackException {
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector();
TrackGroupArray multiTrackGroups = new TrackGroupArray(VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP,
VIDEO_TRACK_GROUP);
trackSelector.selectTracks(RENDERER_CAPABILITIES, multiTrackGroups);
trackSelector.assertMappedTrackGroups(0, VIDEO_TRACK_GROUP, VIDEO_TRACK_GROUP);
trackSelector.assertMappedTrackGroups(1, AUDIO_TRACK_GROUP);
}
/**
* Tests the result of {@link MappingTrackSelector#selectTracks(RendererCapabilities[],
* TrackGroupArray[], int[][][])} is propagated correctly to the result of
* {@link MappingTrackSelector#selectTracks(RendererCapabilities[], TrackGroupArray)}.
*/
public void testSelectTracks() throws ExoPlaybackException {
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector(TRACK_SELECTIONS);
TrackSelectorResult result = trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS);
assertEquals(TRACK_SELECTIONS[0], result.selections.get(0));
assertEquals(TRACK_SELECTIONS[1], result.selections.get(1));
}
/**
* Tests that a null override clears a track selection.
*/
public void testSelectTracksWithNullOverride() throws ExoPlaybackException {
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector(TRACK_SELECTIONS);
trackSelector.setSelectionOverride(0, new TrackGroupArray(VIDEO_TRACK_GROUP), null);
TrackSelectorResult result = trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS);
assertNull(result.selections.get(0));
assertEquals(TRACK_SELECTIONS[1], result.selections.get(1));
}
/**
* Tests that a null override can be cleared.
*/
public void testSelectTracksWithClearedNullOverride() throws ExoPlaybackException {
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector(TRACK_SELECTIONS);
trackSelector.setSelectionOverride(0, new TrackGroupArray(VIDEO_TRACK_GROUP), null);
trackSelector.clearSelectionOverride(0, new TrackGroupArray(VIDEO_TRACK_GROUP));
TrackSelectorResult result = trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS);
assertEquals(TRACK_SELECTIONS[0], result.selections.get(0));
assertEquals(TRACK_SELECTIONS[1], result.selections.get(1));
}
/**
* Tests that an override is not applied for a different set of available track groups.
*/
public void testSelectTracksWithNullOverrideForDifferentTracks() throws ExoPlaybackException {
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector(TRACK_SELECTIONS);
trackSelector.setSelectionOverride(0, new TrackGroupArray(VIDEO_TRACK_GROUP), null);
TrackSelectorResult result = trackSelector.selectTracks(RENDERER_CAPABILITIES,
new TrackGroupArray(VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP, VIDEO_TRACK_GROUP));
assertEquals(TRACK_SELECTIONS[0], result.selections.get(0));
assertEquals(TRACK_SELECTIONS[1], result.selections.get(1));
}
/**
* A {@link MappingTrackSelector} that returns a fixed result from
* {@link #selectTracks(RendererCapabilities[], TrackGroupArray[], int[][][])}.
*/
private static final class FakeMappingTrackSelector extends MappingTrackSelector {
private final TrackSelection[] result;
private TrackGroupArray[] lastRendererTrackGroupArrays;
public FakeMappingTrackSelector(TrackSelection... result) {
this.result = result.length == 0 ? null : result;
}
@Override
protected TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities,
TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports)
throws ExoPlaybackException {
lastRendererTrackGroupArrays = rendererTrackGroupArrays;
return result == null ? new TrackSelection[rendererCapabilities.length] : result;
}
public void assertMappedTrackGroups(int rendererIndex, TrackGroup... expected) {
assertEquals(expected.length, lastRendererTrackGroupArrays[rendererIndex].length);
for (int i = 0; i < expected.length; i++) {
assertEquals(expected[i], lastRendererTrackGroupArrays[rendererIndex].get(i));
}
}
}
/**
* A {@link RendererCapabilities} that advertises adaptive support for all tracks of a given type.
*/
private static final class FakeRendererCapabilities implements RendererCapabilities {
private final int trackType;
public FakeRendererCapabilities(int trackType) {
this.trackType = trackType;
}
@Override
public int getTrackType() {
return trackType;
}
@Override
public int supportsFormat(Format format) throws ExoPlaybackException {
return MimeTypes.getTrackType(format.sampleMimeType) == trackType
? (FORMAT_HANDLED | ADAPTIVE_SEAMLESS) : FORMAT_UNSUPPORTED_TYPE;
}
@Override
public int supportsMixedMimeTypeAdaptation() throws ExoPlaybackException {
return ADAPTIVE_SEAMLESS;
}
}
}
/*
* 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.upstream;
import android.net.Uri;
import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.testutil.TestUtil;
/**
* Unit tests for {@link AssetDataSource}.
*/
public final class AssetDataSourceTest extends InstrumentationTestCase {
private static final String DATA_PATH = "binary/1024_incrementing_bytes.mp3";
public void testReadFileUri() throws Exception {
AssetDataSource dataSource = new AssetDataSource(getInstrumentation().getContext());
DataSpec dataSpec = new DataSpec(Uri.parse("file:///android_asset/" + DATA_PATH));
TestUtil.assertDataSourceContent(dataSource, dataSpec,
TestUtil.getByteArray(getInstrumentation(), DATA_PATH));
}
public void testReadAssetUri() throws Exception {
AssetDataSource dataSource = new AssetDataSource(getInstrumentation().getContext());
DataSpec dataSpec = new DataSpec(Uri.parse("asset:///" + DATA_PATH));
TestUtil.assertDataSourceContent(dataSource, dataSpec,
TestUtil.getByteArray(getInstrumentation(), DATA_PATH));
}
}
/*
* 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.upstream;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.testutil.TestUtil;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* Unit tests for {@link ContentDataSource}.
*/
public final class ContentDataSourceTest extends InstrumentationTestCase {
private static final String AUTHORITY = "com.google.android.exoplayer2.core.test";
private static final String DATA_PATH = "binary/1024_incrementing_bytes.mp3";
public void testReadValidUri() throws Exception {
ContentDataSource dataSource = new ContentDataSource(getInstrumentation().getContext());
Uri contentUri = new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(AUTHORITY)
.path(DATA_PATH).build();
DataSpec dataSpec = new DataSpec(contentUri);
TestUtil.assertDataSourceContent(dataSource, dataSpec,
TestUtil.getByteArray(getInstrumentation(), DATA_PATH));
}
public void testReadInvalidUri() throws Exception {
ContentDataSource dataSource = new ContentDataSource(getInstrumentation().getContext());
Uri contentUri = new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(AUTHORITY)
.build();
DataSpec dataSpec = new DataSpec(contentUri);
try {
dataSource.open(dataSpec);
fail();
} catch (ContentDataSource.ContentDataSourceException e) {
// Expected.
assertTrue(e.getCause() instanceof FileNotFoundException);
} finally {
dataSource.close();
}
}
/**
* A {@link ContentProvider} for the test.
*/
public static final class TestContentProvider extends ContentProvider {
@Override
public boolean onCreate() {
return true;
}
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
throw new UnsupportedOperationException();
}
@Override
public AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode)
throws FileNotFoundException {
if (uri.getPath() == null) {
return null;
}
try {
return getContext().getAssets().openFd(uri.getPath().replaceFirst("/", ""));
} catch (IOException e) {
FileNotFoundException exception = new FileNotFoundException(e.getMessage());
exception.initCause(e);
throw exception;
}
}
@Override
public String getType(@NonNull Uri uri) {
throw new UnsupportedOperationException();
}
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
throw new UnsupportedOperationException();
}
@Override
public int delete(@NonNull Uri uri, String selection,
String[] selectionArgs) {
throw new UnsupportedOperationException();
}
@Override
public int update(@NonNull Uri uri, ContentValues values,
String selection, String[] selectionArgs) {
throw new UnsupportedOperationException();
}
}
}
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
*/ */
package com.google.android.exoplayer2.upstream.cache; package com.google.android.exoplayer2.upstream.cache;
import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCacheEmpty;
import android.net.Uri; import android.net.Uri;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import android.test.MoreAsserts; import android.test.MoreAsserts;
...@@ -38,27 +40,29 @@ public class CacheDataSourceTest extends InstrumentationTestCase { ...@@ -38,27 +40,29 @@ public class CacheDataSourceTest extends InstrumentationTestCase {
private static final String KEY_1 = "key 1"; private static final String KEY_1 = "key 1";
private static final String KEY_2 = "key 2"; private static final String KEY_2 = "key 2";
private File cacheDir; private File tempFolder;
private SimpleCache simpleCache; private SimpleCache cache;
@Override @Override
protected void setUp() throws Exception { public void setUp() throws Exception {
cacheDir = Util.createTempDirectory(getInstrumentation().getContext(), "ExoPlayerTest"); super.setUp();
simpleCache = new SimpleCache(cacheDir, new NoOpCacheEvictor()); tempFolder = Util.createTempDirectory(getInstrumentation().getContext(), "ExoPlayerTest");
cache = new SimpleCache(tempFolder, new NoOpCacheEvictor());
} }
@Override @Override
protected void tearDown() throws Exception { public void tearDown() throws Exception {
Util.recursiveDelete(cacheDir); Util.recursiveDelete(tempFolder);
super.tearDown();
} }
public void testMaxCacheFileSize() throws Exception { public void testMaxCacheFileSize() throws Exception {
CacheDataSource cacheDataSource = createCacheDataSource(false, false); CacheDataSource cacheDataSource = createCacheDataSource(false, false);
assertReadDataContentLength(cacheDataSource, false, false); assertReadDataContentLength(cacheDataSource, false, false);
File[] files = cacheDir.listFiles(); for (String key : cache.getKeys()) {
for (File file : files) { for (CacheSpan cacheSpan : cache.getCachedSpans(key)) {
if (!file.getName().equals(CachedContentIndex.FILE_NAME)) { assertTrue(cacheSpan.length <= MAX_CACHE_FILE_SIZE);
assertTrue(file.length() <= MAX_CACHE_FILE_SIZE); assertTrue(cacheSpan.file.length() <= MAX_CACHE_FILE_SIZE);
} }
} }
} }
...@@ -104,7 +108,7 @@ public class CacheDataSourceTest extends InstrumentationTestCase { ...@@ -104,7 +108,7 @@ public class CacheDataSourceTest extends InstrumentationTestCase {
// Read partial at EOS but don't cross it so length is unknown // Read partial at EOS but don't cross it so length is unknown
CacheDataSource cacheDataSource = createCacheDataSource(false, true); CacheDataSource cacheDataSource = createCacheDataSource(false, true);
assertReadData(cacheDataSource, true, TEST_DATA.length - 2, 2); assertReadData(cacheDataSource, true, TEST_DATA.length - 2, 2);
assertEquals(C.LENGTH_UNSET, simpleCache.getContentLength(KEY_1)); assertEquals(C.LENGTH_UNSET, cache.getContentLength(KEY_1));
// Now do an unbounded request for whole data. This will cause a bounded request from upstream. // Now do an unbounded request for whole data. This will cause a bounded request from upstream.
// End of data from upstream shouldn't be mixed up with EOS and cause length set wrong. // End of data from upstream shouldn't be mixed up with EOS and cause length set wrong.
...@@ -124,13 +128,13 @@ public class CacheDataSourceTest extends InstrumentationTestCase { ...@@ -124,13 +128,13 @@ public class CacheDataSourceTest extends InstrumentationTestCase {
CacheDataSource cacheDataSource = createCacheDataSource(false, true, CacheDataSource cacheDataSource = createCacheDataSource(false, true,
CacheDataSource.FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS); CacheDataSource.FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS);
assertReadData(cacheDataSource, true, 0, C.LENGTH_UNSET); assertReadData(cacheDataSource, true, 0, C.LENGTH_UNSET);
MoreAsserts.assertEmpty(simpleCache.getKeys()); MoreAsserts.assertEmpty(cache.getKeys());
} }
public void testReadOnlyCache() throws Exception { public void testReadOnlyCache() throws Exception {
CacheDataSource cacheDataSource = createCacheDataSource(false, false, 0, null); CacheDataSource cacheDataSource = createCacheDataSource(false, false, 0, null);
assertReadDataContentLength(cacheDataSource, false, false); assertReadDataContentLength(cacheDataSource, false, false);
assertEquals(0, cacheDir.list().length); assertCacheEmpty(cache);
} }
private void assertCacheAndRead(boolean unboundedRequest, boolean simulateUnknownLength) private void assertCacheAndRead(boolean unboundedRequest, boolean simulateUnknownLength)
...@@ -155,30 +159,30 @@ public class CacheDataSourceTest extends InstrumentationTestCase { ...@@ -155,30 +159,30 @@ public class CacheDataSourceTest extends InstrumentationTestCase {
assertReadData(cacheDataSource, unknownLength, 0, length); assertReadData(cacheDataSource, unknownLength, 0, length);
assertEquals("When the range specified, CacheDataSource doesn't reach EOS so shouldn't cache " assertEquals("When the range specified, CacheDataSource doesn't reach EOS so shouldn't cache "
+ "content length", !unboundedRequest ? C.LENGTH_UNSET : TEST_DATA.length, + "content length", !unboundedRequest ? C.LENGTH_UNSET : TEST_DATA.length,
simpleCache.getContentLength(KEY_1)); cache.getContentLength(KEY_1));
} }
private void assertReadData(CacheDataSource cacheDataSource, boolean unknownLength, int position, private void assertReadData(CacheDataSource cacheDataSource, boolean unknownLength, int position,
int length) throws IOException { int length) throws IOException {
int actualLength = TEST_DATA.length - position; int testDataLength = TEST_DATA.length - position;
if (length != C.LENGTH_UNSET) { if (length != C.LENGTH_UNSET) {
actualLength = Math.min(actualLength, length); testDataLength = Math.min(testDataLength, length);
} }
assertEquals(unknownLength ? length : actualLength, assertEquals(unknownLength ? length : testDataLength,
cacheDataSource.open(new DataSpec(Uri.EMPTY, position, length, KEY_1))); cacheDataSource.open(new DataSpec(Uri.EMPTY, position, length, KEY_1)));
byte[] buffer = new byte[100]; byte[] buffer = new byte[100];
int index = 0; int totalBytesRead = 0;
while (true) { while (true) {
int read = cacheDataSource.read(buffer, index, buffer.length - index); int read = cacheDataSource.read(buffer, totalBytesRead, buffer.length - totalBytesRead);
if (read == C.RESULT_END_OF_INPUT) { if (read == C.RESULT_END_OF_INPUT) {
break; break;
} }
index += read; totalBytesRead += read;
} }
assertEquals(actualLength, index); assertEquals(testDataLength, totalBytesRead);
MoreAsserts.assertEquals(Arrays.copyOfRange(TEST_DATA, position, position + actualLength), MoreAsserts.assertEquals(Arrays.copyOfRange(TEST_DATA, position, position + testDataLength),
Arrays.copyOf(buffer, index)); Arrays.copyOf(buffer, totalBytesRead));
cacheDataSource.close(); cacheDataSource.close();
} }
...@@ -192,7 +196,7 @@ public class CacheDataSourceTest extends InstrumentationTestCase { ...@@ -192,7 +196,7 @@ public class CacheDataSourceTest extends InstrumentationTestCase {
private CacheDataSource createCacheDataSource(boolean setReadException, private CacheDataSource createCacheDataSource(boolean setReadException,
boolean simulateUnknownLength, @CacheDataSource.Flags int flags) { boolean simulateUnknownLength, @CacheDataSource.Flags int flags) {
return createCacheDataSource(setReadException, simulateUnknownLength, flags, return createCacheDataSource(setReadException, simulateUnknownLength, flags,
new CacheDataSink(simpleCache, MAX_CACHE_FILE_SIZE)); new CacheDataSink(cache, MAX_CACHE_FILE_SIZE));
} }
private CacheDataSource createCacheDataSource(boolean setReadException, private CacheDataSource createCacheDataSource(boolean setReadException,
...@@ -204,7 +208,7 @@ public class CacheDataSourceTest extends InstrumentationTestCase { ...@@ -204,7 +208,7 @@ public class CacheDataSourceTest extends InstrumentationTestCase {
if (setReadException) { if (setReadException) {
fakeData.appendReadError(new IOException("Shouldn't read from upstream")); fakeData.appendReadError(new IOException("Shouldn't read from upstream"));
} }
return new CacheDataSource(simpleCache, upstream, new FileDataSource(), cacheWriteDataSink, return new CacheDataSource(cache, upstream, new FileDataSource(), cacheWriteDataSink,
flags, null); flags, null);
} }
......
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