Merge pull request #3253 from google/dev-v2-r2.5.2

r2.5.2
This commit is contained in:
ojw28 2017-09-10 18:29:42 +01:00 committed by GitHub
commit 2fb09dbb79
75 changed files with 1125 additions and 366 deletions

View File

@ -9,34 +9,37 @@ and extend, and can be updated through Play Store application updates.
## Documentation ## ## Documentation ##
* The [developer guide][] provides a wealth of information to help you get * The [developer guide][] provides a wealth of information.
started. * The [class reference][] documents ExoPlayer classes.
* The [class reference][] documents the ExoPlayer library classes.
* The [release notes][] document the major changes in each release. * 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 [developer guide]: https://google.github.io/ExoPlayer/guide.html
[class reference]: https://google.github.io/ExoPlayer/doc/reference [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 ## ## 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. 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 The easiest way to get started using ExoPlayer is to add it as a gradle
dependency. You need to make sure you have the jcenter repository included in dependency. You need to make sure you have the JCenter and Google repositories
the `build.gradle` file in the root of your project: included in the `build.gradle` file in the root of your project:
```gradle ```gradle
repositories { repositories {
jcenter() jcenter()
google()
} }
``` ```
Next add a gradle compile dependency to the `build.gradle` file of your app 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 ```gradle
compile 'com.google.android.exoplayer:exoplayer:r2.X.X' compile 'com.google.android.exoplayer:exoplayer:r2.X.X'
@ -53,8 +56,8 @@ compile 'com.google.android.exoplayer:exoplayer-dash:r2.X.X'
compile 'com.google.android.exoplayer:exoplayer-ui: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 The available library modules are listed below. Adding a dependency to the full
ExoPlayer library is equivalent to adding dependencies on all of the modules library is equivalent to adding dependencies on all of the library modules
individually. individually.
* `exoplayer-core`: Core functionality (required). * `exoplayer-core`: Core functionality (required).
@ -63,11 +66,16 @@ individually.
* `exoplayer-smoothstreaming`: Support for SmoothStreaming content. * `exoplayer-smoothstreaming`: Support for SmoothStreaming content.
* `exoplayer-ui`: UI components and resources for use with ExoPlayer. * `exoplayer-ui`: UI components and resources for use with ExoPlayer.
For more details, see the project on [Bintray][]. For information about the In addition to library modules, ExoPlayer has multiple extension modules that
latest versions, see the [Release notes][]. 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 [Bintray]: https://bintray.com/google/exoplayer
[Release notes]: https://github.com/google/ExoPlayer/blob/release-v2/RELEASENOTES.md
### Locally ### ### Locally ###
@ -99,22 +107,16 @@ depend on them as you would on any other local module, for example:
```gradle ```gradle
compile project(':exoplayer-library-core') compile project(':exoplayer-library-core')
compile project(':exoplayer-library-dash') compile project(':exoplayer-library-dash')
compile project(':exoplayer-library-ui) compile project(':exoplayer-library-ui')
``` ```
## Developing ExoPlayer ## ## Developing ExoPlayer ##
#### Project branches #### #### Project branches ####
* The project has `dev-vX` and `release-vX` branches, where `X` is the major * Development work happens on the `dev-v2` branch. Pull requests should
version number. normally be made to this branch.
* Most development work happens on the `dev-vX` branch with the highest major * The `release-v2` branch holds the most recent release.
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`.
#### Using Android Studio #### #### Using Android Studio ####

View File

@ -1,5 +1,31 @@
# Release notes # # Release notes #
### r2.5.2 ###
* IMA extension: Fix issue where ad playback could end prematurely for some
content types ([#3180](https://github.com/google/ExoPlayer/issues/3180)).
* RTMP extension: Fix SIGABRT on fast RTMP stream restart
([#3156](https://github.com/google/ExoPlayer/issues/3156)).
* UI: Allow app to manually specify ad markers
([#3184](https://github.com/google/ExoPlayer/issues/3184)).
* DASH: Expose segment indices to subclasses of DefaultDashChunkSource
([#3037](https://github.com/google/ExoPlayer/issues/3037)).
* Captions: Added robustness against malformed WebVTT captions
([#3228](https://github.com/google/ExoPlayer/issues/3228)).
* DRM: Support forcing a specific license URL.
* Fix playback error when seeking in media loaded through content:// URIs
([#3216](https://github.com/google/ExoPlayer/issues/3216)).
* Fix issue playing MP4s in which the last atom specifies a size of zero
([#3191](https://github.com/google/ExoPlayer/issues/3191)).
* Workaround playback failures on some Xiaomi devices
([#3171](https://github.com/google/ExoPlayer/issues/3171)).
* Workaround SIGSEGV issue on some devices when setting and swapping surface for
secure playbacks ([#3215](https://github.com/google/ExoPlayer/issues/3215)).
* Workaround for Nexus 7 issue when swapping output surface
([#3236](https://github.com/google/ExoPlayer/issues/3236)).
* Workaround for SimpleExoPlayerView's surface not being hidden properly
([#3160](https://github.com/google/ExoPlayer/issues/3160)).
### r2.5.1 ### ### r2.5.1 ###
* Fix an issue that could cause the reported playback position to stop advancing * Fix an issue that could cause the reported playback position to stop advancing

View File

@ -14,9 +14,12 @@
buildscript { buildscript {
repositories { repositories {
jcenter() jcenter()
maven {
url "https://maven.google.com"
}
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:2.3.3' classpath 'com.android.tools.build:gradle:3.0.0-beta4'
classpath 'com.novoda:bintray-release:0.5.0' classpath 'com.novoda:bintray-release:0.5.0'
} }
// Workaround for the following test coverage issue. Remove when fixed: // Workaround for the following test coverage issue. Remove when fixed:

View File

@ -24,7 +24,7 @@ project.ext {
supportLibraryVersion = '25.4.0' supportLibraryVersion = '25.4.0'
dexmakerVersion = '1.2' dexmakerVersion = '1.2'
mockitoVersion = '1.9.5' mockitoVersion = '1.9.5'
releaseVersion = 'r2.5.1' releaseVersion = 'r2.5.2'
modulePrefix = ':' modulePrefix = ':'
if (gradle.ext.has('exoplayerModulePrefix')) { if (gradle.ext.has('exoplayerModulePrefix')) {
modulePrefix += gradle.ext.exoplayerModulePrefix modulePrefix += gradle.ext.exoplayerModulePrefix

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 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. developing other applications that make use of the ExoPlayer library.

View File

@ -39,9 +39,15 @@ android {
disable 'MissingTranslation' disable 'MissingTranslation'
} }
flavorDimensions "extensions"
productFlavors { productFlavors {
noExtensions noExtensions {
withExtensions dimension "extensions"
}
withExtensions {
dimension "extensions"
}
} }
} }
@ -56,4 +62,5 @@ dependencies {
withExtensionsCompile project(path: modulePrefix + 'extension-ima') withExtensionsCompile project(path: modulePrefix + 'extension-ima')
withExtensionsCompile project(path: modulePrefix + 'extension-opus') withExtensionsCompile project(path: modulePrefix + 'extension-opus')
withExtensionsCompile project(path: modulePrefix + 'extension-vp9') withExtensionsCompile project(path: modulePrefix + 'extension-vp9')
withExtensionsCompile project(path: modulePrefix + 'extension-rtmp')
} }

View File

@ -16,8 +16,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.exoplayer2.demo" package="com.google.android.exoplayer2.demo"
android:versionCode="2501" android:versionCode="2502"
android:versionName="2.5.1"> android:versionName="2.5.2">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

View File

@ -431,6 +431,8 @@ import java.util.Locale;
return "YES"; return "YES";
case RendererCapabilities.FORMAT_EXCEEDS_CAPABILITIES: case RendererCapabilities.FORMAT_EXCEEDS_CAPABILITIES:
return "NO_EXCEEDS_CAPABILITIES"; return "NO_EXCEEDS_CAPABILITIES";
case RendererCapabilities.FORMAT_UNSUPPORTED_DRM:
return "NO_UNSUPPORTED_DRM";
case RendererCapabilities.FORMAT_UNSUPPORTED_SUBTYPE: case RendererCapabilities.FORMAT_UNSUPPORTED_SUBTYPE:
return "NO_UNSUPPORTED_TYPE"; return "NO_UNSUPPORTED_TYPE";
case RendererCapabilities.FORMAT_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 = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector);
player.addListener(this); player.addListener(this);
player.addListener(eventLogger); player.addListener(eventLogger);
player.addMetadataOutput(eventLogger);
player.setAudioDebugListener(eventLogger); player.setAudioDebugListener(eventLogger);
player.setVideoDebugListener(eventLogger); player.setVideoDebugListener(eventLogger);
player.setMetadataOutput(eventLogger);
simpleExoPlayerView.setPlayer(player); simpleExoPlayerView.setPlayer(player);
player.setPlayWhenReady(shouldAutoPlay); player.setPlayWhenReady(shouldAutoPlay);

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.

View File

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

@ -1,18 +1,17 @@
# ExoPlayer FFmpeg extension # # ExoPlayer FFmpeg extension #
## Description ## The FFmpeg extension provides `FfmpegAudioRenderer`, which uses FFmpeg for
decoding and can render audio encoded in a variety of formats.
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
## Build instructions ## ## Build instructions ##
To use this extension you need to clone the ExoPlayer repository and depend on 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 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 [top level README][]. The extension is not provided via JCenter (see [#2781][]
native components as follows: for more information).
In addition, it's necessary to build the extension's native components as
follows:
* Set the following environment variables: * Set the following environment variables:
@ -34,7 +33,11 @@ NDK_PATH="<path to Android NDK>"
HOST_PLATFORM="linux-x86_64" 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: 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 ${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 [top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
[Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html [Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
[#2781]: https://github.com/google/ExoPlayer/issues/2781
[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 # # ExoPlayer Flac extension #
## Description ## The Flac extension provides `FlacExtractor` and `LibflacAudioRenderer`, which
use libFLAC (the Flac decoding library) to extract and decode FLAC audio.
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
## Build instructions ## ## Build instructions ##
To use this extension you need to clone the ExoPlayer repository and depend on 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 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 [top level README][].
native components as follows:
In addition, it's necessary to build the extension's native components as
follows:
* Set the following environment variables: * 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 [top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
[Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html [Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
## 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

@ -1,7 +1,5 @@
# ExoPlayer GVR extension # # ExoPlayer GVR extension #
## Description ##
The GVR extension wraps the [Google VR SDK for Android][]. It provides a The GVR extension wraps the [Google VR SDK for Android][]. It provides a
GvrAudioProcessor, which uses [GvrAudioSurround][] to provide binaural rendering GvrAudioProcessor, which uses [GvrAudioSurround][] to provide binaural rendering
of surround sound and ambisonic soundfields. of surround sound and ambisonic soundfields.
@ -11,17 +9,7 @@ of surround sound and ambisonic soundfields.
## Getting the extension ## ## Getting the extension ##
The easiest way to use the extension is to add it as a gradle dependency. You The easiest way to use the extension is to add it as a gradle dependency:
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:
```gradle ```gradle
compile 'com.google.android.exoplayer:extension-gvr:rX.X.X' 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 ## ## Using the extension ##
* If using SimpleExoPlayer, override SimpleExoPlayer.buildAudioProcessors to * If using `DefaultRenderersFactory`, override
return a GvrAudioProcessor. `DefaultRenderersFactory.buildAudioProcessors` to return a
* If constructing renderers directly, pass a GvrAudioProcessor to `GvrAudioProcessor`.
MediaCodecAudioRenderer's constructor. * If constructing renderers directly, pass a `GvrAudioProcessor` to
`MediaCodecAudioRenderer`'s constructor.
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md [top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
## 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 # # ExoPlayer IMA extension #
## Description ##
The IMA extension is a [MediaSource][] implementation wrapping the The IMA extension is a [MediaSource][] implementation wrapping the
[Interactive Media Ads SDK for Android][IMA]. You can use it to insert ads [Interactive Media Ads SDK for Android][IMA]. You can use it to insert ads
alongside content. alongside content.
@ -55,3 +53,10 @@ playback.
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md [top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
[sample ad tags]: https://developers.google.com/interactive-media-ads/docs/sdks/android/tags [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

@ -1,3 +1,16 @@
// 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 from: '../../constants.gradle'
apply plugin: 'com.android.library' apply plugin: 'com.android.library'

View File

@ -430,7 +430,9 @@ public final class ImaAdsLoader implements Player.EventListener, VideoAdPlayer,
} else if (!playingAd) { } else if (!playingAd) {
return VideoProgressUpdate.VIDEO_TIME_NOT_READY; return VideoProgressUpdate.VIDEO_TIME_NOT_READY;
} else { } else {
return new VideoProgressUpdate(player.getCurrentPosition(), player.getDuration()); long adDuration = player.getDuration();
return adDuration == C.TIME_UNSET ? VideoProgressUpdate.VIDEO_TIME_NOT_READY
: new VideoProgressUpdate(player.getCurrentPosition(), adDuration);
} }
} }

View File

@ -1,11 +1,9 @@
# ExoPlayer MediaSession extension # # ExoPlayer MediaSession extension #
## Description ## The MediaSession extension mediates between a Player (or ExoPlayer) instance
and a [MediaSession][]. It automatically retrieves and implements playback
The MediaSession extension mediates between an ExoPlayer instance and a actions and syncs the player state with the state of the media session. The
[MediaSession][]. It automatically retrieves and implements playback actions behaviour can be extended to support other playback and custom 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 [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][].
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md [top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
## 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

@ -47,7 +47,7 @@ import java.util.Map;
* Connects a {@link MediaSessionCompat} to a {@link Player}. * Connects a {@link MediaSessionCompat} to a {@link Player}.
* <p> * <p>
* The connector listens for actions sent by the media session's controller and implements these * 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 * automatically synced with the player. The connector can also be optionally extended by providing
* various collaborators: * various collaborators:
* <ul> * <ul>
@ -73,6 +73,10 @@ public final class MediaSessionConnector {
} }
public static final String EXTRAS_PITCH = "EXO_PITCH"; 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. * Interface to which playback preparation actions are delegated.
@ -318,7 +322,6 @@ public final class MediaSessionConnector {
private Player player; private Player player;
private CustomActionProvider[] customActionProviders; private CustomActionProvider[] customActionProviders;
private int currentWindowIndex;
private Map<String, CustomActionProvider> customActionMap; private Map<String, CustomActionProvider> customActionMap;
private ErrorMessageProvider errorMessageProvider; private ErrorMessageProvider errorMessageProvider;
private PlaybackPreparer playbackPreparer; private PlaybackPreparer playbackPreparer;
@ -369,8 +372,7 @@ public final class MediaSessionConnector {
this.handler = new Handler(Looper.myLooper() != null ? Looper.myLooper() this.handler = new Handler(Looper.myLooper() != null ? Looper.myLooper()
: Looper.getMainLooper()); : Looper.getMainLooper());
this.doMaintainMetadata = doMaintainMetadata; this.doMaintainMetadata = doMaintainMetadata;
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS mediaSession.setFlags(BASE_MEDIA_SESSION_FLAGS);
| MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mediaController = mediaSession.getController(); mediaController = mediaSession.getController();
mediaSessionCallback = new MediaSessionCallback(); mediaSessionCallback = new MediaSessionCallback();
exoPlayerEventListener = new ExoPlayerEventListener(); exoPlayerEventListener = new ExoPlayerEventListener();
@ -433,6 +435,8 @@ public final class MediaSessionConnector {
*/ */
public void setQueueEditor(QueueEditor queueEditor) { public void setQueueEditor(QueueEditor queueEditor) {
this.queueEditor = queueEditor; this.queueEditor = queueEditor;
mediaSession.setFlags(queueEditor == null ? BASE_MEDIA_SESSION_FLAGS
: EDITOR_MEDIA_SESSION_FLAGS);
} }
private void updateMediaSessionPlaybackState() { private void updateMediaSessionPlaybackState() {
@ -583,11 +587,20 @@ public final class MediaSessionConnector {
private class ExoPlayerEventListener implements Player.EventListener { private class ExoPlayerEventListener implements Player.EventListener {
private int currentWindowIndex;
private int currentWindowCount;
@Override @Override
public void onTimelineChanged(Timeline timeline, Object manifest) { public void onTimelineChanged(Timeline timeline, Object manifest) {
if (queueNavigator != null) { if (queueNavigator != null) {
queueNavigator.onTimelineChanged(player); 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(); currentWindowIndex = player.getCurrentWindowIndex();
updateMediaSessionMetadata(); updateMediaSessionMetadata();
} }

View File

@ -1,7 +1,5 @@
# ExoPlayer OkHttp extension # # ExoPlayer OkHttp extension #
## Description ##
The OkHttp extension is an [HttpDataSource][] implementation using Square's The OkHttp extension is an [HttpDataSource][] implementation using Square's
[OkHttp][]. [OkHttp][].
@ -49,3 +47,10 @@ new DefaultDataSourceFactory(
new OkHttpDataSourceFactory(...) /* baseDataSourceFactory argument */); new OkHttpDataSourceFactory(...) /* baseDataSourceFactory argument */);
``` ```
respectively. 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 # # ExoPlayer Opus extension #
## Description ## The Opus extension provides `LibopusAudioRenderer`, which uses libopus (the Opus
decoding library) to decode Opus audio.
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
## Build instructions ## ## Build instructions ##
To use this extension you need to clone the ExoPlayer repository and depend on 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 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 [top level README][].
native components as follows:
In addition, it's necessary to build the extension's native components as
follows:
* Set the following environment variables: * Set the following environment variables:
@ -59,3 +56,38 @@ ${NDK_PATH}/ndk-build APP_ABI=all -j4
* Clean and re-build the project. * Clean and re-build the project.
* If you want to use your own version of libopus, place it in * If you want to use your own version of libopus, place it in
`${OPUS_EXT_PATH}/jni/libopus`. `${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

@ -1,7 +1,5 @@
# ExoPlayer RTMP extension # # ExoPlayer RTMP extension #
## Description ##
The RTMP extension is a [DataSource][] implementation for playing [RTMP][] The RTMP extension is a [DataSource][] implementation for playing [RTMP][]
streams using [LibRtmp Client for Android][]. 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 [RTMP]: https://en.wikipedia.org/wiki/Real-Time_Messaging_Protocol
[LibRtmp Client for Android]: https://github.com/ant-media/LibRtmp-Client-for-Android [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: 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][].
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md [top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
## 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

@ -26,7 +26,7 @@ android {
dependencies { dependencies {
compile project(modulePrefix + 'library-core') compile project(modulePrefix + 'library-core')
compile 'net.butterflytv.utils:rtmp-client:0.2.8' compile 'net.butterflytv.utils:rtmp-client:3.0.0'
} }
ext { ext {

View File

@ -1,19 +1,16 @@
# ExoPlayer VP9 extension # # ExoPlayer VP9 extension #
## Description ## The VP9 extension provides `LibvpxVideoRenderer`, which uses libvpx (the VPx
decoding library) to decode VP9 video.
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
## Build instructions ## ## Build instructions ##
To use this extension you need to clone the ExoPlayer repository and depend on 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 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 [top level README][].
native components as follows:
In addition, it's necessary to build the extension's native components as
follows:
* Set the following environment variables: * 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 `${VP9_EXT_PATH}/jni/libvpx` or `${VP9_EXT_PATH}/jni/libyuv` respectively. But
please note that `generate_libvpx_android_configs.sh` and the makefiles need please note that `generate_libvpx_android_configs.sh` and the makefiles need
to be modified to work with arbitrary versions of libvpx and libyuv. to be modified to work with arbitrary versions of libvpx and libyuv.
## 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

@ -197,12 +197,12 @@ DECODER_FUNC(jint, vpxGetFrame, jlong jContext, jobject jOutputBuffer) {
const int32_t uvHeight = (img->d_h + 1) / 2; const int32_t uvHeight = (img->d_h + 1) / 2;
const uint64_t yLength = img->stride[VPX_PLANE_Y] * img->d_h; const uint64_t yLength = img->stride[VPX_PLANE_Y] * img->d_h;
const uint64_t uvLength = img->stride[VPX_PLANE_U] * uvHeight; const uint64_t uvLength = img->stride[VPX_PLANE_U] * uvHeight;
int sample = 0;
if (img->fmt == VPX_IMG_FMT_I42016) { // HBD planar 420. if (img->fmt == VPX_IMG_FMT_I42016) { // HBD planar 420.
// Note: The stride for BT2020 is twice of what we use so this is wasting // Note: The stride for BT2020 is twice of what we use so this is wasting
// memory. The long term goal however is to upload half-float/short so // memory. The long term goal however is to upload half-float/short so
// it's not important to optimize the stride at this time. // it's not important to optimize the stride at this time.
// Y // Y
int sampleY = 0;
for (int y = 0; y < img->d_h; y++) { for (int y = 0; y < img->d_h; y++) {
const uint16_t* srcBase = reinterpret_cast<uint16_t*>( const uint16_t* srcBase = reinterpret_cast<uint16_t*>(
img->planes[VPX_PLANE_Y] + img->stride[VPX_PLANE_Y] * y); img->planes[VPX_PLANE_Y] + img->stride[VPX_PLANE_Y] * y);
@ -210,12 +210,14 @@ DECODER_FUNC(jint, vpxGetFrame, jlong jContext, jobject jOutputBuffer) {
for (int x = 0; x < img->d_w; x++) { for (int x = 0; x < img->d_w; x++) {
// Lightweight dither. Carryover the remainder of each 10->8 bit // Lightweight dither. Carryover the remainder of each 10->8 bit
// conversion to the next pixel. // conversion to the next pixel.
sample += *srcBase++; sampleY += *srcBase++;
*destBase++ = sample >> 2; *destBase++ = sampleY >> 2;
sample = sample & 3; // Remainder. sampleY = sampleY & 3; // Remainder.
} }
} }
// UV // UV
int sampleU = 0;
int sampleV = 0;
const int32_t uvWidth = (img->d_w + 1) / 2; const int32_t uvWidth = (img->d_w + 1) / 2;
for (int y = 0; y < uvHeight; y++) { for (int y = 0; y < uvHeight; y++) {
const uint16_t* srcUBase = reinterpret_cast<uint16_t*>( const uint16_t* srcUBase = reinterpret_cast<uint16_t*>(
@ -228,11 +230,12 @@ DECODER_FUNC(jint, vpxGetFrame, jlong jContext, jobject jOutputBuffer) {
for (int x = 0; x < uvWidth; x++) { for (int x = 0; x < uvWidth; x++) {
// Lightweight dither. Carryover the remainder of each 10->8 bit // Lightweight dither. Carryover the remainder of each 10->8 bit
// conversion to the next pixel. // conversion to the next pixel.
sample += *srcUBase++; sampleU += *srcUBase++;
*destUBase++ = sample >> 2; *destUBase++ = sampleU >> 2;
sample = (*srcVBase++) + (sample & 3); // srcV + previousRemainder. sampleU = sampleU & 3; // Remainder.
*destVBase++ = sample >> 2; sampleV += *srcVBase++;
sample = sample & 3; // Remainder. *destVBase++ = sampleV >> 2;
sampleV = sampleV & 3; // Remainder.
} }
} }
} else { } else {

View File

@ -1,6 +1,6 @@
#Wed Jul 12 10:31:13 BST 2017 #Tue Sep 05 13:43:42 BST 2017
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip

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

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

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

@ -16,7 +16,6 @@
package com.google.android.exoplayer2.extractor.mp4; package com.google.android.exoplayer2.extractor.mp4;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.testutil.ExtractorAsserts; import com.google.android.exoplayer2.testutil.ExtractorAsserts;
import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory;
@ -38,11 +37,6 @@ public final class FragmentedMp4ExtractorTest extends InstrumentationTestCase {
"mp4/sample_fragmented_sei.mp4", getInstrumentation()); "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() { private static ExtractorFactory getExtractorFactory() {
return getExtractorFactory(0); return getExtractorFactory(0);
} }

View File

@ -31,6 +31,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
private static final String TYPICAL_MISSING_TIMECODE = "subrip/typical_missing_timecode"; private static final String TYPICAL_MISSING_TIMECODE = "subrip/typical_missing_timecode";
private static final String TYPICAL_MISSING_SEQUENCE = "subrip/typical_missing_sequence"; private static final String TYPICAL_MISSING_SEQUENCE = "subrip/typical_missing_sequence";
private static final String TYPICAL_NEGATIVE_TIMESTAMPS = "subrip/typical_negative_timestamps"; private static final String TYPICAL_NEGATIVE_TIMESTAMPS = "subrip/typical_negative_timestamps";
private static final String TYPICAL_UNEXPECTED_END = "subrip/typical_unexpected_end";
private static final String NO_END_TIMECODES_FILE = "subrip/no_end_timecodes"; private static final String NO_END_TIMECODES_FILE = "subrip/no_end_timecodes";
public void testDecodeEmpty() throws IOException { public void testDecodeEmpty() throws IOException {
@ -107,6 +108,17 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
assertTypicalCue3(subtitle, 0); assertTypicalCue3(subtitle, 0);
} }
public void testDecodeTypicalUnexpectedEnd() throws IOException {
// Parsing should succeed, parsing the first and second cues only.
SubripDecoder decoder = new SubripDecoder();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_UNEXPECTED_END);
SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false);
assertEquals(4, subtitle.getEventTimeCount());
assertTypicalCue1(subtitle, 0);
assertTypicalCue2(subtitle, 2);
}
public void testDecodeNoEndTimecodes() throws IOException { public void testDecodeNoEndTimecodes() throws IOException {
SubripDecoder decoder = new SubripDecoder(); SubripDecoder decoder = new SubripDecoder();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), NO_END_TIMECODES_FILE); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), NO_END_TIMECODES_FILE);

View File

@ -23,9 +23,12 @@ import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import android.test.MoreAsserts;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
/** /**
* Unit tests for {@link ContentDataSource}. * Unit tests for {@link ContentDataSource}.
@ -35,6 +38,9 @@ public final class ContentDataSourceTest extends InstrumentationTestCase {
private static final String AUTHORITY = "com.google.android.exoplayer2.core.test"; private static final String AUTHORITY = "com.google.android.exoplayer2.core.test";
private static final String DATA_PATH = "binary/1024_incrementing_bytes.mp3"; private static final String DATA_PATH = "binary/1024_incrementing_bytes.mp3";
private static final int TEST_DATA_OFFSET = 1;
private static final int TEST_DATA_LENGTH = 1023;
public void testReadValidUri() throws Exception { public void testReadValidUri() throws Exception {
ContentDataSource dataSource = new ContentDataSource(getInstrumentation().getContext()); ContentDataSource dataSource = new ContentDataSource(getInstrumentation().getContext());
Uri contentUri = new Uri.Builder() Uri contentUri = new Uri.Builder()
@ -64,6 +70,27 @@ public final class ContentDataSourceTest extends InstrumentationTestCase {
} }
} }
public void testReadFromOffsetToEndOfInput() throws Exception {
ContentDataSource dataSource = new ContentDataSource(getInstrumentation().getContext());
Uri contentUri = new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(AUTHORITY)
.path(DATA_PATH).build();
try {
DataSpec dataSpec = new DataSpec(contentUri, TEST_DATA_OFFSET, C.LENGTH_UNSET, null);
long length = dataSource.open(dataSpec);
assertEquals(TEST_DATA_LENGTH, length);
byte[] expectedData = Arrays.copyOfRange(
TestUtil.getByteArray(getInstrumentation(), DATA_PATH), TEST_DATA_OFFSET,
TEST_DATA_OFFSET + TEST_DATA_LENGTH);
byte[] readData = TestUtil.readToEnd(dataSource);
MoreAsserts.assertEquals(expectedData, readData);
assertEquals(C.RESULT_END_OF_INPUT, dataSource.read(new byte[1], 0, 1));
} finally {
dataSource.close();
}
}
/** /**
* A {@link ContentProvider} for the test. * A {@link ContentProvider} for the test.
*/ */

View File

@ -319,8 +319,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
if (timeline.isEmpty() || pendingSeekAcks > 0) { if (timeline.isEmpty() || pendingSeekAcks > 0) {
return maskingWindowPositionMs; return maskingWindowPositionMs;
} else { } else {
timeline.getPeriod(playbackInfo.periodId.periodIndex, period); return playbackInfoPositionUsToWindowPositionMs(playbackInfo.positionUs);
return period.getPositionInWindowMs() + C.usToMs(playbackInfo.positionUs);
} }
} }
@ -330,8 +329,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
if (timeline.isEmpty() || pendingSeekAcks > 0) { if (timeline.isEmpty() || pendingSeekAcks > 0) {
return maskingWindowPositionMs; return maskingWindowPositionMs;
} else { } else {
timeline.getPeriod(playbackInfo.periodId.periodIndex, period); return playbackInfoPositionUsToWindowPositionMs(playbackInfo.bufferedPositionUs);
return period.getPositionInWindowMs() + C.usToMs(playbackInfo.bufferedPositionUs);
} }
} }
@ -358,7 +356,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
@Override @Override
public boolean isPlayingAd() { public boolean isPlayingAd() {
return pendingSeekAcks == 0 && playbackInfo.periodId.adGroupIndex != C.INDEX_UNSET; return pendingSeekAcks == 0 && playbackInfo.periodId.isAd();
} }
@Override @Override
@ -448,6 +446,12 @@ import java.util.concurrent.CopyOnWriteArraySet;
case ExoPlayerImplInternal.MSG_SEEK_ACK: { case ExoPlayerImplInternal.MSG_SEEK_ACK: {
if (--pendingSeekAcks == 0) { if (--pendingSeekAcks == 0) {
playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj; playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj;
if (timeline.isEmpty()) {
// Update the masking variables, which are used when the timeline is empty.
maskingPeriodIndex = 0;
maskingWindowIndex = 0;
maskingWindowPositionMs = 0;
}
if (msg.arg1 != 0) { if (msg.arg1 != 0) {
for (Player.EventListener listener : listeners) { for (Player.EventListener listener : listeners) {
listener.onPositionDiscontinuity(); listener.onPositionDiscontinuity();
@ -472,6 +476,12 @@ import java.util.concurrent.CopyOnWriteArraySet;
timeline = sourceInfo.timeline; timeline = sourceInfo.timeline;
manifest = sourceInfo.manifest; manifest = sourceInfo.manifest;
playbackInfo = sourceInfo.playbackInfo; playbackInfo = sourceInfo.playbackInfo;
if (pendingSeekAcks == 0 && timeline.isEmpty()) {
// Update the masking variables, which are used when the timeline is empty.
maskingPeriodIndex = 0;
maskingWindowIndex = 0;
maskingWindowPositionMs = 0;
}
for (Player.EventListener listener : listeners) { for (Player.EventListener listener : listeners) {
listener.onTimelineChanged(timeline, manifest); listener.onTimelineChanged(timeline, manifest);
} }
@ -500,4 +510,13 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
} }
private long playbackInfoPositionUsToWindowPositionMs(long positionUs) {
long positionMs = C.usToMs(positionUs);
if (!playbackInfo.periodId.isAd()) {
timeline.getPeriod(playbackInfo.periodId.periodIndex, period);
positionMs += period.getPositionInWindowMs();
}
return positionMs;
}
} }

View File

@ -263,13 +263,18 @@ import java.io.IOException;
} }
int messageNumber = customMessagesSent++; int messageNumber = customMessagesSent++;
handler.obtainMessage(MSG_CUSTOM, messages).sendToTarget(); handler.obtainMessage(MSG_CUSTOM, messages).sendToTarget();
boolean wasInterrupted = false;
while (customMessagesProcessed <= messageNumber) { while (customMessagesProcessed <= messageNumber) {
try { try {
wait(); wait();
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); wasInterrupted = true;
} }
} }
if (wasInterrupted) {
// Restore the interrupted status.
Thread.currentThread().interrupt();
}
} }
public synchronized void release() { public synchronized void release() {
@ -277,13 +282,18 @@ import java.io.IOException;
return; return;
} }
handler.sendEmptyMessage(MSG_RELEASE); handler.sendEmptyMessage(MSG_RELEASE);
boolean wasInterrupted = false;
while (!released) { while (!released) {
try { try {
wait(); wait();
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); wasInterrupted = true;
} }
} }
if (wasInterrupted) {
// Restore the interrupted status.
Thread.currentThread().interrupt();
}
internalPlaybackThread.quit(); internalPlaybackThread.quit();
} }

View File

@ -31,13 +31,13 @@ public final class ExoPlayerLibraryInfo {
* The version of the library expressed as a string, for example "1.2.3". * The version of the library expressed as a string, for example "1.2.3".
*/ */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
public static final String VERSION = "2.5.1"; public static final String VERSION = "2.5.2";
/** /**
* The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. * The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}.
*/ */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final String VERSION_SLASHY = "ExoPlayerLib/2.5.1"; public static final String VERSION_SLASHY = "ExoPlayerLib/2.5.2";
/** /**
* The version of the library expressed as an integer, for example 1002003. * The version of the library expressed as an integer, for example 1002003.
@ -47,7 +47,7 @@ public final class ExoPlayerLibraryInfo {
* integer version 123045006 (123-045-006). * integer version 123045006 (123-045-006).
*/ */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final int VERSION_INT = 2005001; public static final int VERSION_INT = 2005002;
/** /**
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions} * Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}

View File

@ -24,7 +24,7 @@ public interface RendererCapabilities {
/** /**
* A mask to apply to the result of {@link #supportsFormat(Format)} to obtain one of * A mask to apply to the result of {@link #supportsFormat(Format)} to obtain one of
* {@link #FORMAT_HANDLED}, {@link #FORMAT_EXCEEDS_CAPABILITIES}, * {@link #FORMAT_HANDLED}, {@link #FORMAT_EXCEEDS_CAPABILITIES}, {@link #FORMAT_UNSUPPORTED_DRM},
* {@link #FORMAT_UNSUPPORTED_SUBTYPE} and {@link #FORMAT_UNSUPPORTED_TYPE}. * {@link #FORMAT_UNSUPPORTED_SUBTYPE} and {@link #FORMAT_UNSUPPORTED_TYPE}.
*/ */
int FORMAT_SUPPORT_MASK = 0b111; int FORMAT_SUPPORT_MASK = 0b111;
@ -117,8 +117,8 @@ public interface RendererCapabilities {
* the bitwise OR of three properties: * the bitwise OR of three properties:
* <ul> * <ul>
* <li>The level of support for the format itself. One of {@link #FORMAT_HANDLED}, * <li>The level of support for the format itself. One of {@link #FORMAT_HANDLED},
* {@link #FORMAT_EXCEEDS_CAPABILITIES}, {@link #FORMAT_UNSUPPORTED_SUBTYPE} and * {@link #FORMAT_EXCEEDS_CAPABILITIES}, {@link #FORMAT_UNSUPPORTED_DRM},
* {@link #FORMAT_UNSUPPORTED_TYPE}.</li> * {@link #FORMAT_UNSUPPORTED_SUBTYPE} and {@link #FORMAT_UNSUPPORTED_TYPE}.</li>
* <li>The level of support for adapting from the format to another format of the same mime type. * <li>The level of support for adapting from the format to another format of the same mime type.
* One of {@link #ADAPTIVE_SEAMLESS}, {@link #ADAPTIVE_NOT_SEAMLESS} and * One of {@link #ADAPTIVE_SEAMLESS}, {@link #ADAPTIVE_NOT_SEAMLESS} and
* {@link #ADAPTIVE_NOT_SUPPORTED}.</li> * {@link #ADAPTIVE_NOT_SUPPORTED}.</li>

View File

@ -41,6 +41,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.google.android.exoplayer2.video.VideoRendererEventListener;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;
/** /**
* An {@link ExoPlayer} implementation that uses default {@link Renderer} components. Instances can * An {@link ExoPlayer} implementation that uses default {@link Renderer} components. Instances can
@ -87,6 +88,9 @@ public class SimpleExoPlayer implements ExoPlayer {
private final ExoPlayer player; private final ExoPlayer player;
private final ComponentListener componentListener; private final ComponentListener componentListener;
private final CopyOnWriteArraySet<VideoListener> videoListeners;
private final CopyOnWriteArraySet<TextRenderer.Output> textOutputs;
private final CopyOnWriteArraySet<MetadataRenderer.Output> metadataOutputs;
private final int videoRendererCount; private final int videoRendererCount;
private final int audioRendererCount; private final int audioRendererCount;
@ -99,9 +103,6 @@ public class SimpleExoPlayer implements ExoPlayer {
private int videoScalingMode; private int videoScalingMode;
private SurfaceHolder surfaceHolder; private SurfaceHolder surfaceHolder;
private TextureView textureView; private TextureView textureView;
private TextRenderer.Output textOutput;
private MetadataRenderer.Output metadataOutput;
private VideoListener videoListener;
private AudioRendererEventListener audioDebugListener; private AudioRendererEventListener audioDebugListener;
private VideoRendererEventListener videoDebugListener; private VideoRendererEventListener videoDebugListener;
private DecoderCounters videoDecoderCounters; private DecoderCounters videoDecoderCounters;
@ -113,6 +114,9 @@ public class SimpleExoPlayer implements ExoPlayer {
protected SimpleExoPlayer(RenderersFactory renderersFactory, TrackSelector trackSelector, protected SimpleExoPlayer(RenderersFactory renderersFactory, TrackSelector trackSelector,
LoadControl loadControl) { LoadControl loadControl) {
componentListener = new ComponentListener(); componentListener = new ComponentListener();
videoListeners = new CopyOnWriteArraySet<>();
textOutputs = new CopyOnWriteArraySet<>();
metadataOutputs = new CopyOnWriteArraySet<>();
Looper eventLooper = Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper(); Looper eventLooper = Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper();
Handler eventHandler = new Handler(eventLooper); Handler eventHandler = new Handler(eventLooper);
renderers = renderersFactory.createRenderers(eventHandler, componentListener, componentListener, renderers = renderersFactory.createRenderers(eventHandler, componentListener, componentListener,
@ -440,63 +444,132 @@ public class SimpleExoPlayer implements ExoPlayer {
} }
/** /**
* Sets a listener to receive video events. * Adds a listener to receive video events.
*
* @param listener The listener to register.
*/
public void addVideoListener(VideoListener listener) {
videoListeners.add(listener);
}
/**
* Removes a listener of video events.
*
* @param listener The listener to unregister.
*/
public void removeVideoListener(VideoListener listener) {
videoListeners.remove(listener);
}
/**
* Sets a listener to receive video events, removing all existing listeners.
* *
* @param listener The listener. * @param listener The listener.
* @deprecated Use {@link #addVideoListener(VideoListener)}.
*/ */
@Deprecated
public void setVideoListener(VideoListener listener) { public void setVideoListener(VideoListener listener) {
videoListener = listener; videoListeners.clear();
if (listener != null) {
addVideoListener(listener);
}
} }
/** /**
* Clears the listener receiving video events if it matches the one passed. Else does nothing. * Equivalent to {@link #removeVideoListener(VideoListener)}.
* *
* @param listener The listener to clear. * @param listener The listener to clear.
* @deprecated Use {@link #removeVideoListener(VideoListener)}.
*/ */
@Deprecated
public void clearVideoListener(VideoListener listener) { public void clearVideoListener(VideoListener listener) {
if (videoListener == listener) { removeVideoListener(listener);
videoListener = null;
}
} }
/** /**
* Sets an output to receive text events. * Registers an output to receive text events.
*
* @param listener The output to register.
*/
public void addTextOutput(TextRenderer.Output listener) {
textOutputs.add(listener);
}
/**
* Removes a text output.
*
* @param listener The output to remove.
*/
public void removeTextOutput(TextRenderer.Output listener) {
textOutputs.remove(listener);
}
/**
* Sets an output to receive text events, removing all existing outputs.
* *
* @param output The output. * @param output The output.
* @deprecated Use {@link #addTextOutput(TextRenderer.Output)}.
*/ */
@Deprecated
public void setTextOutput(TextRenderer.Output output) { public void setTextOutput(TextRenderer.Output output) {
textOutput = output; textOutputs.clear();
} if (output != null) {
addTextOutput(output);
/**
* Clears the output receiving text events if it matches the one passed. Else does nothing.
*
* @param output The output to clear.
*/
public void clearTextOutput(TextRenderer.Output output) {
if (textOutput == output) {
textOutput = null;
} }
} }
/** /**
* Sets a listener to receive metadata events. * Equivalent to {@link #removeTextOutput(TextRenderer.Output)}.
*
* @param output The output to clear.
* @deprecated Use {@link #removeTextOutput(TextRenderer.Output)}.
*/
@Deprecated
public void clearTextOutput(TextRenderer.Output output) {
removeTextOutput(output);
}
/**
* Registers an output to receive metadata events.
*
* @param listener The output to register.
*/
public void addMetadataOutput(MetadataRenderer.Output listener) {
metadataOutputs.add(listener);
}
/**
* Removes a metadata output.
*
* @param listener The output to remove.
*/
public void removeMetadataOutput(MetadataRenderer.Output listener) {
metadataOutputs.remove(listener);
}
/**
* Sets an output to receive metadata events, removing all existing outputs.
* *
* @param output The output. * @param output The output.
* @deprecated Use {@link #addMetadataOutput(MetadataRenderer.Output)}.
*/ */
@Deprecated
public void setMetadataOutput(MetadataRenderer.Output output) { public void setMetadataOutput(MetadataRenderer.Output output) {
metadataOutput = output; metadataOutputs.clear();
if (output != null) {
addMetadataOutput(output);
}
} }
/** /**
* Clears the output receiving metadata events if it matches the one passed. Else does nothing. * Equivalent to {@link #removeMetadataOutput(MetadataRenderer.Output)}.
* *
* @param output The output to clear. * @param output The output to clear.
* @deprecated Use {@link #removeMetadataOutput(MetadataRenderer.Output)}.
*/ */
@Deprecated
public void clearMetadataOutput(MetadataRenderer.Output output) { public void clearMetadataOutput(MetadataRenderer.Output output) {
if (metadataOutput == output) { removeMetadataOutput(output);
metadataOutput = null;
}
} }
/** /**
@ -803,7 +876,7 @@ public class SimpleExoPlayer implements ExoPlayer {
@Override @Override
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
float pixelWidthHeightRatio) { float pixelWidthHeightRatio) {
if (videoListener != null) { for (VideoListener videoListener : videoListeners) {
videoListener.onVideoSizeChanged(width, height, unappliedRotationDegrees, videoListener.onVideoSizeChanged(width, height, unappliedRotationDegrees,
pixelWidthHeightRatio); pixelWidthHeightRatio);
} }
@ -815,8 +888,10 @@ public class SimpleExoPlayer implements ExoPlayer {
@Override @Override
public void onRenderedFirstFrame(Surface surface) { public void onRenderedFirstFrame(Surface surface) {
if (videoListener != null && SimpleExoPlayer.this.surface == surface) { if (SimpleExoPlayer.this.surface == surface) {
videoListener.onRenderedFirstFrame(); for (VideoListener videoListener : videoListeners) {
videoListener.onRenderedFirstFrame();
}
} }
if (videoDebugListener != null) { if (videoDebugListener != null) {
videoDebugListener.onRenderedFirstFrame(surface); videoDebugListener.onRenderedFirstFrame(surface);
@ -889,7 +964,7 @@ public class SimpleExoPlayer implements ExoPlayer {
@Override @Override
public void onCues(List<Cue> cues) { public void onCues(List<Cue> cues) {
if (textOutput != null) { for (TextRenderer.Output textOutput : textOutputs) {
textOutput.onCues(cues); textOutput.onCues(cues);
} }
} }
@ -898,7 +973,7 @@ public class SimpleExoPlayer implements ExoPlayer {
@Override @Override
public void onMetadata(Metadata metadata) { public void onMetadata(Metadata metadata) {
if (metadataOutput != null) { for (MetadataRenderer.Output metadataOutput : metadataOutputs) {
metadataOutput.onMetadata(metadata); metadataOutput.onMetadata(metadata);
} }
} }

View File

@ -593,30 +593,6 @@ public abstract class Timeline {
} }
} }
/**
* Returns whether the given window is the last window of the timeline depending on the
* {@code repeatMode}.
*
* @param windowIndex A window index.
* @param repeatMode A repeat mode.
* @return Whether the window of the given index is the last window of the timeline.
*/
public final boolean isLastWindow(int windowIndex, @Player.RepeatMode int repeatMode) {
return getNextWindowIndex(windowIndex, repeatMode) == C.INDEX_UNSET;
}
/**
* Returns whether the given window is the first window of the timeline depending on the
* {@code repeatMode}.
*
* @param windowIndex A window index.
* @param repeatMode A repeat mode.
* @return Whether the window of the given index is the first window of the timeline.
*/
public final boolean isFirstWindow(int windowIndex, @Player.RepeatMode int repeatMode) {
return getPreviousWindowIndex(windowIndex, repeatMode) == C.INDEX_UNSET;
}
/** /**
* Populates a {@link Window} with data for the window at the specified index. Does not populate * Populates a {@link Window} with data for the window at the specified index. Does not populate
* {@link Window#id}. * {@link Window#id}.

View File

@ -241,7 +241,7 @@ import java.util.Arrays;
for (int i = 0; i < period; i++) { for (int i = 0; i < period; i++) {
short sVal = samples[position + i]; short sVal = samples[position + i];
short pVal = samples[position + period + i]; short pVal = samples[position + period + i];
diff += sVal >= pVal ? sVal - pVal : pVal - sVal; diff += Math.abs(sVal - pVal);
} }
// Note that the highest number of samples we add into diff will be less than 256, since we // Note that the highest number of samples we add into diff will be less than 256, since we
// skip samples. Thus, diff is a 24 bit number, and we can safely multiply by numSamples // skip samples. Thus, diff is a 24 bit number, and we can safely multiply by numSamples

View File

@ -1,20 +1,37 @@
/*
* 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.drm; package com.google.android.exoplayer2.drm;
/** /**
* An exception when doing drm decryption using the In-App Drm * Thrown when a non-platform component fails to decrypt data.
*/ */
public class DecryptionException extends Exception { public class DecryptionException extends Exception {
private final int errorCode;
/**
* A component specific error code.
*/
public final int errorCode;
/**
* @param errorCode A component specific error code.
* @param message The detail message.
*/
public DecryptionException(int errorCode, String message) { public DecryptionException(int errorCode, String message) {
super(message); super(message);
this.errorCode = errorCode; this.errorCode = errorCode;
} }
/**
* Get error code
*/
public int getErrorCode() {
return errorCode;
}
} }

View File

@ -28,7 +28,9 @@ import java.util.Map;
@TargetApi(16) @TargetApi(16)
public interface DrmSession<T extends ExoMediaCrypto> { public interface DrmSession<T extends ExoMediaCrypto> {
/** Wraps the throwable which is the cause of the error state. */ /**
* Wraps the throwable which is the cause of the error state.
*/
class DrmSessionException extends Exception { class DrmSessionException extends Exception {
public DrmSessionException(Throwable cause) { public DrmSessionException(Throwable cause) {

View File

@ -24,7 +24,6 @@ import com.google.android.exoplayer2.drm.ExoMediaDrm.ProvisionRequest;
import com.google.android.exoplayer2.upstream.DataSourceInputStream; import com.google.android.exoplayer2.upstream.DataSourceInputStream;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource.Factory;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
@ -39,33 +38,33 @@ import java.util.UUID;
public final class HttpMediaDrmCallback implements MediaDrmCallback { public final class HttpMediaDrmCallback implements MediaDrmCallback {
private final HttpDataSource.Factory dataSourceFactory; private final HttpDataSource.Factory dataSourceFactory;
private final String defaultUrl; private final String defaultLicenseUrl;
private final boolean forceDefaultLicenseUrl;
private final Map<String, String> keyRequestProperties; private final Map<String, String> keyRequestProperties;
/** /**
* @param defaultUrl The default license URL. * @param defaultLicenseUrl The default license URL. Used for key requests that do not specify
* their own license URL.
* @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances. * @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
*/ */
public HttpMediaDrmCallback(String defaultUrl, HttpDataSource.Factory dataSourceFactory) { public HttpMediaDrmCallback(String defaultLicenseUrl, HttpDataSource.Factory dataSourceFactory) {
this(defaultUrl, dataSourceFactory, null); this(defaultLicenseUrl, false, dataSourceFactory);
} }
/** /**
* @deprecated Use {@link HttpMediaDrmCallback#HttpMediaDrmCallback(String, Factory)}. Request * @param defaultLicenseUrl The default license URL. Used for key requests that do not specify
* properties can be set by calling {@link #setKeyRequestProperty(String, String)}. * their own license URL, or for all key requests if {@code forceDefaultLicenseUrl} is
* @param defaultUrl The default license URL. * set to true.
* @param forceDefaultLicenseUrl Whether to use {@code defaultLicenseUrl} for key requests that
* include their own license URL.
* @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances. * @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
* @param keyRequestProperties Request properties to set when making key requests, or null.
*/ */
@Deprecated public HttpMediaDrmCallback(String defaultLicenseUrl, boolean forceDefaultLicenseUrl,
public HttpMediaDrmCallback(String defaultUrl, HttpDataSource.Factory dataSourceFactory, HttpDataSource.Factory dataSourceFactory) {
Map<String, String> keyRequestProperties) {
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.defaultUrl = defaultUrl; this.defaultLicenseUrl = defaultLicenseUrl;
this.forceDefaultLicenseUrl = forceDefaultLicenseUrl;
this.keyRequestProperties = new HashMap<>(); this.keyRequestProperties = new HashMap<>();
if (keyRequestProperties != null) {
this.keyRequestProperties.putAll(keyRequestProperties);
}
} }
/** /**
@ -112,8 +111,8 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback {
@Override @Override
public byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws Exception { public byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws Exception {
String url = request.getDefaultUrl(); String url = request.getDefaultUrl();
if (TextUtils.isEmpty(url)) { if (forceDefaultLicenseUrl || TextUtils.isEmpty(url)) {
url = defaultUrl; url = defaultLicenseUrl;
} }
Map<String, String> requestProperties = new HashMap<>(); Map<String, String> requestProperties = new HashMap<>();
// Add standard request properties for supported schemes. // Add standard request properties for supported schemes.

View File

@ -44,23 +44,47 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
* Instantiates a new instance which uses Widevine CDM. Call {@link #release()} when the instance * Instantiates a new instance which uses Widevine CDM. Call {@link #release()} when the instance
* is no longer required. * is no longer required.
* *
* @param licenseUrl The default license URL. * @param defaultLicenseUrl The default license URL. Used for key requests that do not specify
* their own license URL.
* @param httpDataSourceFactory A factory from which to obtain {@link HttpDataSource} instances. * @param httpDataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
* @return A new instance which uses Widevine CDM. * @return A new instance which uses Widevine CDM.
* @throws UnsupportedDrmException If the Widevine DRM scheme is unsupported or cannot be * @throws UnsupportedDrmException If the Widevine DRM scheme is unsupported or cannot be
* instantiated. * instantiated.
*/ */
public static OfflineLicenseHelper<FrameworkMediaCrypto> newWidevineInstance( public static OfflineLicenseHelper<FrameworkMediaCrypto> newWidevineInstance(
String licenseUrl, Factory httpDataSourceFactory) throws UnsupportedDrmException { String defaultLicenseUrl, Factory httpDataSourceFactory)
return newWidevineInstance( throws UnsupportedDrmException {
new HttpMediaDrmCallback(licenseUrl, httpDataSourceFactory), null); return newWidevineInstance(defaultLicenseUrl, false, httpDataSourceFactory, null);
} }
/** /**
* Instantiates a new instance which uses Widevine CDM. Call {@link #release()} when the instance * Instantiates a new instance which uses Widevine CDM. Call {@link #release()} when the instance
* is no longer required. * is no longer required.
* *
* @param callback Performs key and provisioning requests. * @param defaultLicenseUrl The default license URL. Used for key requests that do not specify
* their own license URL.
* @param forceDefaultLicenseUrl Whether to use {@code defaultLicenseUrl} for key requests that
* include their own license URL.
* @param httpDataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
* @return A new instance which uses Widevine CDM.
* @throws UnsupportedDrmException If the Widevine DRM scheme is unsupported or cannot be
* instantiated.
*/
public static OfflineLicenseHelper<FrameworkMediaCrypto> newWidevineInstance(
String defaultLicenseUrl, boolean forceDefaultLicenseUrl, Factory httpDataSourceFactory)
throws UnsupportedDrmException {
return newWidevineInstance(defaultLicenseUrl, forceDefaultLicenseUrl, httpDataSourceFactory,
null);
}
/**
* Instantiates a new instance which uses Widevine CDM. Call {@link #release()} when the instance
* is no longer required.
*
* @param defaultLicenseUrl The default license URL. Used for key requests that do not specify
* their own license URL.
* @param forceDefaultLicenseUrl Whether to use {@code defaultLicenseUrl} for key requests that
* include their own license URL.
* @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument
* to {@link MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null. * to {@link MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null.
* @return A new instance which uses Widevine CDM. * @return A new instance which uses Widevine CDM.
@ -70,9 +94,11 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
* MediaDrmCallback, HashMap, Handler, EventListener) * MediaDrmCallback, HashMap, Handler, EventListener)
*/ */
public static OfflineLicenseHelper<FrameworkMediaCrypto> newWidevineInstance( public static OfflineLicenseHelper<FrameworkMediaCrypto> newWidevineInstance(
MediaDrmCallback callback, HashMap<String, String> optionalKeyRequestParameters) String defaultLicenseUrl, boolean forceDefaultLicenseUrl, Factory httpDataSourceFactory,
HashMap<String, String> optionalKeyRequestParameters)
throws UnsupportedDrmException { throws UnsupportedDrmException {
return new OfflineLicenseHelper<>(FrameworkMediaDrm.newInstance(C.WIDEVINE_UUID), callback, return new OfflineLicenseHelper<>(FrameworkMediaDrm.newInstance(C.WIDEVINE_UUID),
new HttpMediaDrmCallback(defaultLicenseUrl, forceDefaultLicenseUrl, httpDataSourceFactory),
optionalKeyRequestParameters); optionalKeyRequestParameters);
} }
@ -116,9 +142,32 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
optionalKeyRequestParameters, new Handler(handlerThread.getLooper()), eventListener); optionalKeyRequestParameters, new Handler(handlerThread.getLooper()), eventListener);
} }
/** Releases the helper. Should be called when the helper is no longer required. */ /**
public void release() { * @see DefaultDrmSessionManager#getPropertyByteArray
handlerThread.quit(); */
public synchronized byte[] getPropertyByteArray(String key) {
return drmSessionManager.getPropertyByteArray(key);
}
/**
* @see DefaultDrmSessionManager#setPropertyByteArray
*/
public synchronized void setPropertyByteArray(String key, byte[] value) {
drmSessionManager.setPropertyByteArray(key, value);
}
/**
* @see DefaultDrmSessionManager#getPropertyString
*/
public synchronized String getPropertyString(String key) {
return drmSessionManager.getPropertyString(key);
}
/**
* @see DefaultDrmSessionManager#setPropertyString
*/
public synchronized void setPropertyString(String key, String value) {
drmSessionManager.setPropertyString(key, value);
} }
/** /**
@ -186,6 +235,13 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
return licenseDurationRemainingSec; return licenseDurationRemainingSec;
} }
/**
* Releases the helper. Should be called when the helper is no longer required.
*/
public void release() {
handlerThread.quit();
}
private byte[] blockingKeyRequest(@Mode int licenseMode, byte[] offlineLicenseKeySetId, private byte[] blockingKeyRequest(@Mode int licenseMode, byte[] offlineLicenseKeySetId,
DrmInitData drmInitData) throws DrmSessionException { DrmInitData drmInitData) throws DrmSessionException {
DrmSession<T> drmSession = openBlockingKeyRequest(licenseMode, offlineLicenseKeySetId, DrmSession<T> drmSession = openBlockingKeyRequest(licenseMode, offlineLicenseKeySetId,

View File

@ -39,9 +39,14 @@ import java.util.List;
public static final int LONG_HEADER_SIZE = 16; public static final int LONG_HEADER_SIZE = 16;
/** /**
* Value for the first 32 bits of atomSize when the atom size is actually a long value. * Value for the size field in an atom that defines its size in the largesize field.
*/ */
public static final int LONG_SIZE_PREFIX = 1; public static final int DEFINES_LARGE_SIZE = 1;
/**
* Value for the size field in an atom that extends to the end of the file.
*/
public static final int EXTENDS_TO_END_SIZE = 0;
public static final int TYPE_ftyp = Util.getIntegerCodeForString("ftyp"); public static final int TYPE_ftyp = Util.getIntegerCodeForString("ftyp");
public static final int TYPE_avc1 = Util.getIntegerCodeForString("avc1"); public static final int TYPE_avc1 = Util.getIntegerCodeForString("avc1");

View File

@ -995,9 +995,10 @@ import java.util.List;
int objectTypeIndication = parent.readUnsignedByte(); int objectTypeIndication = parent.readUnsignedByte();
String mimeType; String mimeType;
switch (objectTypeIndication) { switch (objectTypeIndication) {
case 0x6B: case 0x60:
mimeType = MimeTypes.AUDIO_MPEG; case 0x61:
return Pair.create(mimeType, null); mimeType = MimeTypes.VIDEO_MPEG2;
break;
case 0x20: case 0x20:
mimeType = MimeTypes.VIDEO_MP4V; mimeType = MimeTypes.VIDEO_MP4V;
break; break;
@ -1007,6 +1008,9 @@ import java.util.List;
case 0x23: case 0x23:
mimeType = MimeTypes.VIDEO_H265; mimeType = MimeTypes.VIDEO_H265;
break; break;
case 0x6B:
mimeType = MimeTypes.AUDIO_MPEG;
return Pair.create(mimeType, null);
case 0x40: case 0x40:
case 0x66: case 0x66:
case 0x67: case 0x67:
@ -1034,8 +1038,8 @@ import java.util.List;
parent.skipBytes(12); parent.skipBytes(12);
// Start of the AudioSpecificConfig. // Start of the DecoderSpecificInfo.
parent.skipBytes(1); // AudioSpecificConfig tag parent.skipBytes(1); // DecoderSpecificInfo tag
int initializationDataSize = parseExpandableClassSize(parent); int initializationDataSize = parseExpandableClassSize(parent);
byte[] initializationData = new byte[initializationDataSize]; byte[] initializationData = new byte[initializationDataSize];
parent.readBytes(initializationData, 0, initializationDataSize); parent.readBytes(initializationData, 0, initializationDataSize);

View File

@ -283,12 +283,22 @@ public final class FragmentedMp4Extractor implements Extractor {
atomType = atomHeader.readInt(); atomType = atomHeader.readInt();
} }
if (atomSize == Atom.LONG_SIZE_PREFIX) { if (atomSize == Atom.DEFINES_LARGE_SIZE) {
// Read the extended atom size. // Read the large size.
int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE; int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE;
input.readFully(atomHeader.data, Atom.HEADER_SIZE, headerBytesRemaining); input.readFully(atomHeader.data, Atom.HEADER_SIZE, headerBytesRemaining);
atomHeaderBytesRead += headerBytesRemaining; atomHeaderBytesRead += headerBytesRemaining;
atomSize = atomHeader.readUnsignedLongToLong(); atomSize = atomHeader.readUnsignedLongToLong();
} else if (atomSize == Atom.EXTENDS_TO_END_SIZE) {
// The atom extends to the end of the file. Note that if the atom is within a container we can
// work out its size even if the input length is unknown.
long endPosition = input.getLength();
if (endPosition == C.LENGTH_UNSET && !containerAtoms.isEmpty()) {
endPosition = containerAtoms.peek().endPosition;
}
if (endPosition != C.LENGTH_UNSET) {
atomSize = endPosition - input.getPosition() + atomHeaderBytesRead;
}
} }
if (atomSize < atomHeaderBytesRead) { if (atomSize < atomHeaderBytesRead) {

View File

@ -205,12 +205,26 @@ public final class Mp4Extractor implements Extractor, SeekMap {
atomType = atomHeader.readInt(); atomType = atomHeader.readInt();
} }
if (atomSize == Atom.LONG_SIZE_PREFIX) { if (atomSize == Atom.DEFINES_LARGE_SIZE) {
// Read the extended atom size. // Read the large size.
int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE; int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE;
input.readFully(atomHeader.data, Atom.HEADER_SIZE, headerBytesRemaining); input.readFully(atomHeader.data, Atom.HEADER_SIZE, headerBytesRemaining);
atomHeaderBytesRead += headerBytesRemaining; atomHeaderBytesRead += headerBytesRemaining;
atomSize = atomHeader.readUnsignedLongToLong(); atomSize = atomHeader.readUnsignedLongToLong();
} else if (atomSize == Atom.EXTENDS_TO_END_SIZE) {
// The atom extends to the end of the file. Note that if the atom is within a container we can
// work out its size even if the input length is unknown.
long endPosition = input.getLength();
if (endPosition == C.LENGTH_UNSET && !containerAtoms.isEmpty()) {
endPosition = containerAtoms.peek().endPosition;
}
if (endPosition != C.LENGTH_UNSET) {
atomSize = endPosition - input.getPosition() + atomHeaderBytesRead;
}
}
if (atomSize < atomHeaderBytesRead) {
throw new ParserException("Atom size less than header length (unsupported).");
} }
if (shouldParseContainerAtom(atomType)) { if (shouldParseContainerAtom(atomType)) {

View File

@ -104,11 +104,18 @@ import java.io.IOException;
input.peekFully(buffer.data, 0, headerSize); input.peekFully(buffer.data, 0, headerSize);
long atomSize = buffer.readUnsignedInt(); long atomSize = buffer.readUnsignedInt();
int atomType = buffer.readInt(); int atomType = buffer.readInt();
if (atomSize == Atom.LONG_SIZE_PREFIX) { if (atomSize == Atom.DEFINES_LARGE_SIZE) {
// Read the large atom size.
headerSize = Atom.LONG_HEADER_SIZE; headerSize = Atom.LONG_HEADER_SIZE;
input.peekFully(buffer.data, Atom.HEADER_SIZE, Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE); input.peekFully(buffer.data, Atom.HEADER_SIZE, Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE);
buffer.setLimit(Atom.LONG_HEADER_SIZE); buffer.setLimit(Atom.LONG_HEADER_SIZE);
atomSize = buffer.readUnsignedLongToLong(); atomSize = buffer.readUnsignedLongToLong();
} else if (atomSize == Atom.EXTENDS_TO_END_SIZE) {
// The atom extends to the end of the file.
long endPosition = input.getLength();
if (endPosition != C.LENGTH_UNSET) {
atomSize = endPosition - input.getPosition() + headerSize;
}
} }
if (atomSize < headerSize) { if (atomSize < headerSize) {

View File

@ -288,9 +288,11 @@ public final class MediaCodecUtil {
return false; return false;
} }
// Work around https://github.com/google/ExoPlayer/issues/1528 // Work around https://github.com/google/ExoPlayer/issues/1528 and
// https://github.com/google/ExoPlayer/issues/3171
if (Util.SDK_INT < 18 && "OMX.MTK.AUDIO.DECODER.AAC".equals(name) if (Util.SDK_INT < 18 && "OMX.MTK.AUDIO.DECODER.AAC".equals(name)
&& "a70".equals(Util.DEVICE)) { && ("a70".equals(Util.DEVICE)
|| ("Xiaomi".equals(Util.MANUFACTURER) && Util.DEVICE.startsWith("HM")))) {
return false; return false;
} }

View File

@ -16,12 +16,15 @@
package com.google.android.exoplayer2.source; package com.google.android.exoplayer2.source;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection;
import java.io.IOException; import java.io.IOException;
/** /**
* A source of a single period of media. * Loads media corresponding to a {@link Timeline.Period}, and allows that media to be read. All
* methods are called on the player's internal playback thread, as described in the
* {@link ExoPlayer} Javadoc.
*/ */
public interface MediaPeriod extends SequenceableLoader { public interface MediaPeriod extends SequenceableLoader {

View File

@ -23,7 +23,19 @@ import com.google.android.exoplayer2.upstream.Allocator;
import java.io.IOException; import java.io.IOException;
/** /**
* A source of media consisting of one or more {@link MediaPeriod}s. * Defines and provides media to be played by an {@link ExoPlayer}. A MediaSource has two main
* responsibilities:
* <ul>
* <li>To provide the player with a {@link Timeline} defining the structure of its media, and to
* provide a new timeline whenever the structure of the media changes. The MediaSource provides
* these timelines by calling {@link Listener#onSourceInfoRefreshed} on the {@link Listener}
* passed to {@link #prepareSource(ExoPlayer, boolean, Listener)}.</li>
* <li>To provide {@link MediaPeriod} instances for the periods in its timeline. MediaPeriods are
* obtained by calling {@link #createPeriod(MediaPeriodId, Allocator)}, and provide a way for the
* player to load and read the media.</li>
* </ul>
* All methods are called on the player's internal playback thread, as described in the
* {@link ExoPlayer} Javadoc.
*/ */
public interface MediaSource { public interface MediaSource {

View File

@ -69,6 +69,11 @@ public final class SubripDecoder extends SimpleSubtitleDecoder {
// Read and parse the timing line. // Read and parse the timing line.
boolean haveEndTimecode = false; boolean haveEndTimecode = false;
currentLine = subripData.readLine(); currentLine = subripData.readLine();
if (currentLine == null) {
Log.w(TAG, "Unexpected end");
break;
}
Matcher matcher = SUBRIP_TIMING_LINE.matcher(currentLine); Matcher matcher = SUBRIP_TIMING_LINE.matcher(currentLine);
if (matcher.matches()) { if (matcher.matches()) {
cueTimesUs.add(parseTimecode(matcher, 1)); cueTimesUs.add(parseTimecode(matcher, 1));

View File

@ -21,6 +21,7 @@ import android.text.Layout.Alignment;
import android.text.Spannable; import android.text.Spannable;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.AbsoluteSizeSpan; import android.text.style.AbsoluteSizeSpan;
import android.text.style.AlignmentSpan; import android.text.style.AlignmentSpan;
import android.text.style.BackgroundColorSpan; import android.text.style.BackgroundColorSpan;
@ -92,19 +93,24 @@ import java.util.regex.Pattern;
/* package */ boolean parseCue(ParsableByteArray webvttData, WebvttCue.Builder builder, /* package */ boolean parseCue(ParsableByteArray webvttData, WebvttCue.Builder builder,
List<WebvttCssStyle> styles) { List<WebvttCssStyle> styles) {
String firstLine = webvttData.readLine(); String firstLine = webvttData.readLine();
if (firstLine == null) {
return false;
}
Matcher cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(firstLine); Matcher cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(firstLine);
if (cueHeaderMatcher.matches()) { if (cueHeaderMatcher.matches()) {
// We have found the timestamps in the first line. No id present. // We have found the timestamps in the first line. No id present.
return parseCue(null, cueHeaderMatcher, webvttData, builder, textBuilder, styles); return parseCue(null, cueHeaderMatcher, webvttData, builder, textBuilder, styles);
} else { }
// The first line is not the timestamps, but could be the cue id. // The first line is not the timestamps, but could be the cue id.
String secondLine = webvttData.readLine(); String secondLine = webvttData.readLine();
cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(secondLine); if (secondLine == null) {
if (cueHeaderMatcher.matches()) { return false;
// We can do the rest of the parsing, including the id. }
return parseCue(firstLine.trim(), cueHeaderMatcher, webvttData, builder, textBuilder, cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(secondLine);
styles); if (cueHeaderMatcher.matches()) {
} // We can do the rest of the parsing, including the id.
return parseCue(firstLine.trim(), cueHeaderMatcher, webvttData, builder, textBuilder,
styles);
} }
return false; return false;
} }
@ -233,7 +239,7 @@ import java.util.regex.Pattern;
// Parse the cue text. // Parse the cue text.
textBuilder.setLength(0); textBuilder.setLength(0);
String line; String line;
while ((line = webvttData.readLine()) != null && !line.isEmpty()) { while (!TextUtils.isEmpty(line = webvttData.readLine())) {
if (textBuilder.length() > 0) { if (textBuilder.length() > 0) {
textBuilder.append("\n"); textBuilder.append("\n");
} }

View File

@ -767,7 +767,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
* @throws ExoPlaybackException If an error occurs while selecting the tracks. * @throws ExoPlaybackException If an error occurs while selecting the tracks.
*/ */
protected TrackSelection selectAudioTrack(TrackGroupArray groups, int[][] formatSupport, protected TrackSelection selectAudioTrack(TrackGroupArray groups, int[][] formatSupport,
Parameters params, TrackSelection.Factory adaptiveTrackSelectionFactory) { Parameters params, TrackSelection.Factory adaptiveTrackSelectionFactory)
throws ExoPlaybackException {
int selectedGroupIndex = C.INDEX_UNSET; int selectedGroupIndex = C.INDEX_UNSET;
int selectedTrackIndex = C.INDEX_UNSET; int selectedTrackIndex = C.INDEX_UNSET;
int selectedTrackScore = 0; int selectedTrackScore = 0;
@ -893,7 +894,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
* @throws ExoPlaybackException If an error occurs while selecting the tracks. * @throws ExoPlaybackException If an error occurs while selecting the tracks.
*/ */
protected TrackSelection selectTextTrack(TrackGroupArray groups, int[][] formatSupport, protected TrackSelection selectTextTrack(TrackGroupArray groups, int[][] formatSupport,
Parameters params) { Parameters params) throws ExoPlaybackException {
TrackGroup selectedGroup = null; TrackGroup selectedGroup = null;
int selectedTrackIndex = 0; int selectedTrackIndex = 0;
int selectedTrackScore = 0; int selectedTrackScore = 0;
@ -960,7 +961,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
* @throws ExoPlaybackException If an error occurs while selecting the tracks. * @throws ExoPlaybackException If an error occurs while selecting the tracks.
*/ */
protected TrackSelection selectOtherTrack(int trackType, TrackGroupArray groups, protected TrackSelection selectOtherTrack(int trackType, TrackGroupArray groups,
int[][] formatSupport, Parameters params) { int[][] formatSupport, Parameters params) throws ExoPlaybackException {
TrackGroup selectedGroup = null; TrackGroup selectedGroup = null;
int selectedTrackIndex = 0; int selectedTrackIndex = 0;
int selectedTrackScore = 0; int selectedTrackScore = 0;

View File

@ -199,6 +199,7 @@ public abstract class MappingTrackSelector extends TrackSelector {
* @param trackIndex The index of the track within the track group. * @param trackIndex The index of the track within the track group.
* @return One of {@link RendererCapabilities#FORMAT_HANDLED}, * @return One of {@link RendererCapabilities#FORMAT_HANDLED},
* {@link RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES}, * {@link RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES},
* {@link RendererCapabilities#FORMAT_UNSUPPORTED_DRM},
* {@link RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE} and * {@link RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE} and
* {@link RendererCapabilities#FORMAT_UNSUPPORTED_TYPE}. * {@link RendererCapabilities#FORMAT_UNSUPPORTED_TYPE}.
*/ */
@ -214,6 +215,7 @@ public abstract class MappingTrackSelector extends TrackSelector {
* Tracks for which {@link #getTrackFormatSupport(int, int, int)} returns * Tracks for which {@link #getTrackFormatSupport(int, int, int)} returns
* {@link RendererCapabilities#FORMAT_HANDLED} are always considered. * {@link RendererCapabilities#FORMAT_HANDLED} are always considered.
* Tracks for which {@link #getTrackFormatSupport(int, int, int)} returns * Tracks for which {@link #getTrackFormatSupport(int, int, int)} returns
* {@link RendererCapabilities#FORMAT_UNSUPPORTED_DRM},
* {@link RendererCapabilities#FORMAT_UNSUPPORTED_TYPE} or * {@link RendererCapabilities#FORMAT_UNSUPPORTED_TYPE} or
* {@link RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE} are never considered. * {@link RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE} are never considered.
* Tracks for which {@link #getTrackFormatSupport(int, int, int)} returns * Tracks for which {@link #getTrackFormatSupport(int, int, int)} returns
@ -615,12 +617,12 @@ public abstract class MappingTrackSelector extends TrackSelector {
/** /**
* Finds the renderer to which the provided {@link TrackGroup} should be mapped. * Finds the renderer to which the provided {@link TrackGroup} should be mapped.
* <p> * <p>
* A {@link TrackGroup} is mapped to the renderer that reports * A {@link TrackGroup} is mapped to the renderer that reports the highest of (listed in
* {@link RendererCapabilities#FORMAT_HANDLED} support for one or more of the tracks in the group, * decreasing order of support) {@link RendererCapabilities#FORMAT_HANDLED},
* or {@link RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES} if no such renderer exists, or * {@link RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES},
* {@link RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE} if again no such renderer exists. In * {@link RendererCapabilities#FORMAT_UNSUPPORTED_DRM} and
* the case that two or more renderers report the same level of support, the renderer with the * {@link RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE}. In the case that two or more renderers
* lowest index is associated. * report the same level of support, the renderer with the lowest index is associated.
* <p> * <p>
* If all renderers report {@link RendererCapabilities#FORMAT_UNSUPPORTED_TYPE} for all of the * If all renderers report {@link RendererCapabilities#FORMAT_UNSUPPORTED_TYPE} for all of the
* tracks in the group, then {@code renderers.length} is returned to indicate that the group was * tracks in the group, then {@code renderers.length} is returned to indicate that the group was

View File

@ -76,8 +76,8 @@ public final class ContentDataSource implements DataSource {
throw new FileNotFoundException("Could not open file descriptor for: " + uri); throw new FileNotFoundException("Could not open file descriptor for: " + uri);
} }
inputStream = new FileInputStream(assetFileDescriptor.getFileDescriptor()); inputStream = new FileInputStream(assetFileDescriptor.getFileDescriptor());
long assertStartOffset = assetFileDescriptor.getStartOffset(); long assetStartOffset = assetFileDescriptor.getStartOffset();
long skipped = inputStream.skip(assertStartOffset + dataSpec.position) - assertStartOffset; long skipped = inputStream.skip(assetStartOffset + dataSpec.position) - assetStartOffset;
if (skipped != dataSpec.position) { if (skipped != dataSpec.position) {
// We expect the skip to be satisfied in full. If it isn't then we're probably trying to // We expect the skip to be satisfied in full. If it isn't then we're probably trying to
// skip beyond the end of the data. // skip beyond the end of the data.
@ -86,8 +86,8 @@ public final class ContentDataSource implements DataSource {
if (dataSpec.length != C.LENGTH_UNSET) { if (dataSpec.length != C.LENGTH_UNSET) {
bytesRemaining = dataSpec.length; bytesRemaining = dataSpec.length;
} else { } else {
bytesRemaining = assetFileDescriptor.getLength(); long assetFileDescriptorLength = assetFileDescriptor.getLength();
if (bytesRemaining == AssetFileDescriptor.UNKNOWN_LENGTH) { if (assetFileDescriptorLength == AssetFileDescriptor.UNKNOWN_LENGTH) {
// The asset must extend to the end of the file. // The asset must extend to the end of the file.
bytesRemaining = inputStream.available(); bytesRemaining = inputStream.available();
if (bytesRemaining == 0) { if (bytesRemaining == 0) {
@ -96,6 +96,8 @@ public final class ContentDataSource implements DataSource {
// case, so treat as unbounded. // case, so treat as unbounded.
bytesRemaining = C.LENGTH_UNSET; bytesRemaining = C.LENGTH_UNSET;
} }
} else {
bytesRemaining = assetFileDescriptorLength - skipped;
} }
} }
} catch (IOException e) { } catch (IOException e) {

View File

@ -64,6 +64,7 @@ import javax.crypto.spec.SecretKeySpec;
private final AtomicFile atomicFile; private final AtomicFile atomicFile;
private final Cipher cipher; private final Cipher cipher;
private final SecretKeySpec secretKeySpec; private final SecretKeySpec secretKeySpec;
private final boolean encrypt;
private boolean changed; private boolean changed;
private ReusableBufferedOutputStream bufferedOutputStream; private ReusableBufferedOutputStream bufferedOutputStream;
@ -80,10 +81,21 @@ import javax.crypto.spec.SecretKeySpec;
* Creates a CachedContentIndex which works on the index file in the given cacheDir. * Creates a CachedContentIndex which works on the index file in the given cacheDir.
* *
* @param cacheDir Directory where the index file is kept. * @param cacheDir Directory where the index file is kept.
* @param secretKey If not null, cache keys will be stored encrypted on filesystem using AES/CBC. * @param secretKey 16 byte AES key for reading and writing the cache index.
* The key must be 16 bytes long.
*/ */
public CachedContentIndex(File cacheDir, byte[] secretKey) { public CachedContentIndex(File cacheDir, byte[] secretKey) {
this(cacheDir, secretKey, secretKey != null);
}
/**
* Creates a CachedContentIndex which works on the index file in the given cacheDir.
*
* @param cacheDir Directory where the index file is kept.
* @param secretKey 16 byte AES key for reading, and optionally writing, the cache index.
* @param encrypt When false, a plaintext index will be written.
*/
public CachedContentIndex(File cacheDir, byte[] secretKey, boolean encrypt) {
this.encrypt = encrypt;
if (secretKey != null) { if (secretKey != null) {
Assertions.checkArgument(secretKey.length == 16); Assertions.checkArgument(secretKey.length == 16);
try { try {
@ -288,10 +300,11 @@ import javax.crypto.spec.SecretKeySpec;
output = new DataOutputStream(bufferedOutputStream); output = new DataOutputStream(bufferedOutputStream);
output.writeInt(VERSION); output.writeInt(VERSION);
int flags = cipher != null ? FLAG_ENCRYPTED_INDEX : 0; boolean writeEncrypted = encrypt && cipher != null;
int flags = writeEncrypted ? FLAG_ENCRYPTED_INDEX : 0;
output.writeInt(flags); output.writeInt(flags);
if (cipher != null) { if (writeEncrypted) {
byte[] initializationVector = new byte[16]; byte[] initializationVector = new byte[16];
new Random().nextBytes(initializationVector); new Random().nextBytes(initializationVector);
output.write(initializationVector); output.write(initializationVector);

View File

@ -61,10 +61,24 @@ public final class SimpleCache implements Cache {
* The key must be 16 bytes long. * The key must be 16 bytes long.
*/ */
public SimpleCache(File cacheDir, CacheEvictor evictor, byte[] secretKey) { public SimpleCache(File cacheDir, CacheEvictor evictor, byte[] secretKey) {
this(cacheDir, evictor, secretKey, secretKey != null);
}
/**
* Constructs the cache. The cache will delete any unrecognized files from the directory. Hence
* the directory cannot be used to store other files.
*
* @param cacheDir A dedicated cache directory.
* @param evictor The evictor to be used.
* @param secretKey If not null, cache keys will be stored encrypted on filesystem using AES/CBC.
* The key must be 16 bytes long.
* @param encrypt When false, a plaintext index will be written.
*/
public SimpleCache(File cacheDir, CacheEvictor evictor, byte[] secretKey, boolean encrypt) {
this.cacheDir = cacheDir; this.cacheDir = cacheDir;
this.evictor = evictor; this.evictor = evictor;
this.lockedSpans = new HashMap<>(); this.lockedSpans = new HashMap<>();
this.index = new CachedContentIndex(cacheDir, secretKey); this.index = new CachedContentIndex(cacheDir, secretKey, encrypt);
this.listeners = new HashMap<>(); this.listeners = new HashMap<>();
// Start cache initialization. // Start cache initialization.
final ConditionVariable conditionVariable = new ConditionVariable(); final ConditionVariable conditionVariable = new ConditionVariable();

View File

@ -85,6 +85,7 @@ public final class MimeTypes {
public static final String APPLICATION_CAMERA_MOTION = BASE_TYPE_APPLICATION + "/x-camera-motion"; public static final String APPLICATION_CAMERA_MOTION = BASE_TYPE_APPLICATION + "/x-camera-motion";
public static final String APPLICATION_EMSG = BASE_TYPE_APPLICATION + "/x-emsg"; public static final String APPLICATION_EMSG = BASE_TYPE_APPLICATION + "/x-emsg";
public static final String APPLICATION_DVBSUBS = BASE_TYPE_APPLICATION + "/dvbsubs"; public static final String APPLICATION_DVBSUBS = BASE_TYPE_APPLICATION + "/dvbsubs";
public static final String APPLICATION_EXIF = BASE_TYPE_APPLICATION + "/x-exif";
private MimeTypes() {} private MimeTypes() {}
@ -184,9 +185,9 @@ public final class MimeTypes {
return MimeTypes.VIDEO_H264; return MimeTypes.VIDEO_H264;
} else if (codec.startsWith("hev1") || codec.startsWith("hvc1")) { } else if (codec.startsWith("hev1") || codec.startsWith("hvc1")) {
return MimeTypes.VIDEO_H265; return MimeTypes.VIDEO_H265;
} else if (codec.startsWith("vp9")) { } else if (codec.startsWith("vp9") || codec.startsWith("vp09")) {
return MimeTypes.VIDEO_VP9; return MimeTypes.VIDEO_VP9;
} else if (codec.startsWith("vp8")) { } else if (codec.startsWith("vp8") || codec.startsWith("vp08")) {
return MimeTypes.VIDEO_VP8; return MimeTypes.VIDEO_VP8;
} else if (codec.startsWith("mp4a")) { } else if (codec.startsWith("mp4a")) {
return MimeTypes.AUDIO_AAC; return MimeTypes.AUDIO_AAC;

View File

@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.util; package com.google.android.exoplayer2.util;
import com.google.android.exoplayer2.C;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@ -428,7 +429,7 @@ public final class ParsableByteArray {
* @return The string encoded by the bytes. * @return The string encoded by the bytes.
*/ */
public String readString(int length) { public String readString(int length) {
return readString(length, Charset.defaultCharset()); return readString(length, Charset.forName(C.UTF8_NAME));
} }
/** /**

View File

@ -253,7 +253,7 @@ public final class Util {
* @return The code points encoding using UTF-8. * @return The code points encoding using UTF-8.
*/ */
public static byte[] getUtf8Bytes(String value) { public static byte[] getUtf8Bytes(String value) {
return value.getBytes(Charset.defaultCharset()); // UTF-8 is the default on Android. return value.getBytes(Charset.forName(C.UTF8_NAME));
} }
/** /**

View File

@ -34,6 +34,8 @@ import static android.opengl.EGL14.EGL_WINDOW_BIT;
import static android.opengl.EGL14.eglChooseConfig; import static android.opengl.EGL14.eglChooseConfig;
import static android.opengl.EGL14.eglCreateContext; import static android.opengl.EGL14.eglCreateContext;
import static android.opengl.EGL14.eglCreatePbufferSurface; import static android.opengl.EGL14.eglCreatePbufferSurface;
import static android.opengl.EGL14.eglDestroyContext;
import static android.opengl.EGL14.eglDestroySurface;
import static android.opengl.EGL14.eglGetDisplay; import static android.opengl.EGL14.eglGetDisplay;
import static android.opengl.EGL14.eglInitialize; import static android.opengl.EGL14.eglInitialize;
import static android.opengl.EGL14.eglMakeCurrent; import static android.opengl.EGL14.eglMakeCurrent;
@ -89,12 +91,7 @@ public final class DummySurface extends Surface {
*/ */
public static synchronized boolean isSecureSupported(Context context) { public static synchronized boolean isSecureSupported(Context context) {
if (!secureSupportedInitialized) { if (!secureSupportedInitialized) {
if (Util.SDK_INT >= 17) { secureSupported = Util.SDK_INT >= 24 && enableSecureDummySurfaceV24(context);
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
String extensions = EGL14.eglQueryString(display, EGL10.EGL_EXTENSIONS);
secureSupported = extensions != null && extensions.contains("EGL_EXT_protected_content")
&& !deviceNeedsSecureDummySurfaceWorkaround(context);
}
secureSupportedInitialized = true; secureSupportedInitialized = true;
} }
return secureSupported; return secureSupported;
@ -147,20 +144,28 @@ public final class DummySurface extends Surface {
} }
/** /**
* Returns whether the device is known to advertise secure surface textures but not implement them * Returns whether use of secure dummy surfaces should be enabled.
* correctly.
* *
* @param context Any {@link Context}. * @param context Any {@link Context}.
*/ */
private static boolean deviceNeedsSecureDummySurfaceWorkaround(Context context) {
return Util.SDK_INT == 24
&& (Util.MODEL.startsWith("SM-G950") || Util.MODEL.startsWith("SM-G955"))
&& !hasVrModeHighPerformanceSystemFeatureV24(context.getPackageManager());
}
@TargetApi(24) @TargetApi(24)
private static boolean hasVrModeHighPerformanceSystemFeatureV24(PackageManager packageManager) { private static boolean enableSecureDummySurfaceV24(Context context) {
return packageManager.hasSystemFeature(PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE); EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
String eglExtensions = EGL14.eglQueryString(display, EGL10.EGL_EXTENSIONS);
if (eglExtensions == null || !eglExtensions.contains("EGL_EXT_protected_content")) {
// EGL_EXT_protected_content is required to enable secure dummy surfaces.
return false;
}
if (Util.SDK_INT == 24 && "samsung".equals(Util.MANUFACTURER)) {
// Samsung devices running API level 24 are known to be broken [Internal: b/37197802].
return false;
}
if (Util.SDK_INT < 26 && !context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
// Pre API level 26 devices were not well tested unless they supported VR mode.
return false;
}
return true;
} }
private static class DummySurfaceThread extends HandlerThread implements OnFrameAvailableListener, private static class DummySurfaceThread extends HandlerThread implements OnFrameAvailableListener,
@ -171,6 +176,9 @@ public final class DummySurface extends Surface {
private static final int MSG_RELEASE = 3; private static final int MSG_RELEASE = 3;
private final int[] textureIdHolder; private final int[] textureIdHolder;
private EGLDisplay display;
private EGLContext context;
private EGLSurface pbuffer;
private Handler handler; private Handler handler;
private SurfaceTexture surfaceTexture; private SurfaceTexture surfaceTexture;
@ -255,7 +263,7 @@ public final class DummySurface extends Surface {
} }
private void initInternal(boolean secure) { private void initInternal(boolean secure) {
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
Assertions.checkState(display != null, "eglGetDisplay failed"); Assertions.checkState(display != null, "eglGetDisplay failed");
int[] version = new int[2]; int[] version = new int[2];
@ -292,8 +300,8 @@ public final class DummySurface extends Surface {
EGL_CONTEXT_CLIENT_VERSION, 2, EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE}; EGL_NONE};
} }
EGLContext context = eglCreateContext(display, config, android.opengl.EGL14.EGL_NO_CONTEXT, context = eglCreateContext(display, config, android.opengl.EGL14.EGL_NO_CONTEXT, glAttributes,
glAttributes, 0); 0);
Assertions.checkState(context != null, "eglCreateContext failed"); Assertions.checkState(context != null, "eglCreateContext failed");
int[] pbufferAttributes; int[] pbufferAttributes;
@ -309,7 +317,7 @@ public final class DummySurface extends Surface {
EGL_HEIGHT, 1, EGL_HEIGHT, 1,
EGL_NONE}; EGL_NONE};
} }
EGLSurface pbuffer = eglCreatePbufferSurface(display, config, pbufferAttributes, 0); pbuffer = eglCreatePbufferSurface(display, config, pbufferAttributes, 0);
Assertions.checkState(pbuffer != null, "eglCreatePbufferSurface failed"); Assertions.checkState(pbuffer != null, "eglCreatePbufferSurface failed");
boolean eglMadeCurrent = eglMakeCurrent(display, pbuffer, pbuffer, context); boolean eglMadeCurrent = eglMakeCurrent(display, pbuffer, pbuffer, context);
@ -323,11 +331,22 @@ public final class DummySurface extends Surface {
private void releaseInternal() { private void releaseInternal() {
try { try {
surfaceTexture.release(); if (surfaceTexture != null) {
surfaceTexture.release();
glDeleteTextures(1, textureIdHolder, 0);
}
} finally { } finally {
if (pbuffer != null) {
eglDestroySurface(display, pbuffer);
}
if (context != null) {
eglDestroyContext(display, context);
}
pbuffer = null;
context = null;
display = null;
surface = null; surface = null;
surfaceTexture = null; surfaceTexture = null;
glDeleteTextures(1, textureIdHolder, 0);
} }
} }

View File

@ -77,6 +77,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
private Format[] streamFormats; private Format[] streamFormats;
private CodecMaxValues codecMaxValues; private CodecMaxValues codecMaxValues;
private boolean codecNeedsSetOutputSurfaceWorkaround;
private Surface surface; private Surface surface;
private Surface dummySurface; private Surface dummySurface;
@ -354,7 +355,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
int state = getState(); int state = getState();
if (state == STATE_ENABLED || state == STATE_STARTED) { if (state == STATE_ENABLED || state == STATE_STARTED) {
MediaCodec codec = getCodec(); MediaCodec codec = getCodec();
if (Util.SDK_INT >= 23 && codec != null && surface != null) { if (Util.SDK_INT >= 23 && codec != null && surface != null
&& !codecNeedsSetOutputSurfaceWorkaround) {
setOutputSurfaceV23(codec, surface); setOutputSurfaceV23(codec, surface);
} else { } else {
releaseCodec(); releaseCodec();
@ -425,6 +427,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
protected void onCodecInitialized(String name, long initializedTimestampMs, protected void onCodecInitialized(String name, long initializedTimestampMs,
long initializationDurationMs) { long initializationDurationMs) {
eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs); eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs);
codecNeedsSetOutputSurfaceWorkaround = codecNeedsSetOutputSurfaceWorkaround(name);
} }
@Override @Override
@ -735,28 +738,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
return earlyUs < -30000; return earlyUs < -30000;
} }
@SuppressLint("InlinedApi")
private static MediaFormat getMediaFormat(Format format, CodecMaxValues codecMaxValues,
boolean deviceNeedsAutoFrcWorkaround, int tunnelingAudioSessionId) {
MediaFormat frameworkMediaFormat = format.getFrameworkMediaFormatV16();
// Set the maximum adaptive video dimensions.
frameworkMediaFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, codecMaxValues.width);
frameworkMediaFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, codecMaxValues.height);
// Set the maximum input size.
if (codecMaxValues.inputSize != Format.NO_VALUE) {
frameworkMediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, codecMaxValues.inputSize);
}
// Set FRC workaround.
if (deviceNeedsAutoFrcWorkaround) {
frameworkMediaFormat.setInteger("auto-frc", 0);
}
// Configure tunneling if enabled.
if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) {
configureTunnelingV21(frameworkMediaFormat, tunnelingAudioSessionId);
}
return frameworkMediaFormat;
}
@TargetApi(23) @TargetApi(23)
private static void setOutputSurfaceV23(MediaCodec codec, Surface surface) { private static void setOutputSurfaceV23(MediaCodec codec, Surface surface) {
codec.setOutputSurface(surface); codec.setOutputSurface(surface);
@ -812,6 +793,40 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
return new CodecMaxValues(maxWidth, maxHeight, maxInputSize); return new CodecMaxValues(maxWidth, maxHeight, maxInputSize);
} }
/**
* Returns the framework {@link MediaFormat} that should be used to configure the decoder when
* playing media in the specified input format.
*
* @param format The format of input media.
* @param codecMaxValues The codec's maximum supported values.
* @param deviceNeedsAutoFrcWorkaround Whether the device is known to enable frame-rate conversion
* logic that negatively impacts ExoPlayer.
* @param tunnelingAudioSessionId The audio session id to use for tunneling, or
* {@link C#AUDIO_SESSION_ID_UNSET} if tunneling should not be enabled.
* @return The framework {@link MediaFormat} that should be used to configure the decoder.
*/
@SuppressLint("InlinedApi")
protected MediaFormat getMediaFormat(Format format, CodecMaxValues codecMaxValues,
boolean deviceNeedsAutoFrcWorkaround, int tunnelingAudioSessionId) {
MediaFormat frameworkMediaFormat = format.getFrameworkMediaFormatV16();
// Set the maximum adaptive video dimensions.
frameworkMediaFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, codecMaxValues.width);
frameworkMediaFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, codecMaxValues.height);
// Set the maximum input size.
if (codecMaxValues.inputSize != Format.NO_VALUE) {
frameworkMediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, codecMaxValues.inputSize);
}
// Set FRC workaround.
if (deviceNeedsAutoFrcWorkaround) {
frameworkMediaFormat.setInteger("auto-frc", 0);
}
// Configure tunneling if enabled.
if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) {
configureTunnelingV21(frameworkMediaFormat, tunnelingAudioSessionId);
}
return frameworkMediaFormat;
}
/** /**
* Returns a maximum video size to use when configuring a codec for {@code format} in a way * Returns a maximum video size to use when configuring a codec for {@code format} in a way
* that will allow possible adaptation to other compatible formats that are expected to have the * that will allow possible adaptation to other compatible formats that are expected to have the
@ -951,6 +966,18 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
return Util.SDK_INT <= 22 && "foster".equals(Util.DEVICE) && "NVIDIA".equals(Util.MANUFACTURER); return Util.SDK_INT <= 22 && "foster".equals(Util.DEVICE) && "NVIDIA".equals(Util.MANUFACTURER);
} }
/**
* Returns whether the device is known to implement {@link MediaCodec#setOutputSurface(Surface)}
* incorrectly.
* <p>
* If true is returned then we fall back to releasing and re-instantiating the codec instead.
*/
private static boolean codecNeedsSetOutputSurfaceWorkaround(String name) {
// Work around https://github.com/google/ExoPlayer/issues/3236
return ("deb".equals(Util.DEVICE) || "flo".equals(Util.DEVICE))
&& "OMX.qcom.video.decoder.avc".equals(name);
}
/** /**
* Returns whether a codec with suitable {@link CodecMaxValues} will support adaptation between * Returns whether a codec with suitable {@link CodecMaxValues} will support adaptation between
* two {@link Format}s. * two {@link Format}s.

12
library/dash/README.md Normal file
View File

@ -0,0 +1,12 @@
# ExoPlayer DASH library module #
Provides support for Dynamic Adaptive Streaming over HTTP (DASH) content. To
play DASH content, instantiate a `DashMediaSource` and pass it to
`ExoPlayer.prepare`.
## Links ##
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.source.dash.*`
belong to this module.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html

View File

@ -85,14 +85,14 @@ public class DefaultDashChunkSource implements DashChunkSource {
private final int[] adaptationSetIndices; private final int[] adaptationSetIndices;
private final TrackSelection trackSelection; private final TrackSelection trackSelection;
private final int trackType; private final int trackType;
private final RepresentationHolder[] representationHolders;
private final DataSource dataSource; private final DataSource dataSource;
private final long elapsedRealtimeOffsetMs; private final long elapsedRealtimeOffsetMs;
private final int maxSegmentsPerLoad; private final int maxSegmentsPerLoad;
protected final RepresentationHolder[] representationHolders;
private DashManifest manifest; private DashManifest manifest;
private int periodIndex; private int periodIndex;
private IOException fatalError; private IOException fatalError;
private boolean missingLastSegment; private boolean missingLastSegment;
@ -377,9 +377,12 @@ public class DefaultDashChunkSource implements DashChunkSource {
// Protected classes. // Protected classes.
/**
* Holds information about a single {@link Representation}.
*/
protected static final class RepresentationHolder { protected static final class RepresentationHolder {
public final ChunkExtractorWrapper extractorWrapper; /* package */ final ChunkExtractorWrapper extractorWrapper;
public Representation representation; public Representation representation;
public DashSegmentIndex segmentIndex; public DashSegmentIndex segmentIndex;
@ -387,7 +390,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
private long periodDurationUs; private long periodDurationUs;
private int segmentNumShift; private int segmentNumShift;
public RepresentationHolder(long periodDurationUs, Representation representation, /* package */ RepresentationHolder(long periodDurationUs, Representation representation,
boolean enableEventMessageTrack, boolean enableCea608Track) { boolean enableEventMessageTrack, boolean enableCea608Track) {
this.periodDurationUs = periodDurationUs; this.periodDurationUs = periodDurationUs;
this.representation = representation; this.representation = representation;
@ -417,8 +420,8 @@ public class DefaultDashChunkSource implements DashChunkSource {
segmentIndex = representation.getIndex(); segmentIndex = representation.getIndex();
} }
public void updateRepresentation(long newPeriodDurationUs, Representation newRepresentation) /* package */ void updateRepresentation(long newPeriodDurationUs,
throws BehindLiveWindowException{ Representation newRepresentation) throws BehindLiveWindowException {
DashSegmentIndex oldIndex = representation.getIndex(); DashSegmentIndex oldIndex = representation.getIndex();
DashSegmentIndex newIndex = newRepresentation.getIndex(); DashSegmentIndex newIndex = newRepresentation.getIndex();

11
library/hls/README.md Normal file
View File

@ -0,0 +1,11 @@
# ExoPlayer HLS library module #
Provides support for HTTP Live Streaming (HLS) content. To play HLS content,
instantiate a `HlsMediaSource` and pass it to `ExoPlayer.prepare`.
## Links ##
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.source.hls.*`
belong to this module.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html

View File

@ -92,6 +92,7 @@ import java.util.List;
private byte[] scratchSpace; private byte[] scratchSpace;
private IOException fatalError; private IOException fatalError;
private HlsUrl expectedPlaylistUrl; private HlsUrl expectedPlaylistUrl;
private boolean independentSegments;
private Uri encryptionKeyUri; private Uri encryptionKeyUri;
private byte[] encryptionKey; private byte[] encryptionKey;
@ -206,10 +207,11 @@ import java.util.List;
int oldVariantIndex = previous == null ? C.INDEX_UNSET int oldVariantIndex = previous == null ? C.INDEX_UNSET
: trackGroup.indexOf(previous.trackFormat); : trackGroup.indexOf(previous.trackFormat);
expectedPlaylistUrl = null; expectedPlaylistUrl = null;
// Use start time of the previous chunk rather than its end time because switching format will // Unless segments are known to be independent, switching variant will require downloading
// require downloading overlapping segments. // overlapping segments. Hence we use the start time of the previous chunk rather than its end
long bufferedDurationUs = previous == null ? 0 // time for this case.
: Math.max(0, previous.startTimeUs - playbackPositionUs); long bufferedDurationUs = previous == null ? 0 : Math.max(0,
(independentSegments ? previous.endTimeUs : previous.startTimeUs) - playbackPositionUs);
// Select the variant. // Select the variant.
trackSelection.updateSelectedTrack(bufferedDurationUs); trackSelection.updateSelectedTrack(bufferedDurationUs);
@ -224,12 +226,13 @@ import java.util.List;
return; return;
} }
HlsMediaPlaylist mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl); HlsMediaPlaylist mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl);
independentSegments = mediaPlaylist.hasIndependentSegmentsTag;
// Select the chunk. // Select the chunk.
int chunkMediaSequence; int chunkMediaSequence;
if (previous == null || switchingVariant) { if (previous == null || switchingVariant) {
long targetPositionUs = previous == null ? playbackPositionUs long targetPositionUs = previous == null ? playbackPositionUs
: mediaPlaylist.hasIndependentSegmentsTag ? previous.endTimeUs : previous.startTimeUs; : independentSegments ? previous.endTimeUs : previous.startTimeUs;
if (!mediaPlaylist.hasEndTag && targetPositionUs >= mediaPlaylist.getEndTimeUs()) { if (!mediaPlaylist.hasEndTag && targetPositionUs >= mediaPlaylist.getEndTimeUs()) {
// If the playlist is too old to contain the chunk, we need to refresh it. // If the playlist is too old to contain the chunk, we need to refresh it.
chunkMediaSequence = mediaPlaylist.mediaSequence + mediaPlaylist.segments.size(); chunkMediaSequence = mediaPlaylist.mediaSequence + mediaPlaylist.segments.size();

View File

@ -0,0 +1,12 @@
# ExoPlayer SmoothStreaming library module #
Provides support for Smooth Streaming content. To play Smooth Streaming content,
instantiate a `SsMediaSource` and pass it to `ExoPlayer.prepare`.
## Links ##
* [Javadoc][]: Classes matching
`com.google.android.exoplayer2.source.smoothstreaming.*` belong to this
module.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html

10
library/ui/README.md Normal file
View File

@ -0,0 +1,10 @@
# ExoPlayer UI library module #
Provides UI components and resources for use with ExoPlayer.
## Links ##
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ui.*`
belong to this module.
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html

View File

@ -32,7 +32,8 @@ public final class AspectRatioFrameLayout extends FrameLayout {
* Resize modes for {@link AspectRatioFrameLayout}. * Resize modes for {@link AspectRatioFrameLayout}.
*/ */
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef({RESIZE_MODE_FIT, RESIZE_MODE_FIXED_WIDTH, RESIZE_MODE_FIXED_HEIGHT, RESIZE_MODE_FILL}) @IntDef({RESIZE_MODE_FIT, RESIZE_MODE_FIXED_WIDTH, RESIZE_MODE_FIXED_HEIGHT, RESIZE_MODE_FILL,
RESIZE_MODE_ZOOM})
public @interface ResizeMode {} public @interface ResizeMode {}
/** /**
@ -51,6 +52,10 @@ public final class AspectRatioFrameLayout extends FrameLayout {
* The specified aspect ratio is ignored. * The specified aspect ratio is ignored.
*/ */
public static final int RESIZE_MODE_FILL = 3; public static final int RESIZE_MODE_FILL = 3;
/**
* Either the width or height is increased to obtain the desired aspect ratio.
*/
public static final int RESIZE_MODE_ZOOM = 4;
/** /**
* The {@link FrameLayout} will not resize itself if the fractional difference between its natural * The {@link FrameLayout} will not resize itself if the fractional difference between its natural
@ -85,7 +90,7 @@ public final class AspectRatioFrameLayout extends FrameLayout {
} }
/** /**
* Set the aspect ratio that this view should satisfy. * Sets the aspect ratio that this view should satisfy.
* *
* @param widthHeightRatio The width to height ratio. * @param widthHeightRatio The width to height ratio.
*/ */
@ -96,6 +101,13 @@ public final class AspectRatioFrameLayout extends FrameLayout {
} }
} }
/**
* Returns the resize mode.
*/
public @ResizeMode int getResizeMode() {
return resizeMode;
}
/** /**
* Sets the resize mode. * Sets the resize mode.
* *
@ -132,6 +144,13 @@ public final class AspectRatioFrameLayout extends FrameLayout {
case RESIZE_MODE_FIXED_HEIGHT: case RESIZE_MODE_FIXED_HEIGHT:
width = (int) (height * videoAspectRatio); width = (int) (height * videoAspectRatio);
break; break;
case RESIZE_MODE_ZOOM:
if (aspectDeformation > 0) {
width = (int) (height * videoAspectRatio);
} else {
height = (int) (width / videoAspectRatio);
}
break;
default: default:
if (aspectDeformation > 0) { if (aspectDeformation > 0) {
height = (int) (width / videoAspectRatio); height = (int) (width / videoAspectRatio);

View File

@ -22,6 +22,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.SystemClock; import android.os.SystemClock;
import android.support.annotation.Nullable;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -312,6 +313,8 @@ public class PlaybackControlView extends FrameLayout {
private long hideAtMs; private long hideAtMs;
private long[] adGroupTimesMs; private long[] adGroupTimesMs;
private boolean[] playedAdGroups; private boolean[] playedAdGroups;
private long[] extraAdGroupTimesMs;
private boolean[] extraPlayedAdGroups;
private final Runnable updateProgressAction = new Runnable() { private final Runnable updateProgressAction = new Runnable() {
@Override @Override
@ -336,15 +339,19 @@ public class PlaybackControlView extends FrameLayout {
} }
public PlaybackControlView(Context context, AttributeSet attrs, int defStyleAttr) { public PlaybackControlView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr); this(context, attrs, defStyleAttr, attrs);
}
public PlaybackControlView(Context context, AttributeSet attrs, int defStyleAttr,
AttributeSet playbackAttrs) {
super(context, attrs, defStyleAttr);
int controllerLayoutId = R.layout.exo_playback_control_view; int controllerLayoutId = R.layout.exo_playback_control_view;
rewindMs = DEFAULT_REWIND_MS; rewindMs = DEFAULT_REWIND_MS;
fastForwardMs = DEFAULT_FAST_FORWARD_MS; fastForwardMs = DEFAULT_FAST_FORWARD_MS;
showTimeoutMs = DEFAULT_SHOW_TIMEOUT_MS; showTimeoutMs = DEFAULT_SHOW_TIMEOUT_MS;
repeatToggleModes = DEFAULT_REPEAT_TOGGLE_MODES; repeatToggleModes = DEFAULT_REPEAT_TOGGLE_MODES;
if (attrs != null) { if (playbackAttrs != null) {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, TypedArray a = context.getTheme().obtainStyledAttributes(playbackAttrs,
R.styleable.PlaybackControlView, 0, 0); R.styleable.PlaybackControlView, 0, 0);
try { try {
rewindMs = a.getInt(R.styleable.PlaybackControlView_rewind_increment, rewindMs); rewindMs = a.getInt(R.styleable.PlaybackControlView_rewind_increment, rewindMs);
@ -364,6 +371,8 @@ public class PlaybackControlView extends FrameLayout {
formatter = new Formatter(formatBuilder, Locale.getDefault()); formatter = new Formatter(formatBuilder, Locale.getDefault());
adGroupTimesMs = new long[0]; adGroupTimesMs = new long[0];
playedAdGroups = new boolean[0]; playedAdGroups = new boolean[0];
extraAdGroupTimesMs = new long[0];
extraPlayedAdGroups = new boolean[0];
componentListener = new ComponentListener(); componentListener = new ComponentListener();
controlDispatcher = DEFAULT_CONTROL_DISPATCHER; controlDispatcher = DEFAULT_CONTROL_DISPATCHER;
@ -462,6 +471,29 @@ public class PlaybackControlView extends FrameLayout {
updateTimeBarMode(); updateTimeBarMode();
} }
/**
* Sets the millisecond positions of extra ad markers relative to the start of the window (or
* timeline, if in multi-window mode) and whether each extra ad has been played or not. The
* markers are shown in addition to any ad markers for ads in the player's timeline.
*
* @param extraAdGroupTimesMs The millisecond timestamps of the extra ad markers to show, or
* {@code null} to show no extra ad markers.
* @param extraPlayedAdGroups Whether each ad has been played, or {@code null} to show no extra ad
* markers.
*/
public void setExtraAdGroupMarkers(@Nullable long[] extraAdGroupTimesMs,
@Nullable boolean[] extraPlayedAdGroups) {
if (extraAdGroupTimesMs == null) {
this.extraAdGroupTimesMs = new long[0];
this.extraPlayedAdGroups = new boolean[0];
} else {
Assertions.checkArgument(extraAdGroupTimesMs.length == extraPlayedAdGroups.length);
this.extraAdGroupTimesMs = extraAdGroupTimesMs;
this.extraPlayedAdGroups = extraPlayedAdGroups;
}
updateProgress();
}
/** /**
* Sets the {@link VisibilityListener}. * Sets the {@link VisibilityListener}.
* *
@ -647,9 +679,10 @@ public class PlaybackControlView extends FrameLayout {
int windowIndex = player.getCurrentWindowIndex(); int windowIndex = player.getCurrentWindowIndex();
timeline.getWindow(windowIndex, window); timeline.getWindow(windowIndex, window);
isSeekable = window.isSeekable; isSeekable = window.isSeekable;
enablePrevious = !timeline.isFirstWindow(windowIndex, player.getRepeatMode()) enablePrevious = isSeekable || !window.isDynamic
|| isSeekable || !window.isDynamic; || timeline.getPreviousWindowIndex(windowIndex, player.getRepeatMode()) != C.INDEX_UNSET;
enableNext = !timeline.isLastWindow(windowIndex, player.getRepeatMode()) || window.isDynamic; enableNext = window.isDynamic
|| timeline.getNextWindowIndex(windowIndex, player.getRepeatMode()) != C.INDEX_UNSET;
if (player.isPlayingAd()) { if (player.isPlayingAd()) {
// Always hide player controls during ads. // Always hide player controls during ads.
hide(); hide();
@ -768,7 +801,15 @@ public class PlaybackControlView extends FrameLayout {
bufferedPosition += player.getBufferedPosition(); bufferedPosition += player.getBufferedPosition();
} }
if (timeBar != null) { if (timeBar != null) {
timeBar.setAdGroupTimesMs(adGroupTimesMs, playedAdGroups, adGroupCount); int extraAdGroupCount = extraAdGroupTimesMs.length;
int totalAdGroupCount = adGroupCount + extraAdGroupCount;
if (totalAdGroupCount > adGroupTimesMs.length) {
adGroupTimesMs = Arrays.copyOf(adGroupTimesMs, totalAdGroupCount);
playedAdGroups = Arrays.copyOf(playedAdGroups, totalAdGroupCount);
}
System.arraycopy(extraAdGroupTimesMs, 0, adGroupTimesMs, adGroupCount, extraAdGroupCount);
System.arraycopy(extraPlayedAdGroups, 0, playedAdGroups, adGroupCount, extraAdGroupCount);
timeBar.setAdGroupTimesMs(adGroupTimesMs, playedAdGroups, totalAdGroupCount);
} }
} }
if (durationView != null) { if (durationView != null) {

View File

@ -239,7 +239,7 @@ public final class SimpleExoPlayerView extends FrameLayout {
controller = null; controller = null;
componentListener = null; componentListener = null;
overlayFrameLayout = null; overlayFrameLayout = null;
ImageView logo = new ImageView(context, attrs); ImageView logo = new ImageView(context);
if (Util.SDK_INT >= 23) { if (Util.SDK_INT >= 23) {
configureEditModeLogoV23(getResources(), logo); configureEditModeLogoV23(getResources(), logo);
} else { } else {
@ -329,9 +329,9 @@ public final class SimpleExoPlayerView extends FrameLayout {
if (customController != null) { if (customController != null) {
this.controller = customController; this.controller = customController;
} else if (controllerPlaceholder != null) { } else if (controllerPlaceholder != null) {
// Note: rewindMs and fastForwardMs are passed via attrs, so we don't need to make explicit // Propagate attrs as playbackAttrs so that PlaybackControlView's custom attributes are
// calls to set them. // transferred, but standard FrameLayout attributes (e.g. background) are not.
this.controller = new PlaybackControlView(context, attrs); this.controller = new PlaybackControlView(context, null, 0, attrs);
controller.setLayoutParams(controllerPlaceholder.getLayoutParams()); controller.setLayoutParams(controllerPlaceholder.getLayoutParams());
ViewGroup parent = ((ViewGroup) controllerPlaceholder.getParent()); ViewGroup parent = ((ViewGroup) controllerPlaceholder.getParent());
int controllerIndex = parent.indexOfChild(controllerPlaceholder); int controllerIndex = parent.indexOfChild(controllerPlaceholder);
@ -379,9 +379,7 @@ public final class SimpleExoPlayerView extends FrameLayout {
} }
/** /**
* Set the {@link SimpleExoPlayer} to use. The {@link SimpleExoPlayer#setTextOutput} and * Set the {@link SimpleExoPlayer} to use.
* {@link SimpleExoPlayer#setVideoListener} method of the player will be called and previous
* assignments are overridden.
* <p> * <p>
* To transition a {@link SimpleExoPlayer} from targeting one view to another, it's recommended to * To transition a {@link SimpleExoPlayer} from targeting one view to another, it's recommended to
* use {@link #switchTargetView(SimpleExoPlayer, SimpleExoPlayerView, SimpleExoPlayerView)} rather * use {@link #switchTargetView(SimpleExoPlayer, SimpleExoPlayerView, SimpleExoPlayerView)} rather
@ -397,8 +395,8 @@ public final class SimpleExoPlayerView extends FrameLayout {
} }
if (this.player != null) { if (this.player != null) {
this.player.removeListener(componentListener); this.player.removeListener(componentListener);
this.player.clearTextOutput(componentListener); this.player.removeTextOutput(componentListener);
this.player.clearVideoListener(componentListener); this.player.removeVideoListener(componentListener);
if (surfaceView instanceof TextureView) { if (surfaceView instanceof TextureView) {
this.player.clearVideoTextureView((TextureView) surfaceView); this.player.clearVideoTextureView((TextureView) surfaceView);
} else if (surfaceView instanceof SurfaceView) { } else if (surfaceView instanceof SurfaceView) {
@ -418,8 +416,8 @@ public final class SimpleExoPlayerView extends FrameLayout {
} else if (surfaceView instanceof SurfaceView) { } else if (surfaceView instanceof SurfaceView) {
player.setVideoSurfaceView((SurfaceView) surfaceView); player.setVideoSurfaceView((SurfaceView) surfaceView);
} }
player.setVideoListener(componentListener); player.addVideoListener(componentListener);
player.setTextOutput(componentListener); player.addTextOutput(componentListener);
player.addListener(componentListener); player.addListener(componentListener);
maybeShowController(false); maybeShowController(false);
updateForCurrentTrackSelections(); updateForCurrentTrackSelections();
@ -429,6 +427,15 @@ public final class SimpleExoPlayerView extends FrameLayout {
} }
} }
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
if (surfaceView instanceof SurfaceView) {
// Work around https://github.com/google/ExoPlayer/issues/3160
surfaceView.setVisibility(visibility);
}
}
/** /**
* Sets the resize mode. * Sets the resize mode.
* *
@ -668,10 +675,15 @@ public final class SimpleExoPlayerView extends FrameLayout {
} }
/** /**
* Gets the view onto which video is rendered. This is either a {@link SurfaceView} (default) * Gets the view onto which video is rendered. This is a:
* or a {@link TextureView} if the {@code use_texture_view} view attribute has been set to true. * <ul>
* <li>{@link SurfaceView} by default, or if the {@code surface_type} attribute is set to
* {@code surface_view}.</li>
* <li>{@link TextureView} if {@code surface_type} is {@code texture_view}.</li>
* <li>{@code null} if {@code surface_type} is {@code none}.</li>
* </ul>
* *
* @return Either a {@link SurfaceView} or a {@link TextureView}. * @return The {@link SurfaceView}, {@link TextureView} or {@code null}.
*/ */
public View getVideoSurfaceView() { public View getVideoSurfaceView() {
return surfaceView; return surfaceView;

View File

@ -181,7 +181,7 @@ public class TestUtil {
byte[] expectedData) throws IOException { byte[] expectedData) throws IOException {
try { try {
long length = dataSource.open(dataSpec); long length = dataSource.open(dataSpec);
Assert.assertEquals(length, expectedData.length); Assert.assertEquals(expectedData.length, length);
byte[] readData = TestUtil.readToEnd(dataSource); byte[] readData = TestUtil.readToEnd(dataSource);
MoreAsserts.assertEquals(expectedData, readData); MoreAsserts.assertEquals(expectedData, readData);
} finally { } finally {