Commit e7c60a2a by ojw28 Committed by GitHub

Merge pull request #3493 from google/dev-v2-r2.6.0

r2.6.0
parents ab6f9aea 3562fe1c
Showing with 1552 additions and 395 deletions
......@@ -39,6 +39,7 @@ proguard-project.txt
# Other
.DS_Store
cmake-build-debug
dist
tmp
......
# Mercurial's .hgignore files can only be used in the root directory.
# You can still apply these rules by adding
# include:path/to/this/directory/.hgignore to the top-level .hgignore file.
# Ensure same syntax as in .gitignore can be used
syntax:glob
# Android generated
bin
gen
libs
obj
lint.xml
# IntelliJ IDEA
.idea
*.iml
*.ipr
*.iws
classes
gen-external-apklibs
# Eclipse
.project
.classpath
.settings
.checkstyle
.cproject
# Gradle
.gradle
build
buildout
out
# Maven
target
release.properties
pom.xml.*
# Ant
ant.properties
local.properties
proguard.cfg
proguard-project.txt
# Other
.DS_Store
cmake-build-debug
dist
tmp
# VP9 extension
extensions/vp9/src/main/jni/libvpx
extensions/vp9/src/main/jni/libvpx_android_configs
extensions/vp9/src/main/jni/libyuv
# Opus extension
extensions/opus/src/main/jni/libopus
# FLAC extension
extensions/flac/src/main/jni/flac
# FFmpeg extension
extensions/ffmpeg/src/main/jni/ffmpeg
# Cronet extension
extensions/cronet/jniLibs/*
!extensions/cronet/jniLibs/README.md
extensions/cronet/libs/*
!extensions/cronet/libs/README.md
......@@ -69,7 +69,7 @@ individually.
In addition to library modules, ExoPlayer has multiple extension modules that
depend on external libraries to provide additional functionality. Some
extensions are available from JCenter, whereas others must be built manaully.
Browse the [extensions directory] and their individual READMEs for details.
Browse the [extensions directory][] and their individual READMEs for details.
More information on the library and extension modules that are available from
JCenter can be found on [Bintray][].
......
# Release notes #
### 2.6.0 ###
* Removed "r" prefix from versions. This release is "2.6.0", not "r2.6.0".
* New `Player.DefaultEventListener` abstract class can be extended to avoid
having to implement all methods defined by `Player.EventListener`.
* Added a reason to `EventListener.onPositionDiscontinuity`
([#3252](https://github.com/google/ExoPlayer/issues/3252)).
* New `setShuffleModeEnabled` method for enabling shuffled playback.
* SimpleExoPlayer: Support for multiple video, text and metadata outputs.
* Support for `Renderer`s that don't consume any media
([#3212](https://github.com/google/ExoPlayer/issues/3212)).
* Fix reporting of internal position discontinuities via
`Player.onPositionDiscontinuity`. `DISCONTINUITY_REASON_SEEK_ADJUSTMENT` is
added to disambiguate position adjustments during seeks from other types of
internal position discontinuity.
* Fix potential `IndexOutOfBoundsException` when calling `ExoPlayer.getDuration`
([#3362](https://github.com/google/ExoPlayer/issues/3362)).
* Fix playbacks involving looping, concatenation and ads getting stuck when
media contains tracks with uneven durations
([#1874](https://github.com/google/ExoPlayer/issues/1874)).
* Fix issue with `ContentDataSource` when reading from certain `ContentProvider`
implementations ([#3426](https://github.com/google/ExoPlayer/issues/3426)).
* Better playback experience when the video decoder cannot keep up, by skipping
to key-frames. This is particularly relevant for variable speed playbacks.
* Allow `SingleSampleMediaSource` to suppress load errors
([#3140](https://github.com/google/ExoPlayer/issues/3140)).
* `DynamicConcatenatingMediaSource`: Allow specifying a callback to be invoked
after a dynamic playlist modification has been applied
([#3407](https://github.com/google/ExoPlayer/issues/3407)).
* Audio: New `AudioSink` interface allows customization of audio output path.
* Offline: Added `Downloader` implementations for DASH, HLS, SmoothStreaming
and progressive streams.
* Track selection:
* Fixed adaptive track selection logic for live playbacks
([#3017](https://github.com/google/ExoPlayer/issues/3017)).
* Added ability to select the lowest bitrate tracks.
* DASH:
* Don't crash when a malformed or unexpected manifest update occurs
([#2795](https://github.com/google/ExoPlayer/issues/2795)).
* HLS:
* Support for Widevine protected FMP4 variants.
* Support CEA-608 in FMP4 variants.
* Support extractor injection
([#2748](https://github.com/google/ExoPlayer/issues/2748)).
* DRM:
* Improved compatibility with ClearKey content
([#3138](https://github.com/google/ExoPlayer/issues/3138)).
* Support multiple PSSH boxes of the same type.
* Retry initial provisioning and key requests if they fail
* Fix incorrect parsing of non-CENC sinf boxes.
* IMA extension:
* Expose `AdsLoader` via getter
([#3322](https://github.com/google/ExoPlayer/issues/3322)).
* Handle `setPlayWhenReady` calls during ad playbacks
([#3303](https://github.com/google/ExoPlayer/issues/3303)).
* Ignore seeks if an ad is playing
([#3309](https://github.com/google/ExoPlayer/issues/3309)).
* Improve robustness of `ImaAdsLoader` in case content is not paused between
content to ad transitions
([#3430](https://github.com/google/ExoPlayer/issues/3430)).
* UI:
* Allow specifying a `Drawable` for the `TimeBar` scrubber
([#3337](https://github.com/google/ExoPlayer/issues/3337)).
* Allow multiple listeners on `TimeBar`
([#3406](https://github.com/google/ExoPlayer/issues/3406)).
* New Leanback extension: Simplifies binding Exoplayer to Leanback UI
components.
* Unit tests moved to Robolectric.
* Misc bugfixes.
### r2.5.4 ###
* Remove unnecessary media playlist fetches during playback of live HLS streams.
......
......@@ -14,12 +14,10 @@
buildscript {
repositories {
jcenter()
maven {
url "https://maven.google.com"
}
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0-beta4'
classpath 'com.android.tools.build:gradle:3.0.0'
classpath 'com.novoda:bintray-release:0.5.0'
}
// Workaround for the following test coverage issue. Remove when fixed:
......@@ -34,9 +32,7 @@ buildscript {
allprojects {
repositories {
jcenter()
maven {
url "https://maven.google.com"
}
google()
}
project.ext {
exoplayerPublishEnabled = true
......
......@@ -12,19 +12,23 @@
// 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
// Important: ExoPlayer specifies a minSdkVersion of 14 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'
minSdkVersion = 14
compileSdkVersion = 26
targetSdkVersion = 26
buildToolsVersion = '26.0.2'
testSupportLibraryVersion = '0.5'
supportLibraryVersion = '25.4.0'
supportLibraryVersion = '27.0.0'
playServicesLibraryVersion = '11.4.2'
dexmakerVersion = '1.2'
mockitoVersion = '1.9.5'
releaseVersion = 'r2.5.4'
junitVersion = '4.12'
truthVersion = '0.35'
robolectricVersion = '3.4.2'
releaseVersion = '2.6.0'
modulePrefix = ':'
if (gradle.ext.has('exoplayerModulePrefix')) {
modulePrefix += gradle.ext.exoplayerModulePrefix
......
......@@ -33,6 +33,7 @@ include modulePrefix + 'extension-okhttp'
include modulePrefix + 'extension-opus'
include modulePrefix + 'extension-vp9'
include modulePrefix + 'extension-rtmp'
include modulePrefix + 'extension-leanback'
project(modulePrefix + 'library').projectDir = new File(rootDir, 'library/all')
project(modulePrefix + 'library-core').projectDir = new File(rootDir, 'library/core')
......@@ -50,6 +51,7 @@ project(modulePrefix + 'extension-okhttp').projectDir = new File(rootDir, 'exten
project(modulePrefix + 'extension-opus').projectDir = new File(rootDir, 'extensions/opus')
project(modulePrefix + 'extension-vp9').projectDir = new File(rootDir, 'extensions/vp9')
project(modulePrefix + 'extension-rtmp').projectDir = new File(rootDir, 'extensions/rtmp')
project(modulePrefix + 'extension-leanback').projectDir = new File(rootDir, 'extensions/leanback')
if (gradle.ext.has('exoplayerIncludeCronetExtension')
&& gradle.ext.exoplayerIncludeCronetExtension) {
......
# ExoPlayer demos #
This directory contains applications that demonstrate how to use ExoPlayer.
Browse the individual demos and their READMEs to learn more.
# IMA demo application #
This folder contains a demo application that showcases ExoPlayer integration
with the IMA SDK.
// 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.
apply from: '../../constants.gradle'
apply plugin: 'com.android.application'
android {
compileSdkVersion project.ext.compileSdkVersion
buildToolsVersion project.ext.buildToolsVersion
defaultConfig {
minSdkVersion 16
targetSdkVersion project.ext.targetSdkVersion
}
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt')
}
debug {
jniDebuggable = true
}
}
lintOptions {
// The demo app does not have translations.
disable 'MissingTranslation'
}
}
dependencies {
compile project(modulePrefix + 'library-core')
compile project(modulePrefix + 'library-ui')
compile project(modulePrefix + 'extension-ima')
}
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.exoplayer2.imademo"
android:versionCode="2600"
android:versionName="2.6.0">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="26"/>
<application android:label="@string/application_name" android:icon="@mipmap/ic_launcher"
android:largeHeap="true" android:allowBackup="false">
<activity android:name="com.google.android.exoplayer2.imademo.MainActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
android:label="@string/application_name"
android:theme="@style/PlayerTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</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.imademo;
import android.app.Activity;
import android.os.Bundle;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ui.SimpleExoPlayerView;
/**
* Main Activity for the IMA plugin demo. {@link ExoPlayer} objects are created by
* {@link PlayerManager}, which this class instantiates.
*/
public final class MainActivity extends Activity {
private SimpleExoPlayerView playerView;
private PlayerManager player;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
playerView = findViewById(R.id.player_view);
player = new PlayerManager(this);
}
@Override
public void onResume() {
super.onResume();
player.init(this, playerView);
}
@Override
public void onPause() {
super.onPause();
player.reset();
}
@Override
public void onDestroy() {
player.release();
super.onDestroy();
}
}
/*
* 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.imademo;
import android.content.Context;
import android.net.Uri;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.ext.ima.ImaAdsLoader;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.ui.SimpleExoPlayerView;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.util.Util;
/**
* Manages the {@link ExoPlayer}, the IMA plugin and all video playback.
*/
/* package */ final class PlayerManager {
private final ImaAdsLoader adsLoader;
private SimpleExoPlayer player;
private long contentPosition;
public PlayerManager(Context context) {
String adTag = context.getString(R.string.ad_tag_url);
adsLoader = new ImaAdsLoader(context, Uri.parse(adTag));
}
public void init(Context context, SimpleExoPlayerView simpleExoPlayerView) {
// Create a default track selector.
BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
TrackSelection.Factory videoTrackSelectionFactory =
new AdaptiveTrackSelection.Factory(bandwidthMeter);
TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
// Create a player instance.
player = ExoPlayerFactory.newSimpleInstance(context, trackSelector);
// Bind the player to the view.
simpleExoPlayerView.setPlayer(player);
// Produces DataSource instances through which media data is loaded.
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(context,
Util.getUserAgent(context, context.getString(R.string.application_name)));
// Produces Extractor instances for parsing the content media (i.e. not the ad).
ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
// This is the MediaSource representing the content media (i.e. not the ad).
String contentUrl = context.getString(R.string.content_url);
MediaSource contentMediaSource = new ExtractorMediaSource(
Uri.parse(contentUrl), dataSourceFactory, extractorsFactory, null, null);
// Compose the content media source into a new AdsMediaSource with both ads and content.
MediaSource mediaSourceWithAds = new AdsMediaSource(contentMediaSource, dataSourceFactory,
adsLoader, simpleExoPlayerView.getOverlayFrameLayout());
// Prepare the player with the source.
player.seekTo(contentPosition);
player.prepare(mediaSourceWithAds);
player.setPlayWhenReady(true);
}
public void reset() {
if (player != null) {
contentPosition = player.getContentPosition();
player.release();
player = null;
}
}
public void release() {
if (player != null) {
player.release();
player = null;
}
adsLoader.release();
}
}
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.google.android.exoplayer2.ui.SimpleExoPlayerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true"/>
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<string name="application_name">Exo IMA Demo</string>
<string name="content_url"><![CDATA[http://rmcdn.2mdn.net/MotifFiles/html/1248596/android_1330378998288.mp4]]></string>
<string name="ad_tag_url"><![CDATA[https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear&correlator=]]></string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 The Android Open Source Project
<!-- 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.
......@@ -13,12 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="ExoMediaButton">
<item name="android:background">?android:attr/selectableItemBackground</item>
<item name="android:layout_width">@dimen/exo_media_button_width</item>
<item name="android:layout_height">@dimen/exo_media_button_height</item>
<style name="PlayerTheme" parent="android:Theme.Holo">
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/black</item>
</style>
</resources>
......@@ -11,7 +11,7 @@
// 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.
apply from: '../constants.gradle'
apply from: '../../constants.gradle'
apply plugin: 'com.android.application'
android {
......
......@@ -16,14 +16,14 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.exoplayer2.demo"
android:versionCode="2504"
android:versionName="2.5.4">
android:versionCode="2600"
android:versionName="2.6.0">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-feature android:name="android.software.leanback" android:required="false"/>
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="25"/>
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="26"/>
<application
android:label="@string/application_name"
......
......@@ -29,7 +29,7 @@ import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataRenderer;
import com.google.android.exoplayer2.metadata.MetadataOutput;
import com.google.android.exoplayer2.metadata.emsg.EventMessage;
import com.google.android.exoplayer2.metadata.id3.ApicFrame;
import com.google.android.exoplayer2.metadata.id3.CommentFrame;
......@@ -55,10 +55,9 @@ import java.util.Locale;
/**
* Logs player events using {@link Log}.
*/
/* package */ final class EventLogger implements Player.EventListener, AudioRendererEventListener,
VideoRendererEventListener, AdaptiveMediaSourceEventListener,
ExtractorMediaSource.EventListener, DefaultDrmSessionManager.EventListener,
MetadataRenderer.Output {
/* package */ final class EventLogger implements Player.EventListener, MetadataOutput,
AudioRendererEventListener, VideoRendererEventListener, AdaptiveMediaSourceEventListener,
ExtractorMediaSource.EventListener, DefaultDrmSessionManager.EventListener {
private static final String TAG = "EventLogger";
private static final int MAX_TIMELINE_ITEM_LINES = 3;
......@@ -101,8 +100,13 @@ import java.util.Locale;
}
@Override
public void onPositionDiscontinuity() {
Log.d(TAG, "positionDiscontinuity");
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
Log.d(TAG, "shuffleModeEnabled [" + shuffleModeEnabled + "]");
}
@Override
public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) {
Log.d(TAG, "positionDiscontinuity [" + getDiscontinuityReasonString(reason) + "]");
}
@Override
......@@ -205,7 +209,12 @@ import java.util.Locale;
Log.d(TAG, "]");
}
// MetadataRenderer.Output
@Override
public void onSeekProcessed() {
Log.d(TAG, "seekProcessed");
}
// MetadataOutput
@Override
public void onMetadata(Metadata metadata) {
......@@ -244,7 +253,7 @@ import java.util.Locale;
}
@Override
public void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
public void onAudioSinkUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
printInternalError("audioTrackUnderrun [" + bufferSize + ", " + bufferSizeMs + ", "
+ elapsedSinceLastFeedMs + "]", null);
}
......@@ -480,4 +489,19 @@ import java.util.Locale;
return "?";
}
}
private static String getDiscontinuityReasonString(@Player.DiscontinuityReason int reason) {
switch (reason) {
case Player.DISCONTINUITY_REASON_PERIOD_TRANSITION:
return "PERIOD_TRANSITION";
case Player.DISCONTINUITY_REASON_SEEK:
return "SEEK";
case Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT:
return "SEEK_ADJUSTMENT";
case Player.DISCONTINUITY_REASON_INTERNAL:
return "INTERNAL";
default:
return "?";
}
}
}
......@@ -90,7 +90,7 @@ public class SampleChooserActivity extends Activity {
Toast.makeText(getApplicationContext(), R.string.sample_list_load_error, Toast.LENGTH_LONG)
.show();
}
ExpandableListView sampleList = (ExpandableListView) findViewById(R.id.sample_list);
ExpandableListView sampleList = findViewById(R.id.sample_list);
sampleList.setAdapter(new SampleAdapter(this, groups));
sampleList.setOnChildClickListener(new OnChildClickListener() {
@Override
......@@ -182,6 +182,7 @@ public class SampleChooserActivity extends Activity {
UUID drmUuid = null;
String drmLicenseUrl = null;
String[] drmKeyRequestProperties = null;
boolean drmMultiSession = false;
boolean preferExtensionDecoders = false;
ArrayList<UriSample> playlistSamples = null;
String adTagUri = null;
......@@ -220,6 +221,9 @@ public class SampleChooserActivity extends Activity {
reader.endObject();
drmKeyRequestProperties = drmKeyRequestPropertiesList.toArray(new String[0]);
break;
case "drm_multi_session":
drmMultiSession = reader.nextBoolean();
break;
case "prefer_extension_decoders":
Assertions.checkState(!insidePlaylist,
"Invalid attribute on nested item: prefer_extension_decoders");
......@@ -242,15 +246,16 @@ public class SampleChooserActivity extends Activity {
}
}
reader.endObject();
DrmInfo drmInfo = drmUuid == null ? null : new DrmInfo(drmUuid, drmLicenseUrl,
drmKeyRequestProperties, drmMultiSession);
if (playlistSamples != null) {
UriSample[] playlistSamplesArray = playlistSamples.toArray(
new UriSample[playlistSamples.size()]);
return new PlaylistSample(sampleName, drmUuid, drmLicenseUrl, drmKeyRequestProperties,
preferExtensionDecoders, playlistSamplesArray);
return new PlaylistSample(sampleName, preferExtensionDecoders, drmInfo,
playlistSamplesArray);
} else {
return new UriSample(sampleName, drmUuid, drmLicenseUrl, drmKeyRequestProperties,
preferExtensionDecoders, uri, extension, adTagUri);
return new UriSample(sampleName, preferExtensionDecoders, drmInfo, uri, extension,
adTagUri);
}
}
......@@ -271,7 +276,7 @@ public class SampleChooserActivity extends Activity {
return C.WIDEVINE_UUID;
case "playready":
return C.PLAYREADY_UUID;
case "cenc":
case "clearkey":
return C.CLEARKEY_UUID;
default:
try {
......@@ -372,31 +377,47 @@ public class SampleChooserActivity extends Activity {
}
private abstract static class Sample {
public final String name;
public final boolean preferExtensionDecoders;
private static final class DrmInfo {
public final UUID drmSchemeUuid;
public final String drmLicenseUrl;
public final String[] drmKeyRequestProperties;
public final boolean drmMultiSession;
public Sample(String name, UUID drmSchemeUuid, String drmLicenseUrl,
String[] drmKeyRequestProperties, boolean preferExtensionDecoders) {
this.name = name;
public DrmInfo(UUID drmSchemeUuid, String drmLicenseUrl,
String[] drmKeyRequestProperties, boolean drmMultiSession) {
this.drmSchemeUuid = drmSchemeUuid;
this.drmLicenseUrl = drmLicenseUrl;
this.drmKeyRequestProperties = drmKeyRequestProperties;
this.drmMultiSession = drmMultiSession;
}
public void updateIntent(Intent intent) {
Assertions.checkNotNull(intent);
intent.putExtra(PlayerActivity.DRM_SCHEME_UUID_EXTRA, drmSchemeUuid.toString());
intent.putExtra(PlayerActivity.DRM_LICENSE_URL, drmLicenseUrl);
intent.putExtra(PlayerActivity.DRM_KEY_REQUEST_PROPERTIES, drmKeyRequestProperties);
intent.putExtra(PlayerActivity.DRM_MULTI_SESSION, drmMultiSession);
}
}
private abstract static class Sample {
public final String name;
public final boolean preferExtensionDecoders;
public final DrmInfo drmInfo;
public Sample(String name, boolean preferExtensionDecoders, DrmInfo drmInfo) {
this.name = name;
this.preferExtensionDecoders = preferExtensionDecoders;
this.drmInfo = drmInfo;
}
public Intent buildIntent(Context context) {
Intent intent = new Intent(context, PlayerActivity.class);
intent.putExtra(PlayerActivity.PREFER_EXTENSION_DECODERS, preferExtensionDecoders);
if (drmSchemeUuid != null) {
intent.putExtra(PlayerActivity.DRM_SCHEME_UUID_EXTRA, drmSchemeUuid.toString());
intent.putExtra(PlayerActivity.DRM_LICENSE_URL, drmLicenseUrl);
intent.putExtra(PlayerActivity.DRM_KEY_REQUEST_PROPERTIES, drmKeyRequestProperties);
if (drmInfo != null) {
drmInfo.updateIntent(intent);
}
return intent;
}
......@@ -408,10 +429,9 @@ public class SampleChooserActivity extends Activity {
public final String extension;
public final String adTagUri;
public UriSample(String name, UUID drmSchemeUuid, String drmLicenseUrl,
String[] drmKeyRequestProperties, boolean preferExtensionDecoders, String uri,
public UriSample(String name, boolean preferExtensionDecoders, DrmInfo drmInfo, String uri,
String extension, String adTagUri) {
super(name, drmSchemeUuid, drmLicenseUrl, drmKeyRequestProperties, preferExtensionDecoders);
super(name, preferExtensionDecoders, drmInfo);
this.uri = uri;
this.extension = extension;
this.adTagUri = adTagUri;
......@@ -432,10 +452,9 @@ public class SampleChooserActivity extends Activity {
public final UriSample[] children;
public PlaylistSample(String name, UUID drmSchemeUuid, String drmLicenseUrl,
String[] drmKeyRequestProperties, boolean preferExtensionDecoders,
public PlaylistSample(String name, boolean preferExtensionDecoders, DrmInfo drmInfo,
UriSample... children) {
super(name, drmSchemeUuid, drmLicenseUrl, drmKeyRequestProperties, preferExtensionDecoders);
super(name, preferExtensionDecoders, drmInfo);
this.children = children;
}
......
......@@ -109,7 +109,7 @@ import java.util.Arrays;
private View buildView(Context context) {
LayoutInflater inflater = LayoutInflater.from(context);
View view = inflater.inflate(R.layout.track_selection_dialog, null);
ViewGroup root = (ViewGroup) view.findViewById(R.id.root);
ViewGroup root = view.findViewById(R.id.root);
TypedArray attributeArray = context.getTheme().obtainStyledAttributes(
new int[] {android.R.attr.selectableItemBackground});
......
......@@ -13,7 +13,6 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name">ExoPlayer</string>
......
......@@ -13,7 +13,6 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="PlayerTheme" parent="android:Theme.Holo">
......
......@@ -27,6 +27,11 @@ android {
sourceSets.main {
jniLibs.srcDirs = ['jniLibs']
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
......
......@@ -18,7 +18,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer.ext.cronet">
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="24"/>
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="26"/>
<application android:debuggable="true"
android:allowBackup="false"
......
......@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ext.cronet;
import static org.junit.Assert.assertArrayEquals;
......@@ -22,11 +21,8 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.MockitoAnnotations.initMocks;
import android.annotation.TargetApi;
import android.os.Build.VERSION_CODES;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
......@@ -68,7 +64,6 @@ public final class ByteArrayUploadDataProviderTest {
assertArrayEquals(TEST_DATA, byteBuffer.array());
}
@TargetApi(VERSION_CODES.GINGERBREAD)
@Test
public void testReadPartialBuffer() throws IOException {
byte[] firstHalf = Arrays.copyOfRange(TEST_DATA, 0, TEST_DATA.length / 2);
......
......@@ -3,6 +3,14 @@
The FFmpeg extension provides `FfmpegAudioRenderer`, which uses FFmpeg for
decoding and can render audio encoded in a variety of formats.
## License note ##
Please note that whilst the code in this repository is licensed under
[Apache 2.0][], using this extension also requires building and including one or
more external libraries as described below. These are licensed separately.
[Apache 2.0]: https://github.com/google/ExoPlayer/blob/release-v2/LICENSE
## Build instructions ##
To use this extension you need to clone the ExoPlayer repository and depend on
......
......@@ -22,6 +22,7 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.AudioProcessor;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.MimeTypes;
......@@ -58,13 +59,18 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer {
}
@Override
protected int supportsFormatInternal(Format format) {
if (!FfmpegLibrary.isAvailable()) {
protected int supportsFormatInternal(DrmSessionManager<ExoMediaCrypto> drmSessionManager,
Format format) {
String sampleMimeType = format.sampleMimeType;
if (!FfmpegLibrary.isAvailable() || !MimeTypes.isAudio(sampleMimeType)) {
return FORMAT_UNSUPPORTED_TYPE;
} else if (!FfmpegLibrary.supportsFormat(sampleMimeType)) {
return FORMAT_UNSUPPORTED_SUBTYPE;
} else if (!supportsFormatDrm(drmSessionManager, format.drmInitData)) {
return FORMAT_UNSUPPORTED_DRM;
} else {
return FORMAT_HANDLED;
}
String mimeType = format.sampleMimeType;
return FfmpegLibrary.supportsFormat(mimeType) ? FORMAT_HANDLED
: MimeTypes.isAudio(mimeType) ? FORMAT_UNSUPPORTED_SUBTYPE : FORMAT_UNSUPPORTED_TYPE;
}
@Override
......
......@@ -3,6 +3,14 @@
The Flac extension provides `FlacExtractor` and `LibflacAudioRenderer`, which
use libFLAC (the Flac decoding library) to extract and decode FLAC audio.
## License note ##
Please note that whilst the code in this repository is licensed under
[Apache 2.0][], using this extension also requires building and including one or
more external libraries as described below. These are licensed separately.
[Apache 2.0]: https://github.com/google/ExoPlayer/blob/release-v2/LICENSE
## Build instructions ##
To use this extension you need to clone the ExoPlayer repository and depend on
......@@ -30,7 +38,7 @@ NDK_PATH="<path to Android NDK>"
```
cd "${FLAC_EXT_PATH}/jni" && \
curl http://downloads.xiph.org/releases/flac/flac-1.3.1.tar.xz | tar xJ && \
curl https://ftp.osuosl.org/pub/xiph/releases/flac/flac-1.3.1.tar.xz | tar xJ && \
mv flac-1.3.1 flac
```
......
......@@ -18,7 +18,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer2.ext.flac.test">
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="24"/>
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="26"/>
<application android:debuggable="true"
android:allowBackup="false"
......
......@@ -22,15 +22,11 @@ import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
/**
......@@ -45,20 +41,22 @@ public class FlacPlaybackTest extends InstrumentationTestCase {
}
private void playUri(String uri) throws ExoPlaybackException {
TestPlaybackThread thread = new TestPlaybackThread(Uri.parse(uri),
TestPlaybackRunnable testPlaybackRunnable = new TestPlaybackRunnable(Uri.parse(uri),
getInstrumentation().getContext());
Thread thread = new Thread(testPlaybackRunnable);
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
fail(); // Should never happen.
}
if (thread.playbackException != null) {
throw thread.playbackException;
if (testPlaybackRunnable.playbackException != null) {
throw testPlaybackRunnable.playbackException;
}
}
private static class TestPlaybackThread extends Thread implements Player.EventListener {
private static class TestPlaybackRunnable extends Player.DefaultEventListener
implements Runnable {
private final Context context;
private final Uri uri;
......@@ -66,7 +64,7 @@ public class FlacPlaybackTest extends InstrumentationTestCase {
private ExoPlayer player;
private ExoPlaybackException playbackException;
public TestPlaybackThread(Uri uri, Context context) {
public TestPlaybackRunnable(Uri uri, Context context) {
this.uri = uri;
this.context = context;
}
......@@ -90,31 +88,6 @@ public class FlacPlaybackTest extends InstrumentationTestCase {
}
@Override
public void onLoadingChanged(boolean isLoading) {
// Do nothing.
}
@Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
// Do nothing.
}
@Override
public void onPositionDiscontinuity() {
// Do nothing.
}
@Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
// Do nothing.
}
@Override
public void onTimelineChanged(Timeline timeline, Object manifest) {
// Do nothing.
}
@Override
public void onPlayerError(ExoPlaybackException error) {
playbackException = error;
}
......@@ -123,20 +96,11 @@ public class FlacPlaybackTest extends InstrumentationTestCase {
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if (playbackState == Player.STATE_ENDED
|| (playbackState == Player.STATE_IDLE && playbackException != null)) {
releasePlayerAndQuitLooper();
player.release();
Looper.myLooper().quit();
}
}
@Override
public void onRepeatModeChanged(int repeatMode) {
// Do nothing.
}
private void releasePlayerAndQuitLooper() {
player.release();
Looper.myLooper().quit();
}
}
}
......@@ -15,6 +15,8 @@
*/
package com.google.android.exoplayer2.ext.flac;
import static com.google.android.exoplayer2.util.Util.getPcmEncoding;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.Extractor;
......@@ -122,10 +124,20 @@ public final class FlacExtractor implements Extractor {
}
});
Format mediaFormat = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_RAW, null,
streamInfo.bitRate(), Format.NO_VALUE, streamInfo.channels, streamInfo.sampleRate,
C.ENCODING_PCM_16BIT, null, null, 0, null);
Format mediaFormat =
Format.createAudioSampleFormat(
null,
MimeTypes.AUDIO_RAW,
null,
streamInfo.bitRate(),
Format.NO_VALUE,
streamInfo.channels,
streamInfo.sampleRate,
getPcmEncoding(streamInfo.bitsPerSample),
null,
null,
0,
null);
trackOutput.format(mediaFormat);
outputBuffer = new ParsableByteArray(streamInfo.maxDecodedFrameSize());
......
......@@ -20,6 +20,7 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.AudioProcessor;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.MimeTypes;
......@@ -46,9 +47,16 @@ public class LibflacAudioRenderer extends SimpleDecoderAudioRenderer {
}
@Override
protected int supportsFormatInternal(Format format) {
return FlacLibrary.isAvailable() && MimeTypes.AUDIO_FLAC.equalsIgnoreCase(format.sampleMimeType)
? FORMAT_HANDLED : FORMAT_UNSUPPORTED_TYPE;
protected int supportsFormatInternal(DrmSessionManager<ExoMediaCrypto> drmSessionManager,
Format format) {
if (!FlacLibrary.isAvailable()
|| !MimeTypes.AUDIO_FLAC.equalsIgnoreCase(format.sampleMimeType)) {
return FORMAT_UNSUPPORTED_TYPE;
} else if (!supportsFormatDrm(drmSessionManager, format.drmInitData)) {
return FORMAT_UNSUPPORTED_DRM;
} else {
return FORMAT_HANDLED;
}
}
@Override
......
......@@ -42,6 +42,9 @@
#define CHECK(x) \
if (!(x)) ALOGE("Check failed: %s ", #x)
const int endian = 1;
#define isBigEndian() (*(reinterpret_cast<const char *>(&endian)) == 0)
// The FLAC parser calls our C++ static callbacks using C calling conventions,
// inside FLAC__stream_decoder_process_until_end_of_metadata
// and FLAC__stream_decoder_process_single.
......@@ -180,85 +183,42 @@ void FLACParser::errorCallback(FLAC__StreamDecoderErrorStatus status) {
mErrorStatus = status;
}
// Copy samples from FLAC native 32-bit non-interleaved to 16-bit interleaved.
// Copy samples from FLAC native 32-bit non-interleaved to
// correct bit-depth (non-zero padded), interleaved.
// These are candidates for optimization if needed.
static void copyMono8(int16_t *dst, const int *const *src, unsigned nSamples,
unsigned /* nChannels */) {
for (unsigned i = 0; i < nSamples; ++i) {
*dst++ = src[0][i] << 8;
}
}
static void copyStereo8(int16_t *dst, const int *const *src, unsigned nSamples,
unsigned /* nChannels */) {
for (unsigned i = 0; i < nSamples; ++i) {
*dst++ = src[0][i] << 8;
*dst++ = src[1][i] << 8;
}
}
static void copyMultiCh8(int16_t *dst, const int *const *src, unsigned nSamples,
unsigned nChannels) {
static void copyToByteArrayBigEndian(int8_t *dst, const int *const *src,
unsigned bytesPerSample, unsigned nSamples,
unsigned nChannels) {
for (unsigned i = 0; i < nSamples; ++i) {
for (unsigned c = 0; c < nChannels; ++c) {
*dst++ = src[c][i] << 8;
// point to the first byte of the source address
// and then skip the first few bytes (most significant bytes)
// depending on the bit depth
const int8_t *byteSrc =
reinterpret_cast<const int8_t *>(&src[c][i]) + 4 - bytesPerSample;
memcpy(dst, byteSrc, bytesPerSample);
dst = dst + bytesPerSample;
}
}
}
static void copyMono16(int16_t *dst, const int *const *src, unsigned nSamples,
unsigned /* nChannels */) {
for (unsigned i = 0; i < nSamples; ++i) {
*dst++ = src[0][i];
}
}
static void copyStereo16(int16_t *dst, const int *const *src, unsigned nSamples,
unsigned /* nChannels */) {
for (unsigned i = 0; i < nSamples; ++i) {
*dst++ = src[0][i];
*dst++ = src[1][i];
}
}
static void copyMultiCh16(int16_t *dst, const int *const *src,
unsigned nSamples, unsigned nChannels) {
static void copyToByteArrayLittleEndian(int8_t *dst, const int *const *src,
unsigned bytesPerSample,
unsigned nSamples, unsigned nChannels) {
for (unsigned i = 0; i < nSamples; ++i) {
for (unsigned c = 0; c < nChannels; ++c) {
*dst++ = src[c][i];
// with little endian, the most significant bytes will be at the end
// copy the bytes in little endian will remove the most significant byte
// so we are good here.
memcpy(dst, &(src[c][i]), bytesPerSample);
dst = dst + bytesPerSample;
}
}
}
// 24-bit versions should do dithering or noise-shaping, here or in AudioFlinger
static void copyMono24(int16_t *dst, const int *const *src, unsigned nSamples,
unsigned /* nChannels */) {
for (unsigned i = 0; i < nSamples; ++i) {
*dst++ = src[0][i] >> 8;
}
}
static void copyStereo24(int16_t *dst, const int *const *src, unsigned nSamples,
static void copyTrespass(int8_t * /* dst */, const int *const * /* src */,
unsigned /* bytesPerSample */, unsigned /* nSamples */,
unsigned /* nChannels */) {
for (unsigned i = 0; i < nSamples; ++i) {
*dst++ = src[0][i] >> 8;
*dst++ = src[1][i] >> 8;
}
}
static void copyMultiCh24(int16_t *dst, const int *const *src,
unsigned nSamples, unsigned nChannels) {
for (unsigned i = 0; i < nSamples; ++i) {
for (unsigned c = 0; c < nChannels; ++c) {
*dst++ = src[c][i] >> 8;
}
}
}
static void copyTrespass(int16_t * /* dst */, const int *const * /* src */,
unsigned /* nSamples */, unsigned /* nChannels */) {
TRESPASS();
}
......@@ -340,6 +300,7 @@ bool FLACParser::decodeMetadata() {
case 8:
case 16:
case 24:
case 32:
break;
default:
ALOGE("unsupported bits per sample %u", getBitsPerSample());
......@@ -363,23 +324,11 @@ bool FLACParser::decodeMetadata() {
ALOGE("unsupported sample rate %u", getSampleRate());
return false;
}
// configure the appropriate copy function, defaulting to trespass
static const struct {
unsigned mChannels;
unsigned mBitsPerSample;
void (*mCopy)(int16_t *dst, const int *const *src, unsigned nSamples,
unsigned nChannels);
} table[] = {
{1, 8, copyMono8}, {2, 8, copyStereo8}, {8, 8, copyMultiCh8},
{1, 16, copyMono16}, {2, 16, copyStereo16}, {8, 16, copyMultiCh16},
{1, 24, copyMono24}, {2, 24, copyStereo24}, {8, 24, copyMultiCh24},
};
for (unsigned i = 0; i < sizeof(table) / sizeof(table[0]); ++i) {
if (table[i].mChannels >= getChannels() &&
table[i].mBitsPerSample == getBitsPerSample()) {
mCopy = table[i].mCopy;
break;
}
// configure the appropriate copy function based on device endianness.
if (isBigEndian()) {
mCopy = copyToByteArrayBigEndian;
} else {
mCopy = copyToByteArrayLittleEndian;
}
} else {
ALOGE("missing STREAMINFO");
......@@ -424,7 +373,8 @@ size_t FLACParser::readBuffer(void *output, size_t output_size) {
return -1;
}
size_t bufferSize = blocksize * getChannels() * sizeof(int16_t);
unsigned bytesPerSample = getBitsPerSample() >> 3;
size_t bufferSize = blocksize * getChannels() * bytesPerSample;
if (bufferSize > output_size) {
ALOGE(
"FLACParser::readBuffer not enough space in output buffer "
......@@ -434,8 +384,8 @@ size_t FLACParser::readBuffer(void *output, size_t output_size) {
}
// copy PCM from FLAC write buffer to our media buffer, with interleaving.
(*mCopy)(reinterpret_cast<int16_t *>(output), mWriteBuffer, blocksize,
getChannels());
(*mCopy)(reinterpret_cast<int8_t *>(output), mWriteBuffer, bytesPerSample,
blocksize, getChannels());
// fill in buffer metadata
CHECK(mWriteHeader.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER);
......
......@@ -86,8 +86,8 @@ class FLACParser {
private:
DataSource *mDataSource;
void (*mCopy)(int16_t *dst, const int *const *src, unsigned nSamples,
unsigned nChannels);
void (*mCopy)(int8_t *dst, const int *const *src, unsigned bytesPerSample,
unsigned nSamples, unsigned nChannels);
// handle to underlying libFLAC parser
FLAC__StreamDecoder *mDecoder;
......
......@@ -26,7 +26,7 @@ android {
dependencies {
compile project(modulePrefix + 'library-core')
compile 'com.google.vr:sdk-audio:1.60.1'
compile 'com.google.vr:sdk-audio:1.80.0'
}
ext {
......
......@@ -139,6 +139,11 @@ public final class GvrAudioProcessor implements AudioProcessor {
}
@Override
public int getOutputSampleRateHz() {
return sampleRateHz;
}
@Override
public void queueInput(ByteBuffer input) {
int position = input.position();
int readBytes = gvrAudioSurround.addInput(input, position, input.limit() - position);
......
# ExoPlayer IMA extension #
The IMA extension is a [MediaSource][] implementation wrapping the
The IMA extension is an [AdsLoader][] 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://google.github.io/ExoPlayer/doc/reference/index.html?com/google/android/exoplayer2/source/MediaSource.html
[AdsLoader]: https://google.github.io/ExoPlayer/doc/reference/index.html?com/google/android/exoplayer2/source/ads/AdsLoader.html
## Getting the extension ##
......@@ -27,7 +27,7 @@ locally. Instructions for doing this can be found in ExoPlayer's
## Using the extension ##
To play ads alongside a single-window content `MediaSource`, prepare the player
with an `ImaAdsMediaSource` constructed using an `ImaAdsLoader`, the content
with an `AdsMediaSource` constructed using an `ImaAdsLoader`, the content
`MediaSource` and an overlay `ViewGroup` on top of the player. Pass an ad tag
URI from your ad campaign when creating the `ImaAdsLoader`. The IMA
documentation includes some [sample ad tags][] for testing.
......@@ -38,7 +38,7 @@ background, and are recreated when the player returns to the foreground. When
playing ads it is necessary to persist ad playback state while in the background
by keeping a reference to the `ImaAdsLoader`. Reuse it when resuming playback of
the same content/ads by passing it in when constructing the new
`ImaAdsMediaSource`. It is also important to persist the player position when
`AdsMediaSource`. It is also important to persist the player position when
entering the background by storing the value of `player.getContentPosition()`.
On returning to the foreground, seek to that position before preparing the new
player instance. Finally, it is important to call `ImaAdsLoader.release()` when
......
......@@ -19,7 +19,7 @@ android {
buildToolsVersion project.ext.buildToolsVersion
defaultConfig {
minSdkVersion 14
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
......@@ -28,14 +28,15 @@ android {
dependencies {
compile project(modulePrefix + 'library-core')
// This dependency is necessary to force the supportLibraryVersion of
// com.android.support:support-v4 to be used. Else an older version (25.2.0) is included via:
// com.google.android.gms:play-services-ads:11.0.2
// |-- com.google.android.gms:play-services-ads-lite:[11.0.2] -> 11.0.2
// |-- com.google.android.gms:play-services-basement:[11.0.2] -> 11.0.2
// com.android.support:support-v4 to be used. Else an older version (25.2.0)
// is included via:
// com.google.android.gms:play-services-ads:11.4.2
// |-- com.google.android.gms:play-services-ads-lite:11.4.2
// |-- com.google.android.gms:play-services-basement:11.4.2
// |-- com.android.support:support-v4:25.2.0
compile 'com.android.support:support-v4:' + supportLibraryVersion
compile 'com.google.ads.interactivemedia.v3:interactivemedia:3.7.4'
compile 'com.google.android.gms:play-services-ads:11.0.2'
compile 'com.google.android.gms:play-services-ads:' + playServicesLibraryVersion
androidTestCompile project(modulePrefix + 'library')
androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion
......
# ExoPlayer Leanback extension #
This [Leanback][] Extension provides a [PlayerAdapter][] implementation for
ExoPlayer.
[PlayerAdapter]: https://developer.android.com/reference/android/support/v17/leanback/media/PlayerAdapter.html
[Leanback]: https://developer.android.com/reference/android/support/v17/leanback/package-summary.html
## Getting the extension ##
The easiest way to use the extension is to add it as a gradle dependency:
```gradle
compile 'com.google.android.exoplayer:extension-leanback:rX.X.X'
```
where `rX.X.X` is the version, which must match the version of the ExoPlayer
library being used.
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][].
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
## Links ##
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.leanback.*`
belong to this module.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html
// 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.
apply from: '../../constants.gradle'
apply plugin: 'com.android.library'
android {
compileSdkVersion project.ext.compileSdkVersion
buildToolsVersion project.ext.buildToolsVersion
defaultConfig {
minSdkVersion 17
targetSdkVersion project.ext.targetSdkVersion
}
}
dependencies {
compile project(modulePrefix + 'library-core')
compile('com.android.support:leanback-v17:' + supportLibraryVersion)
}
ext {
javadocTitle = 'Leanback extension for Exoplayer library'
}
apply from: '../../javadoc_library.gradle'
ext {
releaseArtifact = 'extension-leanback'
releaseDescription = 'Leanback extension for ExoPlayer.'
}
apply from: '../../publish.gradle'
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest package="com.google.android.exoplayer2.ext.leanback"/>
......@@ -27,7 +27,6 @@ android {
dependencies {
compile project(modulePrefix + 'library-core')
compile 'com.android.support:support-media-compat:' + supportLibraryVersion
compile 'com.android.support:appcompat-v7:' + supportLibraryVersion
}
ext {
......
......@@ -15,10 +15,12 @@
*/
package com.google.android.exoplayer2.ext.mediasession;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.support.v4.media.session.PlaybackStateCompat;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.util.RepeatModeUtil;
/**
* A default implementation of {@link MediaSessionConnector.PlaybackController}.
......@@ -38,33 +40,37 @@ public class DefaultPlaybackController implements MediaSessionConnector.Playback
private static final long BASE_ACTIONS = PlaybackStateCompat.ACTION_PLAY_PAUSE
| PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE
| PlaybackStateCompat.ACTION_STOP;
| PlaybackStateCompat.ACTION_STOP | PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE
| PlaybackStateCompat.ACTION_SET_REPEAT_MODE;
protected final long rewindIncrementMs;
protected final long fastForwardIncrementMs;
protected final int repeatToggleModes;
/**
* Creates a new instance.
* <p>
* Equivalent to {@code DefaultPlaybackController(
* DefaultPlaybackController.DEFAULT_REWIND_MS,
* DefaultPlaybackController.DEFAULT_FAST_FORWARD_MS)}.
* Equivalent to {@code DefaultPlaybackController(DefaultPlaybackController.DEFAULT_REWIND_MS,
* DefaultPlaybackController.DEFAULT_FAST_FORWARD_MS,
* MediaSessionConnector.DEFAULT_REPEAT_TOGGLE_MODES)}.
*/
public DefaultPlaybackController() {
this(DEFAULT_REWIND_MS, DEFAULT_FAST_FORWARD_MS);
this(DEFAULT_REWIND_MS, DEFAULT_FAST_FORWARD_MS,
MediaSessionConnector.DEFAULT_REPEAT_TOGGLE_MODES);
}
/**
* Creates a new instance with the given fast forward and rewind increments.
*
* @param rewindIncrementMs The rewind increment in milliseconds. A zero or negative value will
* @param rewindIncrementMs The rewind increment in milliseconds. A zero or negative value will
* cause the rewind action to be disabled.
* @param fastForwardIncrementMs The fast forward increment in milliseconds. A zero or negative
* value will cause the fast forward action to be removed.
* @param repeatToggleModes The available repeatToggleModes.
*/
public DefaultPlaybackController(long rewindIncrementMs, long fastForwardIncrementMs) {
public DefaultPlaybackController(long rewindIncrementMs, long fastForwardIncrementMs,
@RepeatModeUtil.RepeatToggleModes int repeatToggleModes) {
this.rewindIncrementMs = rewindIncrementMs;
this.fastForwardIncrementMs = fastForwardIncrementMs;
this.repeatToggleModes = repeatToggleModes;
}
@Override
......@@ -125,4 +131,44 @@ public class DefaultPlaybackController implements MediaSessionConnector.Playback
player.stop();
}
@Override
public void onSetShuffleMode(Player player, int shuffleMode) {
player.setShuffleModeEnabled(shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_ALL
|| shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_GROUP);
}
@Override
public void onSetRepeatMode(Player player, int repeatMode) {
int selectedExoPlayerRepeatMode = player.getRepeatMode();
switch (repeatMode) {
case PlaybackStateCompat.REPEAT_MODE_ALL:
case PlaybackStateCompat.REPEAT_MODE_GROUP:
if ((repeatToggleModes & RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL) != 0) {
selectedExoPlayerRepeatMode = Player.REPEAT_MODE_ALL;
}
break;
case PlaybackStateCompat.REPEAT_MODE_ONE:
if ((repeatToggleModes & RepeatModeUtil.REPEAT_TOGGLE_MODE_ONE) != 0) {
selectedExoPlayerRepeatMode = Player.REPEAT_MODE_ONE;
}
break;
default:
selectedExoPlayerRepeatMode = Player.REPEAT_MODE_OFF;
break;
}
player.setRepeatMode(selectedExoPlayerRepeatMode);
}
// CommandReceiver implementation.
@Override
public String[] getCommands() {
return null;
}
@Override
public void onCommand(Player player, String command, Bundle extras, ResultReceiver cb) {
// Do nothing.
}
}
package com.google.android.exoplayer2.ext.mediasession;
/*
* Copyright (c) 2017 The Android Open Source Project
*
......@@ -14,6 +13,7 @@ package com.google.android.exoplayer2.ext.mediasession;
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ext.mediasession;
import android.content.Context;
import android.os.Bundle;
......@@ -26,12 +26,6 @@ import com.google.android.exoplayer2.util.RepeatModeUtil;
*/
public final class RepeatModeActionProvider implements MediaSessionConnector.CustomActionProvider {
/**
* The default repeat toggle modes.
*/
public static final @RepeatModeUtil.RepeatToggleModes int DEFAULT_REPEAT_TOGGLE_MODES =
RepeatModeUtil.REPEAT_TOGGLE_MODE_ONE | RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL;
private static final String ACTION_REPEAT_MODE = "ACTION_EXO_REPEAT_MODE";
private final Player player;
......@@ -45,13 +39,13 @@ public final class RepeatModeActionProvider implements MediaSessionConnector.Cus
* Creates a new instance.
* <p>
* Equivalent to {@code RepeatModeActionProvider(context, player,
* RepeatModeActionProvider.DEFAULT_REPEAT_TOGGLE_MODES)}.
* MediaSessionConnector.DEFAULT_REPEAT_TOGGLE_MODES)}.
*
* @param context The context.
* @param player The player on which to toggle the repeat mode.
*/
public RepeatModeActionProvider(Context context, Player player) {
this(context, player, DEFAULT_REPEAT_TOGGLE_MODES);
this(context, player, MediaSessionConnector.DEFAULT_REPEAT_TOGGLE_MODES);
}
/**
......
/*
* Copyright (c) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ext.mediasession;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.RatingCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.source.DynamicConcatenatingMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.util.Util;
import java.util.List;
/**
* A {@link MediaSessionConnector.QueueEditor} implementation based on the
* {@link DynamicConcatenatingMediaSource}.
* <p>
* This class implements the {@link MediaSessionConnector.CommandReceiver} interface and handles
* the {@link #COMMAND_MOVE_QUEUE_ITEM} to move a queue item instead of removing and inserting it.
* This allows to move the currently playing window without interrupting playback.
*/
public final class TimelineQueueEditor implements MediaSessionConnector.QueueEditor,
MediaSessionConnector.CommandReceiver {
public static final String COMMAND_MOVE_QUEUE_ITEM = "exo_move_window";
public static final String EXTRA_FROM_INDEX = "from_index";
public static final String EXTRA_TO_INDEX = "to_index";
/**
* Factory to create {@link MediaSource}s.
*/
public interface MediaSourceFactory {
/**
* Creates a {@link MediaSource} for the given {@link MediaDescriptionCompat}.
*
* @param description The {@link MediaDescriptionCompat} to create a media source for.
* @return A {@link MediaSource} or {@code null} if no source can be created for the given
* description.
*/
@Nullable MediaSource createMediaSource(MediaDescriptionCompat description);
}
/**
* Adapter to get {@link MediaDescriptionCompat} of items in the queue and to notify the
* application about changes in the queue to sync the data structure backing the
* {@link MediaSessionConnector}.
*/
public interface QueueDataAdapter {
/**
* Gets the {@link MediaDescriptionCompat} for a {@code position}.
*
* @param position The position in the queue for which to provide a description.
* @return A {@link MediaDescriptionCompat}.
*/
MediaDescriptionCompat getMediaDescription(int position);
/**
* Adds a {@link MediaDescriptionCompat} at the given {@code position}.
*
* @param position The position at which to add.
* @param description The {@link MediaDescriptionCompat} to be added.
*/
void add(int position, MediaDescriptionCompat description);
/**
* Removes the item at the given {@code position}.
*
* @param position The position at which to remove the item.
*/
void remove(int position);
/**
* Moves a queue item from position {@code from} to position {@code to}.
*
* @param from The position from which to remove the item.
* @param to The target position to which to move the item.
*/
void move(int from, int to);
}
/**
* Used to evaluate whether two {@link MediaDescriptionCompat} are considered equal.
*/
interface MediaDescriptionEqualityChecker {
/**
* Returns {@code true} whether the descriptions are considered equal.
*
* @param d1 The first {@link MediaDescriptionCompat}.
* @param d2 The second {@link MediaDescriptionCompat}.
* @return {@code true} if considered equal.
*/
boolean equals(MediaDescriptionCompat d1, MediaDescriptionCompat d2);
}
/**
* Media description comparator comparing the media IDs. Media IDs are considered equals if both
* are {@code null}.
*/
public static final class MediaIdEqualityChecker implements MediaDescriptionEqualityChecker {
@Override
public boolean equals(MediaDescriptionCompat d1, MediaDescriptionCompat d2) {
return Util.areEqual(d1.getMediaId(), d2.getMediaId());
}
}
private final MediaControllerCompat mediaController;
private final QueueDataAdapter queueDataAdapter;
private final MediaSourceFactory sourceFactory;
private final MediaDescriptionEqualityChecker equalityChecker;
private final DynamicConcatenatingMediaSource queueMediaSource;
/**
* Creates a new {@link TimelineQueueEditor} with a given mediaSourceFactory.
*
* @param mediaController A {@link MediaControllerCompat} to read the current queue.
* @param queueMediaSource The {@link DynamicConcatenatingMediaSource} to
* manipulate.
* @param queueDataAdapter A {@link QueueDataAdapter} to change the backing data.
* @param sourceFactory The {@link MediaSourceFactory} to build media sources.
*/
public TimelineQueueEditor(@NonNull MediaControllerCompat mediaController,
@NonNull DynamicConcatenatingMediaSource queueMediaSource,
@NonNull QueueDataAdapter queueDataAdapter, @NonNull MediaSourceFactory sourceFactory) {
this(mediaController, queueMediaSource, queueDataAdapter, sourceFactory,
new MediaIdEqualityChecker());
}
/**
* Creates a new {@link TimelineQueueEditor} with a given mediaSourceFactory.
*
* @param mediaController A {@link MediaControllerCompat} to read the current queue.
* @param queueMediaSource The {@link DynamicConcatenatingMediaSource} to
* manipulate.
* @param queueDataAdapter A {@link QueueDataAdapter} to change the backing data.
* @param sourceFactory The {@link MediaSourceFactory} to build media sources.
* @param equalityChecker The {@link MediaDescriptionEqualityChecker} to match queue items.
*/
public TimelineQueueEditor(@NonNull MediaControllerCompat mediaController,
@NonNull DynamicConcatenatingMediaSource queueMediaSource,
@NonNull QueueDataAdapter queueDataAdapter, @NonNull MediaSourceFactory sourceFactory,
@NonNull MediaDescriptionEqualityChecker equalityChecker) {
this.mediaController = mediaController;
this.queueMediaSource = queueMediaSource;
this.queueDataAdapter = queueDataAdapter;
this.sourceFactory = sourceFactory;
this.equalityChecker = equalityChecker;
}
@Override
public long getSupportedQueueEditorActions(@Nullable Player player) {
return 0;
}
@Override
public void onAddQueueItem(Player player, MediaDescriptionCompat description) {
onAddQueueItem(player, description, player.getCurrentTimeline().getWindowCount());
}
@Override
public void onAddQueueItem(Player player, MediaDescriptionCompat description, int index) {
MediaSource mediaSource = sourceFactory.createMediaSource(description);
if (mediaSource != null) {
queueDataAdapter.add(index, description);
queueMediaSource.addMediaSource(index, mediaSource);
}
}
@Override
public void onRemoveQueueItem(Player player, MediaDescriptionCompat description) {
List<MediaSessionCompat.QueueItem> queue = mediaController.getQueue();
for (int i = 0; i < queue.size(); i++) {
if (equalityChecker.equals(queue.get(i).getDescription(), description)) {
onRemoveQueueItemAt(player, i);
return;
}
}
}
@Override
public void onRemoveQueueItemAt(Player player, int index) {
queueDataAdapter.remove(index);
queueMediaSource.removeMediaSource(index);
}
@Override
public void onSetRating(Player player, RatingCompat rating) {
// Do nothing.
}
// CommandReceiver implementation.
@NonNull
@Override
public String[] getCommands() {
return new String[] {COMMAND_MOVE_QUEUE_ITEM};
}
@Override
public void onCommand(Player player, String command, Bundle extras, ResultReceiver cb) {
int from = extras.getInt(EXTRA_FROM_INDEX, C.INDEX_UNSET);
int to = extras.getInt(EXTRA_TO_INDEX, C.INDEX_UNSET);
if (from != C.INDEX_UNSET && to != C.INDEX_UNSET) {
queueDataAdapter.move(from, to);
queueMediaSource.moveMediaSource(from, to);
}
}
}
......@@ -15,6 +15,8 @@
*/
package com.google.android.exoplayer2.ext.mediasession;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.support.annotation.Nullable;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.session.MediaSessionCompat;
......@@ -23,7 +25,6 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
......@@ -126,8 +127,7 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
if (timeline.isEmpty()) {
return;
}
int previousWindowIndex = timeline.getPreviousWindowIndex(player.getCurrentWindowIndex(),
player.getRepeatMode());
int previousWindowIndex = player.getPreviousWindowIndex();
if (player.getCurrentPosition() > MAX_POSITION_FOR_SEEK_TO_PREVIOUS
|| previousWindowIndex == C.INDEX_UNSET) {
player.seekTo(0);
......@@ -154,16 +154,22 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
if (timeline.isEmpty()) {
return;
}
int nextWindowIndex = timeline.getNextWindowIndex(player.getCurrentWindowIndex(),
player.getRepeatMode());
int nextWindowIndex = player.getNextWindowIndex();
if (nextWindowIndex != C.INDEX_UNSET) {
player.seekTo(nextWindowIndex, C.TIME_UNSET);
}
}
// CommandReceiver implementation.
@Override
public String[] getCommands() {
return null;
}
@Override
public void onSetShuffleModeEnabled(Player player, boolean enabled) {
// TODO: Implement this.
public void onCommand(Player player, String command, Bundle extras, ResultReceiver cb) {
// Do nothing.
}
private void publishFloatingQueueWindow(Player player) {
......@@ -186,3 +192,4 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
}
}
......@@ -6,6 +6,14 @@ The OkHttp extension is an [HttpDataSource][] implementation using Square's
[HttpDataSource]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/upstream/HttpDataSource.html
[OkHttp]: https://square.github.io/okhttp/
## License note ##
Please note that whilst the code in this repository is licensed under
[Apache 2.0][], using this extension requires depending on OkHttp, which is
licensed separately.
[Apache 2.0]: https://github.com/google/ExoPlayer/blob/release-v2/LICENSE
## Getting the extension ##
The easiest way to use the extension is to add it as a gradle dependency:
......
......@@ -31,7 +31,7 @@ android {
dependencies {
compile project(modulePrefix + 'library-core')
compile('com.squareup.okhttp3:okhttp:3.8.1') {
compile('com.squareup.okhttp3:okhttp:3.9.0') {
exclude group: 'org.json'
}
}
......
......@@ -3,6 +3,14 @@
The Opus extension provides `LibopusAudioRenderer`, which uses libopus (the Opus
decoding library) to decode Opus audio.
## License note ##
Please note that whilst the code in this repository is licensed under
[Apache 2.0][], using this extension also requires building and including one or
more external libraries as described below. These are licensed separately.
[Apache 2.0]: https://github.com/google/ExoPlayer/blob/release-v2/LICENSE
## Build instructions ##
To use this extension you need to clone the ExoPlayer repository and depend on
......
......@@ -18,7 +18,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer2.ext.opus.test">
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="24"/>
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="26"/>
<application android:debuggable="true"
android:allowBackup="false"
......
......@@ -22,15 +22,11 @@ import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
/**
......@@ -45,20 +41,22 @@ public class OpusPlaybackTest extends InstrumentationTestCase {
}
private void playUri(String uri) throws ExoPlaybackException {
TestPlaybackThread thread = new TestPlaybackThread(Uri.parse(uri),
TestPlaybackRunnable testPlaybackRunnable = new TestPlaybackRunnable(Uri.parse(uri),
getInstrumentation().getContext());
Thread thread = new Thread(testPlaybackRunnable);
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
fail(); // Should never happen.
}
if (thread.playbackException != null) {
throw thread.playbackException;
if (testPlaybackRunnable.playbackException != null) {
throw testPlaybackRunnable.playbackException;
}
}
private static class TestPlaybackThread extends Thread implements Player.EventListener {
private static class TestPlaybackRunnable extends Player.DefaultEventListener
implements Runnable {
private final Context context;
private final Uri uri;
......@@ -66,7 +64,7 @@ public class OpusPlaybackTest extends InstrumentationTestCase {
private ExoPlayer player;
private ExoPlaybackException playbackException;
public TestPlaybackThread(Uri uri, Context context) {
public TestPlaybackRunnable(Uri uri, Context context) {
this.uri = uri;
this.context = context;
}
......@@ -90,31 +88,6 @@ public class OpusPlaybackTest extends InstrumentationTestCase {
}
@Override
public void onLoadingChanged(boolean isLoading) {
// Do nothing.
}
@Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
// Do nothing.
}
@Override
public void onPositionDiscontinuity() {
// Do nothing.
}
@Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
// Do nothing.
}
@Override
public void onTimelineChanged(Timeline timeline, Object manifest) {
// Do nothing.
}
@Override
public void onPlayerError(ExoPlaybackException error) {
playbackException = error;
}
......@@ -123,20 +96,11 @@ public class OpusPlaybackTest extends InstrumentationTestCase {
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if (playbackState == Player.STATE_ENDED
|| (playbackState == Player.STATE_IDLE && playbackException != null)) {
releasePlayerAndQuitLooper();
player.release();
Looper.myLooper().quit();
}
}
@Override
public void onRepeatModeChanged(int repeatMode) {
// Do nothing.
}
private void releasePlayerAndQuitLooper() {
player.release();
Looper.myLooper().quit();
}
}
}
......@@ -71,9 +71,16 @@ public final class LibopusAudioRenderer extends SimpleDecoderAudioRenderer {
}
@Override
protected int supportsFormatInternal(Format format) {
return OpusLibrary.isAvailable() && MimeTypes.AUDIO_OPUS.equalsIgnoreCase(format.sampleMimeType)
? FORMAT_HANDLED : FORMAT_UNSUPPORTED_TYPE;
protected int supportsFormatInternal(DrmSessionManager<ExoMediaCrypto> drmSessionManager,
Format format) {
if (!OpusLibrary.isAvailable()
|| !MimeTypes.AUDIO_OPUS.equalsIgnoreCase(format.sampleMimeType)) {
return FORMAT_UNSUPPORTED_TYPE;
} else if (!supportsFormatDrm(drmSessionManager, format.drmInitData)) {
return FORMAT_UNSUPPORTED_DRM;
} else {
return FORMAT_HANDLED;
}
}
@Override
......
......@@ -7,6 +7,14 @@ streams using [LibRtmp Client for Android][].
[RTMP]: https://en.wikipedia.org/wiki/Real-Time_Messaging_Protocol
[LibRtmp Client for Android]: https://github.com/ant-media/LibRtmp-Client-for-Android
## License note ##
Please note that whilst the code in this repository is licensed under
[Apache 2.0][], using this extension requires depending on LibRtmp Client for
Android, which is licensed separately.
[Apache 2.0]: https://github.com/google/ExoPlayer/blob/release-v2/LICENSE
## Getting the extension ##
The easiest way to use the extension is to add it as a gradle dependency:
......
......@@ -3,6 +3,14 @@
The VP9 extension provides `LibvpxVideoRenderer`, which uses libvpx (the VPx
decoding library) to decode VP9 video.
## License note ##
Please note that whilst the code in this repository is licensed under
[Apache 2.0][], using this extension also requires building and including one or
more external libraries as described below. These are licensed separately.
[Apache 2.0]: https://github.com/google/ExoPlayer/blob/release-v2/LICENSE
## Build instructions ##
To use this extension you need to clone the ExoPlayer repository and depend on
......
......@@ -18,7 +18,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer2.ext.vp9.test">
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="24"/>
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="26"/>
<application android:debuggable="true"
android:allowBackup="false"
......
......@@ -23,15 +23,11 @@ import android.util.Log;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
/**
......@@ -74,20 +70,22 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
}
private void playUri(String uri) throws ExoPlaybackException {
TestPlaybackThread thread = new TestPlaybackThread(Uri.parse(uri),
TestPlaybackRunnable testPlaybackRunnable = new TestPlaybackRunnable(Uri.parse(uri),
getInstrumentation().getContext());
Thread thread = new Thread(testPlaybackRunnable);
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
fail(); // Should never happen.
}
if (thread.playbackException != null) {
throw thread.playbackException;
if (testPlaybackRunnable.playbackException != null) {
throw testPlaybackRunnable.playbackException;
}
}
private static class TestPlaybackThread extends Thread implements Player.EventListener {
private static class TestPlaybackRunnable extends Player.DefaultEventListener
implements Runnable {
private final Context context;
private final Uri uri;
......@@ -95,7 +93,7 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
private ExoPlayer player;
private ExoPlaybackException playbackException;
public TestPlaybackThread(Uri uri, Context context) {
public TestPlaybackRunnable(Uri uri, Context context) {
this.uri = uri;
this.context = context;
}
......@@ -122,31 +120,6 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
}
@Override
public void onLoadingChanged(boolean isLoading) {
// Do nothing.
}
@Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
// Do nothing.
}
@Override
public void onPositionDiscontinuity() {
// Do nothing.
}
@Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
// Do nothing.
}
@Override
public void onTimelineChanged(Timeline timeline, Object manifest) {
// Do nothing.
}
@Override
public void onPlayerError(ExoPlaybackException error) {
playbackException = error;
}
......@@ -155,20 +128,11 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if (playbackState == Player.STATE_ENDED
|| (playbackState == Player.STATE_IDLE && playbackException != null)) {
releasePlayerAndQuitLooper();
player.release();
Looper.myLooper().quit();
}
}
@Override
public void onRepeatModeChanged(int repeatMode) {
// Do nothing.
}
private void releasePlayerAndQuitLooper() {
player.release();
Looper.myLooper().quit();
}
}
}
......@@ -109,12 +109,12 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
private DrmSession<ExoMediaCrypto> drmSession;
private DrmSession<ExoMediaCrypto> pendingDrmSession;
@ReinitializationState
private int decoderReinitializationState;
private @ReinitializationState int decoderReinitializationState;
private boolean decoderReceivedBuffers;
private Bitmap bitmap;
private boolean renderedFirstFrame;
private boolean forceRenderFrame;
private long joiningDeadlineMs;
private Surface surface;
private VpxOutputBufferRenderer outputBufferRenderer;
......@@ -129,6 +129,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
private long droppedFrameAccumulationStartTimeMs;
private int droppedFrames;
private int consecutiveDroppedFrameCount;
private int buffersInCodecCount;
/**
* @param scaleToFit Whether video frames should be scaled to fit when rendering.
......@@ -194,8 +195,12 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
@Override
public int supportsFormat(Format format) {
return VpxLibrary.isAvailable() && MimeTypes.VIDEO_VP9.equalsIgnoreCase(format.sampleMimeType)
? (FORMAT_HANDLED | ADAPTIVE_SEAMLESS) : FORMAT_UNSUPPORTED_TYPE;
if (!VpxLibrary.isAvailable() || !MimeTypes.VIDEO_VP9.equalsIgnoreCase(format.sampleMimeType)) {
return FORMAT_UNSUPPORTED_TYPE;
} else if (!supportsFormatDrm(drmSessionManager, format.drmInitData)) {
return FORMAT_UNSUPPORTED_DRM;
}
return FORMAT_HANDLED | ADAPTIVE_SEAMLESS;
}
@Override
......@@ -253,6 +258,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
return false;
}
decoderCounters.skippedOutputBufferCount += outputBuffer.skippedOutputBufferCount;
buffersInCodecCount -= outputBuffer.skippedOutputBufferCount;
}
if (nextOutputBuffer == null) {
......@@ -275,26 +281,42 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
if (outputMode == VpxDecoder.OUTPUT_MODE_NONE) {
// Skip frames in sync with playback, so we'll be at the right frame if the mode changes.
if (isBufferLate(outputBuffer.timeUs - positionUs)) {
forceRenderFrame = false;
skipBuffer();
buffersInCodecCount--;
return true;
}
return false;
}
if (forceRenderFrame) {
forceRenderFrame = false;
renderBuffer();
buffersInCodecCount--;
return true;
}
final long nextOutputBufferTimeUs =
nextOutputBuffer != null && !nextOutputBuffer.isEndOfStream()
? nextOutputBuffer.timeUs : C.TIME_UNSET;
if (shouldDropOutputBuffer(
long earlyUs = outputBuffer.timeUs - positionUs;
if (shouldDropBuffersToKeyframe(earlyUs) && maybeDropBuffersToKeyframe(positionUs)) {
forceRenderFrame = true;
return false;
} else if (shouldDropOutputBuffer(
outputBuffer.timeUs, nextOutputBufferTimeUs, positionUs, joiningDeadlineMs)) {
dropBuffer();
buffersInCodecCount--;
return true;
}
// If we have yet to render a frame to the current output (either initially or immediately
// following a seek), render one irrespective of the state or current position.
if (!renderedFirstFrame
|| (getState() == STATE_STARTED && outputBuffer.timeUs <= positionUs + 30000)) {
|| (getState() == STATE_STARTED && earlyUs <= 30000)) {
renderBuffer();
buffersInCodecCount--;
}
return false;
}
......@@ -303,18 +325,29 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
* Returns whether the current frame should be dropped.
*
* @param outputBufferTimeUs The timestamp of the current output buffer.
* @param nextOutputBufferTimeUs The timestamp of the next output buffer or
* {@link C#TIME_UNSET} if the next output buffer is unavailable.
* @param nextOutputBufferTimeUs The timestamp of the next output buffer or {@link C#TIME_UNSET}
* if the next output buffer is unavailable.
* @param positionUs The current playback position.
* @param joiningDeadlineMs The joining deadline.
* @return Returns whether to drop the current output buffer.
*/
protected boolean shouldDropOutputBuffer(long outputBufferTimeUs, long nextOutputBufferTimeUs,
private boolean shouldDropOutputBuffer(long outputBufferTimeUs, long nextOutputBufferTimeUs,
long positionUs, long joiningDeadlineMs) {
return isBufferLate(outputBufferTimeUs - positionUs)
&& (joiningDeadlineMs != C.TIME_UNSET || nextOutputBufferTimeUs != C.TIME_UNSET);
}
/**
* Returns whether to drop all buffers from the buffer being processed to the keyframe at or after
* the current playback position, if possible.
*
* @param earlyUs The time until the current buffer should be presented in microseconds. A
* negative value indicates that the buffer is late.
*/
private boolean shouldDropBuffersToKeyframe(long earlyUs) {
return isBufferVeryLate(earlyUs);
}
private void renderBuffer() {
int bufferMode = outputBuffer.mode;
boolean renderRgb = bufferMode == VpxDecoder.OUTPUT_MODE_RGB && surface != null;
......@@ -338,18 +371,35 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
}
private void dropBuffer() {
decoderCounters.droppedOutputBufferCount++;
droppedFrames++;
consecutiveDroppedFrameCount++;
decoderCounters.maxConsecutiveDroppedOutputBufferCount = Math.max(
consecutiveDroppedFrameCount, decoderCounters.maxConsecutiveDroppedOutputBufferCount);
if (droppedFrames == maxDroppedFramesToNotify) {
maybeNotifyDroppedFrames();
}
updateDroppedBufferCounters(1);
outputBuffer.release();
outputBuffer = null;
}
private boolean maybeDropBuffersToKeyframe(long positionUs) throws ExoPlaybackException {
int droppedSourceBufferCount = skipSource(positionUs);
if (droppedSourceBufferCount == 0) {
return false;
}
decoderCounters.droppedToKeyframeCount++;
// We dropped some buffers to catch up, so update the decoder counters and flush the codec,
// which releases all pending buffers buffers including the current output buffer.
updateDroppedBufferCounters(buffersInCodecCount + droppedSourceBufferCount);
flushDecoder();
return true;
}
private void updateDroppedBufferCounters(int droppedBufferCount) {
decoderCounters.droppedBufferCount += droppedBufferCount;
droppedFrames += droppedBufferCount;
consecutiveDroppedFrameCount += droppedBufferCount;
decoderCounters.maxConsecutiveDroppedBufferCount = Math.max(consecutiveDroppedFrameCount,
decoderCounters.maxConsecutiveDroppedBufferCount);
if (droppedFrames >= maxDroppedFramesToNotify) {
maybeNotifyDroppedFrames();
}
}
private void skipBuffer() {
decoderCounters.skippedOutputBufferCount++;
outputBuffer.release();
......@@ -422,6 +472,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
inputBuffer.flip();
inputBuffer.colorInfo = formatHolder.format.colorInfo;
decoder.queueInputBuffer(inputBuffer);
buffersInCodecCount++;
decoderReceivedBuffers = true;
decoderCounters.inputBufferCount++;
inputBuffer = null;
......@@ -441,6 +492,8 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
private void flushDecoder() throws ExoPlaybackException {
waitingForKeys = false;
forceRenderFrame = false;
buffersInCodecCount = 0;
if (decoderReinitializationState != REINITIALIZATION_STATE_NONE) {
releaseDecoder();
maybeInitDecoder();
......@@ -597,6 +650,8 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
decoderCounters.decoderReleaseCount++;
decoderReinitializationState = REINITIALIZATION_STATE_NONE;
decoderReceivedBuffers = false;
forceRenderFrame = false;
buffersInCodecCount = 0;
}
private void onInputFormatChanged(Format newFormat) throws ExoPlaybackException {
......@@ -731,8 +786,13 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
}
private static boolean isBufferLate(long earlyUs) {
// Class a buffer as late if it should have been presented more than 30ms ago.
// Class a buffer as late if it should have been presented more than 30 ms ago.
return earlyUs < -30000;
}
private static boolean isBufferVeryLate(long earlyUs) {
// Class a buffer as very late if it should have been presented more than 500 ms ago.
return earlyUs < -500000;
}
}
......@@ -120,14 +120,16 @@ import java.nio.ByteBuffer;
}
}
outputBuffer.init(inputBuffer.timeUs, outputMode);
int getFrameResult = vpxGetFrame(vpxDecContext, outputBuffer);
if (getFrameResult == 1) {
outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
} else if (getFrameResult == -1) {
return new VpxDecoderException("Buffer initialization failed.");
if (!inputBuffer.isDecodeOnly()) {
outputBuffer.init(inputBuffer.timeUs, outputMode);
int getFrameResult = vpxGetFrame(vpxDecContext, outputBuffer);
if (getFrameResult == 1) {
outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
} else if (getFrameResult == -1) {
return new VpxDecoderException("Buffer initialization failed.");
}
outputBuffer.colorInfo = inputBuffer.colorInfo;
}
outputBuffer.colorInfo = inputBuffer.colorInfo;
return null;
}
......
......@@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer2.ext.vp9;
import android.annotation.TargetApi;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;
......@@ -23,7 +22,6 @@ import android.util.AttributeSet;
/**
* A GLSurfaceView extension that scales itself to the given aspect ratio.
*/
@TargetApi(11)
public class VpxVideoSurfaceView extends GLSurfaceView implements VpxOutputBufferRenderer {
private final VpxRenderer renderer;
......
......@@ -13,5 +13,4 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest package="com.google.android.exoplayer2"/>
......@@ -28,6 +28,9 @@ android {
androidTest {
java.srcDirs += "../../testutils/src/main/java/"
}
test {
java.srcDirs += "../../testutils/src/main/java/"
}
}
buildTypes {
......@@ -44,6 +47,10 @@ dependencies {
androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion
androidTestCompile 'org.mockito:mockito-core:' + mockitoVersion
testCompile 'com.google.truth:truth:' + truthVersion
testCompile 'junit:junit:' + junitVersion
testCompile 'org.mockito:mockito-core:' + mockitoVersion
testCompile 'org.robolectric:robolectric:' + robolectricVersion
}
ext {
......
......@@ -18,7 +18,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer2.core.test">
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="24"/>
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="26"/>
<application android:debuggable="true"
android:allowBackup="false"
......
WEBVTT # This comment is allowed
# First timestamp is missing the 1/1000ths component, but parse anyway.
00:00 --> 00:01.234
This is the first subtitle.
02.345 --> 00:03.456
This is the second subtitle.
0.0.0 --> 00:05.678
This should be discarded (too many dots).
00:06.789 --> not-a-timestamp
This should be discarded (not a timestamp).
......@@ -33,23 +33,25 @@ public class TimelineTest extends TestCase {
Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(1, 111));
TimelineAsserts.assertWindowIds(timeline, 111);
TimelineAsserts.assertPeriodCounts(timeline, 1);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, C.INDEX_UNSET);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, 0);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, 0);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, 0);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, 0);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, false,
C.INDEX_UNSET);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 0);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 0);
}
public void testMultiPeriodTimeline() {
Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(5, 111));
TimelineAsserts.assertWindowIds(timeline, 111);
TimelineAsserts.assertPeriodCounts(timeline, 5);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, C.INDEX_UNSET);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, 0);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, 0);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, 0);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, 0);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, false,
C.INDEX_UNSET);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0);
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 0);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, C.INDEX_UNSET);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0);
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 0);
}
}
......@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.drm;
import static org.mockito.Matchers.any;
......@@ -24,10 +23,9 @@ import android.test.MoreAsserts;
import android.util.Pair;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import java.util.HashMap;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
* Tests {@link OfflineLicenseHelper}.
......@@ -35,15 +33,15 @@ import org.mockito.Mock;
public class OfflineLicenseHelperTest extends InstrumentationTestCase {
private OfflineLicenseHelper<?> offlineLicenseHelper;
@Mock private HttpDataSource httpDataSource;
@Mock private MediaDrmCallback mediaDrmCallback;
@Mock private ExoMediaDrm<ExoMediaCrypto> mediaDrm;
@Override
protected void setUp() throws Exception {
TestUtil.setUpMockito(this);
setUpMockito(this);
when(mediaDrm.openSession()).thenReturn(new byte[] {1, 2, 3});
offlineLicenseHelper = new OfflineLicenseHelper<>(mediaDrm, mediaDrmCallback, null);
offlineLicenseHelper = new OfflineLicenseHelper<>(C.WIDEVINE_UUID, mediaDrm, mediaDrmCallback,
null);
}
@Override
......@@ -154,8 +152,18 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase {
}
private static DrmInitData newDrmInitData() {
return new DrmInitData(new SchemeData(C.WIDEVINE_UUID, "cenc", "mimeType",
return new DrmInitData(new SchemeData(C.WIDEVINE_UUID, "mimeType",
new byte[] {1, 4, 7, 0, 3, 6}));
}
/**
* Sets up Mockito for an instrumentation test.
*/
private static void setUpMockito(InstrumentationTestCase instrumentationTestCase) {
// Workaround for https://code.google.com/p/dexmaker/issues/detail?id=2.
System.setProperty("dexmaker.dexcache",
instrumentationTestCase.getInstrumentation().getTargetContext().getCacheDir().getPath());
MockitoAnnotations.initMocks(instrumentationTestCase);
}
}
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