commit
2fb09dbb79
50
README.md
50
README.md
@ -9,34 +9,37 @@ and extend, and can be updated through Play Store application updates.
|
||||
|
||||
## Documentation ##
|
||||
|
||||
* The [developer guide][] provides a wealth of information to help you get
|
||||
started.
|
||||
* The [class reference][] documents the ExoPlayer library classes.
|
||||
* The [developer guide][] provides a wealth of information.
|
||||
* The [class reference][] documents ExoPlayer classes.
|
||||
* The [release notes][] document the major changes in each release.
|
||||
* Follow our [developer blog][] to keep up to date with the latest ExoPlayer
|
||||
developments!
|
||||
|
||||
[developer guide]: https://google.github.io/ExoPlayer/guide.html
|
||||
[class reference]: https://google.github.io/ExoPlayer/doc/reference
|
||||
[release notes]: https://github.com/google/ExoPlayer/blob/dev-v2/RELEASENOTES.md
|
||||
[release notes]: https://github.com/google/ExoPlayer/blob/release-v2/RELEASENOTES.md
|
||||
[developer blog]: https://medium.com/google-exoplayer
|
||||
|
||||
## Using ExoPlayer ##
|
||||
|
||||
ExoPlayer modules can be obtained via jCenter. It's also possible to clone the
|
||||
ExoPlayer modules can be obtained from JCenter. It's also possible to clone the
|
||||
repository and depend on the modules locally.
|
||||
|
||||
### Via jCenter ###
|
||||
### From JCenter ###
|
||||
|
||||
The easiest way to get started using ExoPlayer is to add it as a gradle
|
||||
dependency. You need to make sure you have the jcenter repository included in
|
||||
the `build.gradle` file in the root of your project:
|
||||
dependency. You need to make sure you have the JCenter and Google repositories
|
||||
included in the `build.gradle` file in the root of your project:
|
||||
|
||||
```gradle
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
```
|
||||
|
||||
Next add a gradle compile dependency to the `build.gradle` file of your app
|
||||
module. The following will add a dependency to the full ExoPlayer library:
|
||||
module. The following will add a dependency to the full library:
|
||||
|
||||
```gradle
|
||||
compile 'com.google.android.exoplayer:exoplayer:r2.X.X'
|
||||
@ -53,8 +56,8 @@ compile 'com.google.android.exoplayer:exoplayer-dash:r2.X.X'
|
||||
compile 'com.google.android.exoplayer:exoplayer-ui:r2.X.X'
|
||||
```
|
||||
|
||||
The available modules are listed below. Adding a dependency to the full
|
||||
ExoPlayer library is equivalent to adding dependencies on all of the modules
|
||||
The available library modules are listed below. Adding a dependency to the full
|
||||
library is equivalent to adding dependencies on all of the library modules
|
||||
individually.
|
||||
|
||||
* `exoplayer-core`: Core functionality (required).
|
||||
@ -63,11 +66,16 @@ individually.
|
||||
* `exoplayer-smoothstreaming`: Support for SmoothStreaming content.
|
||||
* `exoplayer-ui`: UI components and resources for use with ExoPlayer.
|
||||
|
||||
For more details, see the project on [Bintray][]. For information about the
|
||||
latest versions, see the [Release notes][].
|
||||
In addition to library modules, ExoPlayer has multiple extension modules that
|
||||
depend on external libraries to provide additional functionality. Some
|
||||
extensions are available from JCenter, whereas others must be built manaully.
|
||||
Browse the [extensions directory] and their individual READMEs for details.
|
||||
|
||||
More information on the library and extension modules that are available from
|
||||
JCenter can be found on [Bintray][].
|
||||
|
||||
[extensions directory][]: https://github.com/google/ExoPlayer/tree/release-v2/extensions/
|
||||
[Bintray]: https://bintray.com/google/exoplayer
|
||||
[Release notes]: https://github.com/google/ExoPlayer/blob/release-v2/RELEASENOTES.md
|
||||
|
||||
### Locally ###
|
||||
|
||||
@ -99,22 +107,16 @@ depend on them as you would on any other local module, for example:
|
||||
```gradle
|
||||
compile project(':exoplayer-library-core')
|
||||
compile project(':exoplayer-library-dash')
|
||||
compile project(':exoplayer-library-ui)
|
||||
compile project(':exoplayer-library-ui')
|
||||
```
|
||||
|
||||
## Developing ExoPlayer ##
|
||||
|
||||
#### Project branches ####
|
||||
|
||||
* The project has `dev-vX` and `release-vX` branches, where `X` is the major
|
||||
version number.
|
||||
* Most development work happens on the `dev-vX` branch with the highest major
|
||||
version number. Pull requests should normally be made to this branch.
|
||||
* Bug fixes may be submitted to older `dev-vX` branches. When doing this, the
|
||||
same (or an equivalent) fix should also be submitted to all subsequent
|
||||
`dev-vX` branches.
|
||||
* A `release-vX` branch holds the most recent stable release for major version
|
||||
`X`.
|
||||
* Development work happens on the `dev-v2` branch. Pull requests should
|
||||
normally be made to this branch.
|
||||
* The `release-v2` branch holds the most recent release.
|
||||
|
||||
#### Using Android Studio ####
|
||||
|
||||
|
@ -1,5 +1,31 @@
|
||||
# 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 ###
|
||||
|
||||
* Fix an issue that could cause the reported playback position to stop advancing
|
||||
|
@ -14,9 +14,12 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
maven {
|
||||
url "https://maven.google.com"
|
||||
}
|
||||
}
|
||||
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'
|
||||
}
|
||||
// Workaround for the following test coverage issue. Remove when fixed:
|
||||
|
@ -24,7 +24,7 @@ project.ext {
|
||||
supportLibraryVersion = '25.4.0'
|
||||
dexmakerVersion = '1.2'
|
||||
mockitoVersion = '1.9.5'
|
||||
releaseVersion = 'r2.5.1'
|
||||
releaseVersion = 'r2.5.2'
|
||||
modulePrefix = ':'
|
||||
if (gradle.ext.has('exoplayerModulePrefix')) {
|
||||
modulePrefix += gradle.ext.exoplayerModulePrefix
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Demo application #
|
||||
# ExoPlayer main demo #
|
||||
|
||||
This folder contains a demo application that uses ExoPlayer to play a number
|
||||
This is the main ExoPlayer demo application. It uses ExoPlayer to play a number
|
||||
of test streams. It can be used as a starting point or reference project when
|
||||
developing other applications that make use of the ExoPlayer library.
|
||||
|
@ -39,9 +39,15 @@ android {
|
||||
disable 'MissingTranslation'
|
||||
}
|
||||
|
||||
flavorDimensions "extensions"
|
||||
|
||||
productFlavors {
|
||||
noExtensions
|
||||
withExtensions
|
||||
noExtensions {
|
||||
dimension "extensions"
|
||||
}
|
||||
withExtensions {
|
||||
dimension "extensions"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,4 +62,5 @@ dependencies {
|
||||
withExtensionsCompile project(path: modulePrefix + 'extension-ima')
|
||||
withExtensionsCompile project(path: modulePrefix + 'extension-opus')
|
||||
withExtensionsCompile project(path: modulePrefix + 'extension-vp9')
|
||||
withExtensionsCompile project(path: modulePrefix + 'extension-rtmp')
|
||||
}
|
||||
|
@ -16,8 +16,8 @@
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.google.android.exoplayer2.demo"
|
||||
android:versionCode="2501"
|
||||
android:versionName="2.5.1">
|
||||
android:versionCode="2502"
|
||||
android:versionName="2.5.2">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
@ -431,6 +431,8 @@ import java.util.Locale;
|
||||
return "YES";
|
||||
case RendererCapabilities.FORMAT_EXCEEDS_CAPABILITIES:
|
||||
return "NO_EXCEEDS_CAPABILITIES";
|
||||
case RendererCapabilities.FORMAT_UNSUPPORTED_DRM:
|
||||
return "NO_UNSUPPORTED_DRM";
|
||||
case RendererCapabilities.FORMAT_UNSUPPORTED_SUBTYPE:
|
||||
return "NO_UNSUPPORTED_TYPE";
|
||||
case RendererCapabilities.FORMAT_UNSUPPORTED_TYPE:
|
||||
|
@ -294,9 +294,9 @@ public class PlayerActivity extends Activity implements OnClickListener, EventLi
|
||||
player = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector);
|
||||
player.addListener(this);
|
||||
player.addListener(eventLogger);
|
||||
player.addMetadataOutput(eventLogger);
|
||||
player.setAudioDebugListener(eventLogger);
|
||||
player.setVideoDebugListener(eventLogger);
|
||||
player.setMetadataOutput(eventLogger);
|
||||
|
||||
simpleExoPlayerView.setPlayer(player);
|
||||
player.setPlayWhenReady(shouldAutoPlay);
|
||||
|
5
extensions/README.md
Normal file
5
extensions/README.md
Normal 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.
|
@ -1,10 +1,8 @@
|
||||
# ExoPlayer Cronet extension #
|
||||
|
||||
## Description ##
|
||||
|
||||
The Cronet extension is an [HttpDataSource][] implementation using [Cronet][].
|
||||
|
||||
[HttpDataSource]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer/upstream/HttpDataSource.html
|
||||
[HttpDataSource]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/upstream/HttpDataSource.html
|
||||
[Cronet]: https://chromium.googlesource.com/chromium/src/+/master/components/cronet?autodive=0%2F%2F
|
||||
|
||||
## Build instructions ##
|
||||
@ -22,12 +20,9 @@ and enable the extension:
|
||||
1. Copy the content of the downloaded `libs` directory into the `jniLibs`
|
||||
directory of this extension
|
||||
|
||||
* In your `settings.gradle` file, add the following line before the line that
|
||||
applies `core_settings.gradle`:
|
||||
|
||||
```gradle
|
||||
gradle.ext.exoplayerIncludeCronetExtension = true;
|
||||
```
|
||||
* In your `settings.gradle` file, add
|
||||
`gradle.ext.exoplayerIncludeCronetExtension = true` before the line that
|
||||
applies `core_settings.gradle`.
|
||||
|
||||
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
|
||||
[here]: https://console.cloud.google.com/storage/browser/chromium-cronet/android
|
||||
@ -56,3 +51,10 @@ new DefaultDataSourceFactory(
|
||||
new CronetDataSourceFactory(...) /* baseDataSourceFactory argument */);
|
||||
```
|
||||
respectively.
|
||||
|
||||
## Links ##
|
||||
|
||||
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.cronet.*`
|
||||
belong to this module.
|
||||
|
||||
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html
|
||||
|
@ -1,18 +1,17 @@
|
||||
# ExoPlayer FFmpeg extension #
|
||||
|
||||
## Description ##
|
||||
|
||||
The FFmpeg extension is a [Renderer][] implementation that uses FFmpeg to decode
|
||||
audio.
|
||||
|
||||
[Renderer]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/Renderer.html
|
||||
The FFmpeg extension provides `FfmpegAudioRenderer`, which uses FFmpeg for
|
||||
decoding and can render audio encoded in a variety of formats.
|
||||
|
||||
## Build instructions ##
|
||||
|
||||
To use this extension you need to clone the ExoPlayer repository and depend on
|
||||
its modules locally. Instructions for doing this can be found in ExoPlayer's
|
||||
[top level README][]. In addition, it's necessary to build the extension's
|
||||
native components as follows:
|
||||
[top level README][]. The extension is not provided via JCenter (see [#2781][]
|
||||
for more information).
|
||||
|
||||
In addition, it's necessary to build the extension's native components as
|
||||
follows:
|
||||
|
||||
* Set the following environment variables:
|
||||
|
||||
@ -34,7 +33,11 @@ NDK_PATH="<path to Android NDK>"
|
||||
HOST_PLATFORM="linux-x86_64"
|
||||
```
|
||||
|
||||
* Fetch and build FFmpeg. For example, to fetch and build for armeabi-v7a,
|
||||
* Fetch and build FFmpeg. The configuration flags determine which formats will
|
||||
be supported. See the [Supported formats][] page for more details of the
|
||||
available flags.
|
||||
|
||||
For example, to fetch and build for armeabi-v7a,
|
||||
arm64-v8a and x86 on Linux x86_64:
|
||||
|
||||
```
|
||||
@ -103,5 +106,42 @@ cd "${FFMPEG_EXT_PATH}"/jni && \
|
||||
${NDK_PATH}/ndk-build APP_ABI="armeabi-v7a arm64-v8a x86" -j4
|
||||
```
|
||||
|
||||
## Using the extension ##
|
||||
|
||||
Once you've followed the instructions above to check out, build and depend on
|
||||
the extension, the next step is to tell ExoPlayer to use `FfmpegAudioRenderer`.
|
||||
How you do this depends on which player API you're using:
|
||||
|
||||
* If you're passing a `DefaultRenderersFactory` to
|
||||
`ExoPlayerFactory.newSimpleInstance`, you can enable using the extension by
|
||||
setting the `extensionRendererMode` parameter of the `DefaultRenderersFactory`
|
||||
constructor to `EXTENSION_RENDERER_MODE_ON`. This will use
|
||||
`FfmpegAudioRenderer` for playback if `MediaCodecAudioRenderer` doesn't
|
||||
support the input format. Pass `EXTENSION_RENDERER_MODE_PREFER` to give
|
||||
`FfmpegAudioRenderer` priority over `MediaCodecAudioRenderer`.
|
||||
* If you've subclassed `DefaultRenderersFactory`, add an `FfmpegAudioRenderer`
|
||||
to the output list in `buildAudioRenderers`. ExoPlayer will use the first
|
||||
`Renderer` in the list that supports the input media format.
|
||||
* If you've implemented your own `RenderersFactory`, return an
|
||||
`FfmpegAudioRenderer` instance from `createRenderers`. ExoPlayer will use the
|
||||
first `Renderer` in the returned array that supports the input media format.
|
||||
* If you're using `ExoPlayerFactory.newInstance`, pass an `FfmpegAudioRenderer`
|
||||
in the array of `Renderer`s. ExoPlayer will use the first `Renderer` in the
|
||||
list that supports the input media format.
|
||||
|
||||
Note: These instructions assume you're using `DefaultTrackSelector`. If you have
|
||||
a custom track selector the choice of `Renderer` is up to your implementation,
|
||||
so you need to make sure you are passing an `FfmpegAudioRenderer` to the player,
|
||||
then implement your own logic to use the renderer for a given track.
|
||||
|
||||
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
|
||||
[Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
|
||||
[#2781]: https://github.com/google/ExoPlayer/issues/2781
|
||||
[Supported formats]: https://google.github.io/ExoPlayer/supported-formats.html#ffmpeg-extension
|
||||
|
||||
## Links ##
|
||||
|
||||
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.ffmpeg.*`
|
||||
belong to this module.
|
||||
|
||||
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html
|
||||
|
@ -1,19 +1,16 @@
|
||||
# ExoPlayer Flac extension #
|
||||
|
||||
## Description ##
|
||||
|
||||
The Flac extension is a [Renderer][] implementation that helps you bundle
|
||||
libFLAC (the Flac decoding library) into your app and use it along with
|
||||
ExoPlayer to play Flac audio on Android devices.
|
||||
|
||||
[Renderer]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/Renderer.html
|
||||
The Flac extension provides `FlacExtractor` and `LibflacAudioRenderer`, which
|
||||
use libFLAC (the Flac decoding library) to extract and decode FLAC audio.
|
||||
|
||||
## Build instructions ##
|
||||
|
||||
To use this extension you need to clone the ExoPlayer repository and depend on
|
||||
its modules locally. Instructions for doing this can be found in ExoPlayer's
|
||||
[top level README][]. In addition, it's necessary to build the extension's
|
||||
native components as follows:
|
||||
[top level README][].
|
||||
|
||||
In addition, it's necessary to build the extension's native components as
|
||||
follows:
|
||||
|
||||
* Set the following environment variables:
|
||||
|
||||
@ -46,3 +43,47 @@ ${NDK_PATH}/ndk-build APP_ABI=all -j4
|
||||
|
||||
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
|
||||
[Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
|
||||
|
||||
## Using the extension ##
|
||||
|
||||
Once you've followed the instructions above to check out, build and depend on
|
||||
the extension, the next step is to tell ExoPlayer to use the extractor and/or
|
||||
renderer.
|
||||
|
||||
### Using `FlacExtractor` ###
|
||||
|
||||
`FlacExtractor` is used via `ExtractorMediaSource`. If you're using
|
||||
`DefaultExtractorsFactory`, `FlacExtractor` will automatically be used to read
|
||||
`.flac` files. If you're not using `DefaultExtractorsFactory`, return a
|
||||
`FlacExtractor` from your `ExtractorsFactory.createExtractors` implementation.
|
||||
|
||||
### Using `LibflacAudioRenderer` ###
|
||||
|
||||
* If you're passing a `DefaultRenderersFactory` to
|
||||
`ExoPlayerFactory.newSimpleInstance`, you can enable using the extension by
|
||||
setting the `extensionRendererMode` parameter of the `DefaultRenderersFactory`
|
||||
constructor to `EXTENSION_RENDERER_MODE_ON`. This will use
|
||||
`LibflacAudioRenderer` for playback if `MediaCodecAudioRenderer` doesn't
|
||||
support the input format. Pass `EXTENSION_RENDERER_MODE_PREFER` to give
|
||||
`LibflacAudioRenderer` priority over `MediaCodecAudioRenderer`.
|
||||
* If you've subclassed `DefaultRenderersFactory`, add a `LibflacAudioRenderer`
|
||||
to the output list in `buildAudioRenderers`. ExoPlayer will use the first
|
||||
`Renderer` in the list that supports the input media format.
|
||||
* If you've implemented your own `RenderersFactory`, return a
|
||||
`LibflacAudioRenderer` instance from `createRenderers`. ExoPlayer will use the
|
||||
first `Renderer` in the returned array that supports the input media format.
|
||||
* If you're using `ExoPlayerFactory.newInstance`, pass a `LibflacAudioRenderer`
|
||||
in the array of `Renderer`s. ExoPlayer will use the first `Renderer` in the
|
||||
list that supports the input media format.
|
||||
|
||||
Note: These instructions assume you're using `DefaultTrackSelector`. If you have
|
||||
a custom track selector the choice of `Renderer` is up to your implementation,
|
||||
so you need to make sure you are passing an `LibflacAudioRenderer` to the
|
||||
player, then implement your own logic to use the renderer for a given track.
|
||||
|
||||
## Links ##
|
||||
|
||||
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.flac.*`
|
||||
belong to this module.
|
||||
|
||||
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html
|
||||
|
@ -1,7 +1,5 @@
|
||||
# ExoPlayer GVR extension #
|
||||
|
||||
## Description ##
|
||||
|
||||
The GVR extension wraps the [Google VR SDK for Android][]. It provides a
|
||||
GvrAudioProcessor, which uses [GvrAudioSurround][] to provide binaural rendering
|
||||
of surround sound and ambisonic soundfields.
|
||||
@ -11,17 +9,7 @@ of surround sound and ambisonic soundfields.
|
||||
|
||||
## Getting the extension ##
|
||||
|
||||
The easiest way to use the extension is to add it as a gradle dependency. You
|
||||
need to make sure you have the jcenter repository included in the `build.gradle`
|
||||
file in the root of your project:
|
||||
|
||||
```gradle
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
```
|
||||
|
||||
Next, include the following in your module's `build.gradle` file:
|
||||
The easiest way to use the extension is to add it as a gradle dependency:
|
||||
|
||||
```gradle
|
||||
compile 'com.google.android.exoplayer:extension-gvr:rX.X.X'
|
||||
@ -36,9 +24,17 @@ locally. Instructions for doing this can be found in ExoPlayer's
|
||||
|
||||
## Using the extension ##
|
||||
|
||||
* If using SimpleExoPlayer, override SimpleExoPlayer.buildAudioProcessors to
|
||||
return a GvrAudioProcessor.
|
||||
* If constructing renderers directly, pass a GvrAudioProcessor to
|
||||
MediaCodecAudioRenderer's constructor.
|
||||
* If using `DefaultRenderersFactory`, override
|
||||
`DefaultRenderersFactory.buildAudioProcessors` to return a
|
||||
`GvrAudioProcessor`.
|
||||
* If constructing renderers directly, pass a `GvrAudioProcessor` to
|
||||
`MediaCodecAudioRenderer`'s constructor.
|
||||
|
||||
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
|
||||
|
||||
## Links ##
|
||||
|
||||
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.gvr.*`
|
||||
belong to this module.
|
||||
|
||||
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html
|
||||
|
@ -1,7 +1,5 @@
|
||||
# ExoPlayer IMA extension #
|
||||
|
||||
## Description ##
|
||||
|
||||
The IMA extension is a [MediaSource][] implementation wrapping the
|
||||
[Interactive Media Ads SDK for Android][IMA]. You can use it to insert ads
|
||||
alongside content.
|
||||
@ -55,3 +53,10 @@ playback.
|
||||
|
||||
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
|
||||
[sample ad tags]: https://developers.google.com/interactive-media-ads/docs/sdks/android/tags
|
||||
|
||||
## Links ##
|
||||
|
||||
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.ima.*`
|
||||
belong to this module.
|
||||
|
||||
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html
|
||||
|
@ -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 plugin: 'com.android.library'
|
||||
|
||||
|
@ -430,7 +430,9 @@ public final class ImaAdsLoader implements Player.EventListener, VideoAdPlayer,
|
||||
} else if (!playingAd) {
|
||||
return VideoProgressUpdate.VIDEO_TIME_NOT_READY;
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,9 @@
|
||||
# ExoPlayer MediaSession extension #
|
||||
|
||||
## Description ##
|
||||
|
||||
The MediaSession extension mediates between an ExoPlayer instance and a
|
||||
[MediaSession][]. It automatically retrieves and implements playback actions
|
||||
and syncs the player state with the state of the media session. The behaviour
|
||||
can be extended to support other playback and custom actions.
|
||||
The MediaSession extension mediates between a Player (or ExoPlayer) instance
|
||||
and a [MediaSession][]. It automatically retrieves and implements playback
|
||||
actions and syncs the player state with the state of the media session. The
|
||||
behaviour can be extended to support other playback and custom actions.
|
||||
|
||||
[MediaSession]: https://developer.android.com/reference/android/support/v4/media/session/MediaSessionCompat.html
|
||||
|
||||
@ -25,3 +23,10 @@ locally. Instructions for doing this can be found in ExoPlayer's
|
||||
[top level README][].
|
||||
|
||||
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
|
||||
|
||||
## Links ##
|
||||
|
||||
* [Javadoc][]: Classes matching
|
||||
`com.google.android.exoplayer2.ext.mediasession.*` belong to this module.
|
||||
|
||||
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html
|
||||
|
@ -47,7 +47,7 @@ import java.util.Map;
|
||||
* Connects a {@link MediaSessionCompat} to a {@link Player}.
|
||||
* <p>
|
||||
* The connector listens for actions sent by the media session's controller and implements these
|
||||
* actions by calling appropriate ExoPlayer methods. The playback state of the media session is
|
||||
* actions by calling appropriate player methods. The playback state of the media session is
|
||||
* automatically synced with the player. The connector can also be optionally extended by providing
|
||||
* various collaborators:
|
||||
* <ul>
|
||||
@ -73,6 +73,10 @@ public final class MediaSessionConnector {
|
||||
}
|
||||
|
||||
public static final String EXTRAS_PITCH = "EXO_PITCH";
|
||||
private static final int BASE_MEDIA_SESSION_FLAGS = MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
|
||||
| MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS;
|
||||
private static final int EDITOR_MEDIA_SESSION_FLAGS = BASE_MEDIA_SESSION_FLAGS
|
||||
| MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS;
|
||||
|
||||
/**
|
||||
* Interface to which playback preparation actions are delegated.
|
||||
@ -318,7 +322,6 @@ public final class MediaSessionConnector {
|
||||
|
||||
private Player player;
|
||||
private CustomActionProvider[] customActionProviders;
|
||||
private int currentWindowIndex;
|
||||
private Map<String, CustomActionProvider> customActionMap;
|
||||
private ErrorMessageProvider errorMessageProvider;
|
||||
private PlaybackPreparer playbackPreparer;
|
||||
@ -369,8 +372,7 @@ public final class MediaSessionConnector {
|
||||
this.handler = new Handler(Looper.myLooper() != null ? Looper.myLooper()
|
||||
: Looper.getMainLooper());
|
||||
this.doMaintainMetadata = doMaintainMetadata;
|
||||
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
|
||||
| MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
|
||||
mediaSession.setFlags(BASE_MEDIA_SESSION_FLAGS);
|
||||
mediaController = mediaSession.getController();
|
||||
mediaSessionCallback = new MediaSessionCallback();
|
||||
exoPlayerEventListener = new ExoPlayerEventListener();
|
||||
@ -433,6 +435,8 @@ public final class MediaSessionConnector {
|
||||
*/
|
||||
public void setQueueEditor(QueueEditor queueEditor) {
|
||||
this.queueEditor = queueEditor;
|
||||
mediaSession.setFlags(queueEditor == null ? BASE_MEDIA_SESSION_FLAGS
|
||||
: EDITOR_MEDIA_SESSION_FLAGS);
|
||||
}
|
||||
|
||||
private void updateMediaSessionPlaybackState() {
|
||||
@ -583,11 +587,20 @@ public final class MediaSessionConnector {
|
||||
|
||||
private class ExoPlayerEventListener implements Player.EventListener {
|
||||
|
||||
private int currentWindowIndex;
|
||||
private int currentWindowCount;
|
||||
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline, Object manifest) {
|
||||
if (queueNavigator != null) {
|
||||
queueNavigator.onTimelineChanged(player);
|
||||
}
|
||||
int windowCount = player.getCurrentTimeline().getWindowCount();
|
||||
if (currentWindowCount != windowCount) {
|
||||
// active queue item and queue navigation actions may need to be updated
|
||||
updateMediaSessionPlaybackState();
|
||||
}
|
||||
currentWindowCount = windowCount;
|
||||
currentWindowIndex = player.getCurrentWindowIndex();
|
||||
updateMediaSessionMetadata();
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
# ExoPlayer OkHttp extension #
|
||||
|
||||
## Description ##
|
||||
|
||||
The OkHttp extension is an [HttpDataSource][] implementation using Square's
|
||||
[OkHttp][].
|
||||
|
||||
@ -49,3 +47,10 @@ new DefaultDataSourceFactory(
|
||||
new OkHttpDataSourceFactory(...) /* baseDataSourceFactory argument */);
|
||||
```
|
||||
respectively.
|
||||
|
||||
## Links ##
|
||||
|
||||
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.okhttp.*`
|
||||
belong to this module.
|
||||
|
||||
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html
|
||||
|
@ -1,19 +1,16 @@
|
||||
# ExoPlayer Opus extension #
|
||||
|
||||
## Description ##
|
||||
|
||||
The Opus extension is a [Renderer][] implementation that helps you bundle
|
||||
libopus (the Opus decoding library) into your app and use it along with
|
||||
ExoPlayer to play Opus audio on Android devices.
|
||||
|
||||
[Renderer]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/Renderer.html
|
||||
The Opus extension provides `LibopusAudioRenderer`, which uses libopus (the Opus
|
||||
decoding library) to decode Opus audio.
|
||||
|
||||
## Build instructions ##
|
||||
|
||||
To use this extension you need to clone the ExoPlayer repository and depend on
|
||||
its modules locally. Instructions for doing this can be found in ExoPlayer's
|
||||
[top level README][]. In addition, it's necessary to build the extension's
|
||||
native components as follows:
|
||||
[top level README][].
|
||||
|
||||
In addition, it's necessary to build the extension's native components as
|
||||
follows:
|
||||
|
||||
* Set the following environment variables:
|
||||
|
||||
@ -59,3 +56,38 @@ ${NDK_PATH}/ndk-build APP_ABI=all -j4
|
||||
* Clean and re-build the project.
|
||||
* If you want to use your own version of libopus, place it in
|
||||
`${OPUS_EXT_PATH}/jni/libopus`.
|
||||
|
||||
## Using the extension ##
|
||||
|
||||
Once you've followed the instructions above to check out, build and depend on
|
||||
the extension, the next step is to tell ExoPlayer to use `LibopusAudioRenderer`.
|
||||
How you do this depends on which player API you're using:
|
||||
|
||||
* If you're passing a `DefaultRenderersFactory` to
|
||||
`ExoPlayerFactory.newSimpleInstance`, you can enable using the extension by
|
||||
setting the `extensionRendererMode` parameter of the `DefaultRenderersFactory`
|
||||
constructor to `EXTENSION_RENDERER_MODE_ON`. This will use
|
||||
`LibopusAudioRenderer` for playback if `MediaCodecAudioRenderer` doesn't
|
||||
support the input format. Pass `EXTENSION_RENDERER_MODE_PREFER` to give
|
||||
`LibopusAudioRenderer` priority over `MediaCodecAudioRenderer`.
|
||||
* If you've subclassed `DefaultRenderersFactory`, add a `LibopusAudioRenderer`
|
||||
to the output list in `buildAudioRenderers`. ExoPlayer will use the first
|
||||
`Renderer` in the list that supports the input media format.
|
||||
* If you've implemented your own `RenderersFactory`, return a
|
||||
`LibopusAudioRenderer` instance from `createRenderers`. ExoPlayer will use the
|
||||
first `Renderer` in the returned array that supports the input media format.
|
||||
* If you're using `ExoPlayerFactory.newInstance`, pass a `LibopusAudioRenderer`
|
||||
in the array of `Renderer`s. ExoPlayer will use the first `Renderer` in the
|
||||
list that supports the input media format.
|
||||
|
||||
Note: These instructions assume you're using `DefaultTrackSelector`. If you have
|
||||
a custom track selector the choice of `Renderer` is up to your implementation,
|
||||
so you need to make sure you are passing an `LibopusAudioRenderer` to the
|
||||
player, then implement your own logic to use the renderer for a given track.
|
||||
|
||||
## Links ##
|
||||
|
||||
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.opus.*`
|
||||
belong to this module.
|
||||
|
||||
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html
|
||||
|
@ -1,7 +1,5 @@
|
||||
# ExoPlayer RTMP extension #
|
||||
|
||||
## Description ##
|
||||
|
||||
The RTMP extension is a [DataSource][] implementation for playing [RTMP][]
|
||||
streams using [LibRtmp Client for Android][].
|
||||
|
||||
@ -9,7 +7,7 @@ streams using [LibRtmp Client for Android][].
|
||||
[RTMP]: https://en.wikipedia.org/wiki/Real-Time_Messaging_Protocol
|
||||
[LibRtmp Client for Android]: https://github.com/ant-media/LibRtmp-Client-for-Android
|
||||
|
||||
## Using the extension ##
|
||||
## Getting the extension ##
|
||||
|
||||
The easiest way to use the extension is to add it as a gradle dependency:
|
||||
|
||||
@ -25,3 +23,26 @@ locally. Instructions for doing this can be found in ExoPlayer's
|
||||
[top level README][].
|
||||
|
||||
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md
|
||||
|
||||
## Using the extension ##
|
||||
|
||||
ExoPlayer requests data through `DataSource` instances. These instances are
|
||||
either instantiated and injected from application code, or obtained from
|
||||
instances of `DataSource.Factory` that are instantiated and injected from
|
||||
application code.
|
||||
|
||||
`DefaultDataSource` will automatically use uses the RTMP extension whenever it's
|
||||
available. Hence if your application is using `DefaultDataSource` or
|
||||
`DefaultDataSourceFactory`, adding support for RTMP streams is as simple as
|
||||
adding a dependency to the RTMP extension as described above. No changes to your
|
||||
application code are required. Alternatively, if you know that your application
|
||||
doesn't need to handle any other protocols, you can update any `DataSource`s and
|
||||
`DataSource.Factory` instantiations in your application code to use
|
||||
`RtmpDataSource` and `RtmpDataSourceFactory` directly.
|
||||
|
||||
## Links ##
|
||||
|
||||
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.rtmp.*`
|
||||
belong to this module.
|
||||
|
||||
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html
|
||||
|
@ -26,7 +26,7 @@ android {
|
||||
|
||||
dependencies {
|
||||
compile project(modulePrefix + 'library-core')
|
||||
compile 'net.butterflytv.utils:rtmp-client:0.2.8'
|
||||
compile 'net.butterflytv.utils:rtmp-client:3.0.0'
|
||||
}
|
||||
|
||||
ext {
|
||||
|
@ -1,19 +1,16 @@
|
||||
# ExoPlayer VP9 extension #
|
||||
|
||||
## Description ##
|
||||
|
||||
The VP9 extension is a [Renderer][] implementation that helps you bundle libvpx
|
||||
(the VP9 decoding library) into your app and use it along with ExoPlayer to play
|
||||
VP9 video on Android devices.
|
||||
|
||||
[Renderer]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/Renderer.html
|
||||
The VP9 extension provides `LibvpxVideoRenderer`, which uses libvpx (the VPx
|
||||
decoding library) to decode VP9 video.
|
||||
|
||||
## Build instructions ##
|
||||
|
||||
To use this extension you need to clone the ExoPlayer repository and depend on
|
||||
its modules locally. Instructions for doing this can be found in ExoPlayer's
|
||||
[top level README][]. In addition, it's necessary to build the extension's
|
||||
native components as follows:
|
||||
[top level README][].
|
||||
|
||||
In addition, it's necessary to build the extension's native components as
|
||||
follows:
|
||||
|
||||
* Set the following environment variables:
|
||||
|
||||
@ -76,3 +73,45 @@ ${NDK_PATH}/ndk-build APP_ABI=all -j4
|
||||
`${VP9_EXT_PATH}/jni/libvpx` or `${VP9_EXT_PATH}/jni/libyuv` respectively. But
|
||||
please note that `generate_libvpx_android_configs.sh` and the makefiles need
|
||||
to be modified to work with arbitrary versions of libvpx and libyuv.
|
||||
|
||||
## Using the extension ##
|
||||
|
||||
Once you've followed the instructions above to check out, build and depend on
|
||||
the extension, the next step is to tell ExoPlayer to use `LibvpxVideoRenderer`.
|
||||
How you do this depends on which player API you're using:
|
||||
|
||||
* If you're passing a `DefaultRenderersFactory` to
|
||||
`ExoPlayerFactory.newSimpleInstance`, you can enable using the extension by
|
||||
setting the `extensionRendererMode` parameter of the `DefaultRenderersFactory`
|
||||
constructor to `EXTENSION_RENDERER_MODE_ON`. This will use
|
||||
`LibvpxVideoRenderer` for playback if `MediaCodecVideoRenderer` doesn't
|
||||
support decoding the input VP9 stream. Pass `EXTENSION_RENDERER_MODE_PREFER`
|
||||
to give `LibvpxVideoRenderer` priority over `MediaCodecVideoRenderer`.
|
||||
* If you've subclassed `DefaultRenderersFactory`, add a `LibvpxVideoRenderer`
|
||||
to the output list in `buildVideoRenderers`. ExoPlayer will use the first
|
||||
`Renderer` in the list that supports the input media format.
|
||||
* If you've implemented your own `RenderersFactory`, return a
|
||||
`LibvpxVideoRenderer` instance from `createRenderers`. ExoPlayer will use the
|
||||
first `Renderer` in the returned array that supports the input media format.
|
||||
* If you're using `ExoPlayerFactory.newInstance`, pass a `LibvpxVideoRenderer`
|
||||
in the array of `Renderer`s. ExoPlayer will use the first `Renderer` in the
|
||||
list that supports the input media format.
|
||||
|
||||
Note: These instructions assume you're using `DefaultTrackSelector`. If you have
|
||||
a custom track selector the choice of `Renderer` is up to your implementation,
|
||||
so you need to make sure you are passing an `LibvpxVideoRenderer` to the
|
||||
player, then implement your own logic to use the renderer for a given track.
|
||||
|
||||
`LibvpxVideoRenderer` can optionally output to a `VpxVideoSurfaceView` when not
|
||||
being used via `SimpleExoPlayer`, in which case color space conversion will be
|
||||
performed using a GL shader. To enable this mode, send the renderer a message of
|
||||
type `LibvpxVideoRenderer.MSG_SET_OUTPUT_BUFFER_RENDERER` with the
|
||||
`VpxVideoSurfaceView` as its object, instead of sending `MSG_SET_SURFACE` with a
|
||||
`Surface`.
|
||||
|
||||
## Links ##
|
||||
|
||||
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.vp9.*`
|
||||
belong to this module.
|
||||
|
||||
[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html
|
||||
|
@ -197,12 +197,12 @@ DECODER_FUNC(jint, vpxGetFrame, jlong jContext, jobject jOutputBuffer) {
|
||||
const int32_t uvHeight = (img->d_h + 1) / 2;
|
||||
const uint64_t yLength = img->stride[VPX_PLANE_Y] * img->d_h;
|
||||
const uint64_t uvLength = img->stride[VPX_PLANE_U] * uvHeight;
|
||||
int sample = 0;
|
||||
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
|
||||
// memory. The long term goal however is to upload half-float/short so
|
||||
// it's not important to optimize the stride at this time.
|
||||
// Y
|
||||
int sampleY = 0;
|
||||
for (int y = 0; y < img->d_h; y++) {
|
||||
const uint16_t* srcBase = reinterpret_cast<uint16_t*>(
|
||||
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++) {
|
||||
// Lightweight dither. Carryover the remainder of each 10->8 bit
|
||||
// conversion to the next pixel.
|
||||
sample += *srcBase++;
|
||||
*destBase++ = sample >> 2;
|
||||
sample = sample & 3; // Remainder.
|
||||
sampleY += *srcBase++;
|
||||
*destBase++ = sampleY >> 2;
|
||||
sampleY = sampleY & 3; // Remainder.
|
||||
}
|
||||
}
|
||||
// UV
|
||||
int sampleU = 0;
|
||||
int sampleV = 0;
|
||||
const int32_t uvWidth = (img->d_w + 1) / 2;
|
||||
for (int y = 0; y < uvHeight; y++) {
|
||||
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++) {
|
||||
// Lightweight dither. Carryover the remainder of each 10->8 bit
|
||||
// conversion to the next pixel.
|
||||
sample += *srcUBase++;
|
||||
*destUBase++ = sample >> 2;
|
||||
sample = (*srcVBase++) + (sample & 3); // srcV + previousRemainder.
|
||||
*destVBase++ = sample >> 2;
|
||||
sample = sample & 3; // Remainder.
|
||||
sampleU += *srcUBase++;
|
||||
*destUBase++ = sampleU >> 2;
|
||||
sampleU = sampleU & 3; // Remainder.
|
||||
sampleV += *srcVBase++;
|
||||
*destVBase++ = sampleV >> 2;
|
||||
sampleV = sampleV & 3; // Remainder.
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
||||
#Wed Jul 12 10:31:13 BST 2017
|
||||
#Tue Sep 05 13:43:42 BST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
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
7
library/README.md
Normal 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
13
library/all/README.md
Normal 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
9
library/core/README.md
Normal 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
|
Binary file not shown.
@ -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
|
@ -16,7 +16,6 @@
|
||||
package com.google.android.exoplayer2.extractor.mp4;
|
||||
|
||||
import android.test.InstrumentationTestCase;
|
||||
import com.google.android.exoplayer2.ParserException;
|
||||
import com.google.android.exoplayer2.extractor.Extractor;
|
||||
import com.google.android.exoplayer2.testutil.ExtractorAsserts;
|
||||
import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory;
|
||||
@ -38,11 +37,6 @@ public final class FragmentedMp4ExtractorTest extends InstrumentationTestCase {
|
||||
"mp4/sample_fragmented_sei.mp4", getInstrumentation());
|
||||
}
|
||||
|
||||
public void testAtomWithZeroSize() throws Exception {
|
||||
ExtractorAsserts.assertThrows(getExtractorFactory(), "mp4/sample_fragmented_zero_size_atom.mp4",
|
||||
getInstrumentation(), ParserException.class);
|
||||
}
|
||||
|
||||
private static ExtractorFactory getExtractorFactory() {
|
||||
return getExtractorFactory(0);
|
||||
}
|
||||
|
@ -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_SEQUENCE = "subrip/typical_missing_sequence";
|
||||
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";
|
||||
|
||||
public void testDecodeEmpty() throws IOException {
|
||||
@ -107,6 +108,17 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
|
||||
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 {
|
||||
SubripDecoder decoder = new SubripDecoder();
|
||||
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), NO_END_TIMECODES_FILE);
|
||||
|
@ -23,9 +23,12 @@ import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.test.InstrumentationTestCase;
|
||||
import android.test.MoreAsserts;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 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 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 {
|
||||
ContentDataSource dataSource = new ContentDataSource(getInstrumentation().getContext());
|
||||
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.
|
||||
*/
|
||||
|
@ -319,8 +319,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||
if (timeline.isEmpty() || pendingSeekAcks > 0) {
|
||||
return maskingWindowPositionMs;
|
||||
} else {
|
||||
timeline.getPeriod(playbackInfo.periodId.periodIndex, period);
|
||||
return period.getPositionInWindowMs() + C.usToMs(playbackInfo.positionUs);
|
||||
return playbackInfoPositionUsToWindowPositionMs(playbackInfo.positionUs);
|
||||
}
|
||||
}
|
||||
|
||||
@ -330,8 +329,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||
if (timeline.isEmpty() || pendingSeekAcks > 0) {
|
||||
return maskingWindowPositionMs;
|
||||
} else {
|
||||
timeline.getPeriod(playbackInfo.periodId.periodIndex, period);
|
||||
return period.getPositionInWindowMs() + C.usToMs(playbackInfo.bufferedPositionUs);
|
||||
return playbackInfoPositionUsToWindowPositionMs(playbackInfo.bufferedPositionUs);
|
||||
}
|
||||
}
|
||||
|
||||
@ -358,7 +356,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
@Override
|
||||
public boolean isPlayingAd() {
|
||||
return pendingSeekAcks == 0 && playbackInfo.periodId.adGroupIndex != C.INDEX_UNSET;
|
||||
return pendingSeekAcks == 0 && playbackInfo.periodId.isAd();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -448,6 +446,12 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||
case ExoPlayerImplInternal.MSG_SEEK_ACK: {
|
||||
if (--pendingSeekAcks == 0) {
|
||||
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) {
|
||||
for (Player.EventListener listener : listeners) {
|
||||
listener.onPositionDiscontinuity();
|
||||
@ -472,6 +476,12 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||
timeline = sourceInfo.timeline;
|
||||
manifest = sourceInfo.manifest;
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -263,13 +263,18 @@ import java.io.IOException;
|
||||
}
|
||||
int messageNumber = customMessagesSent++;
|
||||
handler.obtainMessage(MSG_CUSTOM, messages).sendToTarget();
|
||||
boolean wasInterrupted = false;
|
||||
while (customMessagesProcessed <= messageNumber) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
wasInterrupted = true;
|
||||
}
|
||||
}
|
||||
if (wasInterrupted) {
|
||||
// Restore the interrupted status.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void release() {
|
||||
@ -277,13 +282,18 @@ import java.io.IOException;
|
||||
return;
|
||||
}
|
||||
handler.sendEmptyMessage(MSG_RELEASE);
|
||||
boolean wasInterrupted = false;
|
||||
while (!released) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
wasInterrupted = true;
|
||||
}
|
||||
}
|
||||
if (wasInterrupted) {
|
||||
// Restore the interrupted status.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
internalPlaybackThread.quit();
|
||||
}
|
||||
|
||||
|
@ -31,13 +31,13 @@ public final class ExoPlayerLibraryInfo {
|
||||
* 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.
|
||||
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}.
|
||||
*/
|
||||
// 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.
|
||||
@ -47,7 +47,7 @@ public final class ExoPlayerLibraryInfo {
|
||||
* integer version 123045006 (123-045-006).
|
||||
*/
|
||||
// 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}
|
||||
|
@ -24,7 +24,7 @@ public interface RendererCapabilities {
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*/
|
||||
int FORMAT_SUPPORT_MASK = 0b111;
|
||||
@ -117,8 +117,8 @@ public interface RendererCapabilities {
|
||||
* the bitwise OR of three properties:
|
||||
* <ul>
|
||||
* <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_UNSUPPORTED_TYPE}.</li>
|
||||
* {@link #FORMAT_EXCEEDS_CAPABILITIES}, {@link #FORMAT_UNSUPPORTED_DRM},
|
||||
* {@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.
|
||||
* One of {@link #ADAPTIVE_SEAMLESS}, {@link #ADAPTIVE_NOT_SEAMLESS} and
|
||||
* {@link #ADAPTIVE_NOT_SUPPORTED}.</li>
|
||||
|
@ -41,6 +41,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
/**
|
||||
* 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 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 audioRendererCount;
|
||||
|
||||
@ -99,9 +103,6 @@ public class SimpleExoPlayer implements ExoPlayer {
|
||||
private int videoScalingMode;
|
||||
private SurfaceHolder surfaceHolder;
|
||||
private TextureView textureView;
|
||||
private TextRenderer.Output textOutput;
|
||||
private MetadataRenderer.Output metadataOutput;
|
||||
private VideoListener videoListener;
|
||||
private AudioRendererEventListener audioDebugListener;
|
||||
private VideoRendererEventListener videoDebugListener;
|
||||
private DecoderCounters videoDecoderCounters;
|
||||
@ -113,6 +114,9 @@ public class SimpleExoPlayer implements ExoPlayer {
|
||||
protected SimpleExoPlayer(RenderersFactory renderersFactory, TrackSelector trackSelector,
|
||||
LoadControl loadControl) {
|
||||
componentListener = new ComponentListener();
|
||||
videoListeners = new CopyOnWriteArraySet<>();
|
||||
textOutputs = new CopyOnWriteArraySet<>();
|
||||
metadataOutputs = new CopyOnWriteArraySet<>();
|
||||
Looper eventLooper = Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper();
|
||||
Handler eventHandler = new Handler(eventLooper);
|
||||
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.
|
||||
* @deprecated Use {@link #addVideoListener(VideoListener)}.
|
||||
*/
|
||||
@Deprecated
|
||||
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.
|
||||
* @deprecated Use {@link #removeVideoListener(VideoListener)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public void clearVideoListener(VideoListener listener) {
|
||||
if (videoListener == listener) {
|
||||
videoListener = null;
|
||||
}
|
||||
removeVideoListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @deprecated Use {@link #addTextOutput(TextRenderer.Output)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setTextOutput(TextRenderer.Output output) {
|
||||
textOutput = 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;
|
||||
textOutputs.clear();
|
||||
if (output != null) {
|
||||
addTextOutput(output);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @deprecated Use {@link #addMetadataOutput(MetadataRenderer.Output)}.
|
||||
*/
|
||||
@Deprecated
|
||||
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.
|
||||
* @deprecated Use {@link #removeMetadataOutput(MetadataRenderer.Output)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public void clearMetadataOutput(MetadataRenderer.Output output) {
|
||||
if (metadataOutput == output) {
|
||||
metadataOutput = null;
|
||||
}
|
||||
removeMetadataOutput(output);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -803,7 +876,7 @@ public class SimpleExoPlayer implements ExoPlayer {
|
||||
@Override
|
||||
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
|
||||
float pixelWidthHeightRatio) {
|
||||
if (videoListener != null) {
|
||||
for (VideoListener videoListener : videoListeners) {
|
||||
videoListener.onVideoSizeChanged(width, height, unappliedRotationDegrees,
|
||||
pixelWidthHeightRatio);
|
||||
}
|
||||
@ -815,8 +888,10 @@ public class SimpleExoPlayer implements ExoPlayer {
|
||||
|
||||
@Override
|
||||
public void onRenderedFirstFrame(Surface surface) {
|
||||
if (videoListener != null && SimpleExoPlayer.this.surface == surface) {
|
||||
videoListener.onRenderedFirstFrame();
|
||||
if (SimpleExoPlayer.this.surface == surface) {
|
||||
for (VideoListener videoListener : videoListeners) {
|
||||
videoListener.onRenderedFirstFrame();
|
||||
}
|
||||
}
|
||||
if (videoDebugListener != null) {
|
||||
videoDebugListener.onRenderedFirstFrame(surface);
|
||||
@ -889,7 +964,7 @@ public class SimpleExoPlayer implements ExoPlayer {
|
||||
|
||||
@Override
|
||||
public void onCues(List<Cue> cues) {
|
||||
if (textOutput != null) {
|
||||
for (TextRenderer.Output textOutput : textOutputs) {
|
||||
textOutput.onCues(cues);
|
||||
}
|
||||
}
|
||||
@ -898,7 +973,7 @@ public class SimpleExoPlayer implements ExoPlayer {
|
||||
|
||||
@Override
|
||||
public void onMetadata(Metadata metadata) {
|
||||
if (metadataOutput != null) {
|
||||
for (MetadataRenderer.Output metadataOutput : metadataOutputs) {
|
||||
metadataOutput.onMetadata(metadata);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
* {@link Window#id}.
|
||||
|
@ -241,7 +241,7 @@ import java.util.Arrays;
|
||||
for (int i = 0; i < period; i++) {
|
||||
short sVal = samples[position + 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
|
||||
// skip samples. Thus, diff is a 24 bit number, and we can safely multiply by numSamples
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
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) {
|
||||
super(message);
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get error code
|
||||
*/
|
||||
public int getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,9 @@ import java.util.Map;
|
||||
@TargetApi(16)
|
||||
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 {
|
||||
|
||||
public DrmSessionException(Throwable cause) {
|
||||
|
@ -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.DataSpec;
|
||||
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.Util;
|
||||
import java.io.IOException;
|
||||
@ -39,33 +38,33 @@ import java.util.UUID;
|
||||
public final class HttpMediaDrmCallback implements MediaDrmCallback {
|
||||
|
||||
private final HttpDataSource.Factory dataSourceFactory;
|
||||
private final String defaultUrl;
|
||||
private final String defaultLicenseUrl;
|
||||
private final boolean forceDefaultLicenseUrl;
|
||||
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.
|
||||
*/
|
||||
public HttpMediaDrmCallback(String defaultUrl, HttpDataSource.Factory dataSourceFactory) {
|
||||
this(defaultUrl, dataSourceFactory, null);
|
||||
public HttpMediaDrmCallback(String defaultLicenseUrl, HttpDataSource.Factory dataSourceFactory) {
|
||||
this(defaultLicenseUrl, false, dataSourceFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link HttpMediaDrmCallback#HttpMediaDrmCallback(String, Factory)}. Request
|
||||
* properties can be set by calling {@link #setKeyRequestProperty(String, String)}.
|
||||
* @param defaultUrl The default license URL.
|
||||
* @param defaultLicenseUrl The default license URL. Used for key requests that do not specify
|
||||
* their own license URL, or for all key requests if {@code forceDefaultLicenseUrl} is
|
||||
* 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 keyRequestProperties Request properties to set when making key requests, or null.
|
||||
*/
|
||||
@Deprecated
|
||||
public HttpMediaDrmCallback(String defaultUrl, HttpDataSource.Factory dataSourceFactory,
|
||||
Map<String, String> keyRequestProperties) {
|
||||
public HttpMediaDrmCallback(String defaultLicenseUrl, boolean forceDefaultLicenseUrl,
|
||||
HttpDataSource.Factory dataSourceFactory) {
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
this.defaultUrl = defaultUrl;
|
||||
this.defaultLicenseUrl = defaultLicenseUrl;
|
||||
this.forceDefaultLicenseUrl = forceDefaultLicenseUrl;
|
||||
this.keyRequestProperties = new HashMap<>();
|
||||
if (keyRequestProperties != null) {
|
||||
this.keyRequestProperties.putAll(keyRequestProperties);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,8 +111,8 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback {
|
||||
@Override
|
||||
public byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws Exception {
|
||||
String url = request.getDefaultUrl();
|
||||
if (TextUtils.isEmpty(url)) {
|
||||
url = defaultUrl;
|
||||
if (forceDefaultLicenseUrl || TextUtils.isEmpty(url)) {
|
||||
url = defaultLicenseUrl;
|
||||
}
|
||||
Map<String, String> requestProperties = new HashMap<>();
|
||||
// Add standard request properties for supported schemes.
|
||||
|
@ -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
|
||||
* 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.
|
||||
* @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 licenseUrl, Factory httpDataSourceFactory) throws UnsupportedDrmException {
|
||||
return newWidevineInstance(
|
||||
new HttpMediaDrmCallback(licenseUrl, httpDataSourceFactory), null);
|
||||
String defaultLicenseUrl, Factory httpDataSourceFactory)
|
||||
throws UnsupportedDrmException {
|
||||
return newWidevineInstance(defaultLicenseUrl, false, httpDataSourceFactory, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new instance which uses Widevine CDM. Call {@link #release()} when the instance
|
||||
* 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
|
||||
* to {@link MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null.
|
||||
* @return A new instance which uses Widevine CDM.
|
||||
@ -70,9 +94,11 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
|
||||
* MediaDrmCallback, HashMap, Handler, EventListener)
|
||||
*/
|
||||
public static OfflineLicenseHelper<FrameworkMediaCrypto> newWidevineInstance(
|
||||
MediaDrmCallback callback, HashMap<String, String> optionalKeyRequestParameters)
|
||||
String defaultLicenseUrl, boolean forceDefaultLicenseUrl, Factory httpDataSourceFactory,
|
||||
HashMap<String, String> optionalKeyRequestParameters)
|
||||
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);
|
||||
}
|
||||
|
||||
@ -116,9 +142,32 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
|
||||
optionalKeyRequestParameters, new Handler(handlerThread.getLooper()), eventListener);
|
||||
}
|
||||
|
||||
/** Releases the helper. Should be called when the helper is no longer required. */
|
||||
public void release() {
|
||||
handlerThread.quit();
|
||||
/**
|
||||
* @see DefaultDrmSessionManager#getPropertyByteArray
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
DrmInitData drmInitData) throws DrmSessionException {
|
||||
DrmSession<T> drmSession = openBlockingKeyRequest(licenseMode, offlineLicenseKeySetId,
|
||||
|
@ -39,9 +39,14 @@ import java.util.List;
|
||||
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_avc1 = Util.getIntegerCodeForString("avc1");
|
||||
|
@ -995,9 +995,10 @@ import java.util.List;
|
||||
int objectTypeIndication = parent.readUnsignedByte();
|
||||
String mimeType;
|
||||
switch (objectTypeIndication) {
|
||||
case 0x6B:
|
||||
mimeType = MimeTypes.AUDIO_MPEG;
|
||||
return Pair.create(mimeType, null);
|
||||
case 0x60:
|
||||
case 0x61:
|
||||
mimeType = MimeTypes.VIDEO_MPEG2;
|
||||
break;
|
||||
case 0x20:
|
||||
mimeType = MimeTypes.VIDEO_MP4V;
|
||||
break;
|
||||
@ -1007,6 +1008,9 @@ import java.util.List;
|
||||
case 0x23:
|
||||
mimeType = MimeTypes.VIDEO_H265;
|
||||
break;
|
||||
case 0x6B:
|
||||
mimeType = MimeTypes.AUDIO_MPEG;
|
||||
return Pair.create(mimeType, null);
|
||||
case 0x40:
|
||||
case 0x66:
|
||||
case 0x67:
|
||||
@ -1034,8 +1038,8 @@ import java.util.List;
|
||||
|
||||
parent.skipBytes(12);
|
||||
|
||||
// Start of the AudioSpecificConfig.
|
||||
parent.skipBytes(1); // AudioSpecificConfig tag
|
||||
// Start of the DecoderSpecificInfo.
|
||||
parent.skipBytes(1); // DecoderSpecificInfo tag
|
||||
int initializationDataSize = parseExpandableClassSize(parent);
|
||||
byte[] initializationData = new byte[initializationDataSize];
|
||||
parent.readBytes(initializationData, 0, initializationDataSize);
|
||||
|
@ -283,12 +283,22 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
atomType = atomHeader.readInt();
|
||||
}
|
||||
|
||||
if (atomSize == Atom.LONG_SIZE_PREFIX) {
|
||||
// Read the extended atom size.
|
||||
if (atomSize == Atom.DEFINES_LARGE_SIZE) {
|
||||
// Read the large size.
|
||||
int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE;
|
||||
input.readFully(atomHeader.data, Atom.HEADER_SIZE, headerBytesRemaining);
|
||||
atomHeaderBytesRead += headerBytesRemaining;
|
||||
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) {
|
||||
|
@ -205,12 +205,26 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
atomType = atomHeader.readInt();
|
||||
}
|
||||
|
||||
if (atomSize == Atom.LONG_SIZE_PREFIX) {
|
||||
// Read the extended atom size.
|
||||
if (atomSize == Atom.DEFINES_LARGE_SIZE) {
|
||||
// Read the large size.
|
||||
int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE;
|
||||
input.readFully(atomHeader.data, Atom.HEADER_SIZE, headerBytesRemaining);
|
||||
atomHeaderBytesRead += headerBytesRemaining;
|
||||
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)) {
|
||||
|
@ -104,11 +104,18 @@ import java.io.IOException;
|
||||
input.peekFully(buffer.data, 0, headerSize);
|
||||
long atomSize = buffer.readUnsignedInt();
|
||||
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;
|
||||
input.peekFully(buffer.data, Atom.HEADER_SIZE, Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE);
|
||||
buffer.setLimit(Atom.LONG_HEADER_SIZE);
|
||||
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) {
|
||||
|
@ -288,9 +288,11 @@ public final class MediaCodecUtil {
|
||||
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)
|
||||
&& "a70".equals(Util.DEVICE)) {
|
||||
&& ("a70".equals(Util.DEVICE)
|
||||
|| ("Xiaomi".equals(Util.MANUFACTURER) && Util.DEVICE.startsWith("HM")))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -16,12 +16,15 @@
|
||||
package com.google.android.exoplayer2.source;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
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 {
|
||||
|
||||
|
@ -23,7 +23,19 @@ import com.google.android.exoplayer2.upstream.Allocator;
|
||||
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 {
|
||||
|
||||
|
@ -69,6 +69,11 @@ public final class SubripDecoder extends SimpleSubtitleDecoder {
|
||||
// Read and parse the timing line.
|
||||
boolean haveEndTimecode = false;
|
||||
currentLine = subripData.readLine();
|
||||
if (currentLine == null) {
|
||||
Log.w(TAG, "Unexpected end");
|
||||
break;
|
||||
}
|
||||
|
||||
Matcher matcher = SUBRIP_TIMING_LINE.matcher(currentLine);
|
||||
if (matcher.matches()) {
|
||||
cueTimesUs.add(parseTimecode(matcher, 1));
|
||||
|
@ -21,6 +21,7 @@ import android.text.Layout.Alignment;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.AbsoluteSizeSpan;
|
||||
import android.text.style.AlignmentSpan;
|
||||
import android.text.style.BackgroundColorSpan;
|
||||
@ -92,19 +93,24 @@ import java.util.regex.Pattern;
|
||||
/* package */ boolean parseCue(ParsableByteArray webvttData, WebvttCue.Builder builder,
|
||||
List<WebvttCssStyle> styles) {
|
||||
String firstLine = webvttData.readLine();
|
||||
if (firstLine == null) {
|
||||
return false;
|
||||
}
|
||||
Matcher cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(firstLine);
|
||||
if (cueHeaderMatcher.matches()) {
|
||||
// We have found the timestamps in the first line. No id present.
|
||||
return parseCue(null, cueHeaderMatcher, webvttData, builder, textBuilder, styles);
|
||||
} else {
|
||||
// The first line is not the timestamps, but could be the cue id.
|
||||
String secondLine = webvttData.readLine();
|
||||
cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(secondLine);
|
||||
if (cueHeaderMatcher.matches()) {
|
||||
// We can do the rest of the parsing, including the id.
|
||||
return parseCue(firstLine.trim(), cueHeaderMatcher, webvttData, builder, textBuilder,
|
||||
styles);
|
||||
}
|
||||
}
|
||||
// The first line is not the timestamps, but could be the cue id.
|
||||
String secondLine = webvttData.readLine();
|
||||
if (secondLine == null) {
|
||||
return false;
|
||||
}
|
||||
cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(secondLine);
|
||||
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;
|
||||
}
|
||||
@ -233,7 +239,7 @@ import java.util.regex.Pattern;
|
||||
// Parse the cue text.
|
||||
textBuilder.setLength(0);
|
||||
String line;
|
||||
while ((line = webvttData.readLine()) != null && !line.isEmpty()) {
|
||||
while (!TextUtils.isEmpty(line = webvttData.readLine())) {
|
||||
if (textBuilder.length() > 0) {
|
||||
textBuilder.append("\n");
|
||||
}
|
||||
|
@ -767,7 +767,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
* @throws ExoPlaybackException If an error occurs while selecting the tracks.
|
||||
*/
|
||||
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 selectedTrackIndex = C.INDEX_UNSET;
|
||||
int selectedTrackScore = 0;
|
||||
@ -893,7 +894,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
* @throws ExoPlaybackException If an error occurs while selecting the tracks.
|
||||
*/
|
||||
protected TrackSelection selectTextTrack(TrackGroupArray groups, int[][] formatSupport,
|
||||
Parameters params) {
|
||||
Parameters params) throws ExoPlaybackException {
|
||||
TrackGroup selectedGroup = null;
|
||||
int selectedTrackIndex = 0;
|
||||
int selectedTrackScore = 0;
|
||||
@ -960,7 +961,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
* @throws ExoPlaybackException If an error occurs while selecting the tracks.
|
||||
*/
|
||||
protected TrackSelection selectOtherTrack(int trackType, TrackGroupArray groups,
|
||||
int[][] formatSupport, Parameters params) {
|
||||
int[][] formatSupport, Parameters params) throws ExoPlaybackException {
|
||||
TrackGroup selectedGroup = null;
|
||||
int selectedTrackIndex = 0;
|
||||
int selectedTrackScore = 0;
|
||||
|
@ -199,6 +199,7 @@ public abstract class MappingTrackSelector extends TrackSelector {
|
||||
* @param trackIndex The index of the track within the track group.
|
||||
* @return One of {@link RendererCapabilities#FORMAT_HANDLED},
|
||||
* {@link RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES},
|
||||
* {@link RendererCapabilities#FORMAT_UNSUPPORTED_DRM},
|
||||
* {@link RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE} and
|
||||
* {@link RendererCapabilities#FORMAT_UNSUPPORTED_TYPE}.
|
||||
*/
|
||||
@ -214,6 +215,7 @@ public abstract class MappingTrackSelector extends TrackSelector {
|
||||
* Tracks for which {@link #getTrackFormatSupport(int, int, int)} returns
|
||||
* {@link RendererCapabilities#FORMAT_HANDLED} are always considered.
|
||||
* Tracks for which {@link #getTrackFormatSupport(int, int, int)} returns
|
||||
* {@link RendererCapabilities#FORMAT_UNSUPPORTED_DRM},
|
||||
* {@link RendererCapabilities#FORMAT_UNSUPPORTED_TYPE} or
|
||||
* {@link RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE} are never considered.
|
||||
* 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.
|
||||
* <p>
|
||||
* A {@link TrackGroup} is mapped to the renderer that reports
|
||||
* {@link RendererCapabilities#FORMAT_HANDLED} support for one or more of the tracks in the group,
|
||||
* or {@link RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES} if no such renderer exists, or
|
||||
* {@link RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE} if again no such renderer exists. In
|
||||
* the case that two or more renderers report the same level of support, the renderer with the
|
||||
* lowest index is associated.
|
||||
* A {@link TrackGroup} is mapped to the renderer that reports the highest of (listed in
|
||||
* decreasing order of support) {@link RendererCapabilities#FORMAT_HANDLED},
|
||||
* {@link RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES},
|
||||
* {@link RendererCapabilities#FORMAT_UNSUPPORTED_DRM} and
|
||||
* {@link RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE}. In the case that two or more renderers
|
||||
* report the same level of support, the renderer with the lowest index is associated.
|
||||
* <p>
|
||||
* 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
|
||||
|
@ -76,8 +76,8 @@ public final class ContentDataSource implements DataSource {
|
||||
throw new FileNotFoundException("Could not open file descriptor for: " + uri);
|
||||
}
|
||||
inputStream = new FileInputStream(assetFileDescriptor.getFileDescriptor());
|
||||
long assertStartOffset = assetFileDescriptor.getStartOffset();
|
||||
long skipped = inputStream.skip(assertStartOffset + dataSpec.position) - assertStartOffset;
|
||||
long assetStartOffset = assetFileDescriptor.getStartOffset();
|
||||
long skipped = inputStream.skip(assetStartOffset + dataSpec.position) - assetStartOffset;
|
||||
if (skipped != dataSpec.position) {
|
||||
// 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.
|
||||
@ -86,8 +86,8 @@ public final class ContentDataSource implements DataSource {
|
||||
if (dataSpec.length != C.LENGTH_UNSET) {
|
||||
bytesRemaining = dataSpec.length;
|
||||
} else {
|
||||
bytesRemaining = assetFileDescriptor.getLength();
|
||||
if (bytesRemaining == AssetFileDescriptor.UNKNOWN_LENGTH) {
|
||||
long assetFileDescriptorLength = assetFileDescriptor.getLength();
|
||||
if (assetFileDescriptorLength == AssetFileDescriptor.UNKNOWN_LENGTH) {
|
||||
// The asset must extend to the end of the file.
|
||||
bytesRemaining = inputStream.available();
|
||||
if (bytesRemaining == 0) {
|
||||
@ -96,6 +96,8 @@ public final class ContentDataSource implements DataSource {
|
||||
// case, so treat as unbounded.
|
||||
bytesRemaining = C.LENGTH_UNSET;
|
||||
}
|
||||
} else {
|
||||
bytesRemaining = assetFileDescriptorLength - skipped;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
|
@ -64,6 +64,7 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
private final AtomicFile atomicFile;
|
||||
private final Cipher cipher;
|
||||
private final SecretKeySpec secretKeySpec;
|
||||
private final boolean encrypt;
|
||||
private boolean changed;
|
||||
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.
|
||||
*
|
||||
* @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.
|
||||
* The key must be 16 bytes long.
|
||||
* @param secretKey 16 byte AES key for reading and writing the cache index.
|
||||
*/
|
||||
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) {
|
||||
Assertions.checkArgument(secretKey.length == 16);
|
||||
try {
|
||||
@ -288,10 +300,11 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
output = new DataOutputStream(bufferedOutputStream);
|
||||
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);
|
||||
|
||||
if (cipher != null) {
|
||||
if (writeEncrypted) {
|
||||
byte[] initializationVector = new byte[16];
|
||||
new Random().nextBytes(initializationVector);
|
||||
output.write(initializationVector);
|
||||
|
@ -61,10 +61,24 @@ public final class SimpleCache implements Cache {
|
||||
* The key must be 16 bytes long.
|
||||
*/
|
||||
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.evictor = evictor;
|
||||
this.lockedSpans = new HashMap<>();
|
||||
this.index = new CachedContentIndex(cacheDir, secretKey);
|
||||
this.index = new CachedContentIndex(cacheDir, secretKey, encrypt);
|
||||
this.listeners = new HashMap<>();
|
||||
// Start cache initialization.
|
||||
final ConditionVariable conditionVariable = new ConditionVariable();
|
||||
|
@ -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_EMSG = BASE_TYPE_APPLICATION + "/x-emsg";
|
||||
public static final String APPLICATION_DVBSUBS = BASE_TYPE_APPLICATION + "/dvbsubs";
|
||||
public static final String APPLICATION_EXIF = BASE_TYPE_APPLICATION + "/x-exif";
|
||||
|
||||
private MimeTypes() {}
|
||||
|
||||
@ -184,9 +185,9 @@ public final class MimeTypes {
|
||||
return MimeTypes.VIDEO_H264;
|
||||
} else if (codec.startsWith("hev1") || codec.startsWith("hvc1")) {
|
||||
return MimeTypes.VIDEO_H265;
|
||||
} else if (codec.startsWith("vp9")) {
|
||||
} else if (codec.startsWith("vp9") || codec.startsWith("vp09")) {
|
||||
return MimeTypes.VIDEO_VP9;
|
||||
} else if (codec.startsWith("vp8")) {
|
||||
} else if (codec.startsWith("vp8") || codec.startsWith("vp08")) {
|
||||
return MimeTypes.VIDEO_VP8;
|
||||
} else if (codec.startsWith("mp4a")) {
|
||||
return MimeTypes.AUDIO_AAC;
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.util;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
@ -428,7 +429,7 @@ public final class ParsableByteArray {
|
||||
* @return The string encoded by the bytes.
|
||||
*/
|
||||
public String readString(int length) {
|
||||
return readString(length, Charset.defaultCharset());
|
||||
return readString(length, Charset.forName(C.UTF8_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -253,7 +253,7 @@ public final class Util {
|
||||
* @return The code points encoding using UTF-8.
|
||||
*/
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -34,6 +34,8 @@ import static android.opengl.EGL14.EGL_WINDOW_BIT;
|
||||
import static android.opengl.EGL14.eglChooseConfig;
|
||||
import static android.opengl.EGL14.eglCreateContext;
|
||||
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.eglInitialize;
|
||||
import static android.opengl.EGL14.eglMakeCurrent;
|
||||
@ -89,12 +91,7 @@ public final class DummySurface extends Surface {
|
||||
*/
|
||||
public static synchronized boolean isSecureSupported(Context context) {
|
||||
if (!secureSupportedInitialized) {
|
||||
if (Util.SDK_INT >= 17) {
|
||||
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);
|
||||
}
|
||||
secureSupported = Util.SDK_INT >= 24 && enableSecureDummySurfaceV24(context);
|
||||
secureSupportedInitialized = true;
|
||||
}
|
||||
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
|
||||
* correctly.
|
||||
* Returns whether use of secure dummy surfaces should be enabled.
|
||||
*
|
||||
* @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)
|
||||
private static boolean hasVrModeHighPerformanceSystemFeatureV24(PackageManager packageManager) {
|
||||
return packageManager.hasSystemFeature(PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE);
|
||||
private static boolean enableSecureDummySurfaceV24(Context context) {
|
||||
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,
|
||||
@ -171,6 +176,9 @@ public final class DummySurface extends Surface {
|
||||
private static final int MSG_RELEASE = 3;
|
||||
|
||||
private final int[] textureIdHolder;
|
||||
private EGLDisplay display;
|
||||
private EGLContext context;
|
||||
private EGLSurface pbuffer;
|
||||
private Handler handler;
|
||||
private SurfaceTexture surfaceTexture;
|
||||
|
||||
@ -255,7 +263,7 @@ public final class DummySurface extends Surface {
|
||||
}
|
||||
|
||||
private void initInternal(boolean secure) {
|
||||
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
Assertions.checkState(display != null, "eglGetDisplay failed");
|
||||
|
||||
int[] version = new int[2];
|
||||
@ -292,8 +300,8 @@ public final class DummySurface extends Surface {
|
||||
EGL_CONTEXT_CLIENT_VERSION, 2,
|
||||
EGL_NONE};
|
||||
}
|
||||
EGLContext context = eglCreateContext(display, config, android.opengl.EGL14.EGL_NO_CONTEXT,
|
||||
glAttributes, 0);
|
||||
context = eglCreateContext(display, config, android.opengl.EGL14.EGL_NO_CONTEXT, glAttributes,
|
||||
0);
|
||||
Assertions.checkState(context != null, "eglCreateContext failed");
|
||||
|
||||
int[] pbufferAttributes;
|
||||
@ -309,7 +317,7 @@ public final class DummySurface extends Surface {
|
||||
EGL_HEIGHT, 1,
|
||||
EGL_NONE};
|
||||
}
|
||||
EGLSurface pbuffer = eglCreatePbufferSurface(display, config, pbufferAttributes, 0);
|
||||
pbuffer = eglCreatePbufferSurface(display, config, pbufferAttributes, 0);
|
||||
Assertions.checkState(pbuffer != null, "eglCreatePbufferSurface failed");
|
||||
|
||||
boolean eglMadeCurrent = eglMakeCurrent(display, pbuffer, pbuffer, context);
|
||||
@ -323,11 +331,22 @@ public final class DummySurface extends Surface {
|
||||
|
||||
private void releaseInternal() {
|
||||
try {
|
||||
surfaceTexture.release();
|
||||
if (surfaceTexture != null) {
|
||||
surfaceTexture.release();
|
||||
glDeleteTextures(1, textureIdHolder, 0);
|
||||
}
|
||||
} finally {
|
||||
if (pbuffer != null) {
|
||||
eglDestroySurface(display, pbuffer);
|
||||
}
|
||||
if (context != null) {
|
||||
eglDestroyContext(display, context);
|
||||
}
|
||||
pbuffer = null;
|
||||
context = null;
|
||||
display = null;
|
||||
surface = null;
|
||||
surfaceTexture = null;
|
||||
glDeleteTextures(1, textureIdHolder, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,6 +77,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||
|
||||
private Format[] streamFormats;
|
||||
private CodecMaxValues codecMaxValues;
|
||||
private boolean codecNeedsSetOutputSurfaceWorkaround;
|
||||
|
||||
private Surface surface;
|
||||
private Surface dummySurface;
|
||||
@ -354,7 +355,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||
int state = getState();
|
||||
if (state == STATE_ENABLED || state == STATE_STARTED) {
|
||||
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);
|
||||
} else {
|
||||
releaseCodec();
|
||||
@ -425,6 +427,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||
protected void onCodecInitialized(String name, long initializedTimestampMs,
|
||||
long initializationDurationMs) {
|
||||
eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs);
|
||||
codecNeedsSetOutputSurfaceWorkaround = codecNeedsSetOutputSurfaceWorkaround(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -735,28 +738,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||
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)
|
||||
private static void setOutputSurfaceV23(MediaCodec codec, Surface surface) {
|
||||
codec.setOutputSurface(surface);
|
||||
@ -812,6 +793,40 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||
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
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* two {@link Format}s.
|
||||
|
12
library/dash/README.md
Normal file
12
library/dash/README.md
Normal 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
|
@ -85,14 +85,14 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
||||
private final int[] adaptationSetIndices;
|
||||
private final TrackSelection trackSelection;
|
||||
private final int trackType;
|
||||
private final RepresentationHolder[] representationHolders;
|
||||
private final DataSource dataSource;
|
||||
private final long elapsedRealtimeOffsetMs;
|
||||
private final int maxSegmentsPerLoad;
|
||||
|
||||
protected final RepresentationHolder[] representationHolders;
|
||||
|
||||
private DashManifest manifest;
|
||||
private int periodIndex;
|
||||
|
||||
private IOException fatalError;
|
||||
private boolean missingLastSegment;
|
||||
|
||||
@ -377,9 +377,12 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
||||
|
||||
// Protected classes.
|
||||
|
||||
/**
|
||||
* Holds information about a single {@link Representation}.
|
||||
*/
|
||||
protected static final class RepresentationHolder {
|
||||
|
||||
public final ChunkExtractorWrapper extractorWrapper;
|
||||
/* package */ final ChunkExtractorWrapper extractorWrapper;
|
||||
|
||||
public Representation representation;
|
||||
public DashSegmentIndex segmentIndex;
|
||||
@ -387,7 +390,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
||||
private long periodDurationUs;
|
||||
private int segmentNumShift;
|
||||
|
||||
public RepresentationHolder(long periodDurationUs, Representation representation,
|
||||
/* package */ RepresentationHolder(long periodDurationUs, Representation representation,
|
||||
boolean enableEventMessageTrack, boolean enableCea608Track) {
|
||||
this.periodDurationUs = periodDurationUs;
|
||||
this.representation = representation;
|
||||
@ -417,8 +420,8 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
||||
segmentIndex = representation.getIndex();
|
||||
}
|
||||
|
||||
public void updateRepresentation(long newPeriodDurationUs, Representation newRepresentation)
|
||||
throws BehindLiveWindowException{
|
||||
/* package */ void updateRepresentation(long newPeriodDurationUs,
|
||||
Representation newRepresentation) throws BehindLiveWindowException {
|
||||
DashSegmentIndex oldIndex = representation.getIndex();
|
||||
DashSegmentIndex newIndex = newRepresentation.getIndex();
|
||||
|
||||
|
11
library/hls/README.md
Normal file
11
library/hls/README.md
Normal 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
|
@ -92,6 +92,7 @@ import java.util.List;
|
||||
private byte[] scratchSpace;
|
||||
private IOException fatalError;
|
||||
private HlsUrl expectedPlaylistUrl;
|
||||
private boolean independentSegments;
|
||||
|
||||
private Uri encryptionKeyUri;
|
||||
private byte[] encryptionKey;
|
||||
@ -206,10 +207,11 @@ import java.util.List;
|
||||
int oldVariantIndex = previous == null ? C.INDEX_UNSET
|
||||
: trackGroup.indexOf(previous.trackFormat);
|
||||
expectedPlaylistUrl = null;
|
||||
// Use start time of the previous chunk rather than its end time because switching format will
|
||||
// require downloading overlapping segments.
|
||||
long bufferedDurationUs = previous == null ? 0
|
||||
: Math.max(0, previous.startTimeUs - playbackPositionUs);
|
||||
// Unless segments are known to be independent, switching variant will require downloading
|
||||
// overlapping segments. Hence we use the start time of the previous chunk rather than its end
|
||||
// time for this case.
|
||||
long bufferedDurationUs = previous == null ? 0 : Math.max(0,
|
||||
(independentSegments ? previous.endTimeUs : previous.startTimeUs) - playbackPositionUs);
|
||||
|
||||
// Select the variant.
|
||||
trackSelection.updateSelectedTrack(bufferedDurationUs);
|
||||
@ -224,12 +226,13 @@ import java.util.List;
|
||||
return;
|
||||
}
|
||||
HlsMediaPlaylist mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl);
|
||||
independentSegments = mediaPlaylist.hasIndependentSegmentsTag;
|
||||
|
||||
// Select the chunk.
|
||||
int chunkMediaSequence;
|
||||
if (previous == null || switchingVariant) {
|
||||
long targetPositionUs = previous == null ? playbackPositionUs
|
||||
: mediaPlaylist.hasIndependentSegmentsTag ? previous.endTimeUs : previous.startTimeUs;
|
||||
: independentSegments ? previous.endTimeUs : previous.startTimeUs;
|
||||
if (!mediaPlaylist.hasEndTag && targetPositionUs >= mediaPlaylist.getEndTimeUs()) {
|
||||
// If the playlist is too old to contain the chunk, we need to refresh it.
|
||||
chunkMediaSequence = mediaPlaylist.mediaSequence + mediaPlaylist.segments.size();
|
||||
|
12
library/smoothstreaming/README.md
Normal file
12
library/smoothstreaming/README.md
Normal 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
10
library/ui/README.md
Normal 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
|
@ -32,7 +32,8 @@ public final class AspectRatioFrameLayout extends FrameLayout {
|
||||
* Resize modes for {@link AspectRatioFrameLayout}.
|
||||
*/
|
||||
@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 {}
|
||||
|
||||
/**
|
||||
@ -51,6 +52,10 @@ public final class AspectRatioFrameLayout extends FrameLayout {
|
||||
* The specified aspect ratio is ignored.
|
||||
*/
|
||||
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
|
||||
@ -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.
|
||||
*/
|
||||
@ -96,6 +101,13 @@ public final class AspectRatioFrameLayout extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resize mode.
|
||||
*/
|
||||
public @ResizeMode int getResizeMode() {
|
||||
return resizeMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the resize mode.
|
||||
*
|
||||
@ -132,6 +144,13 @@ public final class AspectRatioFrameLayout extends FrameLayout {
|
||||
case RESIZE_MODE_FIXED_HEIGHT:
|
||||
width = (int) (height * videoAspectRatio);
|
||||
break;
|
||||
case RESIZE_MODE_ZOOM:
|
||||
if (aspectDeformation > 0) {
|
||||
width = (int) (height * videoAspectRatio);
|
||||
} else {
|
||||
height = (int) (width / videoAspectRatio);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (aspectDeformation > 0) {
|
||||
height = (int) (width / videoAspectRatio);
|
||||
|
@ -22,6 +22,7 @@ import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
@ -312,6 +313,8 @@ public class PlaybackControlView extends FrameLayout {
|
||||
private long hideAtMs;
|
||||
private long[] adGroupTimesMs;
|
||||
private boolean[] playedAdGroups;
|
||||
private long[] extraAdGroupTimesMs;
|
||||
private boolean[] extraPlayedAdGroups;
|
||||
|
||||
private final Runnable updateProgressAction = new Runnable() {
|
||||
@Override
|
||||
@ -336,15 +339,19 @@ public class PlaybackControlView extends FrameLayout {
|
||||
}
|
||||
|
||||
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;
|
||||
rewindMs = DEFAULT_REWIND_MS;
|
||||
fastForwardMs = DEFAULT_FAST_FORWARD_MS;
|
||||
showTimeoutMs = DEFAULT_SHOW_TIMEOUT_MS;
|
||||
repeatToggleModes = DEFAULT_REPEAT_TOGGLE_MODES;
|
||||
if (attrs != null) {
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
|
||||
if (playbackAttrs != null) {
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(playbackAttrs,
|
||||
R.styleable.PlaybackControlView, 0, 0);
|
||||
try {
|
||||
rewindMs = a.getInt(R.styleable.PlaybackControlView_rewind_increment, rewindMs);
|
||||
@ -364,6 +371,8 @@ public class PlaybackControlView extends FrameLayout {
|
||||
formatter = new Formatter(formatBuilder, Locale.getDefault());
|
||||
adGroupTimesMs = new long[0];
|
||||
playedAdGroups = new boolean[0];
|
||||
extraAdGroupTimesMs = new long[0];
|
||||
extraPlayedAdGroups = new boolean[0];
|
||||
componentListener = new ComponentListener();
|
||||
controlDispatcher = DEFAULT_CONTROL_DISPATCHER;
|
||||
|
||||
@ -462,6 +471,29 @@ public class PlaybackControlView extends FrameLayout {
|
||||
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}.
|
||||
*
|
||||
@ -647,9 +679,10 @@ public class PlaybackControlView extends FrameLayout {
|
||||
int windowIndex = player.getCurrentWindowIndex();
|
||||
timeline.getWindow(windowIndex, window);
|
||||
isSeekable = window.isSeekable;
|
||||
enablePrevious = !timeline.isFirstWindow(windowIndex, player.getRepeatMode())
|
||||
|| isSeekable || !window.isDynamic;
|
||||
enableNext = !timeline.isLastWindow(windowIndex, player.getRepeatMode()) || window.isDynamic;
|
||||
enablePrevious = isSeekable || !window.isDynamic
|
||||
|| timeline.getPreviousWindowIndex(windowIndex, player.getRepeatMode()) != C.INDEX_UNSET;
|
||||
enableNext = window.isDynamic
|
||||
|| timeline.getNextWindowIndex(windowIndex, player.getRepeatMode()) != C.INDEX_UNSET;
|
||||
if (player.isPlayingAd()) {
|
||||
// Always hide player controls during ads.
|
||||
hide();
|
||||
@ -768,7 +801,15 @@ public class PlaybackControlView extends FrameLayout {
|
||||
bufferedPosition += player.getBufferedPosition();
|
||||
}
|
||||
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) {
|
||||
|
@ -239,7 +239,7 @@ public final class SimpleExoPlayerView extends FrameLayout {
|
||||
controller = null;
|
||||
componentListener = null;
|
||||
overlayFrameLayout = null;
|
||||
ImageView logo = new ImageView(context, attrs);
|
||||
ImageView logo = new ImageView(context);
|
||||
if (Util.SDK_INT >= 23) {
|
||||
configureEditModeLogoV23(getResources(), logo);
|
||||
} else {
|
||||
@ -329,9 +329,9 @@ public final class SimpleExoPlayerView extends FrameLayout {
|
||||
if (customController != null) {
|
||||
this.controller = customController;
|
||||
} else if (controllerPlaceholder != null) {
|
||||
// Note: rewindMs and fastForwardMs are passed via attrs, so we don't need to make explicit
|
||||
// calls to set them.
|
||||
this.controller = new PlaybackControlView(context, attrs);
|
||||
// Propagate attrs as playbackAttrs so that PlaybackControlView's custom attributes are
|
||||
// transferred, but standard FrameLayout attributes (e.g. background) are not.
|
||||
this.controller = new PlaybackControlView(context, null, 0, attrs);
|
||||
controller.setLayoutParams(controllerPlaceholder.getLayoutParams());
|
||||
ViewGroup parent = ((ViewGroup) controllerPlaceholder.getParent());
|
||||
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
|
||||
* {@link SimpleExoPlayer#setVideoListener} method of the player will be called and previous
|
||||
* assignments are overridden.
|
||||
* Set the {@link SimpleExoPlayer} to use.
|
||||
* <p>
|
||||
* To transition a {@link SimpleExoPlayer} from targeting one view to another, it's recommended to
|
||||
* use {@link #switchTargetView(SimpleExoPlayer, SimpleExoPlayerView, SimpleExoPlayerView)} rather
|
||||
@ -397,8 +395,8 @@ public final class SimpleExoPlayerView extends FrameLayout {
|
||||
}
|
||||
if (this.player != null) {
|
||||
this.player.removeListener(componentListener);
|
||||
this.player.clearTextOutput(componentListener);
|
||||
this.player.clearVideoListener(componentListener);
|
||||
this.player.removeTextOutput(componentListener);
|
||||
this.player.removeVideoListener(componentListener);
|
||||
if (surfaceView instanceof TextureView) {
|
||||
this.player.clearVideoTextureView((TextureView) surfaceView);
|
||||
} else if (surfaceView instanceof SurfaceView) {
|
||||
@ -418,8 +416,8 @@ public final class SimpleExoPlayerView extends FrameLayout {
|
||||
} else if (surfaceView instanceof SurfaceView) {
|
||||
player.setVideoSurfaceView((SurfaceView) surfaceView);
|
||||
}
|
||||
player.setVideoListener(componentListener);
|
||||
player.setTextOutput(componentListener);
|
||||
player.addVideoListener(componentListener);
|
||||
player.addTextOutput(componentListener);
|
||||
player.addListener(componentListener);
|
||||
maybeShowController(false);
|
||||
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.
|
||||
*
|
||||
@ -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)
|
||||
* or a {@link TextureView} if the {@code use_texture_view} view attribute has been set to true.
|
||||
* Gets the view onto which video is rendered. This is a:
|
||||
* <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() {
|
||||
return surfaceView;
|
||||
|
@ -181,7 +181,7 @@ public class TestUtil {
|
||||
byte[] expectedData) throws IOException {
|
||||
try {
|
||||
long length = dataSource.open(dataSpec);
|
||||
Assert.assertEquals(length, expectedData.length);
|
||||
Assert.assertEquals(expectedData.length, length);
|
||||
byte[] readData = TestUtil.readToEnd(dataSource);
|
||||
MoreAsserts.assertEquals(expectedData, readData);
|
||||
} finally {
|
||||
|
Loading…
x
Reference in New Issue
Block a user