Merge branch 'dev-v2' of https://github.com/google/ExoPlayer into dev-v2

This commit is contained in:
Sebastian Roth 2017-08-28 16:27:43 +08:00
commit bf09aec4f3
323 changed files with 7517 additions and 1685 deletions

View File

@ -9,34 +9,39 @@ and extend, and can be updated through Play Store application updates.
## Documentation ##
* The [developer guide][] provides a wealth of information to help you get
started.
* The [class reference][] documents the ExoPlayer library classes.
* The [developer guide][] provides a wealth of information.
* The [class reference][] documents ExoPlayer classes.
* The [release notes][] document the major changes in each release.
* Follow our [developer blog][] to keep up to date with the latest ExoPlayer
developments!
[developer guide]: https://google.github.io/ExoPlayer/guide.html
[class reference]: https://google.github.io/ExoPlayer/doc/reference
[release notes]: https://github.com/google/ExoPlayer/blob/dev-v2/RELEASENOTES.md
[release notes]: https://github.com/google/ExoPlayer/blob/release-v2/RELEASENOTES.md
[developer blog]: https://medium.com/google-exoplayer
## Using ExoPlayer ##
ExoPlayer modules can be obtained via jCenter. It's also possible to clone the
ExoPlayer modules can be obtained from JCenter. It's also possible to clone the
repository and depend on the modules locally.
### Via jCenter ###
### From JCenter ###
The easiest way to get started using ExoPlayer is to add it as a gradle
dependency. You need to make sure you have the jcenter repository included in
the `build.gradle` file in the root of your project:
dependency. You need to make sure you have the JCenter and Google Maven
repositories included in the `build.gradle` file in the root of your project:
```gradle
repositories {
jcenter()
maven {
url "https://maven.google.com"
}
}
```
Next add a gradle compile dependency to the `build.gradle` file of your app
module. The following will add a dependency to the full ExoPlayer library:
module. The following will add a dependency to the full library:
```gradle
compile 'com.google.android.exoplayer:exoplayer:r2.X.X'
@ -53,8 +58,8 @@ compile 'com.google.android.exoplayer:exoplayer-dash:r2.X.X'
compile 'com.google.android.exoplayer:exoplayer-ui:r2.X.X'
```
The available modules are listed below. Adding a dependency to the full
ExoPlayer library is equivalent to adding dependencies on all of the modules
The available library modules are listed below. Adding a dependency to the full
library is equivalent to adding dependencies on all of the library modules
individually.
* `exoplayer-core`: Core functionality (required).
@ -63,11 +68,16 @@ individually.
* `exoplayer-smoothstreaming`: Support for SmoothStreaming content.
* `exoplayer-ui`: UI components and resources for use with ExoPlayer.
For more details, see the project on [Bintray][]. For information about the
latest versions, see the [Release notes][].
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.
More information on the library and extension modules that are available from
JCenter can be found on [Bintray][].
[extensions directory][]: https://github.com/google/ExoPlayer/tree/release-v2/extensions/
[Bintray]: https://bintray.com/google/exoplayer
[Release notes]: https://github.com/google/ExoPlayer/blob/release-v2/RELEASENOTES.md
### Locally ###
@ -106,15 +116,9 @@ compile project(':exoplayer-library-ui)
#### Project branches ####
* The project has `dev-vX` and `release-vX` branches, where `X` is the major
version number.
* Most development work happens on the `dev-vX` branch with the highest major
version number. Pull requests should normally be made to this branch.
* Bug fixes may be submitted to older `dev-vX` branches. When doing this, the
same (or an equivalent) fix should also be submitted to all subsequent
`dev-vX` branches.
* A `release-vX` branch holds the most recent stable release for major version
`X`.
* Development work happens on the `dev-v2` branch. Pull requests should
normally be made to this branch.
* The `release-v2` branch holds the most recent release.
#### Using Android Studio ####

View File

@ -14,6 +14,9 @@
buildscript {
repositories {
jcenter()
maven {
url "https://maven.google.com"
}
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'

View File

@ -12,16 +12,17 @@
// 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'
testSupportLibraryVersion = '0.5'
supportLibraryVersion = '25.4.0'
supportLibraryVersion = '26.0.1'
playServicesLibraryVersion = '11.0.2'
dexmakerVersion = '1.2'
mockitoVersion = '1.9.5'
releaseVersion = 'r2.5.1'

View File

@ -28,11 +28,13 @@ include modulePrefix + 'extension-ffmpeg'
include modulePrefix + 'extension-flac'
include modulePrefix + 'extension-gvr'
include modulePrefix + 'extension-ima'
include modulePrefix + 'extension-cast'
include modulePrefix + 'extension-mediasession'
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')
@ -45,11 +47,13 @@ project(modulePrefix + 'extension-ffmpeg').projectDir = new File(rootDir, 'exten
project(modulePrefix + 'extension-flac').projectDir = new File(rootDir, 'extensions/flac')
project(modulePrefix + 'extension-gvr').projectDir = new File(rootDir, 'extensions/gvr')
project(modulePrefix + 'extension-ima').projectDir = new File(rootDir, 'extensions/ima')
project(modulePrefix + 'extension-cast').projectDir = new File(rootDir, 'extensions/cast')
project(modulePrefix + 'extension-mediasession').projectDir = new File(rootDir, 'extensions/mediasession')
project(modulePrefix + 'extension-okhttp').projectDir = new File(rootDir, 'extensions/okhttp')
project(modulePrefix + 'extension-opus').projectDir = new File(rootDir, 'extensions/opus')
project(modulePrefix + 'extension-vp9').projectDir = new File(rootDir, 'extensions/vp9')
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) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

4
demos/README.md Normal file
View File

@ -0,0 +1,4 @@
# ExoPlayer demos #
This directory contains applications that demonstrate how to use ExoPlayer.
Browse the individual demos and their READMEs to learn more.

4
demos/cast/README.md Normal file
View File

@ -0,0 +1,4 @@
# Cast demo application #
This folder contains a demo application that showcases ExoPlayer integration
with Google Cast.

51
demos/cast/build.gradle Normal file
View File

@ -0,0 +1,51 @@
// 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-dash')
compile project(modulePrefix + 'library-hls')
compile project(modulePrefix + 'library-smoothstreaming')
compile project(modulePrefix + 'library-ui')
compile project(modulePrefix + 'extension-cast')
}

View File

@ -0,0 +1,43 @@
<?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.castdemo"
android:versionCode="0001"
android:versionName="0.0.1">
<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">
<meta-data android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.google.android.exoplayer2.ext.cast.DefaultCastOptionsProvider" />
<activity android:name="com.google.android.exoplayer2.castdemo.MainActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
android:launchMode="singleTop" android:label="@string/application_name"
android:theme="@style/Theme.AppCompat">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.BROWSABLE"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,92 @@
/*
* 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.castdemo;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.gms.cast.MediaInfo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Utility methods and constants for the Cast demo application.
*/
/* package */ final class CastDemoUtil {
public static final String MIME_TYPE_DASH = "application/dash+xml";
public static final String MIME_TYPE_HLS = "application/vnd.apple.mpegurl";
public static final String MIME_TYPE_SS = "application/vnd.ms-sstr+xml";
public static final String MIME_TYPE_VIDEO_MP4 = MimeTypes.VIDEO_MP4;
/**
* The list of samples available in the cast demo app.
*/
public static final List<Sample> SAMPLES;
/**
* Represents a media sample.
*/
public static final class Sample {
/**
* The uri from which the media sample is obtained.
*/
public final String uri;
/**
* A descriptive name for the sample.
*/
public final String name;
/**
* The mime type of the media sample, as required by {@link MediaInfo#setContentType}.
*/
public final String type;
/**
* @param uri See {@link #uri}.
* @param name See {@link #name}.
* @param type See {@link #type}.
*/
public Sample(String uri, String name, String type) {
this.uri = uri;
this.name = name;
this.type = type;
}
@Override
public String toString() {
return name;
}
}
static {
// App samples.
ArrayList<Sample> samples = new ArrayList<>();
samples.add(new Sample("https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd",
"DASH (clear,MP4,H264)", MIME_TYPE_DASH));
samples.add(new Sample("https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/"
+ "hls/TearsOfSteel.m3u8", "Tears of Steel (HLS)", MIME_TYPE_HLS));
samples.add(new Sample("https://html5demos.com/assets/dizzy.mp4", "Dizzy (MP4)",
MIME_TYPE_VIDEO_MP4));
SAMPLES = Collections.unmodifiableList(samples);
}
private CastDemoUtil() {}
}

View File

@ -0,0 +1,152 @@
/*
* 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.castdemo;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.ext.cast.CastPlayer;
import com.google.android.exoplayer2.ui.PlaybackControlView;
import com.google.android.exoplayer2.ui.SimpleExoPlayerView;
import com.google.android.exoplayer2.util.Util;
import com.google.android.gms.cast.framework.CastButtonFactory;
/**
* An activity that plays video using {@link SimpleExoPlayer} and {@link CastPlayer}.
*/
public class MainActivity extends AppCompatActivity {
private SimpleExoPlayerView simpleExoPlayerView;
private PlaybackControlView castControlView;
private PlayerManager playerManager;
// Activity lifecycle methods.
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
simpleExoPlayerView = (SimpleExoPlayerView) findViewById(R.id.player_view);
simpleExoPlayerView.requestFocus();
castControlView = (PlaybackControlView) findViewById(R.id.cast_control_view);
ListView sampleList = (ListView) findViewById(R.id.sample_list);
sampleList.setAdapter(new SampleListAdapter());
sampleList.setOnItemClickListener(new SampleClickListener());
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.menu, menu);
CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), menu,
R.id.media_route_menu_item);
return true;
}
@Override
public void onStart() {
super.onStart();
if (Util.SDK_INT > 23) {
setupPlayerManager();
}
}
@Override
public void onResume() {
super.onResume();
if ((Util.SDK_INT <= 23)) {
setupPlayerManager();
}
}
@Override
public void onPause() {
super.onPause();
if (Util.SDK_INT <= 23) {
releasePlayerManager();
}
}
@Override
public void onStop() {
super.onStop();
if (Util.SDK_INT > 23) {
releasePlayerManager();
}
}
// Activity input.
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
// If the event was not handled then see if the player view can handle it.
return super.dispatchKeyEvent(event) || playerManager.dispatchKeyEvent(event);
}
// Internal methods.
private void setupPlayerManager() {
playerManager = new PlayerManager(simpleExoPlayerView, castControlView,
getApplicationContext());
}
private void releasePlayerManager() {
playerManager.release();
playerManager = null;
}
// User controls.
private final class SampleListAdapter extends ArrayAdapter<CastDemoUtil.Sample> {
public SampleListAdapter() {
super(getApplicationContext(), android.R.layout.simple_list_item_1, CastDemoUtil.SAMPLES);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
view.setBackgroundColor(Color.WHITE);
return view;
}
}
private class SampleClickListener implements AdapterView.OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (parent.getSelectedItemPosition() != position) {
CastDemoUtil.Sample currentSample = CastDemoUtil.SAMPLES.get(position);
playerManager.setCurrentSample(currentSample, 0, true);
}
}
}
}

View File

@ -0,0 +1,196 @@
/*
* 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.castdemo;
import android.content.Context;
import android.net.Uri;
import android.view.KeyEvent;
import android.view.View;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.ext.cast.CastPlayer;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.dash.DashMediaSource;
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource;
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.ui.PlaybackControlView;
import com.google.android.exoplayer2.ui.SimpleExoPlayerView;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.gms.cast.framework.CastContext;
/**
* Manages players for the ExoPlayer/Cast integration app.
*/
/* package */ final class PlayerManager implements CastPlayer.SessionAvailabilityListener {
private static final int PLAYBACK_REMOTE = 1;
private static final int PLAYBACK_LOCAL = 2;
private static final String USER_AGENT = "ExoCastDemoPlayer";
private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter();
private static final DefaultHttpDataSourceFactory DATA_SOURCE_FACTORY =
new DefaultHttpDataSourceFactory(USER_AGENT, BANDWIDTH_METER);
private final SimpleExoPlayerView exoPlayerView;
private final PlaybackControlView castControlView;
private final CastContext castContext;
private final SimpleExoPlayer exoPlayer;
private final CastPlayer castPlayer;
private int playbackLocation;
private CastDemoUtil.Sample currentSample;
/**
* @param exoPlayerView The {@link SimpleExoPlayerView} for local playback.
* @param castControlView The {@link PlaybackControlView} to control remote playback.
* @param context A {@link Context}.
*/
public PlayerManager(SimpleExoPlayerView exoPlayerView, PlaybackControlView castControlView,
Context context) {
this.exoPlayerView = exoPlayerView;
this.castControlView = castControlView;
castContext = CastContext.getSharedInstance(context);
DefaultTrackSelector trackSelector = new DefaultTrackSelector(BANDWIDTH_METER);
RenderersFactory renderersFactory = new DefaultRenderersFactory(context, null);
exoPlayer = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector);
exoPlayerView.setPlayer(exoPlayer);
castPlayer = new CastPlayer(castContext);
castPlayer.setSessionAvailabilityListener(this);
castControlView.setPlayer(castPlayer);
setPlaybackLocation(castPlayer.isCastSessionAvailable() ? PLAYBACK_REMOTE : PLAYBACK_LOCAL);
}
/**
* Starts playback of the given sample at the given position.
*
* @param currentSample The {@link CastDemoUtil} to play.
* @param positionMs The position at which playback should start.
* @param playWhenReady Whether the player should proceed when ready to do so.
*/
public void setCurrentSample(CastDemoUtil.Sample currentSample, long positionMs,
boolean playWhenReady) {
this.currentSample = currentSample;
if (playbackLocation == PLAYBACK_REMOTE) {
castPlayer.load(currentSample.name, currentSample.uri, currentSample.type, positionMs,
playWhenReady);
} else /* playbackLocation == PLAYBACK_LOCAL */ {
exoPlayer.setPlayWhenReady(playWhenReady);
exoPlayer.seekTo(positionMs);
exoPlayer.prepare(buildMediaSource(currentSample), true, true);
}
}
/**
* Dispatches a given {@link KeyEvent} to whichever view corresponds according to the current
* playback location.
*
* @param event The {@link KeyEvent}.
* @return Whether the event was handled by the target view.
*/
public boolean dispatchKeyEvent(KeyEvent event) {
if (playbackLocation == PLAYBACK_REMOTE) {
return castControlView.dispatchKeyEvent(event);
} else /* playbackLocation == PLAYBACK_REMOTE */ {
return exoPlayerView.dispatchKeyEvent(event);
}
}
/**
* Releases the manager and the players that it holds.
*/
public void release() {
castPlayer.setSessionAvailabilityListener(null);
castPlayer.release();
exoPlayerView.setPlayer(null);
exoPlayer.release();
}
// CastPlayer.SessionAvailabilityListener implementation.
@Override
public void onCastSessionAvailable() {
setPlaybackLocation(PLAYBACK_REMOTE);
}
@Override
public void onCastSessionUnavailable() {
setPlaybackLocation(PLAYBACK_LOCAL);
}
// Internal methods.
private static MediaSource buildMediaSource(CastDemoUtil.Sample sample) {
Uri uri = Uri.parse(sample.uri);
switch (sample.type) {
case CastDemoUtil.MIME_TYPE_SS:
return new SsMediaSource(uri, DATA_SOURCE_FACTORY,
new DefaultSsChunkSource.Factory(DATA_SOURCE_FACTORY), null, null);
case CastDemoUtil.MIME_TYPE_DASH:
return new DashMediaSource(uri, DATA_SOURCE_FACTORY,
new DefaultDashChunkSource.Factory(DATA_SOURCE_FACTORY), null, null);
case CastDemoUtil.MIME_TYPE_HLS:
return new HlsMediaSource(uri, DATA_SOURCE_FACTORY, null, null);
case CastDemoUtil.MIME_TYPE_VIDEO_MP4:
return new ExtractorMediaSource(uri, DATA_SOURCE_FACTORY, new DefaultExtractorsFactory(),
null, null);
default: {
throw new IllegalStateException("Unsupported type: " + sample.type);
}
}
}
private void setPlaybackLocation(int playbackLocation) {
if (this.playbackLocation == playbackLocation) {
return;
}
// View management.
if (playbackLocation == PLAYBACK_LOCAL) {
exoPlayerView.setVisibility(View.VISIBLE);
castControlView.hide();
} else {
exoPlayerView.setVisibility(View.GONE);
castControlView.show();
}
long playbackPositionMs = 0;
boolean playWhenReady = true;
if (exoPlayer != null) {
playbackPositionMs = exoPlayer.getCurrentPosition();
playWhenReady = exoPlayer.getPlayWhenReady();
} else if (this.playbackLocation == PLAYBACK_REMOTE) {
playbackPositionMs = castPlayer.getCurrentPosition();
playWhenReady = castPlayer.getPlayWhenReady();
}
this.playbackLocation = playbackLocation;
if (currentSample != null) {
setCurrentSample(currentSample, playbackPositionMs, playWhenReady);
}
}
}

View File

@ -0,0 +1,41 @@
<?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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true">
<com.google.android.exoplayer2.ui.SimpleExoPlayerView android:id="@+id/player_view"
android:layout_width="match_parent"
app:repeat_toggle_modes="all|one"
android:layout_height="0dp"
android:layout_weight="12" />
<ListView
android:id="@+id/sample_list"
android:choiceMode="singleChoice"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="12" />
<com.google.android.exoplayer2.ui.PlaybackControlView
android:id="@+id/cast_control_view"
android:layout_width="match_parent"
android:layout_height="0dp"
app:show_timeout="-1"
android:layout_weight="2"
android:visibility="gone"/>
</LinearLayout>

View File

@ -0,0 +1,25 @@
<?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.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"
app:showAsAction="always" />
</menu>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,25 @@
<?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">ExoCast Demo</string>
<string name="media_route_menu_title">ExoCast</string>
<string name="error_unsupported_drm">DRM scheme not supported by this device.</string>
</resources>

View File

@ -0,0 +1,22 @@
<?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 xmlns:android="http://schemas.android.com/apk/res/android">
<style name="PlayerTheme" parent="android:Theme.Holo">
<item name="android:windowBackground">@android:color/black</item>
</style>
</resources>

View File

@ -1,5 +1,5 @@
# Demo application #
# ExoPlayer main demo #
This folder contains a demo application that uses ExoPlayer to play a number
This is the main ExoPlayer demo application. It uses ExoPlayer to play a number
of test streams. It can be used as a starting point or reference project when
developing other applications that make use of the ExoPlayer library.

View File

@ -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 {
@ -39,9 +39,15 @@ android {
disable 'MissingTranslation'
}
flavorDimensions "extensions"
productFlavors {
noExtensions
withExtensions
noExtensions {
dimension "extensions"
}
withExtensions {
dimension "extensions"
}
}
}

View File

@ -23,7 +23,7 @@
<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"

View File

@ -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;
@ -100,6 +99,11 @@ import java.util.Locale;
Log.d(TAG, "repeatMode [" + getRepeatModeString(repeatMode) + "]");
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
Log.d(TAG, "shuffleModeEnabled [" + shuffleModeEnabled + "]");
}
@Override
public void onPositionDiscontinuity() {
Log.d(TAG, "positionDiscontinuity");
@ -205,7 +209,7 @@ import java.util.Locale;
Log.d(TAG, "]");
}
// MetadataRenderer.Output
// MetadataOutput
@Override
public void onMetadata(Metadata metadata) {
@ -431,6 +435,8 @@ import java.util.Locale;
return "YES";
case RendererCapabilities.FORMAT_EXCEEDS_CAPABILITIES:
return "NO_EXCEEDS_CAPABILITIES";
case RendererCapabilities.FORMAT_UNSUPPORTED_DRM:
return "NO_UNSUPPORTED_DRM";
case RendererCapabilities.FORMAT_UNSUPPORTED_SUBTYPE:
return "NO_UNSUPPORTED_TYPE";
case RendererCapabilities.FORMAT_UNSUPPORTED_TYPE:

View File

@ -294,9 +294,9 @@ public class PlayerActivity extends Activity implements OnClickListener, EventLi
player = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector);
player.addListener(this);
player.addListener(eventLogger);
player.addMetadataOutput(eventLogger);
player.setAudioDebugListener(eventLogger);
player.setVideoDebugListener(eventLogger);
player.setMetadataOutput(eventLogger);
simpleExoPlayerView.setPlayer(player);
player.setPlayWhenReady(shouldAutoPlay);
@ -501,6 +501,11 @@ public class PlayerActivity extends Activity implements OnClickListener, EventLi
// Do nothing.
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
// Do nothing.
}
@Override
public void onPositionDiscontinuity() {
if (inErrorState) {

View File

@ -271,7 +271,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 {

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

5
extensions/README.md Normal file
View File

@ -0,0 +1,5 @@
# ExoPlayer extensions #
ExoPlayer extensions are modules that depend on external libraries to provide
additional functionality. Browse the individual extensions and their READMEs to
learn more.

33
extensions/cast/README.md Normal file
View File

@ -0,0 +1,33 @@
# ExoPlayer Cast extension #
## Description ##
The cast extension is a [Player][] implementation that controls playback on a
Cast receiver app.
[Player]: https://google.github.io/ExoPlayer/doc/reference/index.html?com/google/android/exoplayer2/Player.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-cast: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
## Using the extension ##
Create a `CastPlayer` and use it to integrate Cast into your app using
ExoPlayer's common Player interface. You can try the Cast Extension to see how a
[PlaybackControlView][] can be used to control playback in a remote receiver app.
[PlaybackControlView]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/ui/PlaybackControlView.html

View File

@ -0,0 +1,45 @@
// 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 14
targetSdkVersion project.ext.targetSdkVersion
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
dependencies {
compile 'com.android.support:appcompat-v7:' + supportLibraryVersion
compile 'com.android.support:mediarouter-v7:' + supportLibraryVersion
compile 'com.google.android.gms:play-services-cast-framework:' + playServicesLibraryVersion
compile project(modulePrefix + 'library-core')
compile project(modulePrefix + 'library-ui')
}
ext {
javadocTitle = 'Cast extension'
}
apply from: '../../javadoc_library.gradle'
ext {
releaseArtifact = 'extension-cast'
releaseDescription = 'Cast extension for ExoPlayer.'
}
apply from: '../../publish.gradle'

View File

@ -0,0 +1,16 @@
<?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.cast"/>

View File

@ -0,0 +1,660 @@
/*
* 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.cast;
import android.support.annotation.Nullable;
import android.util.Log;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import com.google.android.gms.cast.CastStatusCodes;
import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaMetadata;
import com.google.android.gms.cast.MediaStatus;
import com.google.android.gms.cast.MediaTrack;
import com.google.android.gms.cast.framework.CastContext;
import com.google.android.gms.cast.framework.CastSession;
import com.google.android.gms.cast.framework.SessionManager;
import com.google.android.gms.cast.framework.SessionManagerListener;
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
import com.google.android.gms.cast.framework.media.RemoteMediaClient.MediaChannelResult;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.common.api.ResultCallback;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* {@link Player} implementation that communicates with a Cast receiver app.
*
* <p>Calls to the methods in this class depend on the availability of an underlying cast session.
* If no session is available, method calls have no effect. To keep track of the underyling session,
* {@link #isCastSessionAvailable()} can be queried and {@link SessionAvailabilityListener} can be
* implemented and attached to the player.
*
* <p>Methods should be called on the application's main thread.
*
* <p>Known issues:
* <ul>
* <li>Part of the Cast API is not exposed through this interface. For instance, volume settings
* and track selection.</li>
* <li> Repeat mode is not working. See [internal: b/64137174].</li>
* </ul>
*/
public final class CastPlayer implements Player {
/**
* Listener of changes in the cast session availability.
*/
public interface SessionAvailabilityListener {
/**
* Called when a cast session becomes available to the player.
*/
void onCastSessionAvailable();
/**
* Called when the cast session becomes unavailable.
*/
void onCastSessionUnavailable();
}
private static final String TAG = "CastPlayer";
private static final int RENDERER_COUNT = 3;
private static final int RENDERER_INDEX_VIDEO = 0;
private static final int RENDERER_INDEX_AUDIO = 1;
private static final int RENDERER_INDEX_TEXT = 2;
private static final long PROGRESS_REPORT_PERIOD_MS = 1000;
private static final TrackGroupArray EMPTY_TRACK_GROUP_ARRAY = new TrackGroupArray();
private static final TrackSelectionArray EMPTY_TRACK_SELECTION_ARRAY =
new TrackSelectionArray(null, null, null);
private static final long[] EMPTY_TRACK_ID_ARRAY = new long[0];
private final CastContext castContext;
private final Timeline.Window window;
// Result callbacks.
private final StatusListener statusListener;
private final RepeatModeResultCallback repeatModeResultCallback;
private final SeekResultCallback seekResultCallback;
// Listeners.
private final CopyOnWriteArraySet<EventListener> listeners;
private SessionAvailabilityListener sessionAvailabilityListener;
// Internal state.
private RemoteMediaClient remoteMediaClient;
private Timeline currentTimeline;
private TrackGroupArray currentTrackGroups;
private TrackSelectionArray currentTrackSelection;
private long lastReportedPositionMs;
private long pendingSeekPositionMs;
/**
* @param castContext The context from which the cast session is obtained.
*/
public CastPlayer(CastContext castContext) {
this.castContext = castContext;
window = new Timeline.Window();
statusListener = new StatusListener();
repeatModeResultCallback = new RepeatModeResultCallback();
seekResultCallback = new SeekResultCallback();
listeners = new CopyOnWriteArraySet<>();
SessionManager sessionManager = castContext.getSessionManager();
sessionManager.addSessionManagerListener(statusListener, CastSession.class);
CastSession session = sessionManager.getCurrentCastSession();
remoteMediaClient = session != null ? session.getRemoteMediaClient() : null;
pendingSeekPositionMs = C.TIME_UNSET;
updateInternalState();
}
/**
* Loads media into the receiver app.
*
* @param title The title of the media sample.
* @param url The url from which the media is obtained.
* @param contentMimeType The mime type of the content to play.
* @param positionMs The position at which the playback should start in milliseconds.
* @param playWhenReady Whether the player should start playback as soon as it is ready to do so.
*/
public void load(String title, String url, String contentMimeType, long positionMs,
boolean playWhenReady) {
lastReportedPositionMs = 0;
if (remoteMediaClient != null) {
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
movieMetadata.putString(MediaMetadata.KEY_TITLE, title);
MediaInfo mediaInfo = new MediaInfo.Builder(url).setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setContentType(contentMimeType).setMetadata(movieMetadata).build();
remoteMediaClient.load(mediaInfo, playWhenReady, positionMs);
}
}
/**
* Returns whether a cast session is available for playback.
*/
public boolean isCastSessionAvailable() {
return remoteMediaClient != null;
}
/**
* Sets a listener for updates on the cast session availability.
*
* @param listener The {@link SessionAvailabilityListener}.
*/
public void setSessionAvailabilityListener(SessionAvailabilityListener listener) {
sessionAvailabilityListener = listener;
}
// Player implementation.
@Override
public void addListener(EventListener listener) {
listeners.add(listener);
}
@Override
public void removeListener(EventListener listener) {
listeners.remove(listener);
}
@Override
public int getPlaybackState() {
if (remoteMediaClient == null) {
return STATE_IDLE;
}
int receiverAppStatus = remoteMediaClient.getPlayerState();
switch (receiverAppStatus) {
case MediaStatus.PLAYER_STATE_BUFFERING:
return STATE_BUFFERING;
case MediaStatus.PLAYER_STATE_PLAYING:
case MediaStatus.PLAYER_STATE_PAUSED:
return STATE_READY;
case MediaStatus.PLAYER_STATE_IDLE:
case MediaStatus.PLAYER_STATE_UNKNOWN:
default:
return STATE_IDLE;
}
}
@Override
public void setPlayWhenReady(boolean playWhenReady) {
if (remoteMediaClient == null) {
return;
}
if (playWhenReady) {
remoteMediaClient.play();
} else {
remoteMediaClient.pause();
}
}
@Override
public boolean getPlayWhenReady() {
return remoteMediaClient != null && !remoteMediaClient.isPaused();
}
@Override
public void seekToDefaultPosition() {
seekTo(0);
}
@Override
public void seekToDefaultPosition(int windowIndex) {
seekTo(windowIndex, 0);
}
@Override
public void seekTo(long positionMs) {
seekTo(0, positionMs);
}
@Override
public void seekTo(int windowIndex, long positionMs) {
if (remoteMediaClient != null) {
remoteMediaClient.seek(positionMs).setResultCallback(seekResultCallback);
pendingSeekPositionMs = positionMs;
for (EventListener listener : listeners) {
listener.onPositionDiscontinuity();
}
}
}
@Override
public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) {
// Unsupported by the RemoteMediaClient API. Do nothing.
}
@Override
public PlaybackParameters getPlaybackParameters() {
return PlaybackParameters.DEFAULT;
}
@Override
public void stop() {
if (remoteMediaClient != null) {
remoteMediaClient.stop();
}
}
@Override
public void release() {
castContext.getSessionManager().removeSessionManagerListener(statusListener, CastSession.class);
}
@Override
public int getRendererCount() {
// We assume there are three renderers: video, audio, and text.
return RENDERER_COUNT;
}
@Override
public int getRendererType(int index) {
switch (index) {
case RENDERER_INDEX_VIDEO:
return C.TRACK_TYPE_VIDEO;
case RENDERER_INDEX_AUDIO:
return C.TRACK_TYPE_AUDIO;
case RENDERER_INDEX_TEXT:
return C.TRACK_TYPE_TEXT;
default:
throw new IndexOutOfBoundsException();
}
}
@Override
public void setRepeatMode(@RepeatMode int repeatMode) {
if (remoteMediaClient != null) {
int castRepeatMode;
switch (repeatMode) {
case REPEAT_MODE_ONE:
castRepeatMode = MediaStatus.REPEAT_MODE_REPEAT_SINGLE;
break;
case REPEAT_MODE_ALL:
castRepeatMode = MediaStatus.REPEAT_MODE_REPEAT_ALL;
break;
case REPEAT_MODE_OFF:
castRepeatMode = MediaStatus.REPEAT_MODE_REPEAT_OFF;
break;
default:
throw new IllegalArgumentException();
}
remoteMediaClient.queueSetRepeatMode(castRepeatMode, null)
.setResultCallback(repeatModeResultCallback);
}
}
@Override
@RepeatMode public int getRepeatMode() {
if (remoteMediaClient == null) {
return REPEAT_MODE_OFF;
}
MediaStatus mediaStatus = getMediaStatus();
if (mediaStatus == null) {
// No media session active, yet.
return REPEAT_MODE_OFF;
}
int castRepeatMode = mediaStatus.getQueueRepeatMode();
switch (castRepeatMode) {
case MediaStatus.REPEAT_MODE_REPEAT_SINGLE:
return REPEAT_MODE_ONE;
case MediaStatus.REPEAT_MODE_REPEAT_ALL:
case MediaStatus.REPEAT_MODE_REPEAT_ALL_AND_SHUFFLE:
return REPEAT_MODE_ALL;
case MediaStatus.REPEAT_MODE_REPEAT_OFF:
return REPEAT_MODE_OFF;
default:
throw new IllegalStateException();
}
}
@Override
public void setShuffleModeEnabled(boolean shuffleModeEnabled) {
// TODO: Support shuffle mode.
}
@Override
public boolean getShuffleModeEnabled() {
// TODO: Support shuffle mode.
return false;
}
@Override
public TrackSelectionArray getCurrentTrackSelections() {
return currentTrackSelection;
}
@Override
public TrackGroupArray getCurrentTrackGroups() {
return currentTrackGroups;
}
@Override
public Timeline getCurrentTimeline() {
return currentTimeline;
}
@Override
@Nullable public Object getCurrentManifest() {
return null;
}
@Override
public int getCurrentPeriodIndex() {
return 0;
}
@Override
public int getCurrentWindowIndex() {
return 0;
}
@Override
public long getDuration() {
return currentTimeline.isEmpty() ? C.TIME_UNSET
: currentTimeline.getWindow(0, window).getDurationMs();
}
@Override
public long getCurrentPosition() {
return remoteMediaClient == null ? lastReportedPositionMs
: pendingSeekPositionMs != C.TIME_UNSET ? pendingSeekPositionMs
: remoteMediaClient.getApproximateStreamPosition();
}
@Override
public long getBufferedPosition() {
return getCurrentPosition();
}
@Override
public int getBufferedPercentage() {
long position = getBufferedPosition();
long duration = getDuration();
return position == C.TIME_UNSET || duration == C.TIME_UNSET ? 0
: duration == 0 ? 100
: Util.constrainValue((int) ((position * 100) / duration), 0, 100);
}
@Override
public boolean isCurrentWindowDynamic() {
return !currentTimeline.isEmpty()
&& currentTimeline.getWindow(getCurrentWindowIndex(), window).isDynamic;
}
@Override
public boolean isCurrentWindowSeekable() {
return !currentTimeline.isEmpty()
&& currentTimeline.getWindow(getCurrentWindowIndex(), window).isSeekable;
}
@Override
public boolean isPlayingAd() {
return false;
}
@Override
public int getCurrentAdGroupIndex() {
return C.INDEX_UNSET;
}
@Override
public int getCurrentAdIndexInAdGroup() {
return C.INDEX_UNSET;
}
@Override
public boolean isLoading() {
return false;
}
@Override
public long getContentPosition() {
return getCurrentPosition();
}
// Internal methods.
private void setRemoteMediaClient(@Nullable RemoteMediaClient remoteMediaClient) {
if (this.remoteMediaClient == remoteMediaClient) {
// Do nothing.
return;
}
if (this.remoteMediaClient != null) {
this.remoteMediaClient.removeListener(statusListener);
this.remoteMediaClient.removeProgressListener(statusListener);
}
this.remoteMediaClient = remoteMediaClient;
if (remoteMediaClient != null) {
if (sessionAvailabilityListener != null) {
sessionAvailabilityListener.onCastSessionAvailable();
}
remoteMediaClient.addListener(statusListener);
remoteMediaClient.addProgressListener(statusListener, PROGRESS_REPORT_PERIOD_MS);
} else {
if (sessionAvailabilityListener != null) {
sessionAvailabilityListener.onCastSessionUnavailable();
}
}
}
private @Nullable MediaStatus getMediaStatus() {
return remoteMediaClient != null ? remoteMediaClient.getMediaStatus() : null;
}
private @Nullable MediaInfo getMediaInfo() {
return remoteMediaClient != null ? remoteMediaClient.getMediaInfo() : null;
}
private void updateInternalState() {
currentTimeline = Timeline.EMPTY;
currentTrackGroups = EMPTY_TRACK_GROUP_ARRAY;
currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY;
MediaInfo mediaInfo = getMediaInfo();
if (mediaInfo == null) {
return;
}
long streamDurationMs = mediaInfo.getStreamDuration();
boolean isSeekable = streamDurationMs != MediaInfo.UNKNOWN_DURATION;
currentTimeline = new SinglePeriodTimeline(
isSeekable ? C.msToUs(streamDurationMs) : C.TIME_UNSET, isSeekable);
List<MediaTrack> tracks = mediaInfo.getMediaTracks();
if (tracks == null) {
return;
}
MediaStatus mediaStatus = getMediaStatus();
long[] activeTrackIds = mediaStatus != null ? mediaStatus.getActiveTrackIds() : null;
if (activeTrackIds == null) {
activeTrackIds = EMPTY_TRACK_ID_ARRAY;
}
TrackGroup[] trackGroups = new TrackGroup[tracks.size()];
TrackSelection[] trackSelections = new TrackSelection[RENDERER_COUNT];
for (int i = 0; i < tracks.size(); i++) {
MediaTrack mediaTrack = tracks.get(i);
trackGroups[i] = new TrackGroup(CastUtils.mediaTrackToFormat(mediaTrack));
long id = mediaTrack.getId();
int trackType = MimeTypes.getTrackType(mediaTrack.getContentType());
int rendererIndex = getRendererIndexForTrackType(trackType);
if (isTrackActive(id, activeTrackIds) && rendererIndex != C.INDEX_UNSET
&& trackSelections[rendererIndex] == null) {
trackSelections[rendererIndex] = new FixedTrackSelection(trackGroups[i], 0);
}
}
currentTrackSelection = new TrackSelectionArray(trackSelections);
currentTrackGroups = new TrackGroupArray(trackGroups);
}
private static boolean isTrackActive(long id, long[] activeTrackIds) {
for (long activeTrackId : activeTrackIds) {
if (activeTrackId == id) {
return true;
}
}
return false;
}
private static int getRendererIndexForTrackType(int trackType) {
return trackType == C.TRACK_TYPE_VIDEO ? RENDERER_INDEX_VIDEO
: trackType == C.TRACK_TYPE_AUDIO ? RENDERER_INDEX_AUDIO
: trackType == C.TRACK_TYPE_TEXT ? RENDERER_INDEX_TEXT
: C.INDEX_UNSET;
}
private final class StatusListener implements RemoteMediaClient.Listener,
SessionManagerListener<CastSession>, RemoteMediaClient.ProgressListener {
// RemoteMediaClient.ProgressListener implementation.
@Override
public void onProgressUpdated(long progressMs, long unusedDurationMs) {
lastReportedPositionMs = progressMs;
}
// RemoteMediaClient.Listener implementation.
@Override
public void onStatusUpdated() {
boolean playWhenReady = getPlayWhenReady();
int playbackState = getPlaybackState();
for (EventListener listener : listeners) {
listener.onPlayerStateChanged(playWhenReady, playbackState);
}
}
@Override
public void onMetadataUpdated() {
updateInternalState();
for (EventListener listener : listeners) {
listener.onTracksChanged(currentTrackGroups, currentTrackSelection);
listener.onTimelineChanged(currentTimeline, null);
}
}
@Override
public void onQueueStatusUpdated() {}
@Override
public void onPreloadStatusUpdated() {}
@Override
public void onSendingRemoteMediaRequest() {}
@Override
public void onAdBreakStatusUpdated() {}
// SessionManagerListener implementation.
@Override
public void onSessionStarted(CastSession castSession, String s) {
setRemoteMediaClient(castSession.getRemoteMediaClient());
}
@Override
public void onSessionResumed(CastSession castSession, boolean b) {
setRemoteMediaClient(castSession.getRemoteMediaClient());
}
@Override
public void onSessionEnded(CastSession castSession, int i) {
setRemoteMediaClient(null);
}
@Override
public void onSessionSuspended(CastSession castSession, int i) {
setRemoteMediaClient(null);
}
@Override
public void onSessionResumeFailed(CastSession castSession, int statusCode) {
Log.e(TAG, "Session resume failed. Error code " + statusCode + ": "
+ CastUtils.getLogString(statusCode));
}
@Override
public void onSessionStarting(CastSession castSession) {
// Do nothing.
}
@Override
public void onSessionStartFailed(CastSession castSession, int statusCode) {
Log.e(TAG, "Session start failed. Error code " + statusCode + ": "
+ CastUtils.getLogString(statusCode));
}
@Override
public void onSessionEnding(CastSession castSession) {
// Do nothing.
}
@Override
public void onSessionResuming(CastSession castSession, String s) {
// Do nothing.
}
}
// Result callbacks hooks.
private final class RepeatModeResultCallback implements ResultCallback<MediaChannelResult> {
@Override
public void onResult(MediaChannelResult result) {
int statusCode = result.getStatus().getStatusCode();
if (statusCode == CommonStatusCodes.SUCCESS) {
int repeatMode = getRepeatMode();
for (EventListener listener : listeners) {
listener.onRepeatModeChanged(repeatMode);
}
} else {
Log.e(TAG, "Set repeat mode failed. Error code " + statusCode + ": "
+ CastUtils.getLogString(statusCode));
}
}
}
private final class SeekResultCallback implements ResultCallback<MediaChannelResult> {
@Override
public void onResult(MediaChannelResult result) {
int statusCode = result.getStatus().getStatusCode();
if (statusCode == CommonStatusCodes.SUCCESS) {
pendingSeekPositionMs = C.TIME_UNSET;
} else if (statusCode == CastStatusCodes.REPLACED) {
// A seek was executed before this one completed. Do nothing.
} else {
Log.e(TAG, "Seek failed. Error code " + statusCode + ": "
+ CastUtils.getLogString(statusCode));
}
}
}
}

View File

@ -0,0 +1,94 @@
/*
* 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.cast;
import com.google.android.exoplayer2.Format;
import com.google.android.gms.cast.CastStatusCodes;
import com.google.android.gms.cast.MediaTrack;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Utility methods for ExoPlayer/Cast integration.
*/
/* package */ final class CastUtils {
private static final Map<Integer, String> CAST_STATUS_CODE_TO_STRING;
/**
* Returns a descriptive log string for the given {@code statusCode}, or "Unknown." if not one of
* {@link CastStatusCodes}.
*
* @param statusCode A Cast API status code.
* @return A descriptive log string for the given {@code statusCode}, or "Unknown." if not one of
* {@link CastStatusCodes}.
*/
public static String getLogString(int statusCode) {
String description = CAST_STATUS_CODE_TO_STRING.get(statusCode);
return description != null ? description : "Unknown.";
}
/**
* Creates a {@link Format} instance containing all information contained in the given
* {@link MediaTrack} object.
*
* @param mediaTrack The {@link MediaTrack}.
* @return The equivalent {@link Format}.
*/
public static Format mediaTrackToFormat(MediaTrack mediaTrack) {
return Format.createContainerFormat(mediaTrack.getContentId(), mediaTrack.getContentType(),
null, null, Format.NO_VALUE, 0, mediaTrack.getLanguage());
}
static {
HashMap<Integer, String> statusCodeToString = new HashMap<>();
statusCodeToString.put(CastStatusCodes.APPLICATION_NOT_FOUND,
"A requested application could not be found.");
statusCodeToString.put(CastStatusCodes.APPLICATION_NOT_RUNNING,
"A requested application is not currently running.");
statusCodeToString.put(CastStatusCodes.AUTHENTICATION_FAILED, "Authentication failure.");
statusCodeToString.put(CastStatusCodes.CANCELED, "An in-progress request has been "
+ "canceled, most likely because another action has preempted it.");
statusCodeToString.put(CastStatusCodes.ERROR_SERVICE_CREATION_FAILED,
"The Cast Remote Display service could not be created.");
statusCodeToString.put(CastStatusCodes.ERROR_SERVICE_DISCONNECTED,
"The Cast Remote Display service was disconnected.");
statusCodeToString.put(CastStatusCodes.FAILED, "The in-progress request failed.");
statusCodeToString.put(CastStatusCodes.INTERNAL_ERROR, "An internal error has occurred.");
statusCodeToString.put(CastStatusCodes.INTERRUPTED,
"A blocking call was interrupted while waiting and did not run to completion.");
statusCodeToString.put(CastStatusCodes.INVALID_REQUEST, "An invalid request was made.");
statusCodeToString.put(CastStatusCodes.MESSAGE_SEND_BUFFER_TOO_FULL, "A message could "
+ "not be sent because there is not enough room in the send buffer at this time.");
statusCodeToString.put(CastStatusCodes.MESSAGE_TOO_LARGE,
"A message could not be sent because it is too large.");
statusCodeToString.put(CastStatusCodes.NETWORK_ERROR, "Network I/O error.");
statusCodeToString.put(CastStatusCodes.NOT_ALLOWED,
"The request was disallowed and could not be completed.");
statusCodeToString.put(CastStatusCodes.REPLACED,
"The request's progress is no longer being tracked because another request of the same type"
+ " has been made before the first request completed.");
statusCodeToString.put(CastStatusCodes.SUCCESS, "Success.");
statusCodeToString.put(CastStatusCodes.TIMEOUT, "An operation has timed out.");
statusCodeToString.put(CastStatusCodes.UNKNOWN_ERROR,
"An unknown, unexpected error has occurred.");
CAST_STATUS_CODE_TO_STRING = Collections.unmodifiableMap(statusCodeToString);
}
private CastUtils() {}
}

View File

@ -0,0 +1,42 @@
/*
* 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.cast;
import android.content.Context;
import com.google.android.gms.cast.CastMediaControlIntent;
import com.google.android.gms.cast.framework.CastOptions;
import com.google.android.gms.cast.framework.OptionsProvider;
import com.google.android.gms.cast.framework.SessionProvider;
import java.util.List;
/**
* A convenience {@link OptionsProvider} to target the default cast receiver app.
*/
public final class DefaultCastOptionsProvider implements OptionsProvider {
@Override
public CastOptions getCastOptions(Context context) {
return new CastOptions.Builder()
.setReceiverApplicationId(CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID)
.setStopReceiverApplicationWhenEndingSession(true).build();
}
@Override
public List<SessionProvider> getAdditionalSessionProviders(Context context) {
return null;
}
}

View File

@ -1,10 +1,8 @@
# ExoPlayer Cronet extension #
## Description ##
The Cronet extension is an [HttpDataSource][] implementation using [Cronet][].
[HttpDataSource]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer/upstream/HttpDataSource.html
[HttpDataSource]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/upstream/HttpDataSource.html
[Cronet]: https://chromium.googlesource.com/chromium/src/+/master/components/cronet?autodive=0%2F%2F
## Build instructions ##
@ -22,12 +20,9 @@ and enable the extension:
1. Copy the content of the downloaded `libs` directory into the `jniLibs`
directory of this extension
* In your `settings.gradle` file, add the following line before the line that
applies `core_settings.gradle`:
```gradle
gradle.ext.exoplayerIncludeCronetExtension = true;
```
* In your `settings.gradle` file, add
`gradle.ext.exoplayerIncludeCronetExtension = true` before the line that
applies `core_settings.gradle`.
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
[here]: https://console.cloud.google.com/storage/browser/chromium-cronet/android
@ -56,3 +51,10 @@ new DefaultDataSourceFactory(
new CronetDataSourceFactory(...) /* baseDataSourceFactory argument */);
```
respectively.
## Links ##
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.cronet.*`
belong to this module.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html

View File

@ -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"

View File

@ -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);

View File

@ -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;

View File

@ -1,18 +1,17 @@
# ExoPlayer FFmpeg extension #
## Description ##
The FFmpeg extension is a [Renderer][] implementation that uses FFmpeg to decode
audio.
[Renderer]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/Renderer.html
The FFmpeg extension provides `FfmpegAudioRenderer`, which uses FFmpeg for
decoding and can render audio encoded in a variety of formats.
## Build instructions ##
To use this extension you need to clone the ExoPlayer repository and depend on
its modules locally. Instructions for doing this can be found in ExoPlayer's
[top level README][]. In addition, it's necessary to build the extension's
native components as follows:
[top level README][]. The extension is not provided via JCenter (see [#2781][]
for more information).
In addition, it's necessary to build the extension's native components as
follows:
* Set the following environment variables:
@ -34,7 +33,11 @@ NDK_PATH="<path to Android NDK>"
HOST_PLATFORM="linux-x86_64"
```
* Fetch and build FFmpeg. For example, to fetch and build for armeabi-v7a,
* Fetch and build FFmpeg. The configuration flags determine which formats will
be supported. See the [Supported formats][] page for more details of the
available flags.
For example, to fetch and build for armeabi-v7a,
arm64-v8a and x86 on Linux x86_64:
```
@ -103,5 +106,42 @@ cd "${FFMPEG_EXT_PATH}"/jni && \
${NDK_PATH}/ndk-build APP_ABI="armeabi-v7a arm64-v8a x86" -j4
```
## Using the extension ##
Once you've followed the instructions above to check out, build and depend on
the extension, the next step is to tell ExoPlayer to use `FfmpegAudioRenderer`.
How you do this depends on which player API you're using:
* If you're passing a `DefaultRenderersFactory` to
`ExoPlayerFactory.newSimpleInstance`, you can enable using the extension by
setting the `extensionRendererMode` parameter of the `DefaultRenderersFactory`
constructor to `EXTENSION_RENDERER_MODE_ON`. This will use
`FfmpegAudioRenderer` for playback if `MediaCodecAudioRenderer` doesn't
support the input format. Pass `EXTENSION_RENDERER_MODE_PREFER` to give
`FfmpegAudioRenderer` priority over `MediaCodecAudioRenderer`.
* If you've subclassed `DefaultRenderersFactory`, add an `FfmpegAudioRenderer`
to the output list in `buildAudioRenderers`. ExoPlayer will use the first
`Renderer` in the list that supports the input media format.
* If you've implemented your own `RenderersFactory`, return an
`FfmpegAudioRenderer` instance from `createRenderers`. ExoPlayer will use the
first `Renderer` in the returned array that supports the input media format.
* If you're using `ExoPlayerFactory.newInstance`, pass an `FfmpegAudioRenderer`
in the array of `Renderer`s. ExoPlayer will use the first `Renderer` in the
list that supports the input media format.
Note: These instructions assume you're using `DefaultTrackSelector`. If you have
a custom track selector the choice of `Renderer` is up to your implementation,
so you need to make sure you are passing an `FfmpegAudioRenderer` to the player,
then implement your own logic to use the renderer for a given track.
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
[Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
[#2781]: https://github.com/google/ExoPlayer/issues/2781
[Supported formats]: https://google.github.io/ExoPlayer/supported-formats.html#ffmpeg-extension
## Links ##
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.ffmpeg.*`
belong to this module.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html

View File

@ -1,19 +1,16 @@
# ExoPlayer Flac extension #
## Description ##
The Flac extension is a [Renderer][] implementation that helps you bundle
libFLAC (the Flac decoding library) into your app and use it along with
ExoPlayer to play Flac audio on Android devices.
[Renderer]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/Renderer.html
The Flac extension provides `FlacExtractor` and `LibflacAudioRenderer`, which
use libFLAC (the Flac decoding library) to extract and decode FLAC audio.
## Build instructions ##
To use this extension you need to clone the ExoPlayer repository and depend on
its modules locally. Instructions for doing this can be found in ExoPlayer's
[top level README][]. In addition, it's necessary to build the extension's
native components as follows:
[top level README][].
In addition, it's necessary to build the extension's native components as
follows:
* Set the following environment variables:
@ -46,3 +43,47 @@ ${NDK_PATH}/ndk-build APP_ABI=all -j4
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
[Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
## Using the extension ##
Once you've followed the instructions above to check out, build and depend on
the extension, the next step is to tell ExoPlayer to use the extractor and/or
renderer.
### Using `FlacExtractor` ###
`FlacExtractor` is used via `ExtractorMediaSource`. If you're using
`DefaultExtractorsFactory`, `FlacExtractor` will automatically be used to read
`.flac` files. If you're not using `DefaultExtractorsFactory`, return a
`FlacExtractor` from your `ExtractorsFactory.createExtractors` implementation.
### Using `LibflacAudioRenderer` ###
* If you're passing a `DefaultRenderersFactory` to
`ExoPlayerFactory.newSimpleInstance`, you can enable using the extension by
setting the `extensionRendererMode` parameter of the `DefaultRenderersFactory`
constructor to `EXTENSION_RENDERER_MODE_ON`. This will use
`LibflacAudioRenderer` for playback if `MediaCodecAudioRenderer` doesn't
support the input format. Pass `EXTENSION_RENDERER_MODE_PREFER` to give
`LibflacAudioRenderer` priority over `MediaCodecAudioRenderer`.
* If you've subclassed `DefaultRenderersFactory`, add a `LibflacAudioRenderer`
to the output list in `buildAudioRenderers`. ExoPlayer will use the first
`Renderer` in the list that supports the input media format.
* If you've implemented your own `RenderersFactory`, return a
`LibflacAudioRenderer` instance from `createRenderers`. ExoPlayer will use the
first `Renderer` in the returned array that supports the input media format.
* If you're using `ExoPlayerFactory.newInstance`, pass a `LibflacAudioRenderer`
in the array of `Renderer`s. ExoPlayer will use the first `Renderer` in the
list that supports the input media format.
Note: These instructions assume you're using `DefaultTrackSelector`. If you have
a custom track selector the choice of `Renderer` is up to your implementation,
so you need to make sure you are passing an `LibflacAudioRenderer` to the
player, then implement your own logic to use the renderer for a given track.
## Links ##
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.flac.*`
belong to this module.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html

View File

@ -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"

View File

@ -132,6 +132,11 @@ public class FlacPlaybackTest extends InstrumentationTestCase {
// Do nothing.
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
// Do nothing.
}
private void releasePlayerAndQuitLooper() {
player.release();
Looper.myLooper().quit();

View File

@ -1,7 +1,5 @@
# ExoPlayer GVR extension #
## Description ##
The GVR extension wraps the [Google VR SDK for Android][]. It provides a
GvrAudioProcessor, which uses [GvrAudioSurround][] to provide binaural rendering
of surround sound and ambisonic soundfields.
@ -11,17 +9,7 @@ of surround sound and ambisonic soundfields.
## Getting the extension ##
The easiest way to use the extension is to add it as a gradle dependency. You
need to make sure you have the jcenter repository included in the `build.gradle`
file in the root of your project:
```gradle
repositories {
jcenter()
}
```
Next, include the following in your module's `build.gradle` file:
The easiest way to use the extension is to add it as a gradle dependency:
```gradle
compile 'com.google.android.exoplayer:extension-gvr:rX.X.X'
@ -36,9 +24,17 @@ locally. Instructions for doing this can be found in ExoPlayer's
## Using the extension ##
* If using SimpleExoPlayer, override SimpleExoPlayer.buildAudioProcessors to
return a GvrAudioProcessor.
* If constructing renderers directly, pass a GvrAudioProcessor to
MediaCodecAudioRenderer's constructor.
* If using `DefaultRenderersFactory`, override
`DefaultRenderersFactory.buildAudioProcessors` to return a
`GvrAudioProcessor`.
* If constructing renderers directly, pass a `GvrAudioProcessor` to
`MediaCodecAudioRenderer`'s constructor.
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
## Links ##
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.gvr.*`
belong to this module.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html

View File

@ -1,7 +1,5 @@
# ExoPlayer IMA extension #
## Description ##
The IMA extension is a [MediaSource][] implementation wrapping the
[Interactive Media Ads SDK for Android][IMA]. You can use it to insert ads
alongside content.
@ -55,3 +53,10 @@ playback.
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
[sample ad tags]: https://developers.google.com/interactive-media-ads/docs/sdks/android/tags
## Links ##
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.ima.*`
belong to this module.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html

View File

@ -22,7 +22,7 @@ dependencies {
// |-- 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

View File

@ -472,13 +472,13 @@ public final class ImaAdsLoader implements Player.EventListener, VideoAdPlayer,
}
if (!imaPlayingAd) {
imaPlayingAd = true;
for (VideoAdPlayerCallback callback : adCallbacks) {
callback.onPlay();
for (int i = 0; i < adCallbacks.size(); i++) {
adCallbacks.get(i).onPlay();
}
} else if (imaPausedInAd) {
imaPausedInAd = false;
for (VideoAdPlayerCallback callback : adCallbacks) {
callback.onResume();
for (int i = 0; i < adCallbacks.size(); i++) {
adCallbacks.get(i).onResume();
}
}
}
@ -509,8 +509,8 @@ public final class ImaAdsLoader implements Player.EventListener, VideoAdPlayer,
return;
}
imaPausedInAd = true;
for (VideoAdPlayerCallback callback : adCallbacks) {
callback.onPause();
for (int i = 0; i < adCallbacks.size(); i++) {
adCallbacks.get(i).onPause();
}
}
@ -555,8 +555,8 @@ public final class ImaAdsLoader implements Player.EventListener, VideoAdPlayer,
} else if (imaPlayingAd && playbackState == Player.STATE_ENDED) {
// IMA is waiting for the ad playback to finish so invoke the callback now.
// Either CONTENT_RESUME_REQUESTED will be passed next, or playAd will be called again.
for (VideoAdPlayerCallback callback : adCallbacks) {
callback.onEnded();
for (int i = 0; i < adCallbacks.size(); i++) {
adCallbacks.get(i).onEnded();
}
}
}
@ -566,11 +566,16 @@ public final class ImaAdsLoader implements Player.EventListener, VideoAdPlayer,
// Do nothing.
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
// Do nothing.
}
@Override
public void onPlayerError(ExoPlaybackException error) {
if (playingAd) {
for (VideoAdPlayerCallback callback : adCallbacks) {
callback.onError();
for (int i = 0; i < adCallbacks.size(); i++) {
adCallbacks.get(i).onError();
}
}
}
@ -630,8 +635,8 @@ public final class ImaAdsLoader implements Player.EventListener, VideoAdPlayer,
if (adFinished) {
// IMA is waiting for the ad playback to finish so invoke the callback now.
// Either CONTENT_RESUME_REQUESTED will be passed next, or playAd will be called again.
for (VideoAdPlayerCallback callback : adCallbacks) {
callback.onEnded();
for (int i = 0; i < adCallbacks.size(); i++) {
adCallbacks.get(i).onEnded();
}
}
if (!wasPlayingAd && playingAd) {

View File

@ -17,14 +17,14 @@ package com.google.android.exoplayer2.ext.ima;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.ForwardingTimeline;
import com.google.android.exoplayer2.util.Assertions;
/**
* A {@link Timeline} for sources that have ads.
*/
/* package */ final class SinglePeriodAdTimeline extends Timeline {
/* package */ final class SinglePeriodAdTimeline extends ForwardingTimeline {
private final Timeline contentTimeline;
private final long[] adGroupTimesUs;
private final int[] adCounts;
private final int[] adsLoadedCounts;
@ -52,9 +52,9 @@ import com.google.android.exoplayer2.util.Assertions;
public SinglePeriodAdTimeline(Timeline contentTimeline, long[] adGroupTimesUs, int[] adCounts,
int[] adsLoadedCounts, int[] adsPlayedCounts, long[][] adDurationsUs,
long adResumePositionUs) {
super(contentTimeline);
Assertions.checkState(contentTimeline.getPeriodCount() == 1);
Assertions.checkState(contentTimeline.getWindowCount() == 1);
this.contentTimeline = contentTimeline;
this.adGroupTimesUs = adGroupTimesUs;
this.adCounts = adCounts;
this.adsLoadedCounts = adsLoadedCounts;
@ -63,34 +63,13 @@ import com.google.android.exoplayer2.util.Assertions;
this.adResumePositionUs = adResumePositionUs;
}
@Override
public int getWindowCount() {
return 1;
}
@Override
public Window getWindow(int windowIndex, Window window, boolean setIds,
long defaultPositionProjectionUs) {
return contentTimeline.getWindow(windowIndex, window, setIds, defaultPositionProjectionUs);
}
@Override
public int getPeriodCount() {
return 1;
}
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
contentTimeline.getPeriod(periodIndex, period, setIds);
timeline.getPeriod(periodIndex, period, setIds);
period.set(period.id, period.uid, period.windowIndex, period.durationUs,
period.getPositionInWindowUs(), adGroupTimesUs, adCounts, adsLoadedCounts, adsPlayedCounts,
adDurationsUs, adResumePositionUs);
return period;
}
@Override
public int getIndexOfPeriod(Object uid) {
return contentTimeline.getIndexOfPeriod(uid);
}
}

View File

@ -0,0 +1,31 @@
# 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

View File

@ -0,0 +1,41 @@
// 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'

View File

@ -0,0 +1,17 @@
<?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"/>

View File

@ -0,0 +1,295 @@
/*
* 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.leanback;
import android.content.Context;
import android.os.Handler;
import android.support.v17.leanback.R;
import android.support.v17.leanback.media.PlaybackGlueHost;
import android.support.v17.leanback.media.PlayerAdapter;
import android.support.v17.leanback.media.SurfaceHolderGlueHost;
import android.util.Pair;
import android.view.Surface;
import android.view.SurfaceHolder;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.util.ErrorMessageProvider;
/**
* Leanback {@code PlayerAdapter} implementation for {@link SimpleExoPlayer}.
*/
public final class LeanbackPlayerAdapter extends PlayerAdapter {
static {
ExoPlayerLibraryInfo.registerModule("goog.exo.leanback");
}
private final Context context;
private final SimpleExoPlayer player;
private final Handler handler;
private final ComponentListener componentListener;
private final Runnable updatePlayerRunnable;
private ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
private SurfaceHolderGlueHost surfaceHolderGlueHost;
private boolean initialized;
private boolean hasSurface;
private boolean isBuffering;
/**
* Builds an instance. Note that the {@code PlayerAdapter} does not manage the lifecycle of the
* {@link SimpleExoPlayer} instance. The caller remains responsible for releasing the player when
* it's no longer required.
*
* @param context The current context (activity).
* @param player Instance of your exoplayer that needs to be configured.
* @param updatePeriodMs The delay between player control updates, in milliseconds.
*/
public LeanbackPlayerAdapter(Context context, SimpleExoPlayer player, final int updatePeriodMs) {
this.context = context;
this.player = player;
handler = new Handler();
componentListener = new ComponentListener();
updatePlayerRunnable = new Runnable() {
@Override
public void run() {
Callback callback = getCallback();
callback.onCurrentPositionChanged(LeanbackPlayerAdapter.this);
callback.onBufferedPositionChanged(LeanbackPlayerAdapter.this);
handler.postDelayed(this, updatePeriodMs);
}
};
}
@Override
public void onAttachedToHost(PlaybackGlueHost host) {
if (host instanceof SurfaceHolderGlueHost) {
surfaceHolderGlueHost = ((SurfaceHolderGlueHost) host);
surfaceHolderGlueHost.setSurfaceHolderCallback(componentListener);
}
notifyListeners();
player.addListener(componentListener);
player.addVideoListener(componentListener);
}
private void notifyListeners() {
boolean oldIsPrepared = isPrepared();
int playbackState = player.getPlaybackState();
boolean isInitialized = playbackState != Player.STATE_IDLE;
isBuffering = playbackState == Player.STATE_BUFFERING;
boolean hasEnded = playbackState == Player.STATE_ENDED;
initialized = isInitialized;
Callback callback = getCallback();
if (oldIsPrepared != isPrepared()) {
callback.onPreparedStateChanged(this);
}
callback.onPlayStateChanged(this);
callback.onBufferingStateChanged(this, isBuffering || !initialized);
if (hasEnded) {
callback.onPlayCompleted(this);
}
}
/**
* Sets the optional {@link ErrorMessageProvider}.
*
* @param errorMessageProvider The {@link ErrorMessageProvider}.
*/
public void setErrorMessageProvider(
ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider) {
this.errorMessageProvider = errorMessageProvider;
}
@Override
public void onDetachedFromHost() {
player.removeListener(componentListener);
player.removeVideoListener(componentListener);
if (surfaceHolderGlueHost != null) {
surfaceHolderGlueHost.setSurfaceHolderCallback(null);
surfaceHolderGlueHost = null;
}
initialized = false;
hasSurface = false;
Callback callback = getCallback();
callback.onBufferingStateChanged(this, false);
callback.onPlayStateChanged(this);
callback.onPreparedStateChanged(this);
}
@Override
public void setProgressUpdatingEnabled(final boolean enabled) {
handler.removeCallbacks(updatePlayerRunnable);
if (enabled) {
handler.post(updatePlayerRunnable);
}
}
@Override
public boolean isPlaying() {
return initialized && player.getPlayWhenReady();
}
@Override
public long getDuration() {
long durationMs = player.getDuration();
return durationMs != C.TIME_UNSET ? durationMs : -1;
}
@Override
public long getCurrentPosition() {
return initialized ? player.getCurrentPosition() : -1;
}
@Override
public void play() {
if (player.getPlaybackState() == Player.STATE_ENDED) {
player.seekToDefaultPosition();
}
player.setPlayWhenReady(true);
getCallback().onPlayStateChanged(this);
}
@Override
public void pause() {
player.setPlayWhenReady(false);
getCallback().onPlayStateChanged(this);
}
@Override
public void seekTo(long positionMs) {
player.seekTo(positionMs);
}
@Override
public long getBufferedPosition() {
return player.getBufferedPosition();
}
@Override
public boolean isPrepared() {
return initialized && (surfaceHolderGlueHost == null || hasSurface);
}
private void setVideoSurface(Surface surface) {
hasSurface = surface != null;
player.setVideoSurface(surface);
getCallback().onPreparedStateChanged(this);
}
private final class ComponentListener implements Player.EventListener,
SimpleExoPlayer.VideoListener, SurfaceHolder.Callback {
// SurfaceHolder.Callback implementation.
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
setVideoSurface(surfaceHolder.getSurface());
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
// Do nothing.
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
setVideoSurface(null);
}
// Player.EventListener implementation.
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
notifyListeners();
}
@Override
public void onPlayerError(ExoPlaybackException exception) {
Callback callback = getCallback();
if (errorMessageProvider != null) {
Pair<Integer, String> errorMessage = errorMessageProvider.getErrorMessage(exception);
callback.onError(LeanbackPlayerAdapter.this, errorMessage.first, errorMessage.second);
} else {
callback.onError(LeanbackPlayerAdapter.this, exception.type, context.getString(
R.string.lb_media_player_error, exception.type, exception.rendererIndex));
}
}
@Override
public void onLoadingChanged(boolean isLoading) {
// Do nothing.
}
@Override
public void onTimelineChanged(Timeline timeline, Object manifest) {
Callback callback = getCallback();
callback.onDurationChanged(LeanbackPlayerAdapter.this);
callback.onCurrentPositionChanged(LeanbackPlayerAdapter.this);
callback.onBufferedPositionChanged(LeanbackPlayerAdapter.this);
}
@Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
// Do nothing.
}
@Override
public void onPositionDiscontinuity() {
Callback callback = getCallback();
callback.onCurrentPositionChanged(LeanbackPlayerAdapter.this);
callback.onBufferedPositionChanged(LeanbackPlayerAdapter.this);
}
@Override
public void onPlaybackParametersChanged(PlaybackParameters params) {
// Do nothing.
}
@Override
public void onRepeatModeChanged(int repeatMode) {
// Do nothing.
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
// Do nothing.
}
// SimpleExoplayerView.Callback implementation.
@Override
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
float pixelWidthHeightRatio) {
getCallback().onVideoSizeChanged(LeanbackPlayerAdapter.this, width, height);
}
@Override
public void onRenderedFirstFrame() {
// Do nothing.
}
}
}

View File

@ -1,11 +1,9 @@
# ExoPlayer MediaSession extension #
## Description ##
The MediaSession extension mediates between an ExoPlayer instance and a
[MediaSession][]. It automatically retrieves and implements playback actions
and syncs the player state with the state of the media session. The behaviour
can be extended to support other playback and custom actions.
The MediaSession extension mediates between a Player (or ExoPlayer) instance
and a [MediaSession][]. It automatically retrieves and implements playback
actions and syncs the player state with the state of the media session. The
behaviour can be extended to support other playback and custom actions.
[MediaSession]: https://developer.android.com/reference/android/support/v4/media/session/MediaSessionCompat.html
@ -25,3 +23,10 @@ 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.mediasession.*` belong to this module.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html

View File

@ -38,6 +38,7 @@ import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.util.ErrorMessageProvider;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@ -47,7 +48,7 @@ import java.util.Map;
* Connects a {@link MediaSessionCompat} to a {@link Player}.
* <p>
* The connector listens for actions sent by the media session's controller and implements these
* actions by calling appropriate ExoPlayer methods. The playback state of the media session is
* actions by calling appropriate player methods. The playback state of the media session is
* automatically synced with the player. The connector can also be optionally extended by providing
* various collaborators:
* <ul>
@ -73,6 +74,10 @@ public final class MediaSessionConnector {
}
public static final String EXTRAS_PITCH = "EXO_PITCH";
private static final int BASE_MEDIA_SESSION_FLAGS = MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
| MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS;
private static final int EDITOR_MEDIA_SESSION_FLAGS = BASE_MEDIA_SESSION_FLAGS
| MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS;
/**
* Interface to which playback preparation actions are delegated.
@ -293,17 +298,6 @@ public final class MediaSessionConnector {
PlaybackStateCompat.CustomAction getCustomAction();
}
/**
* Converts an exception into an error code and a user readable error message.
*/
public interface ErrorMessageProvider {
/**
* Returns a pair consisting of an error code and a user readable error message for a given
* exception.
*/
Pair<Integer, String> getErrorMessage(ExoPlaybackException playbackException);
}
/**
* The wrapped {@link MediaSessionCompat}.
*/
@ -318,9 +312,8 @@ public final class MediaSessionConnector {
private Player player;
private CustomActionProvider[] customActionProviders;
private int currentWindowIndex;
private Map<String, CustomActionProvider> customActionMap;
private ErrorMessageProvider errorMessageProvider;
private ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
private PlaybackPreparer playbackPreparer;
private QueueNavigator queueNavigator;
private QueueEditor queueEditor;
@ -369,8 +362,7 @@ public final class MediaSessionConnector {
this.handler = new Handler(Looper.myLooper() != null ? Looper.myLooper()
: Looper.getMainLooper());
this.doMaintainMetadata = doMaintainMetadata;
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
| MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mediaSession.setFlags(BASE_MEDIA_SESSION_FLAGS);
mediaController = mediaSession.getController();
mediaSessionCallback = new MediaSessionCallback();
exoPlayerEventListener = new ExoPlayerEventListener();
@ -411,7 +403,8 @@ public final class MediaSessionConnector {
*
* @param errorMessageProvider The error message provider.
*/
public void setErrorMessageProvider(ErrorMessageProvider errorMessageProvider) {
public void setErrorMessageProvider(
ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider) {
this.errorMessageProvider = errorMessageProvider;
}
@ -433,6 +426,8 @@ public final class MediaSessionConnector {
*/
public void setQueueEditor(QueueEditor queueEditor) {
this.queueEditor = queueEditor;
mediaSession.setFlags(queueEditor == null ? BASE_MEDIA_SESSION_FLAGS
: EDITOR_MEDIA_SESSION_FLAGS);
}
private void updateMediaSessionPlaybackState() {
@ -583,11 +578,20 @@ public final class MediaSessionConnector {
private class ExoPlayerEventListener implements Player.EventListener {
private int currentWindowIndex;
private int currentWindowCount;
@Override
public void onTimelineChanged(Timeline timeline, Object manifest) {
if (queueNavigator != null) {
queueNavigator.onTimelineChanged(player);
}
int windowCount = player.getCurrentTimeline().getWindowCount();
if (currentWindowCount != windowCount) {
// active queue item and queue navigation actions may need to be updated
updateMediaSessionPlaybackState();
}
currentWindowCount = windowCount;
currentWindowIndex = player.getCurrentWindowIndex();
updateMediaSessionMetadata();
}
@ -615,6 +619,13 @@ public final class MediaSessionConnector {
updateMediaSessionPlaybackState();
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
mediaSession.setShuffleMode(shuffleModeEnabled ? PlaybackStateCompat.SHUFFLE_MODE_ALL
: PlaybackStateCompat.SHUFFLE_MODE_NONE);
updateMediaSessionPlaybackState();
}
@Override
public void onPlayerError(ExoPlaybackException error) {
playbackException = error;
@ -796,6 +807,14 @@ public final class MediaSessionConnector {
}
}
@Override
public void onSetShuffleMode(int shuffleMode) {
if (canDispatchToQueueNavigator(PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE_ENABLED)) {
queueNavigator.onSetShuffleModeEnabled(player,
shuffleMode != PlaybackStateCompat.SHUFFLE_MODE_NONE);
}
}
@Override
public void onAddQueueItem(MediaDescriptionCompat description) {
if (queueEditor != null) {

View File

@ -14,7 +14,6 @@ package com.google.android.exoplayer2.ext.mediasession;
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.content.Context;
import android.os.Bundle;
import android.support.v4.media.session.PlaybackStateCompat;

View File

@ -163,7 +163,7 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
@Override
public void onSetShuffleModeEnabled(Player player, boolean enabled) {
// TODO: Implement this.
player.setShuffleModeEnabled(enabled);
}
private void publishFloatingQueueWindow(Player player) {

View File

@ -1,7 +1,5 @@
# ExoPlayer OkHttp extension #
## Description ##
The OkHttp extension is an [HttpDataSource][] implementation using Square's
[OkHttp][].
@ -49,3 +47,10 @@ new DefaultDataSourceFactory(
new OkHttpDataSourceFactory(...) /* baseDataSourceFactory argument */);
```
respectively.
## Links ##
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.okhttp.*`
belong to this module.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html

View File

@ -1,19 +1,16 @@
# ExoPlayer Opus extension #
## Description ##
The Opus extension is a [Renderer][] implementation that helps you bundle
libopus (the Opus decoding library) into your app and use it along with
ExoPlayer to play Opus audio on Android devices.
[Renderer]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/Renderer.html
The Opus extension provides `LibopusAudioRenderer`, which uses libopus (the Opus
decoding library) to decode Opus audio.
## Build instructions ##
To use this extension you need to clone the ExoPlayer repository and depend on
its modules locally. Instructions for doing this can be found in ExoPlayer's
[top level README][]. In addition, it's necessary to build the extension's
native components as follows:
[top level README][].
In addition, it's necessary to build the extension's native components as
follows:
* Set the following environment variables:
@ -59,3 +56,38 @@ ${NDK_PATH}/ndk-build APP_ABI=all -j4
* Clean and re-build the project.
* If you want to use your own version of libopus, place it in
`${OPUS_EXT_PATH}/jni/libopus`.
## Using the extension ##
Once you've followed the instructions above to check out, build and depend on
the extension, the next step is to tell ExoPlayer to use `LibopusAudioRenderer`.
How you do this depends on which player API you're using:
* If you're passing a `DefaultRenderersFactory` to
`ExoPlayerFactory.newSimpleInstance`, you can enable using the extension by
setting the `extensionRendererMode` parameter of the `DefaultRenderersFactory`
constructor to `EXTENSION_RENDERER_MODE_ON`. This will use
`LibopusAudioRenderer` for playback if `MediaCodecAudioRenderer` doesn't
support the input format. Pass `EXTENSION_RENDERER_MODE_PREFER` to give
`LibopusAudioRenderer` priority over `MediaCodecAudioRenderer`.
* If you've subclassed `DefaultRenderersFactory`, add a `LibopusAudioRenderer`
to the output list in `buildAudioRenderers`. ExoPlayer will use the first
`Renderer` in the list that supports the input media format.
* If you've implemented your own `RenderersFactory`, return a
`LibopusAudioRenderer` instance from `createRenderers`. ExoPlayer will use the
first `Renderer` in the returned array that supports the input media format.
* If you're using `ExoPlayerFactory.newInstance`, pass a `LibopusAudioRenderer`
in the array of `Renderer`s. ExoPlayer will use the first `Renderer` in the
list that supports the input media format.
Note: These instructions assume you're using `DefaultTrackSelector`. If you have
a custom track selector the choice of `Renderer` is up to your implementation,
so you need to make sure you are passing an `LibopusAudioRenderer` to the
player, then implement your own logic to use the renderer for a given track.
## Links ##
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.opus.*`
belong to this module.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html

View File

@ -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"

View File

@ -132,6 +132,11 @@ public class OpusPlaybackTest extends InstrumentationTestCase {
// Do nothing.
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
// Do nothing.
}
private void releasePlayerAndQuitLooper() {
player.release();
Looper.myLooper().quit();

View File

@ -1,7 +1,5 @@
# ExoPlayer RTMP extension #
## Description ##
The RTMP extension is a [DataSource][] implementation for playing [RTMP][]
streams using [LibRtmp Client for Android][].
@ -9,7 +7,7 @@ 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
## Using the extension ##
## Getting the extension ##
The easiest way to use the extension is to add it as a gradle dependency:
@ -25,3 +23,26 @@ locally. Instructions for doing this can be found in ExoPlayer's
[top level README][].
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
## Using the extension ##
ExoPlayer requests data through `DataSource` instances. These instances are
either instantiated and injected from application code, or obtained from
instances of `DataSource.Factory` that are instantiated and injected from
application code.
`DefaultDataSource` will automatically use uses the RTMP extension whenever it's
available. Hence if your application is using `DefaultDataSource` or
`DefaultDataSourceFactory`, adding support for RTMP streams is as simple as
adding a dependency to the RTMP extension as described above. No changes to your
application code are required. Alternatively, if you know that your application
doesn't need to handle any other protocols, you can update any `DataSource`s and
`DataSource.Factory` instantiations in your application code to use
`RtmpDataSource` and `RtmpDataSourceFactory` directly.
## Links ##
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.rtmp.*`
belong to this module.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html

View File

@ -1,19 +1,16 @@
# ExoPlayer VP9 extension #
## Description ##
The VP9 extension is a [Renderer][] implementation that helps you bundle libvpx
(the VP9 decoding library) into your app and use it along with ExoPlayer to play
VP9 video on Android devices.
[Renderer]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/Renderer.html
The VP9 extension provides `LibvpxVideoRenderer`, which uses libvpx (the VPx
decoding library) to decode VP9 video.
## Build instructions ##
To use this extension you need to clone the ExoPlayer repository and depend on
its modules locally. Instructions for doing this can be found in ExoPlayer's
[top level README][]. In addition, it's necessary to build the extension's
native components as follows:
[top level README][].
In addition, it's necessary to build the extension's native components as
follows:
* Set the following environment variables:
@ -76,3 +73,45 @@ ${NDK_PATH}/ndk-build APP_ABI=all -j4
`${VP9_EXT_PATH}/jni/libvpx` or `${VP9_EXT_PATH}/jni/libyuv` respectively. But
please note that `generate_libvpx_android_configs.sh` and the makefiles need
to be modified to work with arbitrary versions of libvpx and libyuv.
## Using the extension ##
Once you've followed the instructions above to check out, build and depend on
the extension, the next step is to tell ExoPlayer to use `LibvpxVideoRenderer`.
How you do this depends on which player API you're using:
* If you're passing a `DefaultRenderersFactory` to
`ExoPlayerFactory.newSimpleInstance`, you can enable using the extension by
setting the `extensionRendererMode` parameter of the `DefaultRenderersFactory`
constructor to `EXTENSION_RENDERER_MODE_ON`. This will use
`LibvpxVideoRenderer` for playback if `MediaCodecVideoRenderer` doesn't
support decoding the input VP9 stream. Pass `EXTENSION_RENDERER_MODE_PREFER`
to give `LibvpxVideoRenderer` priority over `MediaCodecVideoRenderer`.
* If you've subclassed `DefaultRenderersFactory`, add a `LibvpxVideoRenderer`
to the output list in `buildVideoRenderers`. ExoPlayer will use the first
`Renderer` in the list that supports the input media format.
* If you've implemented your own `RenderersFactory`, return a
`LibvpxVideoRenderer` instance from `createRenderers`. ExoPlayer will use the
first `Renderer` in the returned array that supports the input media format.
* If you're using `ExoPlayerFactory.newInstance`, pass a `LibvpxVideoRenderer`
in the array of `Renderer`s. ExoPlayer will use the first `Renderer` in the
list that supports the input media format.
Note: These instructions assume you're using `DefaultTrackSelector`. If you have
a custom track selector the choice of `Renderer` is up to your implementation,
so you need to make sure you are passing an `LibvpxVideoRenderer` to the
player, then implement your own logic to use the renderer for a given track.
`LibvpxVideoRenderer` can optionally output to a `VpxVideoSurfaceView` when not
being used via `SimpleExoPlayer`, in which case color space conversion will be
performed using a GL shader. To enable this mode, send the renderer a message of
type `LibvpxVideoRenderer.MSG_SET_OUTPUT_BUFFER_RENDERER` with the
`VpxVideoSurfaceView` as its object, instead of sending `MSG_SET_SURFACE` with a
`Surface`.
## Links ##
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.vp9.*`
belong to this module.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html

View File

@ -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"

View File

@ -164,6 +164,11 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
// Do nothing.
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
// Do nothing.
}
private void releasePlayerAndQuitLooper() {
player.release();
Looper.myLooper().quit();

View File

@ -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;

7
library/README.md Normal file
View File

@ -0,0 +1,7 @@
# ExoPlayer library #
The ExoPlayer library is split into multiple modules. See ExoPlayer's
[top level README][] for more information about the available library modules
and how to use them.
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md

13
library/all/README.md Normal file
View File

@ -0,0 +1,13 @@
# ExoPlayer full library #
An empty module that depends on all of the other library modules. Depending on
the full library is equivalent to depending on all of the other library modules
individually. See ExoPlayer's [top level README][] for more information.
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
## Links ##
* [Javadoc][]: Note that this Javadoc is combined with that of other modules.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html

View File

@ -13,5 +13,4 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest package="com.google.android.exoplayer2"/>

9
library/core/README.md Normal file
View File

@ -0,0 +1,9 @@
# ExoPlayer core library module #
The core of the ExoPlayer library.
## Links ##
* [Javadoc][]: Note that this Javadoc is combined with that of other modules.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html

View File

@ -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"

View File

@ -0,0 +1,10 @@
1
00:00:00,000 --> 00:00:01,234
This is the first subtitle.
2
00:00:02,345 --> 00:00:03,456
This is the second subtitle.
Second subtitle with second line.
3

View File

@ -15,20 +15,18 @@
*/
package com.google.android.exoplayer2;
import android.util.Pair;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.testutil.ExoPlayerWrapper;
import com.google.android.exoplayer2.testutil.ActionSchedule;
import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner;
import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner.Builder;
import com.google.android.exoplayer2.testutil.FakeMediaClockRenderer;
import com.google.android.exoplayer2.testutil.FakeMediaSource;
import com.google.android.exoplayer2.testutil.FakeRenderer;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.LinkedList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import junit.framework.TestCase;
/**
@ -43,67 +41,59 @@ public final class ExoPlayerTest extends TestCase {
*/
private static final int TIMEOUT_MS = 10000;
private static final Format TEST_VIDEO_FORMAT = Format.createVideoSampleFormat(null,
MimeTypes.VIDEO_H264, null, Format.NO_VALUE, Format.NO_VALUE, 1280, 720, Format.NO_VALUE,
null, null);
private static final Format TEST_AUDIO_FORMAT = Format.createAudioSampleFormat(null,
MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, null);
/**
* Tests playback of a source that exposes an empty timeline. Playback is expected to end without
* error.
*/
public void testPlayEmptyTimeline() throws Exception {
ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper();
Timeline timeline = Timeline.EMPTY;
MediaSource mediaSource = new FakeMediaSource(timeline, null);
FakeRenderer renderer = new FakeRenderer();
playerWrapper.setup(mediaSource, renderer);
playerWrapper.blockUntilEnded(TIMEOUT_MS);
assertEquals(0, playerWrapper.positionDiscontinuityCount);
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
.setTimeline(timeline).setRenderers(renderer)
.build().start().blockUntilEnded(TIMEOUT_MS);
testRunner.assertPositionDiscontinuityCount(0);
testRunner.assertTimelinesEqual(timeline);
assertEquals(0, renderer.formatReadCount);
assertEquals(0, renderer.bufferReadCount);
assertFalse(renderer.isEnded);
playerWrapper.assertSourceInfosEquals(Pair.create(timeline, null));
}
/**
* Tests playback of a source that exposes a single period.
*/
public void testPlaySinglePeriodTimeline() throws Exception {
ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper();
Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(false, false, 0));
Object manifest = new Object();
MediaSource mediaSource = new FakeMediaSource(timeline, manifest, TEST_VIDEO_FORMAT);
FakeRenderer renderer = new FakeRenderer(TEST_VIDEO_FORMAT);
playerWrapper.setup(mediaSource, renderer);
playerWrapper.blockUntilEnded(TIMEOUT_MS);
assertEquals(0, playerWrapper.positionDiscontinuityCount);
FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT);
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
.setTimeline(timeline).setManifest(manifest).setRenderers(renderer)
.build().start().blockUntilEnded(TIMEOUT_MS);
testRunner.assertPositionDiscontinuityCount(0);
testRunner.assertTimelinesEqual(timeline);
testRunner.assertManifestsEqual(manifest);
testRunner.assertTrackGroupsEqual(new TrackGroupArray(new TrackGroup(Builder.VIDEO_FORMAT)));
assertEquals(1, renderer.formatReadCount);
assertEquals(1, renderer.bufferReadCount);
assertTrue(renderer.isEnded);
assertEquals(new TrackGroupArray(new TrackGroup(TEST_VIDEO_FORMAT)), playerWrapper.trackGroups);
playerWrapper.assertSourceInfosEquals(Pair.create(timeline, manifest));
}
/**
* Tests playback of a source that exposes three periods.
*/
public void testPlayMultiPeriodTimeline() throws Exception {
ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper();
Timeline timeline = new FakeTimeline(
new TimelineWindowDefinition(false, false, 0),
new TimelineWindowDefinition(false, false, 0),
new TimelineWindowDefinition(false, false, 0));
MediaSource mediaSource = new FakeMediaSource(timeline, null, TEST_VIDEO_FORMAT);
FakeRenderer renderer = new FakeRenderer(TEST_VIDEO_FORMAT);
playerWrapper.setup(mediaSource, renderer);
playerWrapper.blockUntilEnded(TIMEOUT_MS);
assertEquals(2, playerWrapper.positionDiscontinuityCount);
FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT);
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
.setTimeline(timeline).setRenderers(renderer)
.build().start().blockUntilEnded(TIMEOUT_MS);
testRunner.assertPositionDiscontinuityCount(2);
testRunner.assertTimelinesEqual(timeline);
assertEquals(3, renderer.formatReadCount);
assertEquals(1, renderer.bufferReadCount);
assertTrue(renderer.isEnded);
playerWrapper.assertSourceInfosEquals(Pair.create(timeline, null));
}
/**
@ -111,16 +101,12 @@ public final class ExoPlayerTest extends TestCase {
* source.
*/
public void testReadAheadToEndDoesNotResetRenderer() throws Exception {
final ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper();
Timeline timeline = new FakeTimeline(
new TimelineWindowDefinition(false, false, 10),
new TimelineWindowDefinition(false, false, 10),
new TimelineWindowDefinition(false, false, 10));
MediaSource mediaSource = new FakeMediaSource(timeline, null, TEST_VIDEO_FORMAT,
TEST_AUDIO_FORMAT);
FakeRenderer videoRenderer = new FakeRenderer(TEST_VIDEO_FORMAT);
FakeMediaClockRenderer audioRenderer = new FakeMediaClockRenderer(TEST_AUDIO_FORMAT) {
final FakeRenderer videoRenderer = new FakeRenderer(Builder.VIDEO_FORMAT);
FakeMediaClockRenderer audioRenderer = new FakeMediaClockRenderer(Builder.AUDIO_FORMAT) {
@Override
public long getPositionUs() {
@ -143,35 +129,30 @@ public final class ExoPlayerTest extends TestCase {
@Override
public boolean isEnded() {
// Allow playback to end once the final period is playing.
return playerWrapper.positionDiscontinuityCount == 2;
return videoRenderer.isEnded();
}
};
playerWrapper.setup(mediaSource, videoRenderer, audioRenderer);
playerWrapper.blockUntilEnded(TIMEOUT_MS);
assertEquals(2, playerWrapper.positionDiscontinuityCount);
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
.setTimeline(timeline).setRenderers(videoRenderer, audioRenderer)
.setSupportedFormats(Builder.VIDEO_FORMAT, Builder.AUDIO_FORMAT)
.build().start().blockUntilEnded(TIMEOUT_MS);
testRunner.assertPositionDiscontinuityCount(2);
testRunner.assertTimelinesEqual(timeline);
assertEquals(1, audioRenderer.positionResetCount);
assertTrue(videoRenderer.isEnded);
assertTrue(audioRenderer.isEnded);
playerWrapper.assertSourceInfosEquals(Pair.create(timeline, null));
}
public void testRepreparationGivesFreshSourceInfo() throws Exception {
ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper();
Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(false, false, 0));
FakeRenderer renderer = new FakeRenderer(TEST_VIDEO_FORMAT);
// Prepare the player with a source with the first manifest and a non-empty timeline
FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT);
Object firstSourceManifest = new Object();
playerWrapper.setup(new FakeMediaSource(timeline, firstSourceManifest, TEST_VIDEO_FORMAT),
renderer);
playerWrapper.blockUntilSourceInfoRefreshed(TIMEOUT_MS);
// Prepare the player again with a source and a new manifest, which will never be exposed.
MediaSource firstSource = new FakeMediaSource(timeline, firstSourceManifest,
Builder.VIDEO_FORMAT);
final CountDownLatch queuedSourceInfoCountDownLatch = new CountDownLatch(1);
final CountDownLatch completePreparationCountDownLatch = new CountDownLatch(1);
playerWrapper.prepare(new FakeMediaSource(timeline, new Object(), TEST_VIDEO_FORMAT) {
MediaSource secondSource = new FakeMediaSource(timeline, new Object(), Builder.VIDEO_FORMAT) {
@Override
public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {
super.prepareSource(player, isTopLevelSource, listener);
@ -185,29 +166,49 @@ public final class ExoPlayerTest extends TestCase {
throw new IllegalStateException(e);
}
}
});
// Prepare the player again with a third source.
queuedSourceInfoCountDownLatch.await();
};
Object thirdSourceManifest = new Object();
playerWrapper.prepare(new FakeMediaSource(timeline, thirdSourceManifest, TEST_VIDEO_FORMAT));
completePreparationCountDownLatch.countDown();
// Wait for playback to complete.
playerWrapper.blockUntilEnded(TIMEOUT_MS);
assertEquals(0, playerWrapper.positionDiscontinuityCount);
assertEquals(1, renderer.formatReadCount);
assertEquals(1, renderer.bufferReadCount);
assertTrue(renderer.isEnded);
assertEquals(new TrackGroupArray(new TrackGroup(TEST_VIDEO_FORMAT)), playerWrapper.trackGroups);
MediaSource thirdSource = new FakeMediaSource(timeline, thirdSourceManifest,
Builder.VIDEO_FORMAT);
// Prepare the player with a source with the first manifest and a non-empty timeline. Prepare
// the player again with a source and a new manifest, which will never be exposed. Allow the
// test thread to prepare the player with a third source, and block the playback thread until
// the test thread's call to prepare() has returned.
ActionSchedule actionSchedule = new ActionSchedule.Builder("testRepreparation")
.waitForTimelineChanged(timeline)
.prepareSource(secondSource)
.executeRunnable(new Runnable() {
@Override
public void run() {
try {
queuedSourceInfoCountDownLatch.await();
} catch (InterruptedException e) {
// Ignore.
}
}
})
.prepareSource(thirdSource)
.executeRunnable(new Runnable() {
@Override
public void run() {
completePreparationCountDownLatch.countDown();
}
})
.build();
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
.setMediaSource(firstSource).setRenderers(renderer).setActionSchedule(actionSchedule)
.build().start().blockUntilEnded(TIMEOUT_MS);
testRunner.assertPositionDiscontinuityCount(0);
// The first source's preparation completed with a non-empty timeline. When the player was
// re-prepared with the second source, it immediately exposed an empty timeline, but the source
// info refresh from the second source was suppressed as we re-prepared with the third source.
playerWrapper.assertSourceInfosEquals(
Pair.create(timeline, firstSourceManifest),
Pair.create(Timeline.EMPTY, null),
Pair.create(timeline, thirdSourceManifest));
testRunner.assertTimelinesEqual(timeline, Timeline.EMPTY, timeline);
testRunner.assertManifestsEqual(firstSourceManifest, null, thirdSourceManifest);
testRunner.assertTrackGroupsEqual(new TrackGroupArray(new TrackGroup(Builder.VIDEO_FORMAT)));
assertEquals(1, renderer.formatReadCount);
assertEquals(1, renderer.bufferReadCount);
assertTrue(renderer.isEnded);
}
public void testRepeatModeChanges() throws Exception {
@ -215,49 +216,22 @@ public final class ExoPlayerTest extends TestCase {
new TimelineWindowDefinition(true, false, 100000),
new TimelineWindowDefinition(true, false, 100000),
new TimelineWindowDefinition(true, false, 100000));
final int[] actionSchedule = { // 0 -> 1
Player.REPEAT_MODE_ONE, // 1 -> 1
Player.REPEAT_MODE_OFF, // 1 -> 2
Player.REPEAT_MODE_ONE, // 2 -> 2
Player.REPEAT_MODE_ALL, // 2 -> 0
Player.REPEAT_MODE_ONE, // 0 -> 0
-1, // 0 -> 0
Player.REPEAT_MODE_OFF, // 0 -> 1
-1, // 1 -> 2
-1 // 2 -> ended
};
int[] expectedWindowIndices = {1, 1, 2, 2, 0, 0, 0, 1, 2};
final LinkedList<Integer> windowIndices = new LinkedList<>();
final CountDownLatch actionCounter = new CountDownLatch(actionSchedule.length);
ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper() {
@Override
@SuppressWarnings("ResourceType")
public void onPositionDiscontinuity() {
super.onPositionDiscontinuity();
int actionIndex = actionSchedule.length - (int) actionCounter.getCount();
if (actionSchedule[actionIndex] != -1) {
player.setRepeatMode(actionSchedule[actionIndex]);
}
windowIndices.add(player.getCurrentWindowIndex());
actionCounter.countDown();
}
};
MediaSource mediaSource = new FakeMediaSource(timeline, null, TEST_VIDEO_FORMAT);
FakeRenderer renderer = new FakeRenderer(TEST_VIDEO_FORMAT);
playerWrapper.setup(mediaSource, renderer);
boolean finished = actionCounter.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
playerWrapper.release();
assertTrue("Test playback timed out waiting for action schedule to end.", finished);
if (playerWrapper.exception != null) {
throw playerWrapper.exception;
}
assertEquals(expectedWindowIndices.length, windowIndices.size());
for (int i = 0; i < expectedWindowIndices.length; i++) {
assertEquals(expectedWindowIndices[i], windowIndices.get(i).intValue());
}
assertEquals(9, playerWrapper.positionDiscontinuityCount);
FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT);
ActionSchedule actionSchedule = new ActionSchedule.Builder("testRepeatMode") // 0 -> 1
.waitForPositionDiscontinuity().setRepeatMode(Player.REPEAT_MODE_ONE) // 1 -> 1
.waitForPositionDiscontinuity().setRepeatMode(Player.REPEAT_MODE_OFF) // 1 -> 2
.waitForPositionDiscontinuity().setRepeatMode(Player.REPEAT_MODE_ONE) // 2 -> 2
.waitForPositionDiscontinuity().setRepeatMode(Player.REPEAT_MODE_ALL) // 2 -> 0
.waitForPositionDiscontinuity().setRepeatMode(Player.REPEAT_MODE_ONE) // 0 -> 0
.waitForPositionDiscontinuity() // 0 -> 0
.waitForPositionDiscontinuity().setRepeatMode(Player.REPEAT_MODE_OFF) // 0 -> end
.build();
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
.setTimeline(timeline).setRenderers(renderer).setActionSchedule(actionSchedule)
.build().start().blockUntilEnded(TIMEOUT_MS);
testRunner.assertPlayedPeriodIndices(0, 1, 1, 2, 2, 0, 0, 0, 1, 2);
testRunner.assertTimelinesEqual(timeline);
assertTrue(renderer.isEnded);
playerWrapper.assertSourceInfosEquals(Pair.create(timeline, null));
}
}

View File

@ -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;

View File

@ -16,7 +16,6 @@
package com.google.android.exoplayer2.extractor.mp4;
import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.testutil.ExtractorAsserts;
import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory;
@ -38,11 +37,6 @@ public final class FragmentedMp4ExtractorTest extends InstrumentationTestCase {
"mp4/sample_fragmented_sei.mp4", getInstrumentation());
}
public void testAtomWithZeroSize() throws Exception {
ExtractorAsserts.assertThrows(getExtractorFactory(), "mp4/sample_fragmented_zero_size_atom.mp4",
getInstrumentation(), ParserException.class);
}
private static ExtractorFactory getExtractorFactory() {
return getExtractorFactory(0);
}

View File

@ -0,0 +1,47 @@
/*
* 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.extractor.mp4;
import android.test.MoreAsserts;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.util.UUID;
import junit.framework.TestCase;
/**
* Tests for {@link PsshAtomUtil}.
*/
public class PsshAtomUtilTest extends TestCase {
public void testBuildPsshAtom() {
byte[] schemeData = new byte[]{0, 1, 2, 3, 4, 5};
byte[] psshAtom = PsshAtomUtil.buildPsshAtom(C.WIDEVINE_UUID, schemeData);
// Read the PSSH atom back and assert its content is as expected.
ParsableByteArray parsablePsshAtom = new ParsableByteArray(psshAtom);
assertEquals(psshAtom.length, parsablePsshAtom.readUnsignedIntToInt()); // length
assertEquals(Atom.TYPE_pssh, parsablePsshAtom.readInt()); // type
int fullAtomInt = parsablePsshAtom.readInt(); // version + flags
assertEquals(0, Atom.parseFullAtomVersion(fullAtomInt));
assertEquals(0, Atom.parseFullAtomFlags(fullAtomInt));
UUID systemId = new UUID(parsablePsshAtom.readLong(), parsablePsshAtom.readLong());
assertEquals(C.WIDEVINE_UUID, systemId);
assertEquals(schemeData.length, parsablePsshAtom.readUnsignedIntToInt());
byte[] psshSchemeData = new byte[schemeData.length];
parsablePsshAtom.readBytes(psshSchemeData, 0, schemeData.length);
MoreAsserts.assertEquals(schemeData, psshSchemeData);
}
}

View File

@ -34,7 +34,7 @@ public class DefaultOggSeekerUtilMethodsTest extends TestCase {
FakeExtractorInput extractorInput = TestData.createInput(
TestUtil.joinByteArrays(
TestUtil.buildTestData(4000, random),
new byte[]{'O', 'g', 'g', 'S'},
new byte[] {'O', 'g', 'g', 'S'},
TestUtil.buildTestData(4000, random)
), false);
skipToNextPage(extractorInput);
@ -45,7 +45,7 @@ public class DefaultOggSeekerUtilMethodsTest extends TestCase {
FakeExtractorInput extractorInput = TestData.createInput(
TestUtil.joinByteArrays(
TestUtil.buildTestData(2046, random),
new byte[]{'O', 'g', 'g', 'S'},
new byte[] {'O', 'g', 'g', 'S'},
TestUtil.buildTestData(4000, random)
), false);
skipToNextPage(extractorInput);
@ -55,7 +55,7 @@ public class DefaultOggSeekerUtilMethodsTest extends TestCase {
public void testSkipToNextPageInputShorterThanPeekLength() throws Exception {
FakeExtractorInput extractorInput = TestData.createInput(
TestUtil.joinByteArrays(
new byte[]{'x', 'O', 'g', 'g', 'S'}
new byte[] {'x', 'O', 'g', 'g', 'S'}
), false);
skipToNextPage(extractorInput);
assertEquals(1, extractorInput.getPosition());
@ -63,7 +63,7 @@ public class DefaultOggSeekerUtilMethodsTest extends TestCase {
public void testSkipToNextPageNoMatch() throws Exception {
FakeExtractorInput extractorInput = TestData.createInput(
new byte[]{'g', 'g', 'S', 'O', 'g', 'g'}, false);
new byte[] {'g', 'g', 'S', 'O', 'g', 'g'}, false);
try {
skipToNextPage(extractorInput);
fail();

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
import com.google.android.exoplayer2.testutil.FakeTrackOutput;
@ -154,20 +155,20 @@ public class AdtsReaderTest extends TestCase {
}
}
public void testAdtsDataOnly() throws Exception {
public void testAdtsDataOnly() throws ParserException {
data.setPosition(ID3_DATA_1.length + ID3_DATA_2.length);
feed();
assertSampleCounts(0, 1);
adtsOutput.assertSample(0, ADTS_CONTENT, 0, C.BUFFER_FLAG_KEY_FRAME, null);
}
private void feedLimited(int limit) {
private void feedLimited(int limit) throws ParserException {
maybeStartPacket();
data.setLimit(limit);
feed();
}
private void feed() {
private void feed() throws ParserException {
maybeStartPacket();
adtsReader.consume(data);
}

View File

@ -0,0 +1,123 @@
/*
* 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.offline;
import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.upstream.DummyDataSource;
import com.google.android.exoplayer2.upstream.cache.Cache;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import org.mockito.Mockito;
/**
* Unit tests for {@link ProgressiveDownloadAction}.
*/
public class ProgressiveDownloadActionTest extends InstrumentationTestCase {
public void testDownloadActionIsNotRemoveAction() throws Exception {
ProgressiveDownloadAction action = new ProgressiveDownloadAction("uri", null, false);
assertFalse(action.isRemoveAction());
}
public void testRemoveActionIsRemoveAction() throws Exception {
ProgressiveDownloadAction action2 = new ProgressiveDownloadAction("uri", null, true);
assertTrue(action2.isRemoveAction());
}
public void testCreateDownloader() throws Exception {
TestUtil.setUpMockito(this);
ProgressiveDownloadAction action = new ProgressiveDownloadAction("uri", null, false);
DownloaderConstructorHelper constructorHelper = new DownloaderConstructorHelper(
Mockito.mock(Cache.class), DummyDataSource.FACTORY);
assertNotNull(action.createDownloader(constructorHelper));
}
public void testSameUriCacheKeyDifferentAction_IsSameMedia() throws Exception {
ProgressiveDownloadAction action1 = new ProgressiveDownloadAction("uri", null, true);
ProgressiveDownloadAction action2 = new ProgressiveDownloadAction("uri", null, false);
assertTrue(action1.isSameMedia(action2));
}
public void testNullCacheKeyDifferentUriAction_IsNotSameMedia() throws Exception {
ProgressiveDownloadAction action3 = new ProgressiveDownloadAction("uri2", null, true);
ProgressiveDownloadAction action4 = new ProgressiveDownloadAction("uri", null, false);
assertFalse(action3.isSameMedia(action4));
}
public void testSameCacheKeyDifferentUriAction_IsSameMedia() throws Exception {
ProgressiveDownloadAction action5 = new ProgressiveDownloadAction("uri2", "key", true);
ProgressiveDownloadAction action6 = new ProgressiveDownloadAction("uri", "key", false);
assertTrue(action5.isSameMedia(action6));
}
public void testSameUriDifferentCacheKeyAction_IsNotSameMedia() throws Exception {
ProgressiveDownloadAction action7 = new ProgressiveDownloadAction("uri", "key", true);
ProgressiveDownloadAction action8 = new ProgressiveDownloadAction("uri", "key2", false);
assertFalse(action7.isSameMedia(action8));
}
public void testEquals() throws Exception {
ProgressiveDownloadAction action1 = new ProgressiveDownloadAction("uri", null, true);
assertTrue(action1.equals(action1));
ProgressiveDownloadAction action2 = new ProgressiveDownloadAction("uri", null, true);
ProgressiveDownloadAction action3 = new ProgressiveDownloadAction("uri", null, true);
assertTrue(action2.equals(action3));
ProgressiveDownloadAction action4 = new ProgressiveDownloadAction("uri", null, true);
ProgressiveDownloadAction action5 = new ProgressiveDownloadAction("uri", null, false);
assertFalse(action4.equals(action5));
ProgressiveDownloadAction action6 = new ProgressiveDownloadAction("uri", null, true);
ProgressiveDownloadAction action7 = new ProgressiveDownloadAction("uri", "key", true);
assertFalse(action6.equals(action7));
ProgressiveDownloadAction action8 = new ProgressiveDownloadAction("uri", "key2", true);
ProgressiveDownloadAction action9 = new ProgressiveDownloadAction("uri", "key", true);
assertFalse(action8.equals(action9));
ProgressiveDownloadAction action10 = new ProgressiveDownloadAction("uri", null, true);
ProgressiveDownloadAction action11 = new ProgressiveDownloadAction("uri2", null, true);
assertFalse(action10.equals(action11));
}
public void testSerializerGetType() throws Exception {
ProgressiveDownloadAction action = new ProgressiveDownloadAction("uri", null, false);
assertNotNull(action.getType());
}
public void testSerializerWriteRead() throws Exception {
doTestSerializationRoundTrip(new ProgressiveDownloadAction("uri1", null, false));
doTestSerializationRoundTrip(new ProgressiveDownloadAction("uri2", "key", true));
}
private void doTestSerializationRoundTrip(ProgressiveDownloadAction action1) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream output = new DataOutputStream(out);
action1.writeToStream(output);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
DataInputStream input = new DataInputStream(in);
DownloadAction action2 = ProgressiveDownloadAction.DESERIALIZER.readFromStream(input);
assertEquals(action1, action2);
}
}

View File

@ -396,6 +396,16 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
throw new UnsupportedOperationException();
}
@Override
public void setShuffleModeEnabled(boolean shuffleModeEnabled) {
throw new UnsupportedOperationException();
}
@Override
public boolean getShuffleModeEnabled() {
throw new UnsupportedOperationException();
}
@Override
public boolean isLoading() {
throw new UnsupportedOperationException();

Some files were not shown because too many files have changed in this diff Show More