Update to androidx.media3

PiperOrigin-RevId: 405656499
This commit is contained in:
Andrew Lewis 2021-10-26 15:16:21 +01:00
parent 68729ecd49
commit 933e207b3e
5097 changed files with 85118 additions and 755348 deletions

View File

@ -13,16 +13,14 @@ Before filing a bug:
------------------------- -------------------------
- Search existing issues, including issues that are closed: - Search existing issues, including issues that are closed:
https://github.com/androidx/media/issues?q=is%3Aissue
- For ExoPlayer-related bugs, please also check the ExoPlayer tracker:
https://github.com/google/ExoPlayer/issues?q=is%3Aissue https://github.com/google/ExoPlayer/issues?q=is%3Aissue
- Consult our developer website: https://exoplayer.dev/
- Check the supported formats: https://exoplayer.dev/supported-formats.html
- Try playing problematic media in the demo app:
http://exoplayer.dev/demo-application.html
When reporting a bug: When reporting a bug:
------------------------- -------------------------
Describe how the issue can be reproduced, ideally using the ExoPlayer demo app Describe how the issue can be reproduced, ideally using one of the demo apps
or a small sample app that youre able to share as source code on GitHub. To or a small sample app that youre able to share as source code on GitHub. To
increase the chance of your issue getting attention, please also include: increase the chance of your issue getting attention, please also include:
@ -34,7 +32,7 @@ increase the chance of your issue getting attention, please also include:
- DRM scheme and license server URL - DRM scheme and license server URL
- Authentication HTTP headers - Authentication HTTP headers
- ExoPlayer version number - AndroidX Media version number
- Android version - Android version
- Android device - Android device

View File

@ -9,8 +9,12 @@ assignees: ''
Before filing a feature request: Before filing a feature request:
----------------------- -----------------------
- Search existing open issues, specifically with the label enhancement: - Search existing open issues, specifically with the label enhancement:
https://github.com/androidx/media/labels/enhancement
- For ExoPlayer-related feature requests, please also check the ExoPlayer
tracker:
https://github.com/google/ExoPlayer/labels/enhancement https://github.com/google/ExoPlayer/labels/enhancement
- Search existing pull requests: https://github.com/google/ExoPlayer/pulls - Search existing pull requests: https://github.com/androidx/media/pulls,
https://github.com/google/ExoPlayer/pulls
When filing a feature request: When filing a feature request:
----------------------- -----------------------

View File

@ -14,9 +14,9 @@ Before filing a question:
- Ask general Android development questions on Stack Overflow - Ask general Android development questions on Stack Overflow
- Search existing issues, including issues that are closed - Search existing issues, including issues that are closed
https://github.com/androidx/media/issues?q=is%3Aissue
- For ExoPlayer-related questions, please also check the ExoPlayer tracker:
https://github.com/google/ExoPlayer/issues?q=is%3Aissue https://github.com/google/ExoPlayer/issues?q=is%3Aissue
- Consult our developer website (https://exoplayer.dev/) and Javadoc
(https://exoplayer.dev/doc/reference/)
When filing a question: When filing a question:
------------------------- -------------------------
@ -34,7 +34,7 @@ In case your question is related to a piece of media:
- DRM scheme and license server URL - DRM scheme and license server URL
- Authentication HTTP headers - Authentication HTTP headers
Don't forget to check supported formats and devices Don't forget to check ExoPlayer's supported formats and devices, if applicable
(https://exoplayer.dev/supported-formats.html). (https://exoplayer.dev/supported-formats.html).
If there's something you don't want to post publicly, please submit the issue, If there's something you don't want to post publicly, please submit the issue,

View File

@ -1,11 +1,14 @@
# How to contribute # # How to contribute
## Reporting issues ## ## Reporting issues
We use the [GitHub issue tracker](https://github.com/google/ExoPlayer/issues) We use the [AndroidX Media issue tracker][] to track bugs, feature requests and
to track bugs, feature requests and questions. questions.
Before filing a new issue, please search the tracker to check if it's already We are still handling ExoPlayer issues on the [ExoPlayer GitHub issue tracker][]
while the ExoPlayer and AndroidX Media projects coexist.
Before filing a new issue, please search the trackers to check if it's already
covered by an existing report. Avoiding duplicates helps us maximize the time we covered by an existing report. Avoiding duplicates helps us maximize the time we
can spend fixing bugs and adding new features. can spend fixing bugs and adding new features.
@ -13,13 +16,16 @@ When filing an issue, be sure to provide enough information for us to
efficiently diagnose and reproduce the problem. In particular, please include efficiently diagnose and reproduce the problem. In particular, please include
all of the information requested in the issue template. all of the information requested in the issue template.
## Pull requests ## [AndroidX Media issue tracker]: https://github.com/androidx/media/issues
[ExoPlayer GitHub issue tracker]: https://github.com/google/ExoPlayer/issues
## Pull requests
We will also consider high quality pull requests. These should normally merge We will also consider high quality pull requests. These should normally merge
into the `dev-v2` branch. Before a pull request can be accepted you must submit into the `main` branch. Before a pull request can be accepted you must submit
a Contributor License Agreement, as described below. a Contributor License Agreement, as described below.
## Contributor license agreement ## ## Contributor license agreement
Contributions to any Google project must be accompanied by a Contributor Contributions to any Google project must be accompanied by a Contributor
License Agreement. This is not a copyright **assignment**, it simply gives License Agreement. This is not a copyright **assignment**, it simply gives

147
README.md
View File

@ -1,86 +1,73 @@
# ExoPlayer <img src="https://img.shields.io/github/v/release/google/ExoPlayer.svg?label=latest"/> # AndroidX Media
ExoPlayer is an application level media player for Android. It provides an AndroidX Media is a collection of libraries for implementing media use cases on
alternative to Androids MediaPlayer API for playing audio and video both Android, including local playback (via ExoPlayer) and media sessions.
locally and over the Internet. ExoPlayer supports features not currently
supported by Androids MediaPlayer API, including DASH and SmoothStreaming
adaptive playbacks. Unlike the MediaPlayer API, ExoPlayer is easy to customize
and extend, and can be updated through Play Store application updates.
## Documentation ## Current status
* The [developer guide][] provides a wealth of information. AndroidX Media is in currently in alpha and we welcome your feedback via the
* The [class reference][] documents ExoPlayer classes. [issue tracker][]. Please consult the [release notes][] for more details about
* The [release notes][] document the major changes in each release. the alpha release.
* Follow our [developer blog][] to keep up to date with the latest ExoPlayer
developments!
[developer guide]: https://exoplayer.dev/guide.html ExoPlayer's new home will be in AndroidX Media, but for now we are publishing it
[class reference]: https://exoplayer.dev/doc/reference both in AndroidX Media and via the existing [ExoPlayer project][]. While
[release notes]: https://github.com/google/ExoPlayer/blob/release-v2/RELEASENOTES.md AndroidX Media is in alpha we recommend that production apps using ExoPlayer
[developer blog]: https://medium.com/google-exoplayer continue to depend on the existing ExoPlayer project. We are still handling
ExoPlayer issues on the [ExoPlayer issue tracker][].
## Using ExoPlayer Updated documentation, including information on migration and a developer guide,
is coming soon.
ExoPlayer modules can be obtained from [the Google Maven repository][]. It's For a high level overview of the initial version of AndroidX Media please see
also possible to clone the repository and depend on the modules locally. the Android Dev Summit talk [What's next for AndroidX Media and ExoPlayer][].
[release notes]: RELEASENOTES.md
[issue tracker]: https://github.com/androidx/media/issues/new
[ExoPlayer project]: https://github.com/google/ExoPlayer
[ExoPlayer issue tracker]: https://github.com/google/ExoPlayer/issues
[What's next for AndroidX Media and ExoPlayer]: https://developer.android.com/events/dev-summit/technical-talks
## Using the libraries
You can get the libraries from [the Google Maven repository][]. It's
also possible to clone this GitHub repository and depend on the modules locally.
[the Google Maven repository]: https://developer.android.com/studio/build/dependencies#google-maven [the Google Maven repository]: https://developer.android.com/studio/build/dependencies#google-maven
### From the Google Maven repository ### From the Google Maven repository
#### 1. Add ExoPlayer module dependencies #### 1. Add module dependencies
The easiest way to get started using ExoPlayer is to add it as a gradle The easiest way to get started using AndroidX Media is to add gradle
dependency in the `build.gradle` file of your app module. The following will add dependencies on the libraries you need in the `build.gradle` file of your app
a dependency to the full library: module.
For example, to depend on ExoPlayer with DASH playback support and UI components
you can add dependencies on the modules like this:
```gradle ```gradle
implementation 'com.google.android.exoplayer:exoplayer:2.X.X' implementation 'androidx.media3:media3-exoplayer:1.X.X'
implementation 'androidx.media3:media3-exoplayer-dash:1.X.X'
implementation 'androidx.media3:media3-ui:1.X.X'
``` ```
where `2.X.X` is your preferred version. where `1.X.X` is your preferred version. All modules must be the same version.
As an alternative to the full library, you can depend on only the library Please see the [AndroidX Media3 developer.android.com page][] for more
modules that you actually need. For example the following will add dependencies information, including a full list of library modules.
on the Core, DASH and UI library modules, as might be required for an app that
only plays DASH content:
```gradle This repository includes some modules that depend on external libraries that
implementation 'com.google.android.exoplayer:exoplayer-core:2.X.X' need to be built manually, and are not available from the Maven repository.
implementation 'com.google.android.exoplayer:exoplayer-dash:2.X.X' Please see the individual READMEs under the [libraries directory][] for more
implementation 'com.google.android.exoplayer:exoplayer-ui:2.X.X' details.
```
When depending on individual modules they must all be the same version. [AndroidX Media3 developer.android.com page]: https://developer.android.com/jetpack/androidx/releases/media3#declaring_dependencies
[libraries directory]: libraries
The available library modules are listed below. Adding a dependency to the full
ExoPlayer library is equivalent to adding dependencies on all of the library
modules individually.
* `exoplayer-core`: Core functionality (required).
* `exoplayer-dash`: Support for DASH content.
* `exoplayer-hls`: Support for HLS content.
* `exoplayer-rtsp`: Support for RTSP content.
* `exoplayer-smoothstreaming`: Support for SmoothStreaming content.
* `exoplayer-transformer`: Media transformation functionality.
* `exoplayer-ui`: UI components and resources for use with ExoPlayer.
In addition to library modules, ExoPlayer has extension modules that depend on
external libraries to provide additional functionality. Some extensions are
available from the Maven repository, whereas others must be built manually.
Browse the [extensions directory][] and their individual READMEs for details.
More information on the library and extension modules that are available can be
found on the [Google Maven ExoPlayer page][].
[extensions directory]: https://github.com/google/ExoPlayer/tree/release-v2/extensions/
[Google Maven ExoPlayer page]: https://maven.google.com/web/index.html#com.google.android.exoplayer
#### 2. Turn on Java 8 support #### 2. Turn on Java 8 support
If not enabled already, you also need to turn on Java 8 support in all If not enabled already, you also need to turn on Java 8 support in all
`build.gradle` files depending on ExoPlayer, by adding the following to the `build.gradle` files depending on AndroidX Media, by adding the following to the
`android` section: `android` section:
```gradle ```gradle
@ -98,45 +85,45 @@ to prevent build errors.
### Locally ### Locally
Cloning the repository and depending on the modules locally is required when Cloning the repository and depending on the modules locally is required when
using some ExoPlayer extension modules. It's also a suitable approach if you using some libraries. It's also a suitable approach if you want to make local
want to make local changes to ExoPlayer, or if you want to use a development changes, or if you want to use the main branch.
branch.
First, clone the repository into a local directory and checkout the desired First, clone the repository into a local directory and checkout the desired
branch: branch:
```sh ```sh
git clone https://github.com/google/ExoPlayer.git git clone https://github.com/androidx/media.git
cd ExoPlayer cd media
git checkout release-v2 git checkout main
``` ```
Next, add the following to your project's `settings.gradle` file, replacing Next, add the following to your project's `settings.gradle` file, replacing
`path/to/exoplayer` with the path to your local copy: `path/to/media` with the path to your local copy:
```gradle ```gradle
gradle.ext.exoplayerModulePrefix = 'exoplayer-' gradle.ext.androidxMediaModulePrefix = 'media-'
apply from: file("path/to/exoplayer/core_settings.gradle") apply from: file("path/to/media/core_settings.gradle")
``` ```
You should now see the ExoPlayer modules appear as part of your project. You can You should now see the AndroidX Media modules appear as part of your project.
depend on them as you would on any other local module, for example: You can depend on them as you would on any other local module, for example:
```gradle ```gradle
implementation project(':exoplayer-library-core') implementation project(':media-lib-exoplayer')
implementation project(':exoplayer-library-dash') implementation project(':media-lib-exoplayer-dash')
implementation project(':exoplayer-library-ui') implementation project(':media-lib-ui')
``` ```
## Developing ExoPlayer ## Developing AndroidX Media
#### Project branches #### Project branches
* Development work happens on the `dev-v2` branch. Pull requests should Development work happens on the `main` branch. Pull requests should normally be
normally be made to this branch. made to this branch.
* The `release-v2` branch holds the most recent release.
We plan to add a release branch soon.
#### Using Android Studio #### Using Android Studio
To develop ExoPlayer using Android Studio, simply open the ExoPlayer project in To develop AndroidX Media using Android Studio, simply open the project in the
the root directory of the repository. root directory of this repository.

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,7 @@ buildscript {
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.0.0' classpath 'com.android.tools.build:gradle:7.0.0'
classpath 'com.google.android.gms:strict-version-matcher-plugin:1.2.2' classpath 'com.google.android.gms:strict-version-matcher-plugin:1.2.2'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21'
} }
} }
allprojects { allprojects {
@ -32,7 +33,7 @@ allprojects {
} }
buildDir = "${externalBuildDir}/${project.name}" buildDir = "${externalBuildDir}/${project.name}"
} }
group = 'com.google.android.exoplayer' group = 'androidx.media3'
} }
apply from: 'javadoc_combined.gradle' apply from: 'javadoc_combined.gradle'

View File

@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
apply from: "$gradle.ext.exoplayerSettingsDir/constants.gradle" apply from: "$gradle.ext.androidxMediaSettingsDir/constants.gradle"
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
android { android {

View File

@ -12,9 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
project.ext { project.ext {
// ExoPlayer version and version code. releaseVersion = '1.0.0-alpha01'
releaseVersion = '2.15.1' releaseVersionCode = 1000000
releaseVersionCode = 2015001
minSdkVersion = 16 minSdkVersion = 16
appTargetSdkVersion = 29 appTargetSdkVersion = 29
// Upgrading this requires [Internal ref: b/193254928] to be fixed, or some // Upgrading this requires [Internal ref: b/193254928] to be fixed, or some
@ -36,6 +35,7 @@ project.ext {
jsr305Version = '3.0.2' jsr305Version = '3.0.2'
kotlinAnnotationsVersion = '1.5.20' kotlinAnnotationsVersion = '1.5.20'
androidxAnnotationVersion = '1.2.0' androidxAnnotationVersion = '1.2.0'
androidxAnnotationExperimentalVersion = '1.1.0'
androidxAppCompatVersion = '1.3.0' androidxAppCompatVersion = '1.3.0'
androidxCollectionVersion = '1.1.0' androidxCollectionVersion = '1.1.0'
androidxCoreVersion = '1.6.0' androidxCoreVersion = '1.6.0'
@ -54,7 +54,7 @@ project.ext {
truthVersion = '1.1.3' truthVersion = '1.1.3'
okhttpVersion = '4.9.1' okhttpVersion = '4.9.1'
modulePrefix = ':' modulePrefix = ':'
if (gradle.ext.has('exoplayerModulePrefix')) { if (gradle.ext.has('androidxMediaModulePrefix')) {
modulePrefix += gradle.ext.exoplayerModulePrefix modulePrefix += gradle.ext.androidxMediaModulePrefix
} }
} }

View File

@ -12,82 +12,78 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
def rootDir = file(".") def rootDir = file(".")
if (!gradle.ext.has('exoplayerSettingsDir')) { if (!gradle.ext.has('androidxMediaSettingsDir')) {
gradle.ext.exoplayerSettingsDir = rootDir.getCanonicalPath() gradle.ext.androidxMediaSettingsDir = rootDir.getCanonicalPath()
} }
def modulePrefix = ':' def modulePrefix = ':'
if (gradle.ext.has('exoplayerModulePrefix')) { if (gradle.ext.has('androidxMediaModulePrefix')) {
modulePrefix += gradle.ext.exoplayerModulePrefix modulePrefix += gradle.ext.androidxMediaModulePrefix
} }
include modulePrefix + 'library-common' include modulePrefix + 'lib-common'
project(modulePrefix + 'library-common').projectDir = new File(rootDir, 'library/common') project(modulePrefix + 'lib-common').projectDir = new File(rootDir, 'libraries/common')
include modulePrefix + 'extension-mediasession' include modulePrefix + 'lib-session'
project(modulePrefix + 'extension-mediasession').projectDir = new File(rootDir, 'extensions/mediasession') project(modulePrefix + 'lib-session').projectDir = new File(rootDir, 'libraries/session')
include modulePrefix + 'extension-media2'
project(modulePrefix + 'extension-media2').projectDir = new File(rootDir, 'extensions/media2')
include modulePrefix + 'library-core' include modulePrefix + 'lib-exoplayer'
project(modulePrefix + 'library-core').projectDir = new File(rootDir, 'library/core') project(modulePrefix + 'lib-exoplayer').projectDir = new File(rootDir, 'libraries/exoplayer')
include modulePrefix + 'library' include modulePrefix + 'lib-exoplayer-dash'
project(modulePrefix + 'library').projectDir = new File(rootDir, 'library/all') project(modulePrefix + 'lib-exoplayer-dash').projectDir = new File(rootDir, 'libraries/exoplayer_dash')
include modulePrefix + 'library-dash' include modulePrefix + 'lib-exoplayer-hls'
project(modulePrefix + 'library-dash').projectDir = new File(rootDir, 'library/dash') project(modulePrefix + 'lib-exoplayer-hls').projectDir = new File(rootDir, 'libraries/exoplayer_hls')
include modulePrefix + 'library-hls' include modulePrefix + 'lib-exoplayer-rtsp'
project(modulePrefix + 'library-hls').projectDir = new File(rootDir, 'library/hls') project(modulePrefix + 'lib-exoplayer-rtsp').projectDir = new File(rootDir, 'libraries/exoplayer_rtsp')
include modulePrefix + 'library-rtsp' include modulePrefix + 'lib-exoplayer-smoothstreaming'
project(modulePrefix + 'library-rtsp').projectDir = new File(rootDir, 'library/rtsp') project(modulePrefix + 'lib-exoplayer-smoothstreaming').projectDir = new File(rootDir, 'libraries/exoplayer_smoothstreaming')
include modulePrefix + 'library-smoothstreaming' include modulePrefix + 'lib-exoplayer-ima'
project(modulePrefix + 'library-smoothstreaming').projectDir = new File(rootDir, 'library/smoothstreaming') project(modulePrefix + 'lib-exoplayer-ima').projectDir = new File(rootDir, 'libraries/exoplayer_ima')
include modulePrefix + 'extension-ima' include modulePrefix + 'lib-exoplayer-workmanager'
project(modulePrefix + 'extension-ima').projectDir = new File(rootDir, 'extensions/ima') project(modulePrefix + 'lib-exoplayer-workmanager').projectDir = new File(rootDir, 'libraries/exoplayer_workmanager')
include modulePrefix + 'extension-workmanager'
project(modulePrefix + 'extension-workmanager').projectDir = new File(rootDir, 'extensions/workmanager')
include modulePrefix + 'library-ui' include modulePrefix + 'lib-ui'
project(modulePrefix + 'library-ui').projectDir = new File(rootDir, 'library/ui') project(modulePrefix + 'lib-ui').projectDir = new File(rootDir, 'libraries/ui')
include modulePrefix + 'extension-leanback' include modulePrefix + 'lib-ui-leanback'
project(modulePrefix + 'extension-leanback').projectDir = new File(rootDir, 'extensions/leanback') project(modulePrefix + 'lib-ui-leanback').projectDir = new File(rootDir, 'libraries/ui_leanback')
include modulePrefix + 'library-database' include modulePrefix + 'lib-database'
project(modulePrefix + 'library-database').projectDir = new File(rootDir, 'library/database') project(modulePrefix + 'lib-database').projectDir = new File(rootDir, 'libraries/database')
include modulePrefix + 'library-datasource' include modulePrefix + 'lib-datasource'
project(modulePrefix + 'library-datasource').projectDir = new File(rootDir, 'library/datasource') project(modulePrefix + 'lib-datasource').projectDir = new File(rootDir, 'libraries/datasource')
include modulePrefix + 'extension-cronet' include modulePrefix + 'lib-datasource-cronet'
project(modulePrefix + 'extension-cronet').projectDir = new File(rootDir, 'extensions/cronet') project(modulePrefix + 'lib-datasource-cronet').projectDir = new File(rootDir, 'libraries/datasource_cronet')
include modulePrefix + 'extension-rtmp' include modulePrefix + 'lib-datasource-rtmp'
project(modulePrefix + 'extension-rtmp').projectDir = new File(rootDir, 'extensions/rtmp') project(modulePrefix + 'lib-datasource-rtmp').projectDir = new File(rootDir, 'libraries/datasource_rtmp')
include modulePrefix + 'extension-okhttp' include modulePrefix + 'lib-datasource-okhttp'
project(modulePrefix + 'extension-okhttp').projectDir = new File(rootDir, 'extensions/okhttp') project(modulePrefix + 'lib-datasource-okhttp').projectDir = new File(rootDir, 'libraries/datasource_okhttp')
include modulePrefix + 'library-decoder' include modulePrefix + 'lib-decoder'
project(modulePrefix + 'library-decoder').projectDir = new File(rootDir, 'library/decoder') project(modulePrefix + 'lib-decoder').projectDir = new File(rootDir, 'libraries/decoder')
include modulePrefix + 'extension-av1' include modulePrefix + 'lib-decoder-av1'
project(modulePrefix + 'extension-av1').projectDir = new File(rootDir, 'extensions/av1') project(modulePrefix + 'lib-decoder-av1').projectDir = new File(rootDir, 'libraries/decoder_av1')
include modulePrefix + 'extension-ffmpeg' include modulePrefix + 'lib-decoder-ffmpeg'
project(modulePrefix + 'extension-ffmpeg').projectDir = new File(rootDir, 'extensions/ffmpeg') project(modulePrefix + 'lib-decoder-ffmpeg').projectDir = new File(rootDir, 'libraries/decoder_ffmpeg')
include modulePrefix + 'extension-flac' include modulePrefix + 'lib-decoder-flac'
project(modulePrefix + 'extension-flac').projectDir = new File(rootDir, 'extensions/flac') project(modulePrefix + 'lib-decoder-flac').projectDir = new File(rootDir, 'libraries/decoder_flac')
include modulePrefix + 'extension-opus' include modulePrefix + 'lib-decoder-opus'
project(modulePrefix + 'extension-opus').projectDir = new File(rootDir, 'extensions/opus') project(modulePrefix + 'lib-decoder-opus').projectDir = new File(rootDir, 'libraries/decoder_opus')
include modulePrefix + 'extension-vp9' include modulePrefix + 'lib-decoder-vp9'
project(modulePrefix + 'extension-vp9').projectDir = new File(rootDir, 'extensions/vp9') project(modulePrefix + 'lib-decoder-vp9').projectDir = new File(rootDir, 'libraries/decoder_vp9')
include modulePrefix + 'library-extractor' include modulePrefix + 'lib-extractor'
project(modulePrefix + 'library-extractor').projectDir = new File(rootDir, 'library/extractor') project(modulePrefix + 'lib-extractor').projectDir = new File(rootDir, 'libraries/extractor')
include modulePrefix + 'extension-cast' include modulePrefix + 'lib-cast'
project(modulePrefix + 'extension-cast').projectDir = new File(rootDir, 'extensions/cast') project(modulePrefix + 'lib-cast').projectDir = new File(rootDir, 'libraries/cast')
include modulePrefix + 'library-transformer' include modulePrefix + 'lib-transformer'
project(modulePrefix + 'library-transformer').projectDir = new File(rootDir, 'library/transformer') project(modulePrefix + 'lib-transformer').projectDir = new File(rootDir, 'libraries/transformer')
include modulePrefix + 'robolectricutils' include modulePrefix + 'test-utils-robolectric'
project(modulePrefix + 'robolectricutils').projectDir = new File(rootDir, 'robolectricutils') project(modulePrefix + 'test-utils-robolectric').projectDir = new File(rootDir, 'libraries/test_utils_robolectric')
include modulePrefix + 'testdata' include modulePrefix + 'test-data'
project(modulePrefix + 'testdata').projectDir = new File(rootDir, 'testdata') project(modulePrefix + 'test-data').projectDir = new File(rootDir, 'libraries/test_data')
include modulePrefix + 'testutils' include modulePrefix + 'test-utils'
project(modulePrefix + 'testutils').projectDir = new File(rootDir, 'testutils') project(modulePrefix + 'test-utils').projectDir = new File(rootDir, 'libraries/test_utils')

View File

@ -1,25 +1,23 @@
# ExoPlayer demos # # Demos
This directory contains applications that demonstrate how to use ExoPlayer. This directory contains apps that demonstrate how to use Android media modules,
Browse the individual demos and their READMEs to learn more. like ExoPlayer. Browse the individual demos and their READMEs to learn more.
## Running a demo ## ## Running a demo
### From Android Studio ### ### From Android Studio
* File -> New -> Import Project -> Specify the root ExoPlayer folder. * File -> New -> Import Project -> Specify the root `media` folder.
* Choose the demo from the run configuration dropdown list. * Choose the demo from the run configuration dropdown list.
* Click Run. * Click Run.
### Using gradle from the command line: ### ### Using gradle from the command line:
* Open a Terminal window at the root ExoPlayer folder. * Open a Terminal window at the root `media` folder.
* Run `./gradlew projects` to show all projects. Demo projects start with `demo`. * Run `./gradlew projects` to show all projects. Demo projects start with `demo`.
* Run `./gradlew :<demo name>:tasks` to view the list of available tasks for * Run `./gradlew :<demo name>:tasks` to view the list of available tasks for
the demo project. Choose an install option from the `Install tasks` section. the demo project. Choose an install option from the `Install tasks` section.
* Run `./gradlew :<demo name>:<install task>`. * Run `./gradlew :<demo name>:<install task>`.
**Example**: For example, `./gradlew :demo:installNoDecoderExtensionsDebug` installs the
ExoPlayer demo app in debug mode with no optional modules.
`./gradlew :demo:installNoDecoderExtensionsDebug` installs the main ExoPlayer
demo app in debug mode with no decoder extensions.

View File

@ -52,13 +52,13 @@ android {
} }
dependencies { dependencies {
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'lib-exoplayer')
implementation project(modulePrefix + 'library-dash') implementation project(modulePrefix + 'lib-exoplayer-dash')
implementation project(modulePrefix + 'library-hls') implementation project(modulePrefix + 'lib-exoplayer-hls')
implementation project(modulePrefix + 'library-rtsp') implementation project(modulePrefix + 'lib-exoplayer-rtsp')
implementation project(modulePrefix + 'library-smoothstreaming') implementation project(modulePrefix + 'lib-exoplayer-smoothstreaming')
implementation project(modulePrefix + 'library-ui') implementation project(modulePrefix + 'lib-ui')
implementation project(modulePrefix + 'extension-cast') implementation project(modulePrefix + 'lib-cast')
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
implementation 'androidx.recyclerview:recyclerview:' + androidxRecyclerViewVersion implementation 'androidx.recyclerview:recyclerview:' + androidxRecyclerViewVersion

View File

@ -14,7 +14,7 @@
limitations under the License. limitations under the License.
--> -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.exoplayer2.castdemo"> package="androidx.media3.demo.cast">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
@ -26,7 +26,7 @@
android:largeHeap="true" android:allowBackup="false"> android:largeHeap="true" android:allowBackup="false">
<meta-data android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME" <meta-data android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.google.android.exoplayer2.ext.cast.DefaultCastOptionsProvider"/> android:value="androidx.media3.cast.DefaultCastOptionsProvider"/>
<activity android:name=".MainActivity" <activity android:name=".MainActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer2.castdemo; package androidx.media3.demo.cast;
import androidx.multidex.MultiDexApplication; import androidx.multidex.MultiDexApplication;

View File

@ -0,0 +1,100 @@
/*
* 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 androidx.media3.demo.cast;
import android.net.Uri;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.MimeTypes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** Utility methods and constants for the Cast demo application. */
/* package */ final class DemoUtil {
public static final String MIME_TYPE_DASH = MimeTypes.APPLICATION_MPD;
public static final String MIME_TYPE_HLS = MimeTypes.APPLICATION_M3U8;
public static final String MIME_TYPE_SS = MimeTypes.APPLICATION_SS;
public static final String MIME_TYPE_VIDEO_MP4 = MimeTypes.VIDEO_MP4;
/** The list of samples available in the cast demo app. */
public static final List<MediaItem> SAMPLES;
static {
ArrayList<MediaItem> samples = new ArrayList<>();
// Clear content.
samples.add(
new MediaItem.Builder()
.setUri("https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd")
.setMediaMetadata(new MediaMetadata.Builder().setTitle("Clear DASH: Tears").build())
.setMimeType(MIME_TYPE_DASH)
.build());
samples.add(
new MediaItem.Builder()
.setUri("https://storage.googleapis.com/shaka-demo-assets/angel-one-hls/hls.m3u8")
.setMediaMetadata(new MediaMetadata.Builder().setTitle("Clear HLS: Angel one").build())
.setMimeType(MIME_TYPE_HLS)
.build());
samples.add(
new MediaItem.Builder()
.setUri("https://html5demos.com/assets/dizzy.mp4")
.setMediaMetadata(new MediaMetadata.Builder().setTitle("Clear MP4: Dizzy").build())
.setMimeType(MIME_TYPE_VIDEO_MP4)
.build());
// DRM content.
samples.add(
new MediaItem.Builder()
.setUri(Uri.parse("https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd"))
.setMediaMetadata(
new MediaMetadata.Builder().setTitle("Widevine DASH cenc: Tears").build())
.setMimeType(MIME_TYPE_DASH)
.setDrmConfiguration(
new MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
.setLicenseUri("https://proxy.uat.widevine.com/proxy?provider=widevine_test")
.build())
.build());
samples.add(
new MediaItem.Builder()
.setUri("https://storage.googleapis.com/wvmedia/cbc1/h264/tears/tears_aes_cbc1.mpd")
.setMediaMetadata(
new MediaMetadata.Builder().setTitle("Widevine DASH cbc1: Tears").build())
.setMimeType(MIME_TYPE_DASH)
.setDrmConfiguration(
new MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
.setLicenseUri("https://proxy.uat.widevine.com/proxy?provider=widevine_test")
.build())
.build());
samples.add(
new MediaItem.Builder()
.setUri("https://storage.googleapis.com/wvmedia/cbcs/h264/tears/tears_aes_cbcs.mpd")
.setMediaMetadata(
new MediaMetadata.Builder().setTitle("Widevine DASH cbcs: Tears").build())
.setMimeType(MIME_TYPE_DASH)
.setDrmConfiguration(
new MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
.setLicenseUri("https://proxy.uat.widevine.com/proxy?provider=widevine_test")
.build())
.build());
SAMPLES = Collections.unmodifiableList(samples);
}
private DemoUtil() {}
}

View File

@ -0,0 +1,316 @@
/*
* 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 androidx.media3.demo.cast;
import android.content.Context;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.ColorUtils;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.ui.PlayerControlView;
import androidx.media3.ui.PlayerView;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.google.android.gms.cast.framework.CastButtonFactory;
import com.google.android.gms.cast.framework.CastContext;
import com.google.android.gms.dynamite.DynamiteModule;
/**
* An activity that plays video using {@link ExoPlayer} and supports casting using ExoPlayer's Cast
* extension.
*/
public class MainActivity extends AppCompatActivity
implements OnClickListener, PlayerManager.Listener {
private PlayerView localPlayerView;
private PlayerControlView castControlView;
private PlayerManager playerManager;
private RecyclerView mediaQueueList;
private MediaQueueListAdapter mediaQueueListAdapter;
private CastContext castContext;
// Activity lifecycle methods.
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Getting the cast context later than onStart can cause device discovery not to take place.
try {
castContext = CastContext.getSharedInstance(this);
} catch (RuntimeException e) {
Throwable cause = e.getCause();
while (cause != null) {
if (cause instanceof DynamiteModule.LoadingException) {
setContentView(R.layout.cast_context_error);
return;
}
cause = cause.getCause();
}
// Unknown error. We propagate it.
throw e;
}
setContentView(R.layout.main_activity);
localPlayerView = findViewById(R.id.local_player_view);
localPlayerView.requestFocus();
castControlView = findViewById(R.id.cast_control_view);
mediaQueueList = findViewById(R.id.sample_list);
ItemTouchHelper helper = new ItemTouchHelper(new RecyclerViewCallback());
helper.attachToRecyclerView(mediaQueueList);
mediaQueueList.setLayoutManager(new LinearLayoutManager(this));
mediaQueueList.setHasFixedSize(true);
mediaQueueListAdapter = new MediaQueueListAdapter();
findViewById(R.id.add_sample_button).setOnClickListener(this);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.menu, menu);
CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item);
return true;
}
@Override
public void onResume() {
super.onResume();
if (castContext == null) {
// There is no Cast context to work with. Do nothing.
return;
}
playerManager =
new PlayerManager(
/* listener= */ this,
localPlayerView,
castControlView,
/* context= */ this,
castContext);
mediaQueueList.setAdapter(mediaQueueListAdapter);
}
@Override
public void onPause() {
super.onPause();
if (castContext == null) {
// Nothing to release.
return;
}
mediaQueueListAdapter.notifyItemRangeRemoved(0, mediaQueueListAdapter.getItemCount());
mediaQueueList.setAdapter(null);
playerManager.release();
playerManager = null;
}
// Activity input.
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
// If the event was not handled then see if the player view can handle it.
return super.dispatchKeyEvent(event) || playerManager.dispatchKeyEvent(event);
}
@Override
public void onClick(View view) {
new AlertDialog.Builder(this)
.setTitle(R.string.add_samples)
.setView(buildSampleListView())
.setPositiveButton(android.R.string.ok, null)
.create()
.show();
}
// PlayerManager.Listener implementation.
@Override
public void onQueuePositionChanged(int previousIndex, int newIndex) {
if (previousIndex != C.INDEX_UNSET) {
mediaQueueListAdapter.notifyItemChanged(previousIndex);
}
if (newIndex != C.INDEX_UNSET) {
mediaQueueListAdapter.notifyItemChanged(newIndex);
}
}
@Override
public void onUnsupportedTrack(int trackType) {
if (trackType == C.TRACK_TYPE_AUDIO) {
showToast(R.string.error_unsupported_audio);
} else if (trackType == C.TRACK_TYPE_VIDEO) {
showToast(R.string.error_unsupported_video);
}
}
// Internal methods.
private void showToast(int messageId) {
Toast.makeText(getApplicationContext(), messageId, Toast.LENGTH_LONG).show();
}
private View buildSampleListView() {
View dialogList = getLayoutInflater().inflate(R.layout.sample_list, null);
ListView sampleList = dialogList.findViewById(R.id.sample_list);
sampleList.setAdapter(new SampleListAdapter(this));
sampleList.setOnItemClickListener(
(parent, view, position, id) -> {
playerManager.addItem(DemoUtil.SAMPLES.get(position));
mediaQueueListAdapter.notifyItemInserted(playerManager.getMediaQueueSize() - 1);
});
return dialogList;
}
// Internal classes.
private class MediaQueueListAdapter extends RecyclerView.Adapter<QueueItemViewHolder> {
@Override
@NonNull
public QueueItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
TextView v =
(TextView)
LayoutInflater.from(parent.getContext())
.inflate(android.R.layout.simple_list_item_1, parent, false);
return new QueueItemViewHolder(v);
}
@Override
public void onBindViewHolder(QueueItemViewHolder holder, int position) {
holder.item = Assertions.checkNotNull(playerManager.getItem(position));
TextView view = holder.textView;
view.setText(holder.item.mediaMetadata.title);
// TODO: Solve coloring using the theme's ColorStateList.
view.setTextColor(
ColorUtils.setAlphaComponent(
view.getCurrentTextColor(),
position == playerManager.getCurrentItemIndex() ? 255 : 100));
}
@Override
public int getItemCount() {
return playerManager.getMediaQueueSize();
}
}
private class RecyclerViewCallback extends ItemTouchHelper.SimpleCallback {
private int draggingFromPosition;
private int draggingToPosition;
public RecyclerViewCallback() {
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.START | ItemTouchHelper.END);
draggingFromPosition = C.INDEX_UNSET;
draggingToPosition = C.INDEX_UNSET;
}
@Override
public boolean onMove(
@NonNull RecyclerView list,
RecyclerView.ViewHolder origin,
RecyclerView.ViewHolder target) {
int fromPosition = origin.getAdapterPosition();
int toPosition = target.getAdapterPosition();
if (draggingFromPosition == C.INDEX_UNSET) {
// A drag has started, but changes to the media queue will be reflected in clearView().
draggingFromPosition = fromPosition;
}
draggingToPosition = toPosition;
mediaQueueListAdapter.notifyItemMoved(fromPosition, toPosition);
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
QueueItemViewHolder queueItemHolder = (QueueItemViewHolder) viewHolder;
if (playerManager.removeItem(queueItemHolder.item)) {
mediaQueueListAdapter.notifyItemRemoved(position);
// Update whichever item took its place, in case it became the new selected item.
mediaQueueListAdapter.notifyItemChanged(position);
}
}
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
if (draggingFromPosition != C.INDEX_UNSET) {
QueueItemViewHolder queueItemHolder = (QueueItemViewHolder) viewHolder;
// A drag has ended. We reflect the media queue change in the player.
if (!playerManager.moveItem(queueItemHolder.item, draggingToPosition)) {
// The move failed. The entire sequence of onMove calls since the drag started needs to be
// invalidated.
mediaQueueListAdapter.notifyDataSetChanged();
}
}
draggingFromPosition = C.INDEX_UNSET;
draggingToPosition = C.INDEX_UNSET;
}
}
private class QueueItemViewHolder extends RecyclerView.ViewHolder implements OnClickListener {
public final TextView textView;
public MediaItem item;
public QueueItemViewHolder(TextView textView) {
super(textView);
this.textView = textView;
textView.setOnClickListener(this);
}
@Override
public void onClick(View v) {
playerManager.selectQueueItem(getAdapterPosition());
}
}
private static final class SampleListAdapter extends ArrayAdapter<MediaItem> {
public SampleListAdapter(Context context) {
super(context, android.R.layout.simple_list_item_1, DemoUtil.SAMPLES);
}
@Override
@NonNull
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
View view = super.getView(position, convertView, parent);
((TextView) view).setText(Util.castNonNull(getItem(position)).mediaMetadata.title);
return view;
}
}
}

View File

@ -13,24 +13,24 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer2.castdemo; package androidx.media3.demo.cast;
import android.content.Context; import android.content.Context;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.View; import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.google.android.exoplayer2.C; import androidx.media3.cast.CastPlayer;
import com.google.android.exoplayer2.ExoPlayer; import androidx.media3.cast.SessionAvailabilityListener;
import com.google.android.exoplayer2.MediaItem; import androidx.media3.common.C;
import com.google.android.exoplayer2.Player; import androidx.media3.common.MediaItem;
import com.google.android.exoplayer2.Player.DiscontinuityReason; import androidx.media3.common.Player;
import com.google.android.exoplayer2.Player.TimelineChangeReason; import androidx.media3.common.Player.DiscontinuityReason;
import com.google.android.exoplayer2.Timeline; import androidx.media3.common.Player.TimelineChangeReason;
import com.google.android.exoplayer2.TracksInfo; import androidx.media3.common.Timeline;
import com.google.android.exoplayer2.ext.cast.CastPlayer; import androidx.media3.common.TracksInfo;
import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener; import androidx.media3.exoplayer.ExoPlayer;
import com.google.android.exoplayer2.ui.PlayerControlView; import androidx.media3.ui.PlayerControlView;
import com.google.android.exoplayer2.ui.PlayerView; import androidx.media3.ui.PlayerView;
import com.google.android.gms.cast.framework.CastContext; import com.google.android.gms.cast.framework.CastContext;
import java.util.ArrayList; import java.util.ArrayList;

View File

@ -0,0 +1,19 @@
/*
* Copyright (C) 2020 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.
*/
@NonNullApi
package androidx.media3.demo.cast;
import androidx.media3.common.util.NonNullApi;

View File

@ -1,100 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.castdemo;
import android.net.Uri;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.MediaMetadata;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** Utility methods and constants for the Cast demo application. */
/* package */ final class DemoUtil {
public static final String MIME_TYPE_DASH = MimeTypes.APPLICATION_MPD;
public static final String MIME_TYPE_HLS = MimeTypes.APPLICATION_M3U8;
public static final String MIME_TYPE_SS = MimeTypes.APPLICATION_SS;
public static final String MIME_TYPE_VIDEO_MP4 = MimeTypes.VIDEO_MP4;
/** The list of samples available in the cast demo app. */
public static final List<MediaItem> SAMPLES;
static {
ArrayList<MediaItem> samples = new ArrayList<>();
// Clear content.
samples.add(
new MediaItem.Builder()
.setUri("https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd")
.setMediaMetadata(new MediaMetadata.Builder().setTitle("Clear DASH: Tears").build())
.setMimeType(MIME_TYPE_DASH)
.build());
samples.add(
new MediaItem.Builder()
.setUri("https://storage.googleapis.com/shaka-demo-assets/angel-one-hls/hls.m3u8")
.setMediaMetadata(new MediaMetadata.Builder().setTitle("Clear HLS: Angel one").build())
.setMimeType(MIME_TYPE_HLS)
.build());
samples.add(
new MediaItem.Builder()
.setUri("https://html5demos.com/assets/dizzy.mp4")
.setMediaMetadata(new MediaMetadata.Builder().setTitle("Clear MP4: Dizzy").build())
.setMimeType(MIME_TYPE_VIDEO_MP4)
.build());
// DRM content.
samples.add(
new MediaItem.Builder()
.setUri(Uri.parse("https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd"))
.setMediaMetadata(
new MediaMetadata.Builder().setTitle("Widevine DASH cenc: Tears").build())
.setMimeType(MIME_TYPE_DASH)
.setDrmConfiguration(
new MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
.setLicenseUri("https://proxy.uat.widevine.com/proxy?provider=widevine_test")
.build())
.build());
samples.add(
new MediaItem.Builder()
.setUri("https://storage.googleapis.com/wvmedia/cbc1/h264/tears/tears_aes_cbc1.mpd")
.setMediaMetadata(
new MediaMetadata.Builder().setTitle("Widevine DASH cbc1: Tears").build())
.setMimeType(MIME_TYPE_DASH)
.setDrmConfiguration(
new MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
.setLicenseUri("https://proxy.uat.widevine.com/proxy?provider=widevine_test")
.build())
.build());
samples.add(
new MediaItem.Builder()
.setUri("https://storage.googleapis.com/wvmedia/cbcs/h264/tears/tears_aes_cbcs.mpd")
.setMediaMetadata(
new MediaMetadata.Builder().setTitle("Widevine DASH cbcs: Tears").build())
.setMimeType(MIME_TYPE_DASH)
.setDrmConfiguration(
new MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
.setLicenseUri("https://proxy.uat.widevine.com/proxy?provider=widevine_test")
.build())
.build());
SAMPLES = Collections.unmodifiableList(samples);
}
private DemoUtil() {}
}

View File

@ -1,316 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.castdemo;
import android.content.Context;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.ColorUtils;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.ui.PlayerControlView;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import com.google.android.gms.cast.framework.CastButtonFactory;
import com.google.android.gms.cast.framework.CastContext;
import com.google.android.gms.dynamite.DynamiteModule;
/**
* An activity that plays video using {@link ExoPlayer} and supports casting using ExoPlayer's Cast
* extension.
*/
public class MainActivity extends AppCompatActivity
implements OnClickListener, PlayerManager.Listener {
private PlayerView localPlayerView;
private PlayerControlView castControlView;
private PlayerManager playerManager;
private RecyclerView mediaQueueList;
private MediaQueueListAdapter mediaQueueListAdapter;
private CastContext castContext;
// Activity lifecycle methods.
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Getting the cast context later than onStart can cause device discovery not to take place.
try {
castContext = CastContext.getSharedInstance(this);
} catch (RuntimeException e) {
Throwable cause = e.getCause();
while (cause != null) {
if (cause instanceof DynamiteModule.LoadingException) {
setContentView(R.layout.cast_context_error);
return;
}
cause = cause.getCause();
}
// Unknown error. We propagate it.
throw e;
}
setContentView(R.layout.main_activity);
localPlayerView = findViewById(R.id.local_player_view);
localPlayerView.requestFocus();
castControlView = findViewById(R.id.cast_control_view);
mediaQueueList = findViewById(R.id.sample_list);
ItemTouchHelper helper = new ItemTouchHelper(new RecyclerViewCallback());
helper.attachToRecyclerView(mediaQueueList);
mediaQueueList.setLayoutManager(new LinearLayoutManager(this));
mediaQueueList.setHasFixedSize(true);
mediaQueueListAdapter = new MediaQueueListAdapter();
findViewById(R.id.add_sample_button).setOnClickListener(this);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.menu, menu);
CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item);
return true;
}
@Override
public void onResume() {
super.onResume();
if (castContext == null) {
// There is no Cast context to work with. Do nothing.
return;
}
playerManager =
new PlayerManager(
/* listener= */ this,
localPlayerView,
castControlView,
/* context= */ this,
castContext);
mediaQueueList.setAdapter(mediaQueueListAdapter);
}
@Override
public void onPause() {
super.onPause();
if (castContext == null) {
// Nothing to release.
return;
}
mediaQueueListAdapter.notifyItemRangeRemoved(0, mediaQueueListAdapter.getItemCount());
mediaQueueList.setAdapter(null);
playerManager.release();
playerManager = null;
}
// Activity input.
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
// If the event was not handled then see if the player view can handle it.
return super.dispatchKeyEvent(event) || playerManager.dispatchKeyEvent(event);
}
@Override
public void onClick(View view) {
new AlertDialog.Builder(this)
.setTitle(R.string.add_samples)
.setView(buildSampleListView())
.setPositiveButton(android.R.string.ok, null)
.create()
.show();
}
// PlayerManager.Listener implementation.
@Override
public void onQueuePositionChanged(int previousIndex, int newIndex) {
if (previousIndex != C.INDEX_UNSET) {
mediaQueueListAdapter.notifyItemChanged(previousIndex);
}
if (newIndex != C.INDEX_UNSET) {
mediaQueueListAdapter.notifyItemChanged(newIndex);
}
}
@Override
public void onUnsupportedTrack(int trackType) {
if (trackType == C.TRACK_TYPE_AUDIO) {
showToast(R.string.error_unsupported_audio);
} else if (trackType == C.TRACK_TYPE_VIDEO) {
showToast(R.string.error_unsupported_video);
}
}
// Internal methods.
private void showToast(int messageId) {
Toast.makeText(getApplicationContext(), messageId, Toast.LENGTH_LONG).show();
}
private View buildSampleListView() {
View dialogList = getLayoutInflater().inflate(R.layout.sample_list, null);
ListView sampleList = dialogList.findViewById(R.id.sample_list);
sampleList.setAdapter(new SampleListAdapter(this));
sampleList.setOnItemClickListener(
(parent, view, position, id) -> {
playerManager.addItem(DemoUtil.SAMPLES.get(position));
mediaQueueListAdapter.notifyItemInserted(playerManager.getMediaQueueSize() - 1);
});
return dialogList;
}
// Internal classes.
private class MediaQueueListAdapter extends RecyclerView.Adapter<QueueItemViewHolder> {
@Override
@NonNull
public QueueItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
TextView v =
(TextView)
LayoutInflater.from(parent.getContext())
.inflate(android.R.layout.simple_list_item_1, parent, false);
return new QueueItemViewHolder(v);
}
@Override
public void onBindViewHolder(QueueItemViewHolder holder, int position) {
holder.item = Assertions.checkNotNull(playerManager.getItem(position));
TextView view = holder.textView;
view.setText(holder.item.mediaMetadata.title);
// TODO: Solve coloring using the theme's ColorStateList.
view.setTextColor(
ColorUtils.setAlphaComponent(
view.getCurrentTextColor(),
position == playerManager.getCurrentItemIndex() ? 255 : 100));
}
@Override
public int getItemCount() {
return playerManager.getMediaQueueSize();
}
}
private class RecyclerViewCallback extends ItemTouchHelper.SimpleCallback {
private int draggingFromPosition;
private int draggingToPosition;
public RecyclerViewCallback() {
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.START | ItemTouchHelper.END);
draggingFromPosition = C.INDEX_UNSET;
draggingToPosition = C.INDEX_UNSET;
}
@Override
public boolean onMove(
@NonNull RecyclerView list,
RecyclerView.ViewHolder origin,
RecyclerView.ViewHolder target) {
int fromPosition = origin.getAdapterPosition();
int toPosition = target.getAdapterPosition();
if (draggingFromPosition == C.INDEX_UNSET) {
// A drag has started, but changes to the media queue will be reflected in clearView().
draggingFromPosition = fromPosition;
}
draggingToPosition = toPosition;
mediaQueueListAdapter.notifyItemMoved(fromPosition, toPosition);
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
QueueItemViewHolder queueItemHolder = (QueueItemViewHolder) viewHolder;
if (playerManager.removeItem(queueItemHolder.item)) {
mediaQueueListAdapter.notifyItemRemoved(position);
// Update whichever item took its place, in case it became the new selected item.
mediaQueueListAdapter.notifyItemChanged(position);
}
}
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
if (draggingFromPosition != C.INDEX_UNSET) {
QueueItemViewHolder queueItemHolder = (QueueItemViewHolder) viewHolder;
// A drag has ended. We reflect the media queue change in the player.
if (!playerManager.moveItem(queueItemHolder.item, draggingToPosition)) {
// The move failed. The entire sequence of onMove calls since the drag started needs to be
// invalidated.
mediaQueueListAdapter.notifyDataSetChanged();
}
}
draggingFromPosition = C.INDEX_UNSET;
draggingToPosition = C.INDEX_UNSET;
}
}
private class QueueItemViewHolder extends RecyclerView.ViewHolder implements OnClickListener {
public final TextView textView;
public MediaItem item;
public QueueItemViewHolder(TextView textView) {
super(textView);
this.textView = textView;
textView.setOnClickListener(this);
}
@Override
public void onClick(View v) {
playerManager.selectQueueItem(getAdapterPosition());
}
}
private static final class SampleListAdapter extends ArrayAdapter<MediaItem> {
public SampleListAdapter(Context context) {
super(context, android.R.layout.simple_list_item_1, DemoUtil.SAMPLES);
}
@Override
@NonNull
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
View view = super.getView(position, convertView, parent);
((TextView) view).setText(Util.castNonNull(getItem(position)).mediaMetadata.title);
return view;
}
}
}

View File

@ -1,19 +0,0 @@
/*
* Copyright (C) 2020 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.
*/
@NonNullApi
package com.google.android.exoplayer2.castdemo;
import com.google.android.exoplayer2.util.NonNullApi;

View File

@ -20,7 +20,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:keepScreenOn="true"> android:keepScreenOn="true">
<com.google.android.exoplayer2.ui.PlayerView android:id="@+id/local_player_view" <androidx.media3.ui.PlayerView android:id="@+id/local_player_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="1"
@ -50,7 +50,7 @@
</RelativeLayout> </RelativeLayout>
<com.google.android.exoplayer2.ui.PlayerControlView android:id="@+id/cast_control_view" <androidx.media3.ui.PlayerControlView android:id="@+id/cast_control_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="gone" android:visibility="gone"

View File

@ -45,12 +45,12 @@ android {
} }
dependencies { dependencies {
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'lib-exoplayer')
implementation project(modulePrefix + 'library-dash') implementation project(modulePrefix + 'lib-exoplayer-dash')
implementation project(modulePrefix + 'library-hls') implementation project(modulePrefix + 'lib-exoplayer-hls')
implementation project(modulePrefix + 'library-rtsp') implementation project(modulePrefix + 'lib-exoplayer-rtsp')
implementation project(modulePrefix + 'library-smoothstreaming') implementation project(modulePrefix + 'lib-exoplayer-smoothstreaming')
implementation project(modulePrefix + 'library-ui') implementation project(modulePrefix + 'lib-ui')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkCompatVersion compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkCompatVersion

View File

@ -14,7 +14,7 @@
limitations under the License. limitations under the License.
--> -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.exoplayer2.gldemo"> package="androidx.media3.demo.gl">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
@ -35,7 +35,7 @@
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="com.google.android.exoplayer.gldemo.action.VIEW"/> <action android:name="androidx.media3.demo.gl.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="http"/> <data android:scheme="http"/>
<data android:scheme="https"/> <data android:scheme="https"/>

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer2.gldemo; package androidx.media3.demo.gl;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
@ -25,9 +25,9 @@ import android.graphics.drawable.BitmapDrawable;
import android.opengl.GLES20; import android.opengl.GLES20;
import android.opengl.GLUtils; import android.opengl.GLUtils;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import androidx.media3.common.C;
import com.google.android.exoplayer2.util.Assertions; import androidx.media3.common.util.Assertions;
import com.google.android.exoplayer2.util.GlUtil; import androidx.media3.common.util.GlUtil;
import java.io.IOException; import java.io.IOException;
import java.util.Locale; import java.util.Locale;
import javax.microedition.khronos.opengles.GL10; import javax.microedition.khronos.opengles.GL10;

View File

@ -0,0 +1,197 @@
/*
* Copyright (C) 2020 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 androidx.media3.demo.gl;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.widget.FrameLayout;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.Player;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DefaultDataSource;
import androidx.media3.datasource.DefaultHttpDataSource;
import androidx.media3.datasource.HttpDataSource;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.dash.DashMediaSource;
import androidx.media3.exoplayer.drm.DefaultDrmSessionManager;
import androidx.media3.exoplayer.drm.DrmSessionManager;
import androidx.media3.exoplayer.drm.FrameworkMediaDrm;
import androidx.media3.exoplayer.drm.HttpMediaDrmCallback;
import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.source.ProgressiveMediaSource;
import androidx.media3.exoplayer.util.EventLogger;
import androidx.media3.ui.PlayerView;
import java.util.UUID;
/**
* Activity that demonstrates playback of video to an {@link android.opengl.GLSurfaceView} with
* postprocessing of the video content using GL.
*/
public final class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private static final String DEFAULT_MEDIA_URI =
"https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv";
private static final String ACTION_VIEW = "androidx.media3.demo.gl.action.VIEW";
private static final String EXTENSION_EXTRA = "extension";
private static final String DRM_SCHEME_EXTRA = "drm_scheme";
private static final String DRM_LICENSE_URL_EXTRA = "drm_license_url";
@Nullable private PlayerView playerView;
@Nullable private VideoProcessingGLSurfaceView videoProcessingGLSurfaceView;
@Nullable private ExoPlayer player;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
playerView = findViewById(R.id.player_view);
Context context = getApplicationContext();
boolean requestSecureSurface = getIntent().hasExtra(DRM_SCHEME_EXTRA);
if (requestSecureSurface && !GlUtil.isProtectedContentExtensionSupported(context)) {
Toast.makeText(
context, R.string.error_protected_content_extension_not_supported, Toast.LENGTH_LONG)
.show();
}
VideoProcessingGLSurfaceView videoProcessingGLSurfaceView =
new VideoProcessingGLSurfaceView(
context, requestSecureSurface, new BitmapOverlayVideoProcessor(context));
FrameLayout contentFrame = findViewById(R.id.exo_content_frame);
contentFrame.addView(videoProcessingGLSurfaceView);
this.videoProcessingGLSurfaceView = videoProcessingGLSurfaceView;
}
@Override
public void onStart() {
super.onStart();
if (Util.SDK_INT > 23) {
initializePlayer();
if (playerView != null) {
playerView.onResume();
}
}
}
@Override
public void onResume() {
super.onResume();
if (Util.SDK_INT <= 23 || player == null) {
initializePlayer();
if (playerView != null) {
playerView.onResume();
}
}
}
@Override
public void onPause() {
super.onPause();
if (Util.SDK_INT <= 23) {
if (playerView != null) {
playerView.onPause();
}
releasePlayer();
}
}
@Override
public void onStop() {
super.onStop();
if (Util.SDK_INT > 23) {
if (playerView != null) {
playerView.onPause();
}
releasePlayer();
}
}
private void initializePlayer() {
Intent intent = getIntent();
String action = intent.getAction();
Uri uri =
ACTION_VIEW.equals(action)
? Assertions.checkNotNull(intent.getData())
: Uri.parse(DEFAULT_MEDIA_URI);
DrmSessionManager drmSessionManager;
if (Util.SDK_INT >= 18 && intent.hasExtra(DRM_SCHEME_EXTRA)) {
String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA));
String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));
HttpDataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory();
HttpMediaDrmCallback drmCallback =
new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory);
drmSessionManager =
new DefaultDrmSessionManager.Builder()
.setUuidAndExoMediaDrmProvider(drmSchemeUuid, FrameworkMediaDrm.DEFAULT_PROVIDER)
.build(drmCallback);
} else {
drmSessionManager = DrmSessionManager.DRM_UNSUPPORTED;
}
DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this);
MediaSource mediaSource;
@C.ContentType int type = Util.inferContentType(uri, intent.getStringExtra(EXTENSION_EXTRA));
if (type == C.TYPE_DASH) {
mediaSource =
new DashMediaSource.Factory(dataSourceFactory)
.setDrmSessionManager(drmSessionManager)
.createMediaSource(MediaItem.fromUri(uri));
} else if (type == C.TYPE_OTHER) {
mediaSource =
new ProgressiveMediaSource.Factory(dataSourceFactory)
.setDrmSessionManager(drmSessionManager)
.createMediaSource(MediaItem.fromUri(uri));
} else {
throw new IllegalStateException();
}
ExoPlayer player = new ExoPlayer.Builder(getApplicationContext()).build();
player.setRepeatMode(Player.REPEAT_MODE_ALL);
player.setMediaSource(mediaSource);
player.prepare();
player.play();
VideoProcessingGLSurfaceView videoProcessingGLSurfaceView =
Assertions.checkNotNull(this.videoProcessingGLSurfaceView);
videoProcessingGLSurfaceView.setVideoComponent(
Assertions.checkNotNull(player.getVideoComponent()));
Assertions.checkNotNull(playerView).setPlayer(player);
player.addAnalyticsListener(new EventLogger(/* trackSelector= */ null));
this.player = player;
}
private void releasePlayer() {
Assertions.checkNotNull(playerView).setPlayer(null);
if (player != null) {
player.release();
Assertions.checkNotNull(videoProcessingGLSurfaceView).setVideoComponent(null);
player = null;
}
}
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer2.gldemo; package androidx.media3.demo.gl;
import android.content.Context; import android.content.Context;
import android.graphics.SurfaceTexture; import android.graphics.SurfaceTexture;
@ -25,13 +25,13 @@ import android.os.Handler;
import android.view.Surface; import android.view.Surface;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import androidx.media3.common.C;
import com.google.android.exoplayer2.ExoPlayer; import androidx.media3.common.Format;
import com.google.android.exoplayer2.Format; import androidx.media3.common.util.Assertions;
import com.google.android.exoplayer2.util.Assertions; import androidx.media3.common.util.GlUtil;
import com.google.android.exoplayer2.util.GlUtil; import androidx.media3.common.util.TimedValueQueue;
import com.google.android.exoplayer2.util.TimedValueQueue; import androidx.media3.exoplayer.ExoPlayer;
import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import androidx.media3.exoplayer.video.VideoFrameMetadataListener;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLConfig;

View File

@ -0,0 +1,19 @@
/*
* Copyright (C) 2020 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.
*/
@NonNullApi
package androidx.media3.demo.gl;
import androidx.media3.common.util.NonNullApi;

View File

@ -1,197 +0,0 @@
/*
* Copyright (C) 2020 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.gldemo;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.widget.FrameLayout;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.dash.DashMediaSource;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.EventLogger;
import com.google.android.exoplayer2.util.GlUtil;
import com.google.android.exoplayer2.util.Util;
import java.util.UUID;
/**
* Activity that demonstrates playback of video to an {@link android.opengl.GLSurfaceView} with
* postprocessing of the video content using GL.
*/
public final class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private static final String DEFAULT_MEDIA_URI =
"https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv";
private static final String ACTION_VIEW = "com.google.android.exoplayer.gldemo.action.VIEW";
private static final String EXTENSION_EXTRA = "extension";
private static final String DRM_SCHEME_EXTRA = "drm_scheme";
private static final String DRM_LICENSE_URL_EXTRA = "drm_license_url";
@Nullable private PlayerView playerView;
@Nullable private VideoProcessingGLSurfaceView videoProcessingGLSurfaceView;
@Nullable private ExoPlayer player;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
playerView = findViewById(R.id.player_view);
Context context = getApplicationContext();
boolean requestSecureSurface = getIntent().hasExtra(DRM_SCHEME_EXTRA);
if (requestSecureSurface && !GlUtil.isProtectedContentExtensionSupported(context)) {
Toast.makeText(
context, R.string.error_protected_content_extension_not_supported, Toast.LENGTH_LONG)
.show();
}
VideoProcessingGLSurfaceView videoProcessingGLSurfaceView =
new VideoProcessingGLSurfaceView(
context, requestSecureSurface, new BitmapOverlayVideoProcessor(context));
FrameLayout contentFrame = findViewById(R.id.exo_content_frame);
contentFrame.addView(videoProcessingGLSurfaceView);
this.videoProcessingGLSurfaceView = videoProcessingGLSurfaceView;
}
@Override
public void onStart() {
super.onStart();
if (Util.SDK_INT > 23) {
initializePlayer();
if (playerView != null) {
playerView.onResume();
}
}
}
@Override
public void onResume() {
super.onResume();
if (Util.SDK_INT <= 23 || player == null) {
initializePlayer();
if (playerView != null) {
playerView.onResume();
}
}
}
@Override
public void onPause() {
super.onPause();
if (Util.SDK_INT <= 23) {
if (playerView != null) {
playerView.onPause();
}
releasePlayer();
}
}
@Override
public void onStop() {
super.onStop();
if (Util.SDK_INT > 23) {
if (playerView != null) {
playerView.onPause();
}
releasePlayer();
}
}
private void initializePlayer() {
Intent intent = getIntent();
String action = intent.getAction();
Uri uri =
ACTION_VIEW.equals(action)
? Assertions.checkNotNull(intent.getData())
: Uri.parse(DEFAULT_MEDIA_URI);
DrmSessionManager drmSessionManager;
if (Util.SDK_INT >= 18 && intent.hasExtra(DRM_SCHEME_EXTRA)) {
String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA));
String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));
HttpDataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory();
HttpMediaDrmCallback drmCallback =
new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory);
drmSessionManager =
new DefaultDrmSessionManager.Builder()
.setUuidAndExoMediaDrmProvider(drmSchemeUuid, FrameworkMediaDrm.DEFAULT_PROVIDER)
.build(drmCallback);
} else {
drmSessionManager = DrmSessionManager.DRM_UNSUPPORTED;
}
DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this);
MediaSource mediaSource;
@C.ContentType int type = Util.inferContentType(uri, intent.getStringExtra(EXTENSION_EXTRA));
if (type == C.TYPE_DASH) {
mediaSource =
new DashMediaSource.Factory(dataSourceFactory)
.setDrmSessionManager(drmSessionManager)
.createMediaSource(MediaItem.fromUri(uri));
} else if (type == C.TYPE_OTHER) {
mediaSource =
new ProgressiveMediaSource.Factory(dataSourceFactory)
.setDrmSessionManager(drmSessionManager)
.createMediaSource(MediaItem.fromUri(uri));
} else {
throw new IllegalStateException();
}
ExoPlayer player = new ExoPlayer.Builder(getApplicationContext()).build();
player.setRepeatMode(Player.REPEAT_MODE_ALL);
player.setMediaSource(mediaSource);
player.prepare();
player.play();
VideoProcessingGLSurfaceView videoProcessingGLSurfaceView =
Assertions.checkNotNull(this.videoProcessingGLSurfaceView);
videoProcessingGLSurfaceView.setVideoComponent(
Assertions.checkNotNull(player.getVideoComponent()));
Assertions.checkNotNull(playerView).setPlayer(player);
player.addAnalyticsListener(new EventLogger(/* trackSelector= */ null));
this.player = player;
}
private void releasePlayer() {
Assertions.checkNotNull(playerView).setPlayer(null);
if (player != null) {
player.release();
Assertions.checkNotNull(videoProcessingGLSurfaceView).setVideoComponent(null);
player = null;
}
}
}

View File

@ -1,19 +0,0 @@
/*
* Copyright (C) 2020 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.
*/
@NonNullApi
package com.google.android.exoplayer2.gldemo;
import com.google.android.exoplayer2.util.NonNullApi;

View File

@ -20,7 +20,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:keepScreenOn="true"> android:keepScreenOn="true">
<com.google.android.exoplayer2.ui.PlayerView <androidx.media3.ui.PlayerView
android:id="@+id/player_view" android:id="@+id/player_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -71,20 +71,20 @@ dependencies {
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
implementation 'com.google.android.material:material:' + androidxMaterialVersion implementation 'com.google.android.material:material:' + androidxMaterialVersion
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'lib-exoplayer')
implementation project(modulePrefix + 'library-dash') implementation project(modulePrefix + 'lib-exoplayer-dash')
implementation project(modulePrefix + 'library-hls') implementation project(modulePrefix + 'lib-exoplayer-hls')
implementation project(modulePrefix + 'library-rtsp') implementation project(modulePrefix + 'lib-exoplayer-rtsp')
implementation project(modulePrefix + 'library-smoothstreaming') implementation project(modulePrefix + 'lib-exoplayer-smoothstreaming')
implementation project(modulePrefix + 'library-ui') implementation project(modulePrefix + 'lib-ui')
implementation project(modulePrefix + 'extension-cronet') implementation project(modulePrefix + 'lib-datasource-cronet')
implementation project(modulePrefix + 'extension-ima') implementation project(modulePrefix + 'lib-exoplayer-ima')
withDecoderExtensionsImplementation project(modulePrefix + 'extension-av1') withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-av1')
withDecoderExtensionsImplementation project(modulePrefix + 'extension-ffmpeg') withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-ffmpeg')
withDecoderExtensionsImplementation project(modulePrefix + 'extension-flac') withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-flac')
withDecoderExtensionsImplementation project(modulePrefix + 'extension-opus') withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-opus')
withDecoderExtensionsImplementation project(modulePrefix + 'extension-vp9') withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-vp9')
withDecoderExtensionsImplementation project(modulePrefix + 'extension-rtmp') withDecoderExtensionsImplementation project(modulePrefix + 'lib-datasource-rtmp')
} }
apply plugin: 'com.google.android.gms.strict-version-matcher-plugin' apply plugin: 'com.google.android.gms.strict-version-matcher-plugin'

View File

@ -16,7 +16,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer2.demo"> package="androidx.media3.demo.main">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
@ -69,7 +69,7 @@
android:theme="@style/PlayerTheme" android:theme="@style/PlayerTheme"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="com.google.android.exoplayer.demo.action.VIEW"/> <action android:name="androidx.media3.demo.main.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="http"/> <data android:scheme="http"/>
<data android:scheme="https"/> <data android:scheme="https"/>
@ -78,7 +78,7 @@
<data android:scheme="file"/> <data android:scheme="file"/>
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="com.google.android.exoplayer.demo.action.VIEW_LIST"/> <action android:name="androidx.media3.demo.main.action.VIEW_LIST"/>
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT"/>
</intent-filter> </intent-filter>
</activity> </activity>
@ -86,12 +86,12 @@
<service android:name=".DemoDownloadService" <service android:name=".DemoDownloadService"
android:exported="false"> android:exported="false">
<intent-filter> <intent-filter>
<action android:name="com.google.android.exoplayer.downloadService.action.RESTART"/> <action android:name="androidx.media3.exoplayer.downloadService.action.RESTART"/>
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT"/>
</intent-filter> </intent-filter>
</service> </service>
<service android:name="com.google.android.exoplayer2.scheduler.PlatformScheduler$PlatformSchedulerService" <service android:name="androidx.media3.exoplayer.scheduler.PlatformScheduler$PlatformSchedulerService"
android:permission="android.permission.BIND_JOB_SERVICE" android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true"/> android:exported="true"/>

View File

@ -13,23 +13,23 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer2.demo; package androidx.media3.demo.main;
import static com.google.android.exoplayer2.demo.DemoUtil.DOWNLOAD_NOTIFICATION_CHANNEL_ID; import static androidx.media3.demo.main.DemoUtil.DOWNLOAD_NOTIFICATION_CHANNEL_ID;
import android.app.Notification; import android.app.Notification;
import android.content.Context; import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.offline.Download; import androidx.media3.common.util.NotificationUtil;
import com.google.android.exoplayer2.offline.DownloadManager; import androidx.media3.common.util.Util;
import com.google.android.exoplayer2.offline.DownloadService; import androidx.media3.exoplayer.offline.Download;
import com.google.android.exoplayer2.scheduler.PlatformScheduler; import androidx.media3.exoplayer.offline.DownloadManager;
import com.google.android.exoplayer2.scheduler.Requirements; import androidx.media3.exoplayer.offline.DownloadNotificationHelper;
import com.google.android.exoplayer2.scheduler.Scheduler; import androidx.media3.exoplayer.offline.DownloadService;
import com.google.android.exoplayer2.ui.DownloadNotificationHelper; import androidx.media3.exoplayer.scheduler.PlatformScheduler;
import com.google.android.exoplayer2.util.NotificationUtil; import androidx.media3.exoplayer.scheduler.Requirements;
import com.google.android.exoplayer2.util.Util; import androidx.media3.exoplayer.scheduler.Scheduler;
import java.util.List; import java.util.List;
/** A service for downloading media. */ /** A service for downloading media. */

View File

@ -0,0 +1,222 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.demo.main;
import android.content.Context;
import androidx.media3.common.util.Log;
import androidx.media3.database.DatabaseProvider;
import androidx.media3.database.StandaloneDatabaseProvider;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DefaultDataSource;
import androidx.media3.datasource.DefaultHttpDataSource;
import androidx.media3.datasource.HttpDataSource;
import androidx.media3.datasource.cache.Cache;
import androidx.media3.datasource.cache.CacheDataSource;
import androidx.media3.datasource.cache.NoOpCacheEvictor;
import androidx.media3.datasource.cache.SimpleCache;
import androidx.media3.datasource.cronet.CronetDataSource;
import androidx.media3.datasource.cronet.CronetUtil;
import androidx.media3.exoplayer.DefaultRenderersFactory;
import androidx.media3.exoplayer.RenderersFactory;
import androidx.media3.exoplayer.offline.ActionFileUpgradeUtil;
import androidx.media3.exoplayer.offline.DefaultDownloadIndex;
import androidx.media3.exoplayer.offline.DownloadManager;
import androidx.media3.exoplayer.offline.DownloadNotificationHelper;
import java.io.File;
import java.io.IOException;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.util.concurrent.Executors;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.chromium.net.CronetEngine;
/** Utility methods for the demo app. */
public final class DemoUtil {
public static final String DOWNLOAD_NOTIFICATION_CHANNEL_ID = "download_channel";
/**
* Whether the demo application uses Cronet for networking. Note that Cronet does not provide
* automatic support for cookies (https://github.com/google/ExoPlayer/issues/5975).
*
* <p>If set to false, the platform's default network stack is used with a {@link CookieManager}
* configured in {@link #getHttpDataSourceFactory}.
*/
private static final boolean USE_CRONET_FOR_NETWORKING = true;
private static final String TAG = "DemoUtil";
private static final String DOWNLOAD_ACTION_FILE = "actions";
private static final String DOWNLOAD_TRACKER_ACTION_FILE = "tracked_actions";
private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
private static DataSource.@MonotonicNonNull Factory dataSourceFactory;
private static HttpDataSource.@MonotonicNonNull Factory httpDataSourceFactory;
private static @MonotonicNonNull DatabaseProvider databaseProvider;
private static @MonotonicNonNull File downloadDirectory;
private static @MonotonicNonNull Cache downloadCache;
private static @MonotonicNonNull DownloadManager downloadManager;
private static @MonotonicNonNull DownloadTracker downloadTracker;
private static @MonotonicNonNull DownloadNotificationHelper downloadNotificationHelper;
/** Returns whether extension renderers should be used. */
public static boolean useExtensionRenderers() {
return BuildConfig.USE_DECODER_EXTENSIONS;
}
public static RenderersFactory buildRenderersFactory(
Context context, boolean preferExtensionRenderer) {
@DefaultRenderersFactory.ExtensionRendererMode
int extensionRendererMode =
useExtensionRenderers()
? (preferExtensionRenderer
? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER
: DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON)
: DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF;
return new DefaultRenderersFactory(context.getApplicationContext())
.setExtensionRendererMode(extensionRendererMode);
}
public static synchronized HttpDataSource.Factory getHttpDataSourceFactory(Context context) {
if (httpDataSourceFactory == null) {
if (USE_CRONET_FOR_NETWORKING) {
context = context.getApplicationContext();
@Nullable CronetEngine cronetEngine = CronetUtil.buildCronetEngine(context);
if (cronetEngine != null) {
httpDataSourceFactory =
new CronetDataSource.Factory(cronetEngine, Executors.newSingleThreadExecutor());
}
}
if (httpDataSourceFactory == null) {
// We don't want to use Cronet, or we failed to instantiate a CronetEngine.
CookieManager cookieManager = new CookieManager();
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
CookieHandler.setDefault(cookieManager);
httpDataSourceFactory = new DefaultHttpDataSource.Factory();
}
}
return httpDataSourceFactory;
}
/** Returns a {@link DataSource.Factory}. */
public static synchronized DataSource.Factory getDataSourceFactory(Context context) {
if (dataSourceFactory == null) {
context = context.getApplicationContext();
DefaultDataSource.Factory upstreamFactory =
new DefaultDataSource.Factory(context, getHttpDataSourceFactory(context));
dataSourceFactory = buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache(context));
}
return dataSourceFactory;
}
public static synchronized DownloadNotificationHelper getDownloadNotificationHelper(
Context context) {
if (downloadNotificationHelper == null) {
downloadNotificationHelper =
new DownloadNotificationHelper(context, DOWNLOAD_NOTIFICATION_CHANNEL_ID);
}
return downloadNotificationHelper;
}
public static synchronized DownloadManager getDownloadManager(Context context) {
ensureDownloadManagerInitialized(context);
return downloadManager;
}
public static synchronized DownloadTracker getDownloadTracker(Context context) {
ensureDownloadManagerInitialized(context);
return downloadTracker;
}
private static synchronized Cache getDownloadCache(Context context) {
if (downloadCache == null) {
File downloadContentDirectory =
new File(getDownloadDirectory(context), DOWNLOAD_CONTENT_DIRECTORY);
downloadCache =
new SimpleCache(
downloadContentDirectory, new NoOpCacheEvictor(), getDatabaseProvider(context));
}
return downloadCache;
}
private static synchronized void ensureDownloadManagerInitialized(Context context) {
if (downloadManager == null) {
DefaultDownloadIndex downloadIndex = new DefaultDownloadIndex(getDatabaseProvider(context));
upgradeActionFile(
context, DOWNLOAD_ACTION_FILE, downloadIndex, /* addNewDownloadsAsCompleted= */ false);
upgradeActionFile(
context,
DOWNLOAD_TRACKER_ACTION_FILE,
downloadIndex,
/* addNewDownloadsAsCompleted= */ true);
downloadManager =
new DownloadManager(
context,
getDatabaseProvider(context),
getDownloadCache(context),
getHttpDataSourceFactory(context),
Executors.newFixedThreadPool(/* nThreads= */ 6));
downloadTracker =
new DownloadTracker(context, getHttpDataSourceFactory(context), downloadManager);
}
}
private static synchronized void upgradeActionFile(
Context context,
String fileName,
DefaultDownloadIndex downloadIndex,
boolean addNewDownloadsAsCompleted) {
try {
ActionFileUpgradeUtil.upgradeAndDelete(
new File(getDownloadDirectory(context), fileName),
/* downloadIdProvider= */ null,
downloadIndex,
/* deleteOnFailure= */ true,
addNewDownloadsAsCompleted);
} catch (IOException e) {
Log.e(TAG, "Failed to upgrade action file: " + fileName, e);
}
}
private static synchronized DatabaseProvider getDatabaseProvider(Context context) {
if (databaseProvider == null) {
databaseProvider = new StandaloneDatabaseProvider(context);
}
return databaseProvider;
}
private static synchronized File getDownloadDirectory(Context context) {
if (downloadDirectory == null) {
downloadDirectory = context.getExternalFilesDir(/* type= */ null);
if (downloadDirectory == null) {
downloadDirectory = context.getFilesDir();
}
}
return downloadDirectory;
}
private static CacheDataSource.Factory buildReadOnlyCacheDataSource(
DataSource.Factory upstreamFactory, Cache cache) {
return new CacheDataSource.Factory()
.setCache(cache)
.setUpstreamDataSourceFactory(upstreamFactory)
.setCacheWriteDataSinkFactory(null)
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR);
}
private DemoUtil() {}
}

View File

@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer2.demo; package androidx.media3.demo.main;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull; import static androidx.media3.common.util.Assertions.checkStateNotNull;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
@ -27,28 +27,28 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import com.google.android.exoplayer2.Format; import androidx.media3.common.DrmInitData;
import com.google.android.exoplayer2.MediaItem; import androidx.media3.common.Format;
import com.google.android.exoplayer2.RenderersFactory; import androidx.media3.common.MediaItem;
import com.google.android.exoplayer2.drm.DrmInitData; import androidx.media3.common.TrackGroup;
import com.google.android.exoplayer2.drm.DrmSession; import androidx.media3.common.TrackGroupArray;
import com.google.android.exoplayer2.drm.DrmSessionEventListener; import androidx.media3.common.util.Log;
import com.google.android.exoplayer2.drm.OfflineLicenseHelper; import androidx.media3.common.util.Util;
import com.google.android.exoplayer2.offline.Download; import androidx.media3.datasource.HttpDataSource;
import com.google.android.exoplayer2.offline.DownloadCursor; import androidx.media3.exoplayer.RenderersFactory;
import com.google.android.exoplayer2.offline.DownloadHelper; import androidx.media3.exoplayer.drm.DrmSession;
import com.google.android.exoplayer2.offline.DownloadHelper.LiveContentUnsupportedException; import androidx.media3.exoplayer.drm.DrmSessionEventListener;
import com.google.android.exoplayer2.offline.DownloadIndex; import androidx.media3.exoplayer.drm.OfflineLicenseHelper;
import com.google.android.exoplayer2.offline.DownloadManager; import androidx.media3.exoplayer.offline.Download;
import com.google.android.exoplayer2.offline.DownloadRequest; import androidx.media3.exoplayer.offline.DownloadCursor;
import com.google.android.exoplayer2.offline.DownloadService; import androidx.media3.exoplayer.offline.DownloadHelper;
import com.google.android.exoplayer2.source.TrackGroup; import androidx.media3.exoplayer.offline.DownloadHelper.LiveContentUnsupportedException;
import com.google.android.exoplayer2.source.TrackGroupArray; import androidx.media3.exoplayer.offline.DownloadIndex;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import androidx.media3.exoplayer.offline.DownloadManager;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; import androidx.media3.exoplayer.offline.DownloadRequest;
import com.google.android.exoplayer2.upstream.HttpDataSource; import androidx.media3.exoplayer.offline.DownloadService;
import com.google.android.exoplayer2.util.Log; import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.util.Util; import androidx.media3.exoplayer.trackselection.MappingTrackSelector.MappedTrackInfo;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;

View File

@ -13,19 +13,19 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer2.demo; package androidx.media3.demo.main;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState; import static androidx.media3.common.util.Assertions.checkState;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import androidx.media3.common.C;
import com.google.android.exoplayer2.MediaItem; import androidx.media3.common.MediaItem;
import com.google.android.exoplayer2.MediaMetadata; import androidx.media3.common.MediaMetadata;
import com.google.android.exoplayer2.util.Assertions; import androidx.media3.common.util.Assertions;
import com.google.android.exoplayer2.util.Util; import androidx.media3.common.util.Util;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -38,9 +38,8 @@ public class IntentUtil {
// Actions. // Actions.
public static final String ACTION_VIEW = "com.google.android.exoplayer.demo.action.VIEW"; public static final String ACTION_VIEW = "androidx.media3.demo.main.action.VIEW";
public static final String ACTION_VIEW_LIST = public static final String ACTION_VIEW_LIST = "androidx.media3.demo.main.action.VIEW_LIST";
"com.google.android.exoplayer.demo.action.VIEW_LIST";
// Activity extras. // Activity extras.
public static final String PREFER_EXTENSION_DECODERS_EXTRA = "prefer_extension_decoders"; public static final String PREFER_EXTENSION_DECODERS_EXTRA = "prefer_extension_decoders";

View File

@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer2.demo; package androidx.media3.demo.main;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
@ -31,30 +31,30 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import com.google.android.exoplayer2.C; import androidx.media3.common.AudioAttributes;
import com.google.android.exoplayer2.ExoPlayer; import androidx.media3.common.C;
import com.google.android.exoplayer2.MediaItem; import androidx.media3.common.ErrorMessageProvider;
import com.google.android.exoplayer2.PlaybackException; import androidx.media3.common.MediaItem;
import com.google.android.exoplayer2.Player; import androidx.media3.common.PlaybackException;
import com.google.android.exoplayer2.RenderersFactory; import androidx.media3.common.Player;
import com.google.android.exoplayer2.TracksInfo; import androidx.media3.common.TracksInfo;
import com.google.android.exoplayer2.audio.AudioAttributes; import androidx.media3.common.util.Util;
import com.google.android.exoplayer2.drm.FrameworkMediaDrm; import androidx.media3.datasource.DataSource;
import com.google.android.exoplayer2.ext.ima.ImaAdsLoader; import androidx.media3.exoplayer.ExoPlayer;
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.DecoderInitializationException; import androidx.media3.exoplayer.RenderersFactory;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; import androidx.media3.exoplayer.drm.FrameworkMediaDrm;
import com.google.android.exoplayer2.offline.DownloadRequest; import androidx.media3.exoplayer.ima.ImaAdsLoader;
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory; import androidx.media3.exoplayer.mediacodec.MediaCodecRenderer.DecoderInitializationException;
import com.google.android.exoplayer2.source.MediaSourceFactory; import androidx.media3.exoplayer.mediacodec.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer2.source.ads.AdsLoader; import androidx.media3.exoplayer.offline.DownloadRequest;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import com.google.android.exoplayer2.ui.StyledPlayerControlView; import androidx.media3.exoplayer.source.MediaSourceFactory;
import com.google.android.exoplayer2.ui.StyledPlayerView; import androidx.media3.exoplayer.source.ads.AdsLoader;
import com.google.android.exoplayer2.upstream.DataSource; import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.util.DebugTextViewHelper; import androidx.media3.exoplayer.util.DebugTextViewHelper;
import com.google.android.exoplayer2.util.ErrorMessageProvider; import androidx.media3.exoplayer.util.EventLogger;
import com.google.android.exoplayer2.util.EventLogger; import androidx.media3.ui.StyledPlayerControlView;
import com.google.android.exoplayer2.util.Util; import androidx.media3.ui.StyledPlayerView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;

View File

@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer2.demo; package androidx.media3.demo.main;
import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkArgument;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState; import static androidx.media3.common.util.Assertions.checkState;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -43,17 +43,17 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import com.google.android.exoplayer2.MediaItem; import androidx.media3.common.MediaItem;
import com.google.android.exoplayer2.MediaMetadata; import androidx.media3.common.MediaMetadata;
import com.google.android.exoplayer2.ParserException; import androidx.media3.common.ParserException;
import com.google.android.exoplayer2.RenderersFactory; import androidx.media3.common.util.Log;
import com.google.android.exoplayer2.offline.DownloadService; import androidx.media3.common.util.Util;
import com.google.android.exoplayer2.upstream.DataSource; import androidx.media3.datasource.DataSource;
import com.google.android.exoplayer2.upstream.DataSourceInputStream; import androidx.media3.datasource.DataSourceInputStream;
import com.google.android.exoplayer2.upstream.DataSourceUtil; import androidx.media3.datasource.DataSourceUtil;
import com.google.android.exoplayer2.upstream.DataSpec; import androidx.media3.datasource.DataSpec;
import com.google.android.exoplayer2.util.Log; import androidx.media3.exoplayer.RenderersFactory;
import com.google.android.exoplayer2.util.Util; import androidx.media3.exoplayer.offline.DownloadService;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer2.demo; package androidx.media3.demo.main;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface; import android.content.DialogInterface;
@ -31,14 +31,14 @@ import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter; import androidx.fragment.app.FragmentPagerAdapter;
import androidx.media3.common.C;
import androidx.media3.common.TrackGroupArray;
import androidx.media3.common.util.Assertions;
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector.SelectionOverride;
import androidx.media3.exoplayer.trackselection.MappingTrackSelector.MappedTrackInfo;
import androidx.media3.ui.TrackSelectionView;
import androidx.viewpager.widget.ViewPager; import androidx.viewpager.widget.ViewPager;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.ui.TrackSelectionView;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayout;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;

View File

@ -0,0 +1,19 @@
/*
* Copyright (C) 2020 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.
*/
@NonNullApi
package androidx.media3.demo.main;
import androidx.media3.common.util.NonNullApi;

View File

@ -1,222 +0,0 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.demo;
import android.content.Context;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.database.DatabaseProvider;
import com.google.android.exoplayer2.database.StandaloneDatabaseProvider;
import com.google.android.exoplayer2.ext.cronet.CronetDataSource;
import com.google.android.exoplayer2.ext.cronet.CronetUtil;
import com.google.android.exoplayer2.offline.ActionFileUpgradeUtil;
import com.google.android.exoplayer2.offline.DefaultDownloadIndex;
import com.google.android.exoplayer2.offline.DownloadManager;
import com.google.android.exoplayer2.ui.DownloadNotificationHelper;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.cache.Cache;
import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import com.google.android.exoplayer2.util.Log;
import java.io.File;
import java.io.IOException;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.util.concurrent.Executors;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.chromium.net.CronetEngine;
/** Utility methods for the demo app. */
public final class DemoUtil {
public static final String DOWNLOAD_NOTIFICATION_CHANNEL_ID = "download_channel";
/**
* Whether the demo application uses Cronet for networking. Note that Cronet does not provide
* automatic support for cookies (https://github.com/google/ExoPlayer/issues/5975).
*
* <p>If set to false, the platform's default network stack is used with a {@link CookieManager}
* configured in {@link #getHttpDataSourceFactory}.
*/
private static final boolean USE_CRONET_FOR_NETWORKING = true;
private static final String TAG = "DemoUtil";
private static final String DOWNLOAD_ACTION_FILE = "actions";
private static final String DOWNLOAD_TRACKER_ACTION_FILE = "tracked_actions";
private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
private static DataSource.@MonotonicNonNull Factory dataSourceFactory;
private static HttpDataSource.@MonotonicNonNull Factory httpDataSourceFactory;
private static @MonotonicNonNull DatabaseProvider databaseProvider;
private static @MonotonicNonNull File downloadDirectory;
private static @MonotonicNonNull Cache downloadCache;
private static @MonotonicNonNull DownloadManager downloadManager;
private static @MonotonicNonNull DownloadTracker downloadTracker;
private static @MonotonicNonNull DownloadNotificationHelper downloadNotificationHelper;
/** Returns whether extension renderers should be used. */
public static boolean useExtensionRenderers() {
return BuildConfig.USE_DECODER_EXTENSIONS;
}
public static RenderersFactory buildRenderersFactory(
Context context, boolean preferExtensionRenderer) {
@DefaultRenderersFactory.ExtensionRendererMode
int extensionRendererMode =
useExtensionRenderers()
? (preferExtensionRenderer
? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER
: DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON)
: DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF;
return new DefaultRenderersFactory(context.getApplicationContext())
.setExtensionRendererMode(extensionRendererMode);
}
public static synchronized HttpDataSource.Factory getHttpDataSourceFactory(Context context) {
if (httpDataSourceFactory == null) {
if (USE_CRONET_FOR_NETWORKING) {
context = context.getApplicationContext();
@Nullable CronetEngine cronetEngine = CronetUtil.buildCronetEngine(context);
if (cronetEngine != null) {
httpDataSourceFactory =
new CronetDataSource.Factory(cronetEngine, Executors.newSingleThreadExecutor());
}
}
if (httpDataSourceFactory == null) {
// We don't want to use Cronet, or we failed to instantiate a CronetEngine.
CookieManager cookieManager = new CookieManager();
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
CookieHandler.setDefault(cookieManager);
httpDataSourceFactory = new DefaultHttpDataSource.Factory();
}
}
return httpDataSourceFactory;
}
/** Returns a {@link DataSource.Factory}. */
public static synchronized DataSource.Factory getDataSourceFactory(Context context) {
if (dataSourceFactory == null) {
context = context.getApplicationContext();
DefaultDataSource.Factory upstreamFactory =
new DefaultDataSource.Factory(context, getHttpDataSourceFactory(context));
dataSourceFactory = buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache(context));
}
return dataSourceFactory;
}
public static synchronized DownloadNotificationHelper getDownloadNotificationHelper(
Context context) {
if (downloadNotificationHelper == null) {
downloadNotificationHelper =
new DownloadNotificationHelper(context, DOWNLOAD_NOTIFICATION_CHANNEL_ID);
}
return downloadNotificationHelper;
}
public static synchronized DownloadManager getDownloadManager(Context context) {
ensureDownloadManagerInitialized(context);
return downloadManager;
}
public static synchronized DownloadTracker getDownloadTracker(Context context) {
ensureDownloadManagerInitialized(context);
return downloadTracker;
}
private static synchronized Cache getDownloadCache(Context context) {
if (downloadCache == null) {
File downloadContentDirectory =
new File(getDownloadDirectory(context), DOWNLOAD_CONTENT_DIRECTORY);
downloadCache =
new SimpleCache(
downloadContentDirectory, new NoOpCacheEvictor(), getDatabaseProvider(context));
}
return downloadCache;
}
private static synchronized void ensureDownloadManagerInitialized(Context context) {
if (downloadManager == null) {
DefaultDownloadIndex downloadIndex = new DefaultDownloadIndex(getDatabaseProvider(context));
upgradeActionFile(
context, DOWNLOAD_ACTION_FILE, downloadIndex, /* addNewDownloadsAsCompleted= */ false);
upgradeActionFile(
context,
DOWNLOAD_TRACKER_ACTION_FILE,
downloadIndex,
/* addNewDownloadsAsCompleted= */ true);
downloadManager =
new DownloadManager(
context,
getDatabaseProvider(context),
getDownloadCache(context),
getHttpDataSourceFactory(context),
Executors.newFixedThreadPool(/* nThreads= */ 6));
downloadTracker =
new DownloadTracker(context, getHttpDataSourceFactory(context), downloadManager);
}
}
private static synchronized void upgradeActionFile(
Context context,
String fileName,
DefaultDownloadIndex downloadIndex,
boolean addNewDownloadsAsCompleted) {
try {
ActionFileUpgradeUtil.upgradeAndDelete(
new File(getDownloadDirectory(context), fileName),
/* downloadIdProvider= */ null,
downloadIndex,
/* deleteOnFailure= */ true,
addNewDownloadsAsCompleted);
} catch (IOException e) {
Log.e(TAG, "Failed to upgrade action file: " + fileName, e);
}
}
private static synchronized DatabaseProvider getDatabaseProvider(Context context) {
if (databaseProvider == null) {
databaseProvider = new StandaloneDatabaseProvider(context);
}
return databaseProvider;
}
private static synchronized File getDownloadDirectory(Context context) {
if (downloadDirectory == null) {
downloadDirectory = context.getExternalFilesDir(/* type= */ null);
if (downloadDirectory == null) {
downloadDirectory = context.getFilesDir();
}
}
return downloadDirectory;
}
private static CacheDataSource.Factory buildReadOnlyCacheDataSource(
DataSource.Factory upstreamFactory, Cache cache) {
return new CacheDataSource.Factory()
.setCache(cache)
.setUpstreamDataSourceFactory(upstreamFactory)
.setCacheWriteDataSinkFactory(null)
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR);
}
private DemoUtil() {}
}

View File

@ -1,19 +0,0 @@
/*
* Copyright (C) 2020 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.
*/
@NonNullApi
package com.google.android.exoplayer2.demo;
import com.google.android.exoplayer2.util.NonNullApi;

View File

@ -21,7 +21,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:keepScreenOn="true"> android:keepScreenOn="true">
<com.google.android.exoplayer2.ui.StyledPlayerView android:id="@+id/player_view" <androidx.media3.ui.StyledPlayerView android:id="@+id/player_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:show_shuffle_button="true" app:show_shuffle_button="true"

8
demos/session/README.md Normal file
View File

@ -0,0 +1,8 @@
# Media session demo
This app demonstrates use of media sessions. It's a reference app written in
Kotlin, which demonstrates best practices for media apps that want to advertise
media sessions.
See the [demos README](../README.md) for instructions on how to build and run
this demo.

View File

@ -0,0 +1,69 @@
// Copyright 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
apply from: '../../constants.gradle'
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
compileSdkVersion project.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
defaultConfig {
versionName project.ext.releaseVersion
versionCode project.ext.releaseVersionCode
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion
multiDexEnabled true
}
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles = [
'proguard-rules.txt',
getDefaultProguardFile('proguard-android-optimize.txt')
]
signingConfig signingConfigs.debug
}
debug {
jniDebuggable = true
}
}
lintOptions {
// The demo app isn't indexed, and doesn't have translations.
disable 'GoogleAppIndexingWarning','MissingTranslation'
}
}
dependencies {
implementation 'androidx.core:core-ktx:' + androidxCoreVersion
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
implementation 'com.google.android.material:material:1.3.0'
implementation project(modulePrefix + ':lib-exoplayer')
implementation project(modulePrefix + ':lib-exoplayer-dash')
implementation project(modulePrefix + ':lib-exoplayer-hls')
implementation project(modulePrefix + ':lib-ui')
implementation project(modulePrefix + ':lib-session')
}

View File

@ -0,0 +1,2 @@
# Proguard rules specific to the media3 session demo app.

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="androidx.media3.demo.session">
<uses-sdk/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.Media3Demo">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".PlayerActivity"
android:exported="true">
</activity>
<activity
android:name=".PlayableFolderActivity"
android:exported="true">
</activity>
<service
android:name=".PlaybackService"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
</intent-filter>
</service>
</application>
</manifest>

View File

@ -0,0 +1,491 @@
{
"media": [
{
"id": "video_01",
"title": "Future Scenerio",
"album": "Mango Open Movie project",
"artist": "Blender Foundation",
"genre": "Video",
"source": "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd",
"image": "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd"
},
{
"id": "video_02",
"title": "TTML Netflix Japanese examples (IMSC1.1)",
"source": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-avc-baseline-480.mp4",
"subtitle_uri": "https://storage.googleapis.com/exoplayer-test-media-1/ttml/netflix_japanese_ttml.xml",
"album": "Video with subtitle",
"artist": "Netflix",
"genre": "Video",
"image": "https://cdn.pixabay.com/photo/2014/10/09/13/14/video-481821_960_720.png"
},
{
"id": "wake_up_01",
"title": "Intro - The Way Of Waking Up (feat. Alan Watts)",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/01_-_Intro_-_The_Way_Of_Waking_Up_feat_Alan_Watts.mp3",
"image": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg",
"trackNumber": 1,
"totalTrackCount": 13,
"duration": 90,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_02",
"title": "Geisha",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/02_-_Geisha.mp3",
"image": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg",
"trackNumber": 2,
"totalTrackCount": 13,
"duration": 267,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_03",
"title": "Voyage I - Waterfall",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/03_-_Voyage_I_-_Waterfall.mp3",
"image": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg",
"trackNumber": 3,
"totalTrackCount": 13,
"duration": 264,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_04",
"title": "The Music In You",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/04_-_The_Music_In_You.mp3",
"image": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg",
"trackNumber": 4,
"totalTrackCount": 13,
"duration": 223,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_05",
"title": "The Calm Before The Storm",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/05_-_The_Calm_Before_The_Storm.mp3",
"image": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg",
"trackNumber": 5,
"totalTrackCount": 13,
"duration": 229,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_06",
"title": "No Pain, No Gain",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/06_-_No_Pain_No_Gain.mp3",
"image": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg",
"trackNumber": 6,
"totalTrackCount": 13,
"duration": 304,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_07",
"title": "Voyage II - Satori",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/07_-_Voyage_II_-_Satori.mp3",
"image": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg",
"trackNumber": 7,
"totalTrackCount": 13,
"duration": 256,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_08",
"title": "Reveal the Magic",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/08_-_Reveal_the_Magic.mp3",
"image": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg",
"trackNumber": 8,
"totalTrackCount": 13,
"duration": 293,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_09",
"title": "Hachiko (The Faithtful Dog)",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/09_-_Hachiko_The_Faithtful_Dog.mp3",
"image": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg",
"trackNumber": 9,
"totalTrackCount": 13,
"duration": 185,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_10",
"title": "Wake Up",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/10_-_Wake_Up.mp3",
"image": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg",
"trackNumber": 10,
"totalTrackCount": 13,
"duration": 251,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_11",
"title": "Voyage III - The Space Between Us",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/11_-_Voyage_III_-_The_Space_Between_Us.mp3",
"image": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg",
"trackNumber": 11,
"totalTrackCount": 13,
"duration": 290,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_12",
"title": "Ume No Kaori (feat. Sunawai)",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/12_-_Ume_No_Kaori_feat_Sunawai.mp3",
"image": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg",
"trackNumber": 12,
"totalTrackCount": 13,
"duration": 334,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_13",
"title": "Outro - Totally Here and Now (feat. Alan Watts)",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/13_-_Outro_-_Totally_Here_and_Now_feat_Alan_Watts.mp3",
"image": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg",
"trackNumber": 13,
"totalTrackCount": 13,
"duration": 242,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "irsens_tale_01",
"title": "Intro (.udonthear)",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/01_-_Intro_udonthear.mp3",
"image": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/art.jpg",
"trackNumber": 1,
"totalTrackCount": 9,
"duration": 63,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_02",
"title": "Leaving",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/02_-_Leaving.mp3",
"image": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/art.jpg",
"trackNumber": 2,
"totalTrackCount": 9,
"duration": 170,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_03",
"title": "Irsen's Tale",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/03_-_Irsens_Tale.mp3",
"image": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/art.jpg",
"trackNumber": 3,
"totalTrackCount": 9,
"duration": 164,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_04",
"title": "Moonlight Reprise",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/04_-_Moonlight_Reprise.mp3",
"image": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/art.jpg",
"trackNumber": 4,
"totalTrackCount": 9,
"duration": 181,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_05",
"title": "Nothing Lasts Forever",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/05_-_Nothing_Lasts_Forever.mp3",
"image": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/art.jpg",
"trackNumber": 5,
"totalTrackCount": 9,
"duration": 132,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_06",
"title": "The Moments of Our Mornings",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/06_-_The_Moments_of_Our_Mornings.mp3",
"image": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/art.jpg",
"trackNumber": 6,
"totalTrackCount": 9,
"duration": 104,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_07",
"title": "Laceration",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/07_-_Laceration.mp3",
"image": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/art.jpg",
"trackNumber": 7,
"totalTrackCount": 9,
"duration": 173,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_08",
"title": "Memories",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/08_-_Memories.mp3",
"image": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/art.jpg",
"trackNumber": 8,
"totalTrackCount": 9,
"duration": 213,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_09",
"title": "Outro",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/09_-_Outro.mp3",
"image": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/art.jpg",
"trackNumber": 9,
"totalTrackCount": 9,
"duration": 65,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "jazz_in_paris",
"title": "Jazz in Paris",
"album": "Jazz & Blues",
"artist": "Media Right Productions",
"genre": "Jazz & Blues",
"source": "https://storage.googleapis.com/automotive-media/Jazz_In_Paris.mp3",
"image": "https://storage.googleapis.com/automotive-media/album_art.jpg",
"trackNumber": 1,
"totalTrackCount": 6,
"duration": 103,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "the_messenger",
"title": "The Messenger",
"album": "Jazz & Blues",
"artist": "Silent Partner",
"genre": "Jazz & Blues",
"source": "https://storage.googleapis.com/automotive-media/The_Messenger.mp3",
"image": "https://storage.googleapis.com/automotive-media/album_art.jpg",
"trackNumber": 2,
"totalTrackCount": 6,
"duration": 132,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "talkies",
"title": "Talkies",
"album": "Jazz & Blues",
"artist": "Huma-Huma",
"genre": "Jazz & Blues",
"source": "https://storage.googleapis.com/automotive-media/Talkies.mp3",
"image": "https://storage.googleapis.com/automotive-media/album_art.jpg",
"trackNumber": 3,
"totalTrackCount": 6,
"duration": 162,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "on_the_bach",
"title": "On the Bach",
"album": "Cinematic",
"artist": "Jingle Punks",
"genre": "Cinematic",
"source": "https://storage.googleapis.com/automotive-media/On_the_Bach.mp3",
"image": "https://storage.googleapis.com/automotive-media/album_art.jpg",
"trackNumber": 4,
"totalTrackCount": 6,
"duration": 66,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "the_story_unfolds",
"title": "The Story Unfolds",
"album": "Cinematic",
"artist": "Jingle Punks",
"genre": "Cinematic",
"source": "https://storage.googleapis.com/automotive-media/The_Story_Unfolds.mp3",
"image": "https://storage.googleapis.com/automotive-media/album_art.jpg",
"trackNumber": 5,
"totalTrackCount": 6,
"duration": 91,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "drop_and_roll",
"title": "Drop and Roll",
"album": "Youtube Audio Library Rock",
"artist": "Silent Partner",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Drop_and_Roll.mp3",
"image": "https://storage.googleapis.com/automotive-media/album_art_2.jpg",
"trackNumber": 1,
"totalTrackCount": 7,
"duration": 121,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "motocross",
"title": "Motocross",
"album": "Youtube Audio Library Rock",
"artist": "Topher Mohr and Alex Elena",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Motocross.mp3",
"image": "https://storage.googleapis.com/automotive-media/album_art_2.jpg",
"trackNumber": 2,
"totalTrackCount": 7,
"duration": 182,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "wish_youd_come_true",
"title": "Wish You'd Come True",
"album": "Youtube Audio Library Rock",
"artist": "The 126ers",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Wish_You_d_Come_True.mp3",
"image": "https://storage.googleapis.com/automotive-media/album_art_2.jpg",
"trackNumber": 3,
"totalTrackCount": 7,
"duration": 169,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "awakening",
"title": "Awakening",
"album": "Youtube Audio Library Rock",
"artist": "Silent Partner",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Awakening.mp3",
"image": "https://storage.googleapis.com/automotive-media/album_art_2.jpg",
"trackNumber": 4,
"totalTrackCount": 7,
"duration": 220,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "home",
"title": "Home",
"album": "Youtube Audio Library Rock",
"artist": "Letter Box",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Home.mp3",
"image": "https://storage.googleapis.com/automotive-media/album_art_2.jpg",
"trackNumber": 5,
"totalTrackCount": 7,
"duration": 213,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "tell_the_angels",
"title": "Tell The Angels",
"album": "Youtube Audio Library Rock",
"artist": "Letter Box",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Tell_The_Angels.mp3",
"image": "https://storage.googleapis.com/automotive-media/album_art_2.jpg",
"trackNumber": 6,
"totalTrackCount": 7,
"duration": 208,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "hey_sailor",
"title": "Hey Sailor",
"album": "Youtube Audio Library Rock",
"artist": "Letter Box",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Hey_Sailor.mp3",
"image": "https://storage.googleapis.com/automotive-media/album_art_2.jpg",
"trackNumber": 7,
"totalTrackCount": 7,
"duration": 193,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "keys_to_the_kingdom",
"title": "Keys To The Kingdom",
"album": "Youtube Audio Library Rock 2",
"artist": "The 126ers",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Keys_To_The_Kingdom.mp3",
"image": "https://storage.googleapis.com/automotive-media/album_art_3.jpg",
"trackNumber": 1,
"totalTrackCount": 2,
"duration": 221,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "the_coldest_shoulder",
"title": "The Coldest Shoulder",
"album": "Youtube Audio Library Rock 2",
"artist": "The 126ers",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/The_Coldest_Shoulder.mp3",
"image": "https://storage.googleapis.com/automotive-media/album_art_3.jpg",
"trackNumber": 2,
"totalTrackCount": 2,
"duration": 160,
"site": "https://www.youtube.com/audiolibrary/music"
}
]
}

View File

@ -0,0 +1,185 @@
/*
* Copyright 2021 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 androidx.media3.demo.session
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ListView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.media3.common.MediaItem
import androidx.media3.session.LibraryResult
import androidx.media3.session.MediaBrowser
import androidx.media3.session.SessionToken
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.MoreExecutors
class MainActivity : AppCompatActivity() {
private lateinit var browserFuture: ListenableFuture<MediaBrowser>
private val browser: MediaBrowser?
get() = if (browserFuture.isDone) browserFuture.get() else null
private lateinit var mediaListAdapter: FolderMediaItemArrayAdapter
private lateinit var mediaListView: ListView
private val treePathStack: ArrayDeque<MediaItem> = ArrayDeque()
private var subItemMediaList: MutableList<MediaItem> = mutableListOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// setting up the layout
setContentView(R.layout.activity_main)
mediaListView = findViewById(R.id.media_list_view)
mediaListAdapter = FolderMediaItemArrayAdapter(this, R.layout.folder_items, subItemMediaList)
mediaListView.adapter = mediaListAdapter
// setting up on click. When user click on an item, try to display it
mediaListView.setOnItemClickListener { _, _, position, _ ->
run {
val selectedMediaItem = mediaListAdapter.getItem(position)!!
// TODO(b/192235359): handle the case where the item is playable but it is not a folder
if (selectedMediaItem.mediaMetadata.isPlayable == true) {
val intent = PlayableFolderActivity.createIntent(this, selectedMediaItem.mediaId)
startActivity(intent)
} else {
pushPathStack(selectedMediaItem)
}
}
}
findViewById<ExtendedFloatingActionButton>(R.id.open_player_floating_button)
.setOnClickListener {
// display the playing media items
val intent = Intent(this, PlayerActivity::class.java)
startActivity(intent)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
override fun onBackPressed() {
popPathStack()
}
override fun onStart() {
super.onStart()
initializeBrowser()
}
override fun onStop() {
releaseBrowser()
super.onStop()
}
private fun initializeBrowser() {
browserFuture =
MediaBrowser.Builder(
this,
SessionToken(this, ComponentName(this, PlaybackService::class.java))
)
.buildAsync()
browserFuture.addListener({ pushRoot() }, MoreExecutors.directExecutor())
}
private fun releaseBrowser() {
MediaBrowser.releaseFuture(browserFuture)
}
private fun displayChildrenList(mediaItem: MediaItem) {
val browser = this.browser ?: return
supportActionBar!!.setDisplayHomeAsUpEnabled(treePathStack.size != 1)
val childrenFuture =
browser.getChildren(
mediaItem.mediaId,
/* page= */ 0,
/* pageSize= */ Int.MAX_VALUE,
/* params= */ null
)
subItemMediaList.clear()
childrenFuture.addListener(
{
val result = childrenFuture.get()!!
val children = result.value!!
subItemMediaList.addAll(children)
mediaListAdapter.notifyDataSetChanged()
},
MoreExecutors.directExecutor()
)
}
private fun pushPathStack(mediaItem: MediaItem) {
treePathStack.addLast(mediaItem)
displayChildrenList(treePathStack.last())
}
private fun popPathStack() {
treePathStack.removeLast()
if (treePathStack.size == 0) {
finish()
return
}
displayChildrenList(treePathStack.last())
}
private fun pushRoot() {
// browser can be initialized many times
// only push root at the first initialization
if (!treePathStack.isEmpty()) {
return
}
val browser = this.browser ?: return
val rootFuture = browser.getLibraryRoot(/* params= */ null)
rootFuture.addListener(
{
val result: LibraryResult<MediaItem> = rootFuture.get()!!
val root: MediaItem = result.value!!
pushPathStack(root)
},
MoreExecutors.directExecutor()
)
}
private class FolderMediaItemArrayAdapter(
context: Context,
viewID: Int,
mediaItemList: List<MediaItem>
) : ArrayAdapter<MediaItem>(context, viewID, mediaItemList) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val mediaItem = getItem(position)!!
val returnConvertView =
convertView ?: LayoutInflater.from(context).inflate(R.layout.folder_items, parent, false)
returnConvertView.findViewById<TextView>(R.id.media_item).text = mediaItem.mediaMetadata.title
return returnConvertView
}
}
}

View File

@ -0,0 +1,249 @@
/*
* Copyright 2021 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 androidx.media3.demo.session
import android.content.res.AssetManager
import android.net.Uri
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import androidx.media3.common.MediaMetadata.FOLDER_TYPE_MIXED
import androidx.media3.common.MediaMetadata.FOLDER_TYPE_NONE
import androidx.media3.common.MediaMetadata.FOLDER_TYPE_PLAYLISTS
import androidx.media3.common.util.Util
import com.google.common.collect.ImmutableList
import org.json.JSONObject
/**
* A sample media catalog that represents media items as a tree.
*
* It fetched the data from {@code catalog.json}. The root's children are folders containing media
* items from the same album/artist/genre.
*
* Each app should have their own way of representing the tree. MediaItemTree is used for
* demonstration purpose only.
*/
object MediaItemTree {
private var treeNodes: MutableMap<String, MediaItemNode> = mutableMapOf()
private var titleMap: MutableMap<String, MediaItemNode> = mutableMapOf()
private var isInitialized = false
private const val ROOT_ID = "[rootID]"
private const val ALBUM_ID = "[albumID]"
private const val GENRE_ID = "[genreID]"
private const val ARTIST_ID = "[artistID]"
private const val ALBUM_PREFIX = "[album]"
private const val GENRE_PREFIX = "[genre]"
private const val ARTIST_PREFIX = "[artist]"
private const val ITEM_PREFIX = "[item]"
private class MediaItemNode(val item: MediaItem) {
private val children: MutableList<MediaItem> = ArrayList()
fun addChild(childID: String) {
this.children.add(treeNodes[childID]!!.item)
}
fun getChildren(): List<MediaItem> {
return ImmutableList.copyOf(children)
}
}
private fun buildMediaItem(
title: String,
mediaId: String,
isPlayable: Boolean,
@MediaMetadata.FolderType folderType: Int,
album: String? = null,
artist: String? = null,
genre: String? = null,
sourceUri: Uri? = null,
): MediaItem {
// TODO(b/194280027): add artwork
val metadata =
MediaMetadata.Builder()
.setAlbumTitle(album)
.setTitle(title)
.setArtist(artist)
.setGenre(genre)
.setFolderType(folderType)
.setIsPlayable(isPlayable)
.build()
return MediaItem.Builder()
.setMediaId(mediaId)
.setMediaMetadata(metadata)
.setUri(sourceUri)
.build()
}
private fun loadJSONFromAsset(assets: AssetManager): String {
val buffer = assets.open("catalog.json").use { Util.toByteArray(it) }
return String(buffer, Charsets.UTF_8)
}
fun initialize(assets: AssetManager) {
if (isInitialized) return
isInitialized = true
// create root and folders for album/artist/genre.
treeNodes[ROOT_ID] =
MediaItemNode(
buildMediaItem(
title = "Root Folder",
mediaId = ROOT_ID,
isPlayable = false,
folderType = FOLDER_TYPE_MIXED
)
)
treeNodes[ALBUM_ID] =
MediaItemNode(
buildMediaItem(
title = "Album Folder",
mediaId = ALBUM_ID,
isPlayable = false,
folderType = FOLDER_TYPE_MIXED
)
)
treeNodes[ARTIST_ID] =
MediaItemNode(
buildMediaItem(
title = "Artist Folder",
mediaId = ARTIST_ID,
isPlayable = false,
folderType = FOLDER_TYPE_MIXED
)
)
treeNodes[GENRE_ID] =
MediaItemNode(
buildMediaItem(
title = "Genre Folder",
mediaId = GENRE_ID,
isPlayable = false,
folderType = FOLDER_TYPE_MIXED
)
)
treeNodes[ROOT_ID]!!.addChild(ALBUM_ID)
treeNodes[ROOT_ID]!!.addChild(ARTIST_ID)
treeNodes[ROOT_ID]!!.addChild(GENRE_ID)
// Here, parse the json file in asset for media list.
// We use a file in asset for demo purpose
val jsonObject = JSONObject(loadJSONFromAsset(assets))
val mediaList = jsonObject.getJSONArray("media")
// create subfolder with same artist, album, etc.
for (i in 0 until mediaList.length()) {
addNodeToTree(mediaList.getJSONObject(i))
}
}
private fun addNodeToTree(mediaObject: JSONObject) {
val id = mediaObject.getString("id")
val album = mediaObject.getString("album")
val title = mediaObject.getString("title")
val artist = mediaObject.getString("artist")
val genre = mediaObject.getString("genre")
val sourceUri = Uri.parse(mediaObject.getString("source"))
// key of such items in tree
val idInTree = ITEM_PREFIX + id
val albumFolderIdInTree = ALBUM_PREFIX + album
val artistFolderIdInTree = ARTIST_PREFIX + artist
val genreFolderIdInTree = GENRE_PREFIX + genre
treeNodes[idInTree] =
MediaItemNode(
buildMediaItem(
title = title,
mediaId = idInTree,
isPlayable = true,
album = album,
artist = artist,
genre = genre,
sourceUri = sourceUri,
folderType = FOLDER_TYPE_NONE
)
)
titleMap[title.lowercase()] = treeNodes[idInTree]!!
if (!treeNodes.containsKey(albumFolderIdInTree)) {
treeNodes[albumFolderIdInTree] =
MediaItemNode(
buildMediaItem(
title = album,
mediaId = albumFolderIdInTree,
isPlayable = true,
folderType = FOLDER_TYPE_PLAYLISTS
)
)
treeNodes[ALBUM_ID]!!.addChild(albumFolderIdInTree)
}
treeNodes[albumFolderIdInTree]!!.addChild(idInTree)
// add into artist folder
if (!treeNodes.containsKey(artistFolderIdInTree)) {
treeNodes[artistFolderIdInTree] =
MediaItemNode(
buildMediaItem(
title = artist,
mediaId = artistFolderIdInTree,
isPlayable = true,
folderType = FOLDER_TYPE_PLAYLISTS
)
)
treeNodes[ARTIST_ID]!!.addChild(artistFolderIdInTree)
}
treeNodes[artistFolderIdInTree]!!.addChild(idInTree)
// add into genre folder
if (!treeNodes.containsKey(genreFolderIdInTree)) {
treeNodes[genreFolderIdInTree] =
MediaItemNode(
buildMediaItem(
title = genre,
mediaId = genreFolderIdInTree,
isPlayable = true,
folderType = FOLDER_TYPE_PLAYLISTS
)
)
treeNodes[GENRE_ID]!!.addChild(genreFolderIdInTree)
}
treeNodes[genreFolderIdInTree]!!.addChild(idInTree)
}
fun getItem(id: String): MediaItem? {
return treeNodes[id]?.item
}
fun getRootItem(): MediaItem {
return treeNodes[ROOT_ID]!!.item
}
fun getChildren(id: String): List<MediaItem>? {
return treeNodes[id]?.getChildren()
}
fun getRandomItem(): MediaItem {
var curRoot = getRootItem()
while (curRoot.mediaMetadata.folderType != FOLDER_TYPE_NONE) {
val children = getChildren(curRoot.mediaId)!!
curRoot = children.random()
}
return curRoot
}
fun getItemFromTitle(title: String): MediaItem? {
return titleMap[title]?.item
}
}

View File

@ -0,0 +1,192 @@
/*
* Copyright 2021 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 androidx.media3.demo.session
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.LinearLayout
import android.widget.ListView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.media3.common.MediaItem
import androidx.media3.session.MediaBrowser
import androidx.media3.session.SessionToken
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.MoreExecutors
class PlayableFolderActivity : AppCompatActivity() {
private lateinit var browserFuture: ListenableFuture<MediaBrowser>
private val browser: MediaBrowser?
get() = if (browserFuture.isDone) browserFuture.get() else null
private lateinit var mediaList: ListView
private lateinit var mediaListAdapter: PlayableMediaItemArrayAdapter
private val subItemMediaList: MutableList<MediaItem> = mutableListOf()
companion object {
private const val MEDIA_ITEM_ID_KEY = "MEDIA_ITEM_ID_KEY"
fun createIntent(context: Context, mediaItemID: String): Intent {
val intent = Intent(context, PlayableFolderActivity::class.java)
intent.putExtra(MEDIA_ITEM_ID_KEY, mediaItemID)
return intent
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_playable_folder)
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
mediaList = findViewById(R.id.media_list_view)
mediaListAdapter =
PlayableMediaItemArrayAdapter(this, R.layout.playable_items, subItemMediaList)
mediaList.adapter = mediaListAdapter
mediaList.setOnItemClickListener { _, _, position, _ ->
run {
val browser = this.browser ?: return@run
browser.setMediaItems(subItemMediaList)
browser.shuffleModeEnabled = false
browser.prepare()
browser.seekToDefaultPosition(/* windowIndex= */ position)
browser.play()
val intent = Intent(this, PlayerActivity::class.java)
startActivity(intent)
}
}
findViewById<Button>(R.id.shuffle_button).setOnClickListener {
val browser = this.browser ?: return@setOnClickListener
browser.setMediaItems(subItemMediaList)
browser.shuffleModeEnabled = true
browser.prepare()
browser.play()
val intent = Intent(this, PlayerActivity::class.java)
startActivity(intent)
}
findViewById<Button>(R.id.play_button).setOnClickListener {
val browser = this.browser ?: return@setOnClickListener
browser.setMediaItems(subItemMediaList)
browser.shuffleModeEnabled = false
browser.prepare()
browser.play()
val intent = Intent(this, PlayerActivity::class.java)
startActivity(intent)
}
findViewById<ExtendedFloatingActionButton>(R.id.open_player_floating_button)
.setOnClickListener {
// display the playing media items
val intent = Intent(this, PlayerActivity::class.java)
startActivity(intent)
}
}
override fun onStart() {
super.onStart()
initializeBrowser()
}
override fun onStop() {
super.onStop()
releaseBrowser()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
private fun initializeBrowser() {
browserFuture =
MediaBrowser.Builder(
this,
SessionToken(this, ComponentName(this, PlaybackService::class.java))
)
.buildAsync()
browserFuture.addListener({ displayFolder() }, MoreExecutors.directExecutor())
}
private fun releaseBrowser() {
MediaBrowser.releaseFuture(browserFuture)
}
private fun displayFolder() {
val browser = this.browser ?: return
val id: String = intent.getStringExtra(MEDIA_ITEM_ID_KEY)!!
val mediaItemFuture = browser.getItem(id)
val childrenFuture =
browser.getChildren(id, /* page= */ 0, /* pageSize= */ Int.MAX_VALUE, /* params= */ null)
mediaItemFuture.addListener(
{
val title: TextView = findViewById(R.id.folder_description)
val result = mediaItemFuture.get()!!
title.text = result.value!!.mediaMetadata.title
},
MoreExecutors.directExecutor()
)
childrenFuture.addListener(
{
val result = childrenFuture.get()!!
val children = result.value!!
subItemMediaList.clear()
subItemMediaList.addAll(children)
mediaListAdapter.notifyDataSetChanged()
},
MoreExecutors.directExecutor()
)
}
private inner class PlayableMediaItemArrayAdapter(
context: Context,
viewID: Int,
mediaItemList: List<MediaItem>
) : ArrayAdapter<MediaItem>(context, viewID, mediaItemList) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val mediaItem = getItem(position)!!
val returnConvertView =
convertView ?: LayoutInflater.from(context).inflate(R.layout.playable_items, parent, false)
returnConvertView.findViewById<TextView>(R.id.media_item).text = mediaItem.mediaMetadata.title
returnConvertView.findViewById<TextView>(R.id.add_button).setOnClickListener {
val browser = this@PlayableFolderActivity.browser ?: return@setOnClickListener
browser.addMediaItem(mediaItem)
Snackbar.make(
findViewById<LinearLayout>(R.id.linear_layout),
getString(R.string.added_media_item_format, mediaItem.mediaMetadata.title),
BaseTransientBottomBar.LENGTH_SHORT
)
.show()
}
return returnConvertView
}
}
}

View File

@ -0,0 +1,175 @@
/*
* Copyright 2021 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 androidx.media3.demo.session
import android.app.PendingIntent.FLAG_IMMUTABLE
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.app.TaskStackBuilder
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import androidx.media3.common.AudioAttributes
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.session.LibraryResult
import androidx.media3.session.MediaLibraryService
import androidx.media3.session.MediaSession
import androidx.media3.session.SessionResult
import com.google.common.collect.ImmutableList
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
class PlaybackService : MediaLibraryService() {
private lateinit var player: ExoPlayer
private lateinit var mediaLibrarySession: MediaLibrarySession
private val librarySessionCallback = CustomMediaLibrarySessionCallback()
companion object {
private const val SEARCH_QUERY_PREFIX_COMPAT = "androidx://media3-session/playFromSearch"
private const val SEARCH_QUERY_PREFIX = "androidx://media3-session/setMediaUri"
}
private inner class CustomMediaLibrarySessionCallback :
MediaLibrarySession.MediaLibrarySessionCallback {
override fun onGetLibraryRoot(
session: MediaLibrarySession,
browser: MediaSession.ControllerInfo,
params: LibraryParams?
): ListenableFuture<LibraryResult<MediaItem>> {
return Futures.immediateFuture(
LibraryResult.ofItem(MediaItemTree.getRootItem(), /* params= */ null)
)
}
override fun onGetItem(
session: MediaLibrarySession,
browser: MediaSession.ControllerInfo,
mediaId: String
): ListenableFuture<LibraryResult<MediaItem>> {
val item =
MediaItemTree.getItem(mediaId)
?: return Futures.immediateFuture(
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE)
)
return Futures.immediateFuture(LibraryResult.ofItem(item, /* params= */ null))
}
override fun onGetChildren(
session: MediaLibrarySession,
browser: MediaSession.ControllerInfo,
parentId: String,
page: Int,
pageSize: Int,
params: LibraryParams?
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
val children =
MediaItemTree.getChildren(parentId)
?: return Futures.immediateFuture(
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE)
)
return Futures.immediateFuture(LibraryResult.ofItemList(children, params))
}
private fun setMediaItemFromSearchQuery(query: String) {
// Only accept query with pattern "play [Title]" or "[Title]"
// Where [Title]: must be exactly matched
// If no media with exact name found, play a random media instead
lateinit var mediaTitle: String
if (query.lowercase().startsWith("play ")) {
mediaTitle = query.subSequence(5, query.length).toString()
} else {
mediaTitle = query
}
val item = MediaItemTree.getItemFromTitle(mediaTitle) ?: MediaItemTree.getRandomItem()
player.setMediaItem(item)
player.prepare()
}
override fun onSetMediaUri(
session: MediaSession,
controller: MediaSession.ControllerInfo,
uri: Uri,
extras: Bundle
): Int {
if (uri.toString().startsWith(SEARCH_QUERY_PREFIX) ||
uri.toString().startsWith(SEARCH_QUERY_PREFIX_COMPAT)
) {
var searchQuery =
uri.getQueryParameter("query") ?: return SessionResult.RESULT_ERROR_NOT_SUPPORTED
setMediaItemFromSearchQuery(searchQuery)
return SessionResult.RESULT_SUCCESS
} else {
return SessionResult.RESULT_ERROR_NOT_SUPPORTED
}
}
}
private class CustomMediaItemFiller : MediaSession.MediaItemFiller {
override fun fillInLocalConfiguration(
session: MediaSession,
controller: MediaSession.ControllerInfo,
mediaItem: MediaItem
): MediaItem {
return MediaItemTree.getItem(mediaItem.mediaId) ?: mediaItem
}
}
override fun onCreate() {
super.onCreate()
initializeSessionAndPlayer()
}
override fun onDestroy() {
player.release()
mediaLibrarySession.release()
super.onDestroy()
}
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession {
return mediaLibrarySession
}
private fun initializeSessionAndPlayer() {
player =
ExoPlayer.Builder(this)
.setAudioAttributes(AudioAttributes.DEFAULT, /* handleAudioFocus= */ true)
.build()
MediaItemTree.initialize(assets)
val parentScreenIntent = Intent(this, MainActivity::class.java)
val intent = Intent(this, PlayerActivity::class.java)
val pendingIntent =
TaskStackBuilder.create(this).run {
addNextIntent(parentScreenIntent)
addNextIntent(intent)
val immutableFlag = if (Build.VERSION.SDK_INT >= 23) FLAG_IMMUTABLE else 0
getPendingIntent(0, immutableFlag or FLAG_UPDATE_CURRENT)
}
mediaLibrarySession =
MediaLibrarySession.Builder(this, player, librarySessionCallback)
.setMediaItemFiller(CustomMediaItemFiller())
.setSessionActivity(pendingIntent)
.build()
}
}

View File

@ -0,0 +1,228 @@
/*
* Copyright 2021 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 androidx.media3.demo.session
import android.content.ComponentName
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.ImageView
import android.widget.ListView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import androidx.media3.common.Player
import androidx.media3.session.MediaController
import androidx.media3.session.SessionToken
import androidx.media3.ui.StyledPlayerView
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.MoreExecutors
class PlayerActivity : AppCompatActivity() {
private lateinit var controllerFuture: ListenableFuture<MediaController>
private val controller: MediaController?
get() = if (controllerFuture.isDone) controllerFuture.get() else null
private lateinit var playerView: StyledPlayerView
private lateinit var mediaList: ListView
private lateinit var mediaListAdapter: PlayingMediaItemArrayAdapter
private val subItemMediaList: MutableList<MediaItem> = mutableListOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_player)
playerView = findViewById(R.id.player_view)
mediaList = findViewById(R.id.current_playing_list)
mediaListAdapter = PlayingMediaItemArrayAdapter(this, R.layout.folder_items, subItemMediaList)
mediaList.adapter = mediaListAdapter
mediaList.setOnItemClickListener { _, _, position, _ ->
run {
val controller = this.controller ?: return@run
controller.seekToDefaultPosition(/* windowIndex= */ position)
mediaListAdapter.notifyDataSetChanged()
}
}
findViewById<ImageView>(R.id.shuffle_switch).setOnClickListener {
val controller = this.controller ?: return@setOnClickListener
controller.shuffleModeEnabled = !controller.shuffleModeEnabled
}
findViewById<ImageView>(R.id.repeat_switch).setOnClickListener {
val controller = this.controller ?: return@setOnClickListener
when (controller.repeatMode) {
Player.REPEAT_MODE_ALL -> controller.repeatMode = Player.REPEAT_MODE_OFF
Player.REPEAT_MODE_OFF -> controller.repeatMode = Player.REPEAT_MODE_ONE
Player.REPEAT_MODE_ONE -> controller.repeatMode = Player.REPEAT_MODE_ALL
}
}
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
}
override fun onStart() {
super.onStart()
initializeController()
}
override fun onResume() {
super.onResume()
playerView.onResume()
}
override fun onPause() {
super.onPause()
playerView.onPause()
}
override fun onStop() {
super.onStop()
playerView.player = null
releaseController()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
private fun initializeController() {
controllerFuture =
MediaController.Builder(
this,
SessionToken(this, ComponentName(this, PlaybackService::class.java))
)
.buildAsync()
controllerFuture.addListener({ setController() }, MoreExecutors.directExecutor())
}
private fun releaseController() {
MediaController.releaseFuture(controllerFuture)
}
private fun setController() {
val controller = this.controller ?: return
playerView.player = controller
updateCurrentPlaylistUI()
updateMediaMetadataUI(controller.mediaMetadata)
updateShuffleSwitchUI(controller.shuffleModeEnabled)
updateRepeatSwitchUI(controller.repeatMode)
controller.addListener(
object : Player.Listener {
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
updateMediaMetadataUI(mediaItem?.mediaMetadata ?: MediaMetadata.EMPTY)
}
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
updateShuffleSwitchUI(shuffleModeEnabled)
}
override fun onRepeatModeChanged(repeatMode: Int) {
updateRepeatSwitchUI(repeatMode)
}
}
)
}
private fun updateShuffleSwitchUI(shuffleModeEnabled: Boolean) {
val resId =
if (shuffleModeEnabled) R.drawable.exo_controls_shuffle_on
else R.drawable.exo_controls_shuffle_off
findViewById<ImageView>(R.id.shuffle_switch)
.setImageDrawable(ContextCompat.getDrawable(this, resId))
}
private fun updateRepeatSwitchUI(repeatMode: Int) {
var resId: Int
when (repeatMode) {
Player.REPEAT_MODE_OFF -> resId = R.drawable.exo_controls_repeat_off
Player.REPEAT_MODE_ONE -> resId = R.drawable.exo_controls_repeat_one
Player.REPEAT_MODE_ALL -> resId = R.drawable.exo_controls_repeat_all
else -> resId = R.drawable.exo_icon_repeat_off
}
findViewById<ImageView>(R.id.repeat_switch)
.setImageDrawable(ContextCompat.getDrawable(this, resId))
}
private fun updateMediaMetadataUI(mediaMetadata: MediaMetadata) {
val title: CharSequence = mediaMetadata.title ?: getString(R.string.no_item_prompt)
findViewById<TextView>(R.id.video_title).text = title
findViewById<TextView>(R.id.video_album).text = mediaMetadata.albumTitle
findViewById<TextView>(R.id.video_artist).text = mediaMetadata.artist
findViewById<TextView>(R.id.video_genre).text = mediaMetadata.genre
// Trick to update playlist UI
mediaListAdapter.notifyDataSetChanged()
}
private fun updateCurrentPlaylistUI() {
val controller = this.controller ?: return
subItemMediaList.clear()
for (i in 0 until controller.mediaItemCount) {
subItemMediaList.add(controller.getMediaItemAt(i))
}
mediaListAdapter.notifyDataSetChanged()
}
private inner class PlayingMediaItemArrayAdapter(
context: Context,
viewID: Int,
mediaItemList: List<MediaItem>
) : ArrayAdapter<MediaItem>(context, viewID, mediaItemList) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val mediaItem = getItem(position)!!
val returnConvertView =
convertView ?: LayoutInflater.from(context).inflate(R.layout.playlist_items, parent, false)
returnConvertView.findViewById<TextView>(R.id.media_item).text = mediaItem.mediaMetadata.title
if (position == controller?.currentWindowIndex) {
returnConvertView.setBackgroundColor(ContextCompat.getColor(context, R.color.white))
returnConvertView
.findViewById<TextView>(R.id.media_item)
.setTextColor(ContextCompat.getColor(context, R.color.black))
} else {
returnConvertView.setBackgroundColor(ContextCompat.getColor(context, R.color.black))
returnConvertView
.findViewById<TextView>(R.id.media_item)
.setTextColor(ContextCompat.getColor(context, R.color.white))
}
returnConvertView.findViewById<Button>(R.id.delete_button).setOnClickListener {
val controller = this@PlayerActivity.controller ?: return@setOnClickListener
controller.removeMediaItem(position)
updateCurrentPlaylistUI()
}
return returnConvertView
}
}
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2021 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.
-->
<shape
xmlns:android="http://schemas.android.com/apk/res/android">
<gradient android:startColor="@color/grey" android:endColor="@color/grey"/>
</shape>

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2021 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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ListView
android:id="@+id/media_list_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/open_player_floating_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:textColor="@color/white"
android:layout_margin="10dp"
app:icon="@drawable/exo_icon_play"
app:iconTint="@color/white"
app:backgroundTint="@color/purple_500"
android:text="@string/current_playlist_name"
android:contentDescription="@string/open_player_content_description" />
</FrameLayout>

View File

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2021 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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/linear_layout"
tools:context=".MainActivity">
<TextView
android:id="@+id/folder_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/current_playlist_name"
android:textAppearance="@style/TextAppearance.AppCompat.Display3" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="horizontal">
<Button
android:id="@+id/shuffle_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_margin="10dp"
android:textColor="@color/white"
android:drawableLeft="@drawable/exo_controls_shuffle_on"
android:text="@string/shuffle" />
<Button
android:id="@+id/play_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_margin="10dp"
android:drawableLeft="@drawable/exo_icon_play"
android:text="@string/play_button" />
</LinearLayout>
<ListView
android:id="@+id/media_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/open_player_floating_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:textColor="@color/white"
android:layout_margin="10dp"
app:icon="@drawable/exo_icon_play"
app:iconTint="@color/white"
app:backgroundTint="@color/purple_500"
android:text="@string/current_playlist_name"
android:contentDescription="@string/open_player_content_description" />
</FrameLayout>

View File

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
tools:context=".PlayerActivity">
<androidx.media3.ui.AspectRatioFrameLayout
android:layout_height="300dp"
android:layout_width="match_parent"
>
<androidx.media3.ui.StyledPlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:use_artwork="true" />
</androidx.media3.ui.AspectRatioFrameLayout>
<TextView
android:id="@+id/video_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:paddingBottom="10dp"
android:textColor="@color/white"
android:textSize="20sp" />
<TextView
android:id="@+id/video_album"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:paddingBottom="10dp"
android:textColor="@color/white"
android:textSize="20sp" />
<TextView
android:id="@+id/video_artist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:paddingLeft="10dp"
android:paddingBottom="10dp"/>
<TextView
android:id="@+id/video_genre"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:paddingLeft="10dp"
android:paddingBottom="10dp"
android:textSize="12sp" />
<LinearLayout
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/shuffle_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/shuffle"
android:src="@drawable/exo_controls_shuffle_off"
android:textColor="@color/white" />
<ImageView
android:layout_margin="@dimen/exo_icon_horizontal_margin"
android:id="@+id/repeat_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/exo_controls_repeat_off"
android:textColor="@color/white"
android:contentDescription="@string/repeat"
/>
</LinearLayout>
<ListView
android:id="@+id/current_playing_list"
android:layout_width="match_parent"
android:divider="@drawable/divider"
android:dividerHeight="1px"
android:layout_height="wrap_content"/>
</LinearLayout>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/media_item"
android:layout_width="0dp"
android:layout_weight="5"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:minHeight="50dp" />
</LinearLayout>

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/media_item"
android:layout_width="0dp"
android:layout_weight="5"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:minHeight="50dp" />
<Button
android:focusable="false"
android:focusableInTouchMode="false"
android:layout_width="50dp"
android:layout_height="match_parent"
android:id="@+id/add_button"
android:background="@android:drawable/ic_input_add"
/>
</LinearLayout>

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/media_item"
android:layout_width="0dp"
android:layout_weight="5"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingStart="10dp"
android:textColor="@color/white"
android:paddingEnd="10dp"
android:minHeight="50dp" />
<Button
android:focusable="false"
android:focusableInTouchMode="false"
android:layout_width="50dp"
android:layout_height="match_parent"
android:id="@+id/delete_button"
android:background="@android:drawable/ic_menu_close_clear_cancel"
/>
</LinearLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Media3Demo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">
?attr/colorPrimaryVariant
</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="grey">#FF999999</color>
</resources>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<string name="app_name">Media3 Session Demo</string>
<string name="current_playlist_name">Current playlist</string>
<string name="open_player_content_description">Click to view your play list</string>
<string name="added_media_item_format">Added %1$s to playlist</string>
<string name="shuffle">Shuffle</string>
<string name="repeat">Repeat</string>
<string name="play_button">Play</string>
<string name="no_item_prompt">
"! No media in the play list !\nPlease try to add more from browser"
</string>
</resources>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Media3Demo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">
?attr/colorPrimaryVariant
</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@ -45,11 +45,11 @@ android {
} }
dependencies { dependencies {
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'lib-exoplayer')
implementation project(modulePrefix + 'library-dash') implementation project(modulePrefix + 'lib-exoplayer-dash')
implementation project(modulePrefix + 'library-hls') implementation project(modulePrefix + 'lib-exoplayer-hls')
implementation project(modulePrefix + 'library-rtsp') implementation project(modulePrefix + 'lib-exoplayer-rtsp')
implementation project(modulePrefix + 'library-smoothstreaming') implementation project(modulePrefix + 'lib-exoplayer-smoothstreaming')
implementation project(modulePrefix + 'library-ui') implementation project(modulePrefix + 'lib-ui')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
} }

View File

@ -14,7 +14,7 @@
limitations under the License. limitations under the License.
--> -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.exoplayer2.surfacedemo"> package="androidx.media3.demo.surface">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
@ -33,7 +33,7 @@
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="com.google.android.exoplayer.surfacedemo.action.VIEW"/> <action android:name="androidx.media3.demo.surface.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="http"/> <data android:scheme="http"/>
<data android:scheme="https"/> <data android:scheme="https"/>

View File

@ -0,0 +1,280 @@
/*
* Copyright (C) 2019 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 androidx.media3.demo.surface;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.GridLayout;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.Player;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DefaultDataSource;
import androidx.media3.datasource.DefaultHttpDataSource;
import androidx.media3.datasource.HttpDataSource;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.dash.DashMediaSource;
import androidx.media3.exoplayer.drm.DefaultDrmSessionManager;
import androidx.media3.exoplayer.drm.DrmSessionManager;
import androidx.media3.exoplayer.drm.FrameworkMediaDrm;
import androidx.media3.exoplayer.drm.HttpMediaDrmCallback;
import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.source.ProgressiveMediaSource;
import androidx.media3.ui.PlayerControlView;
import java.util.UUID;
/** Activity that demonstrates use of {@link SurfaceControl} with ExoPlayer. */
public final class MainActivity extends Activity {
private static final String DEFAULT_MEDIA_URI =
"https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv";
private static final String SURFACE_CONTROL_NAME = "surfacedemo";
private static final String ACTION_VIEW = "androidx.media3.demo.surface.action.VIEW";
private static final String EXTENSION_EXTRA = "extension";
private static final String DRM_SCHEME_EXTRA = "drm_scheme";
private static final String DRM_LICENSE_URL_EXTRA = "drm_license_url";
private static final String OWNER_EXTRA = "owner";
private boolean isOwner;
@Nullable private PlayerControlView playerControlView;
@Nullable private SurfaceView fullScreenView;
@Nullable private SurfaceView nonFullScreenView;
@Nullable private SurfaceView currentOutputView;
@Nullable private static ExoPlayer player;
@Nullable private static SurfaceControl surfaceControl;
@Nullable private static Surface videoSurface;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
playerControlView = findViewById(R.id.player_control_view);
fullScreenView = findViewById(R.id.full_screen_view);
fullScreenView.setOnClickListener(
v -> {
setCurrentOutputView(nonFullScreenView);
Assertions.checkNotNull(fullScreenView).setVisibility(View.GONE);
});
attachSurfaceListener(fullScreenView);
isOwner = getIntent().getBooleanExtra(OWNER_EXTRA, /* defaultValue= */ true);
GridLayout gridLayout = findViewById(R.id.grid_layout);
for (int i = 0; i < 9; i++) {
View view;
if (i == 0) {
Button button = new Button(/* context= */ this);
view = button;
button.setText(getString(R.string.no_output_label));
button.setOnClickListener(v -> reparent(/* surfaceView= */ null));
} else if (i == 1) {
Button button = new Button(/* context= */ this);
view = button;
button.setText(getString(R.string.full_screen_label));
button.setOnClickListener(
v -> {
setCurrentOutputView(fullScreenView);
Assertions.checkNotNull(fullScreenView).setVisibility(View.VISIBLE);
});
} else if (i == 2) {
Button button = new Button(/* context= */ this);
view = button;
button.setText(getString(R.string.new_activity_label));
button.setOnClickListener(
v ->
startActivity(
new Intent(MainActivity.this, MainActivity.class)
.putExtra(OWNER_EXTRA, /* value= */ false)));
} else {
SurfaceView surfaceView = new SurfaceView(this);
view = surfaceView;
attachSurfaceListener(surfaceView);
surfaceView.setOnClickListener(
v -> {
setCurrentOutputView(surfaceView);
nonFullScreenView = surfaceView;
});
if (nonFullScreenView == null) {
nonFullScreenView = surfaceView;
}
}
gridLayout.addView(view);
GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams();
layoutParams.width = 0;
layoutParams.height = 0;
layoutParams.columnSpec = GridLayout.spec(i % 3, 1f);
layoutParams.rowSpec = GridLayout.spec(i / 3, 1f);
layoutParams.bottomMargin = 10;
layoutParams.leftMargin = 10;
layoutParams.topMargin = 10;
layoutParams.rightMargin = 10;
view.setLayoutParams(layoutParams);
}
}
@Override
public void onResume() {
super.onResume();
if (isOwner && player == null) {
initializePlayer();
}
setCurrentOutputView(nonFullScreenView);
PlayerControlView playerControlView = Assertions.checkNotNull(this.playerControlView);
playerControlView.setPlayer(player);
playerControlView.show();
}
@Override
public void onPause() {
super.onPause();
Assertions.checkNotNull(playerControlView).setPlayer(null);
}
@Override
public void onDestroy() {
super.onDestroy();
if (isOwner && isFinishing()) {
if (surfaceControl != null) {
surfaceControl.release();
surfaceControl = null;
}
if (videoSurface != null) {
videoSurface.release();
videoSurface = null;
}
if (player != null) {
player.release();
player = null;
}
}
}
private void initializePlayer() {
Intent intent = getIntent();
String action = intent.getAction();
Uri uri =
ACTION_VIEW.equals(action)
? Assertions.checkNotNull(intent.getData())
: Uri.parse(DEFAULT_MEDIA_URI);
DrmSessionManager drmSessionManager;
if (intent.hasExtra(DRM_SCHEME_EXTRA)) {
String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA));
String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));
HttpDataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory();
HttpMediaDrmCallback drmCallback =
new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory);
drmSessionManager =
new DefaultDrmSessionManager.Builder()
.setUuidAndExoMediaDrmProvider(drmSchemeUuid, FrameworkMediaDrm.DEFAULT_PROVIDER)
.build(drmCallback);
} else {
drmSessionManager = DrmSessionManager.DRM_UNSUPPORTED;
}
DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this);
MediaSource mediaSource;
@C.ContentType int type = Util.inferContentType(uri, intent.getStringExtra(EXTENSION_EXTRA));
if (type == C.TYPE_DASH) {
mediaSource =
new DashMediaSource.Factory(dataSourceFactory)
.setDrmSessionManager(drmSessionManager)
.createMediaSource(MediaItem.fromUri(uri));
} else if (type == C.TYPE_OTHER) {
mediaSource =
new ProgressiveMediaSource.Factory(dataSourceFactory)
.setDrmSessionManager(drmSessionManager)
.createMediaSource(MediaItem.fromUri(uri));
} else {
throw new IllegalStateException();
}
ExoPlayer player = new ExoPlayer.Builder(getApplicationContext()).build();
player.setMediaSource(mediaSource);
player.prepare();
player.play();
player.setRepeatMode(Player.REPEAT_MODE_ALL);
surfaceControl =
new SurfaceControl.Builder()
.setName(SURFACE_CONTROL_NAME)
.setBufferSize(/* width= */ 0, /* height= */ 0)
.build();
videoSurface = new Surface(surfaceControl);
player.setVideoSurface(videoSurface);
MainActivity.player = player;
}
private void setCurrentOutputView(@Nullable SurfaceView surfaceView) {
currentOutputView = surfaceView;
if (surfaceView != null && surfaceView.getHolder().getSurface() != null) {
reparent(surfaceView);
}
}
private void attachSurfaceListener(SurfaceView surfaceView) {
surfaceView
.getHolder()
.addCallback(
new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
if (surfaceView == currentOutputView) {
reparent(surfaceView);
}
}
@Override
public void surfaceChanged(
SurfaceHolder surfaceHolder, int format, int width, int height) {}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {}
});
}
private static void reparent(@Nullable SurfaceView surfaceView) {
SurfaceControl surfaceControl = Assertions.checkNotNull(MainActivity.surfaceControl);
if (surfaceView == null) {
new SurfaceControl.Transaction()
.reparent(surfaceControl, /* newParent= */ null)
.setBufferSize(surfaceControl, /* w= */ 0, /* h= */ 0)
.setVisibility(surfaceControl, /* visible= */ false)
.apply();
} else {
SurfaceControl newParentSurfaceControl = surfaceView.getSurfaceControl();
new SurfaceControl.Transaction()
.reparent(surfaceControl, newParentSurfaceControl)
.setBufferSize(surfaceControl, surfaceView.getWidth(), surfaceView.getHeight())
.setVisibility(surfaceControl, /* visible= */ true)
.apply();
}
}
}

View File

@ -0,0 +1,19 @@
/*
* Copyright (C) 2020 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.
*/
@NonNullApi
package androidx.media3.demo.surface;
import androidx.media3.common.util.NonNullApi;

View File

@ -1,280 +0,0 @@
/*
* Copyright (C) 2019 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.surfacedemo;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.GridLayout;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.dash.DashMediaSource;
import com.google.android.exoplayer2.ui.PlayerControlView;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.util.UUID;
/** Activity that demonstrates use of {@link SurfaceControl} with ExoPlayer. */
public final class MainActivity extends Activity {
private static final String DEFAULT_MEDIA_URI =
"https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv";
private static final String SURFACE_CONTROL_NAME = "surfacedemo";
private static final String ACTION_VIEW = "com.google.android.exoplayer.surfacedemo.action.VIEW";
private static final String EXTENSION_EXTRA = "extension";
private static final String DRM_SCHEME_EXTRA = "drm_scheme";
private static final String DRM_LICENSE_URL_EXTRA = "drm_license_url";
private static final String OWNER_EXTRA = "owner";
private boolean isOwner;
@Nullable private PlayerControlView playerControlView;
@Nullable private SurfaceView fullScreenView;
@Nullable private SurfaceView nonFullScreenView;
@Nullable private SurfaceView currentOutputView;
@Nullable private static ExoPlayer player;
@Nullable private static SurfaceControl surfaceControl;
@Nullable private static Surface videoSurface;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
playerControlView = findViewById(R.id.player_control_view);
fullScreenView = findViewById(R.id.full_screen_view);
fullScreenView.setOnClickListener(
v -> {
setCurrentOutputView(nonFullScreenView);
Assertions.checkNotNull(fullScreenView).setVisibility(View.GONE);
});
attachSurfaceListener(fullScreenView);
isOwner = getIntent().getBooleanExtra(OWNER_EXTRA, /* defaultValue= */ true);
GridLayout gridLayout = findViewById(R.id.grid_layout);
for (int i = 0; i < 9; i++) {
View view;
if (i == 0) {
Button button = new Button(/* context= */ this);
view = button;
button.setText(getString(R.string.no_output_label));
button.setOnClickListener(v -> reparent(/* surfaceView= */ null));
} else if (i == 1) {
Button button = new Button(/* context= */ this);
view = button;
button.setText(getString(R.string.full_screen_label));
button.setOnClickListener(
v -> {
setCurrentOutputView(fullScreenView);
Assertions.checkNotNull(fullScreenView).setVisibility(View.VISIBLE);
});
} else if (i == 2) {
Button button = new Button(/* context= */ this);
view = button;
button.setText(getString(R.string.new_activity_label));
button.setOnClickListener(
v ->
startActivity(
new Intent(MainActivity.this, MainActivity.class)
.putExtra(OWNER_EXTRA, /* value= */ false)));
} else {
SurfaceView surfaceView = new SurfaceView(this);
view = surfaceView;
attachSurfaceListener(surfaceView);
surfaceView.setOnClickListener(
v -> {
setCurrentOutputView(surfaceView);
nonFullScreenView = surfaceView;
});
if (nonFullScreenView == null) {
nonFullScreenView = surfaceView;
}
}
gridLayout.addView(view);
GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams();
layoutParams.width = 0;
layoutParams.height = 0;
layoutParams.columnSpec = GridLayout.spec(i % 3, 1f);
layoutParams.rowSpec = GridLayout.spec(i / 3, 1f);
layoutParams.bottomMargin = 10;
layoutParams.leftMargin = 10;
layoutParams.topMargin = 10;
layoutParams.rightMargin = 10;
view.setLayoutParams(layoutParams);
}
}
@Override
public void onResume() {
super.onResume();
if (isOwner && player == null) {
initializePlayer();
}
setCurrentOutputView(nonFullScreenView);
PlayerControlView playerControlView = Assertions.checkNotNull(this.playerControlView);
playerControlView.setPlayer(player);
playerControlView.show();
}
@Override
public void onPause() {
super.onPause();
Assertions.checkNotNull(playerControlView).setPlayer(null);
}
@Override
public void onDestroy() {
super.onDestroy();
if (isOwner && isFinishing()) {
if (surfaceControl != null) {
surfaceControl.release();
surfaceControl = null;
}
if (videoSurface != null) {
videoSurface.release();
videoSurface = null;
}
if (player != null) {
player.release();
player = null;
}
}
}
private void initializePlayer() {
Intent intent = getIntent();
String action = intent.getAction();
Uri uri =
ACTION_VIEW.equals(action)
? Assertions.checkNotNull(intent.getData())
: Uri.parse(DEFAULT_MEDIA_URI);
DrmSessionManager drmSessionManager;
if (intent.hasExtra(DRM_SCHEME_EXTRA)) {
String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA));
String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));
HttpDataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory();
HttpMediaDrmCallback drmCallback =
new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory);
drmSessionManager =
new DefaultDrmSessionManager.Builder()
.setUuidAndExoMediaDrmProvider(drmSchemeUuid, FrameworkMediaDrm.DEFAULT_PROVIDER)
.build(drmCallback);
} else {
drmSessionManager = DrmSessionManager.DRM_UNSUPPORTED;
}
DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this);
MediaSource mediaSource;
@C.ContentType int type = Util.inferContentType(uri, intent.getStringExtra(EXTENSION_EXTRA));
if (type == C.TYPE_DASH) {
mediaSource =
new DashMediaSource.Factory(dataSourceFactory)
.setDrmSessionManager(drmSessionManager)
.createMediaSource(MediaItem.fromUri(uri));
} else if (type == C.TYPE_OTHER) {
mediaSource =
new ProgressiveMediaSource.Factory(dataSourceFactory)
.setDrmSessionManager(drmSessionManager)
.createMediaSource(MediaItem.fromUri(uri));
} else {
throw new IllegalStateException();
}
ExoPlayer player = new ExoPlayer.Builder(getApplicationContext()).build();
player.setMediaSource(mediaSource);
player.prepare();
player.play();
player.setRepeatMode(Player.REPEAT_MODE_ALL);
surfaceControl =
new SurfaceControl.Builder()
.setName(SURFACE_CONTROL_NAME)
.setBufferSize(/* width= */ 0, /* height= */ 0)
.build();
videoSurface = new Surface(surfaceControl);
player.setVideoSurface(videoSurface);
MainActivity.player = player;
}
private void setCurrentOutputView(@Nullable SurfaceView surfaceView) {
currentOutputView = surfaceView;
if (surfaceView != null && surfaceView.getHolder().getSurface() != null) {
reparent(surfaceView);
}
}
private void attachSurfaceListener(SurfaceView surfaceView) {
surfaceView
.getHolder()
.addCallback(
new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
if (surfaceView == currentOutputView) {
reparent(surfaceView);
}
}
@Override
public void surfaceChanged(
SurfaceHolder surfaceHolder, int format, int width, int height) {}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {}
});
}
private static void reparent(@Nullable SurfaceView surfaceView) {
SurfaceControl surfaceControl = Assertions.checkNotNull(MainActivity.surfaceControl);
if (surfaceView == null) {
new SurfaceControl.Transaction()
.reparent(surfaceControl, /* newParent= */ null)
.setBufferSize(surfaceControl, /* w= */ 0, /* h= */ 0)
.setVisibility(surfaceControl, /* visible= */ false)
.apply();
} else {
SurfaceControl newParentSurfaceControl = surfaceView.getSurfaceControl();
new SurfaceControl.Transaction()
.reparent(surfaceControl, newParentSurfaceControl)
.setBufferSize(surfaceControl, surfaceView.getWidth(), surfaceView.getHeight())
.setVisibility(surfaceControl, /* visible= */ true)
.apply();
}
}
}

View File

@ -1,19 +0,0 @@
/*
* Copyright (C) 2020 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.
*/
@NonNullApi
package com.google.android.exoplayer2.surfacedemo;
import com.google.android.exoplayer2.util.NonNullApi;

View File

@ -32,7 +32,7 @@
android:layout_weight="1" android:layout_weight="1"
android:columnCount="3"/> android:columnCount="3"/>
<com.google.android.exoplayer2.ui.PlayerControlView <androidx.media3.ui.PlayerControlView
android:id="@+id/player_control_view" android:id="@+id/player_control_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -1,9 +0,0 @@
# Mercurial's .hgignore files can only be used in the root directory.
# You can still apply these rules by adding
# include:path/to/this/directory/.hgignore to the top-level .hgignore file.
# Ensure same syntax as in .gitignore can be used
syntax:glob
_site
Gemfile.lock

View File

@ -1,3 +0,0 @@
---
layout: 404
---

View File

@ -1 +0,0 @@
exoplayer.dev

View File

@ -1,6 +0,0 @@
source "https://rubygems.org"
gem "github-pages", group: :jekyll_plugins
gem "tzinfo-data"
gem "wdm", "~> 0.1.0" if Gem.win_platform?

View File

@ -1,23 +0,0 @@
Jekyll TeXt Theme
MIT License
Copyright (c) 2017 Tian Qi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,12 +0,0 @@
# ExoPlayer website
The [ExoPlayer website](https://exoplayer.dev/) is hosted on
GitHub Pages, and is statically generated using Jekyll.
* GitHub provides a guide describing how to setup a GitHub Pages site using
Jekyll
[here](https://help.github.com/articles/using-jekyll-as-a-static-site-generator-with-github-pages/).
* GitHub provides a guide describing how to test changes to the site locally
[here](https://help.github.com/articles/setting-up-your-github-pages-site-locally-with-jekyll/).
Once your machine is setup, you can build and run a local instance of the
site using `./run_locally.sh` from the root directory.

View File

@ -1,95 +0,0 @@
## => Site Settings
##############################
text_skin: default # "default" (default), "dark", "forest", "ocean", "chocolate", "orange"
highlight_theme: default # "default" (default), "tomorrow", "tomorrow-night", "tomorrow-night-eighties", "tomorrow-night-blue", "tomorrow-night-bright"
url: https://exoplayer.dev
baseurl:
title: ExoPlayer
description: An application level media player for Android
## => Link roots
##############################
release_v2: https://github.com/google/ExoPlayer/tree/release-v2
exo_sdk: /doc/reference/com/google/android/exoplayer2
android_sdk: https://developer.android.com/reference
google_sdk: https://developers.google.com
## => GitHub Repository (if the site is hosted by GitHub)
##############################
repository: google/ExoPlayer
repository_tree: gh-pages
## => Author and Social
##############################
author:
type : "organization"
name : "ExoPlayer"
github : "google/ExoPlayer"
medium : "google-exoplayer"
## => Paths
##############################
paths:
root : # title link url, "/" (default)
home : # home layout url, "/" (default)
archive : # "/archive.html" (default)
rss : # "/feed.xml" (default)
## => Post
##############################
excerpt_separator: <!--more-->
## => Analytics
##############################
analytics:
provider: google
google:
tracking_id : UA-68257324-1
anonymize_ip: true
## => Search
##############################
search:
provider: google # "default" (default), false, "google", "custom"
## Google Custom Search Engine
google:
custom_search_engine_id: 009764199895742571316:nuhckqry2_e
## => Build
##############################
markdown : kramdown
highlighter : rouge
permalink : date
exclude:
- Gemfile
- README.md
- vendor
defaults:
- scope:
path: ""
values:
footer: true
show_date: false
layout: article
sidebar:
nav: en
## => Plugins
##############################
plugins:
- jekyll-feed
- jekyll-paginate
- jekyll-sitemap
- jekyll-redirect-from
- jemoji

View File

@ -1,111 +0,0 @@
## => English
########################
en: &EN
SUBSCRIBE : "Subscribe"
READMORE : "Read more"
SEARCH : "Search"
CANCEL : "Cancel"
VIEWS : "views"
LAST_UPDATED : "Last updated"
PREVIOUS : "PREVIOUS"
NEXT : "NEXT"
ARTICLE_DATE_FORMAT : "%b %d, %Y"
ARTICLE_LIST_DATE_FORMAT: "%b %d"
STATISTICS : "[POST_COUNT] post articles, [PAGE_COUNT] pages."
LICENSE_ANNOUNCE : "This work is licensed under a [LICENSE] license."
POST_ON_GITHUB : "Edit on Github"
FOLLOW_ME : "Follow me on [NAME]."
FOLLOW_US : "Follow us on [NAME]."
EMAIL_ME : "Send me Email."
EMAIL_US : "Send us Email."
COPYRIGHT_DATES : "2019"
en-GB:
<<: *EN
en-US:
<<: *EN
en-CA:
<<: *EN
en-AU:
<<: *EN
## => Simplified Chinese
########################
zh-Hans: &ZH_HANS
SUBSCRIBE : "订阅"
READMORE : "阅读更多"
SEARCH : "搜索"
CANCEL : "取消"
VIEWS : "阅读"
LAST_UPDATED : "更新于"
PREVIOUS : "上篇"
NEXT : "下篇"
ARTICLE_DATE_FORMAT : "%Y年 %m月%d日"
ARTICLE_LIST_DATE_FORMAT: "%m月%d日"
STATISTICS : "共计 [POST_COUNT] 篇文章,[PAGE_COUNT] 页。"
LICENSE_ANNOUNCE : "本文遵守 [LICENSE] 许可协议。"
POST_ON_GITHUB : "在 Github 上修改"
FOLLOW_ME : "在 [NAME] 上关注我。"
FOLLOW_US : "在 [NAME] 上关注我们。"
EMAIL_ME : "给我发邮件。"
EMAIL_US : "给我们发邮件。"
COPYRIGHT_DATES : "2019"
zh:
<<: *ZH_HANS
zh-CN:
<<: *ZH_HANS
zh-SG:
<<: *ZH_HANS
## => Traditional Chinese
########################
zh-Hant: &ZH_HANT
SUBSCRIBE : "訂閱"
READMORE : "閱讀更多"
SEARCH : "搜索"
CANCEL : "取消"
VIEWS : "閱讀"
LAST_UPDATED : "更新於"
PREVIOUS : "上篇"
NEXT : "下篇"
ARTICLE_DATE_FORMAT : "%Y年 %m月%d日"
ARTICLE_LIST_DATE_FORMAT: "%m月%d日"
STATISTICS : "共計 [POST_COUNT] 篇文章,[PAGE_COUNT] 頁。"
LICENSE_ANNOUNCE : "本文遵守 [LICENSE] 許可協議。"
POST_ON_GITHUB : "在 Github 上修改"
FOLLOW_ME : "在 [NAME] 上關注我。"
FOLLOW_US : "在 [NAME] 上關注我們。"
EMAIL_ME : "給我發郵件。"
EMAIL_US : "給我們發郵件。"
COPYRIGHT_DATES : "2019"
zh-TW:
<<: *ZH_HANT
zh-HK:
<<: *ZH_HANT
## => Korean
########################
ko: &KO
SUBSCRIBE : "구독하기"
READMORE : "더보기"
SEARCH : "검색"
CANCEL : "취소"
VIEWS : "조회"
LAST_UPDATED : "마지막 수정"
PREVIOUS : "이전"
NEXT : "다음"
ARTICLE_DATE_FORMAT : "%Y년 %m월 %d일"
ARTICLE_LIST_DATE_FORMAT: "%m월 %d일"
STATISTICS : "전체 글 [POST_COUNT]개, [PAGE_COUNT] 페이지"
LICENSE_ANNOUNCE : "이 글의 저작권은 [LICENSE] 라이센스를 따릅니다."
POST_ON_GITHUB : "Github에서 확인하기"
FOLLOW_ME : "[NAME]에서 팔로우하기"
FOLLOW_US : "[NAME]에서 팔로우하기"
EMAIL_ME : "이메일 보내기"
EMAIL_US : "이메일 보내기"
COPYRIGHT_DATES : "2019"
ko-KR:
<<: *KO

View File

@ -1,83 +0,0 @@
header:
- title: Javadoc
url: doc/reference/
- title: GitHub
url: https://github.com/google/ExoPlayer
- title: Blog
url: https://medium.com/google-exoplayer
en:
- title:
children:
- title: Home
url: index.html
- title: Pros and cons
url: pros-and-cons.html
- title: Demo application
url: demo-application.html
- title: Supported formats
url: supported-formats.html
- title: Supported devices
url: supported-devices.html
- title: Glossary
url: glossary.html
- title: Getting started
children:
- title: Hello world
url: hello-world.html
- title: Player events
url: listening-to-player-events.html
- title: Playlists
url: playlists.html
- title: Media items
url: media-items.html
- title: Media sources
url: media-sources.html
- title: Track selection
url: track-selection.html
- title: UI components
url: ui-components.html
- title: Downloading media
url: downloading-media.html
- title: Ad insertion
url: ad-insertion.html
- title: Retrieving metadata
url: retrieving-metadata.html
- title: Live streaming
url: live-streaming.html
- title: Network stacks
url: network-stacks.html
- title: Debug logging
url: debug-logging.html
- title: Analytics
url: analytics.html
- title: Media types
children:
- title: DASH
url: dash.html
- title: HLS
url: hls.html
- title: SmoothStreaming
url: smoothstreaming.html
- title: Progressive
url: progressive.html
- title: RTSP
url: rtsp.html
- title: Advanced topics
children:
- title: Digital rights management
url: drm.html
- title: Troubleshooting
url: troubleshooting.html
- title: Customization
url: customization.html
- title: Transforming media
url: transforming-media.html
- title: Battery consumption
url: battery-consumption.html
- title: APK shrinking
url: shrinking.html
- title: OEM testing
url: oems.html
- title: Design documents
url: design-documents.html

View File

@ -1,64 +0,0 @@
default:
text_skin: default
highlight_theme: default
lang: en
paths:
root: /
home: /
archive: /archive.html
rss: /feed.xml
mathjax: false
mathjax_autoNumber: false
mermaid: false
chart: false
toc:
selectors: 'h1,h2,h3'
sources: bootcdn
page:
mode: normal
type: webpage
article_header:
align: left
theme: light
articles:
show_cover: true
show_excerpt: false
show_readmore: false
show_info: false
show_title: true
show_edit_on_github: false
show_date: true
show_tags: true
show_author_profile: false
show_subscribe: false
full_width: false
sharing: false
comment: true
license: false
pageview: false
search: default
sources:
bootcdn:
font_awesome: 'https://use.fontawesome.com/releases/v5.0.13/css/all.css'
jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js'
leancloud_js_sdk: '//cdn1.lncld.net/static/js/3.4.1/av-min.js'
chart: 'https://cdn.bootcss.com/Chart.js/2.7.2/Chart.bundle.min.js'
gitalk:
js: 'https://cdn.bootcss.com/gitalk/1.2.2/gitalk.min.js'
css: 'https://cdn.bootcss.com/gitalk/1.2.2/gitalk.min.css'
valine: 'https://unpkg.com/valine/dist/Valine.min.js' # bootcdn not available
mathjax: 'https://cdn.bootcss.com/mathjax/2.7.4/MathJax.js?config=TeX-MML-AM_CHTML'
mermaid: 'https://cdn.bootcss.com/mermaid/8.0.0-rc.8/mermaid.min.js'
unpkg:
font_awesome: 'https://use.fontawesome.com/releases/v5.0.13/css/all.css'
jquery: 'https://unpkg.com/jquery@3.3.1/dist/jquery.min.js'
leancloud_js_sdk: '//cdn1.lncld.net/static/js/3.4.1/av-min.js'
chart: 'https://unpkg.com/chart.js@2.7.2/dist/Chart.min.js'
gitalk:
js: 'https://unpkg.com/gitalk@1.2.2/dist/gitalk.min.js'
css: 'https://unpkg.com/gitalk@1.2.2/dist/gitalk.css'
valine: 'https//unpkg.com/valine/dist/Valine.min.js'
mathjax: 'https://unpkg.com/mathjax@2.7.4/unpacked/MathJax.js?config=TeX-MML-AM_CHTML'
mermaid: 'https://unpkg.com/mermaid@8.0.0-rc.8/dist/mermaid.min.js'

View File

@ -1,3 +0,0 @@
<!-- start custom analytics snippet -->
<!-- end custom analytics snippet -->

View File

@ -1,14 +0,0 @@
{%- if site.analytics.google.tracking_id -%}
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id={{ site.analytics.google.tracking_id }}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '{{ site.analytics.google.tracking_id }}');
{% if site.analytics.google.anonymize_ip == true %}
gtag('config', '{{ site.analytics.google.tracking_id }}', { 'anonymize_ip': true });
{% endif %}
</script>
{%- endif -%}

View File

@ -1,7 +0,0 @@
{%- if jekyll.environment != 'development' -%}
{%- if site.analytics.provider == 'google' -%}
{%- include analytics-providers/google.html -%}
{%- elsif site.analytics.provider == 'custom' -%}
{%- include analytics-providers/custom.html -%}
{%- endif -%}
{%- endif -%}

View File

@ -1,55 +0,0 @@
{%- include snippets/assign.html
target=site.data.variables.default.page.show_author_profile
source0=layout.show_author_profile source1=page.show_author_profile -%}
{%- assign _show_author_profile = __return -%}
{%- include snippets/assign.html
target=site.data.variables.default.page.show_subscribe
source0=layout.show_subscribe source1=page.show_subscribe -%}
{%- assign _show_subscribe = __return -%}
{%- include snippets/assign.html
target=site.data.variables.default.page.license
source0=layout.license source1=page.license -%}
{%- assign _license = __return -%}
<footer class="article__footer">
{%- if page.modify_date -%}
{%- include snippets/get-locale-string.html key='ARTICLE_DATE_FORMAT' -%}
{%- assign _locale_date_format = __return -%}
{%- include snippets/get-locale-string.html key='LAST_UPDATED' -%}
{%- assign _locale_last_update = __return -%}
<span>{{ _locale_last_update }}
<time itemprop="dateModified" datetime="{{ page.modify_date | date_to_xmlschema }}">{{ page.modify_date | date: _locale_date_format }}</time>
</span>
{%- elsif page.date -%}
<meta itemprop="dateModified" content="{{ page.date | date_to_xmlschema }}">
{%- endif -%}
{%- include article/footer/custom.html -%}
{%- if _show_author_profile -%}
{%- if page.author -%}
{%- assign _author = site.data.authors[page.author] -%}
{%- else -%}
{%- assign _author = site.author -%}
{%- endif -%}
{%- include article/footer/author-profile.html author=_author -%}
{%- endif -%}
{%- if _show_subscribe -%}
<div class="article__subscribe">{%- include article/footer/subscribe.html -%}</div>
{%- endif -%}
{%- if _license != false -%}
{%- assign _data_license = site.data.licenses-%}
{%- if site.license -%}
{%- assign _license_data = _data_license[site.license] -%}
{%- endif -%}
{%- if _license != true -%}
{%- assign _license_data = _data_license[_license] -%}
{%- endif -%}
<div class="article__license">{%- include article/footer/license.html license=_license_data -%}</div>
{%- endif -%}
</footer>

View File

@ -1,49 +0,0 @@
{%- include snippets/get-article-title.html article=include.article-%}
{%- assign _article_title = __return -%}
{%- if include.html != false -%}
{%- include snippets/assign.html
target=site.data.variables.default.page.show_title
source0=layout.show_title source1=include.article.show_title -%}
{%- assign _show_title = __return -%}
{%- include snippets/assign.html
target=site.data.variables.default.page.show_edit_on_github
source0=layout.show_edit_on_github source1=include.article.show_edit_on_github -%}
{%- assign _show_edit_on_github = __return -%}
{%- if _show_title or _show_edit_on_github -%}
<div class="article__header">
{%- if _show_title -%}
<header><h1>{{ _article_title }}</h1></header>
{%- endif -%}
{%- if _show_edit_on_github -%}
{%- if site.repository and site.repository_tree -%}
{%- include snippets/is_collection.html page=include.article -%}
{%- assign _is_article_collection = __return -%}
{%- include snippets/get-locale-string.html key='POST_ON_GITHUB' -%}
{%- assign _locale_post_on_github = __return -%}
{%- if _is_article_collection -%}
{%- include snippets/prepend-path.html path=include.article.path prepend_path=site.collections_dir -%}
{%- assign _article_path = __return -%}
{%- else -%}
{%- assign _article_path = include.article.path -%}
{%- endif -%}
{%- assign _github_path = site.repository | append: '/tree/' | append: site.repository_tree | append: '/' | append: _article_path | replace:'//','/' -%}
<span class="split-space">&nbsp;</span>
<a class="edit-on-github"
title="{{ _locale_post_on_github }}"
href="https://github.com/{{ _github_path }}">
<i class="far fa-edit"></i></a>
{%- endif -%}
{%- endif -%}
</div>
{%- else -%}
<header style="display:none;"><h1>{{ _article_title }}</h1></header>
{%- endif -%}
{%- endif -%}
{%- if include.semantic != false -%}
<meta itemprop="headline" content="{{ _article_title }}">
{%- endif -%}

View File

@ -1,96 +0,0 @@
{%- assign _author = site.data.authors[include.article.author] | default: site.author -%}
{%- if include.html != false -%}
{%- include snippets/assign.html
target=site.data.variables.default.page.show_date
source0=layout.show_date source1=include.article.show_date -%}
{%- assign _show_date = __return -%}
{%- if _show_date and include.article.date -%}
{%- assign _show_date = true -%}
{%- else -%}
{%- assign _show_date = false -%}
{%- endif -%}
{%- include snippets/assign.html
target=site.data.variables.default.page.show_tags
source0=layout.show_tags source1=include.article.show_tags -%}
{%- assign _show_tags = __return -%}
{%- if _show_tags and include.article.tags[0] -%}
{%- assign _show_tags = true -%}
{%- else -%}
{%- assign _show_tags = false -%}
{%- endif -%}
{%- assign _show_author = include.article.author -%}
{%- include snippets/assign.html target=site.data.variables.default.page.pageview
source0=layout.pageview source1=page.pageview -%}
{%- assign _pageview = __return -%}
{%- if _pageview or include.show_pageview -%}
{%- assign _pageview = true -%}
{%- else -%}
{%- assign _pageview = false -%}
{%- endif -%}
{%- assign _paths_archive = site.paths.archive | default: site.data.variables.default.paths.archive -%}
{%- if _show_tags or _show_author or _show_date or _pageview -%}
<div class="article__info clearfix">
{%- if _show_tags -%}
<ul class="left-col menu">
{%- assign _tag_path = _paths_archive | append: '?tag=' -%}
{%- include snippets/prepend-baseurl.html path=_tag_path -%}
{%- for _tag in include.article.tags -%}
{%- assign _tag_path = __return -%}
{%- assign _tag_encode = _tag | strip | url_encode } -%}
<li>
<a class="button button--secondary button--pill button--sm"
href="{{ _tag_path | append: _tag_encode | replace: '//', '/' }}">{{ _tag }}</a>
</li>
{%- endfor -%}
</ul>
{%- endif -%}
{%- if _show_author or _show_date or _pageview -%}
<ul class="right-col menu">
{%- if _show_author -%}
<li><i class="fas fa-user"></i> <span>{{ _author.name }}</span></li>
{%- endif -%}
{%- if _show_date -%}
<li>
{%- include snippets/get-locale-string.html key='ARTICLE_DATE_FORMAT' -%}
<i class="far fa-calendar-alt"></i> <span>{{ include.article.date | date: __return }}</span>
</li>
{%- endif -%}
{%- if _pageview -%}
{%- if site.pageview.provider -%}
{%- include snippets/get-locale-string.html key='VIEWS' -%}
{%- assign _locale_views = __return -%}
<li><i class="far fa-eye"></i> <span class="js-pageview" data-page-key="{{ include.article.key }}">0</span> {{ _locale_views }}</li>
{%- endif -%}
{%- endif -%}
</ul>
{%- endif -%}
</div>
{%- endif -%}
{%- endif -%}
{%- if include.semantic != false -%}
{%- if _author -%}
<meta itemprop="author" content="{{ _author.name }}"/>
{%- endif -%}
{%- if include.article.date -%}
<meta itemprop="datePublished" content="{{ include.article.date | date_to_xmlschema }}">
{%- endif -%}
{%- if include.article.tags[0] -%}
{%- assign _keywords = include.article.tags | join: ',' %}
<meta itemprop="keywords" content="{{ _keywords }}">
{%- endif -%}
{%- endif -%}

View File

@ -1,144 +0,0 @@
{%- assign _excerpt_truncate = include.excerpt_truncate | default: 350 -%}
{%- assign _excerpt_type = include.excerpt_type -%}
{%- include snippets/get-locale-string.html key='READMORE' -%}
{%- assign _locale_readmore = __return -%}
{%- assign _sorted_list = include.articles -%}
{%- if include.group_by == 'year' -%}
{%- assign _sorted_list = _sorted_list | sort: 'date' -%}
{%- endif -%}
{%- if include.reverse -%}
{%- assign _sorted_list = _sorted_list | reverse -%}
{%- endif -%}
{%- if include.type == 'item' -%}
<div class="article-list items items--divided">
{%- elsif include.type == 'brief' -%}
<div class="article-list items">
{%- elsif include.type == 'grid' -%}
{%- if include.size == 'sm' -%}
<div class="article-list grid gird--sm grid--p-3">
{%- else -%}
<div class="article-list grid grid--p-3">
{%- endif -%}
{%- endif -%}
{%- for _article in _sorted_list -%}
{%- include snippets/prepend-baseurl.html path=_article.url -%}
{%- assign _article_url = __return -%}
{%- if _article.cover -%}
{%- include snippets/get-nav-url.html path=_article.cover -%}
{%- assign _article_cover = __return -%}
{%- endif -%}
{%- if include.type == 'item' -%}
{%- if include.article_type == 'BlogPosting' -%}
<article class="item" itemscope itemtype="http://schema.org/BlogPosting">
{%- else -%}
<article class="item">
{%- endif -%}
{%- if _article.cover and include.show_cover-%}
{%- include snippets/get-nav-url.html path=_article.cover -%}
{%- assign _article_cover = __return -%}
<div class="item__image">
{%- if include.cover_size == 'lg' -%}
<img class="image image--lg" src="{{ _article_cover }}" />
{%- elsif include.cover_size == 'sm' -%}
<img class="image image--sm" src="{{ _article_cover }}" />
{%- else -%}
<img class="image" src="{{ _article_cover }}" />
{%- endif -%}
</div>
{%- endif -%}
<div class="item__content">
<header><a href="{{ _article_url }}"><h2 itemprop="headline" class="item__header">{{ _article.title }}</h2></a></header>
<div class="item__description">
{%- if _article.excerpt and include.show_excerpt -%}
<div class="article__content" itemprop="description articleBody">
{%- if _excerpt_type == 'html' -%}
{{ _article.excerpt }}
{%- else -%}
{{ _article.excerpt | strip_html | strip | truncate: _excerpt_truncate }}
{%- endif -%}
</div>
{%- endif -%}
{%- if include.show_readmore -%}
<p><a href="{{ _article_url }}">{{ _locale_readmore }}</a></p>
{%- endif -%}
</div>
{%- if include.show_info -%}
{%- include snippets/assign.html target=site.data.variables.default.page.pageview
source0=_article.pageview -%}
{%- assign _show_pageview = __return -%}
{%- include article-info.html article=_article show_pageview=_show_pageview -%}
{%- endif -%}
</div>
</article>
{%- elsif include.type == 'brief' -%}
{%- assign _tags = '' -%}
{%- for _tag in _article.tags -%}
{%- assign _tag_encode = _tag | strip | url_encode -%}
{%- if forloop.last -%}
{%- assign _tags = _tags | append: _tag_encode -%}
{%- else -%}
{%- assign _tags = _tags | append: _tag_encode | append: ',' -%}
{%- endif -%}
{%- endfor -%}
{%- if include.group_by == 'year' -%}
{%- assign _currentdate = _article.date | date: '%Y' -%}
{%- if _currentdate != _date -%}
{%- unless forloop.first -%}</ul></section>{%- endunless -%}
<section><h2 class="article-list__group-header">{{ _currentdate }}</h2><ul class="items">
{%- assign _date = _currentdate -%}
{%- endif -%}
{%- endif -%}
{%- include snippets/get-locale-string.html key='ARTICLE_LIST_DATE_FORMAT' -%}
<li class="item" itemscope itemtype="http://schema.org/BlogPosting" data-tags="{{ _tags }}">
<div class="item__content">
{%- if include.show_info -%}
<span class="item__meta">{{ _article.date | date: __return }}</span>
{%- endif -%}
<a itemprop="headline" class="item__header" href="{{ _article_url }}">{{ _article.title }}</a></div>
</li>
{%- elsif include.type == 'grid' -%}
{%- if include.size == 'sm' -%}
<div class="cell cell--12 cell--md-4 cell--lg-3">
<div class="card card--flat">
{%- if _article.cover -%}
<div class="card__image">
<img class="image" src="{{ _article_cover }}" />
<div class="overlay overlay--bottom">
<header>
<a href="{{ _article_url }}"><h2 class="card__header">{{ _article.title }}</h2></a>
</header>
</div>
</div>
{%- endif -%}
</div>
</div>
{%- else -%}
<div class="cell cell--12 cell--md-6 cell--lg-4">
<div class="card card--flat">
{%- if _article.cover -%}
<div class="card__image"><img src="{{ _article_cover }}" /></div>
{%- endif -%}
<div class="card__content">
<header>
<a href="{{ _article_url }}"><h2 class="card__header">{{ _article.title }}</h2></a>
</header>
</div>
</div>
</div>
{%- endif -%}
{%- endif -%}
{%- endfor -%}
</div>

View File

@ -1,54 +0,0 @@
{%- if page.sidebar.nav -%}
{%- assign _sidebar_nav = site.data.navigation[page.sidebar.nav] -%}
{%- if _sidebar_nav -%}
{%- assign _find_cur = false -%}
{%- assign _find_next = false -%}
{%- for _item in _sidebar_nav -%}
{%- if _find_next -%} {%- break -%} {%- endif -%}
{%- if _item.children -%}
{%- for _child in _item.children -%}
{%- include snippets/get-nav-url.html path=_child.url -%}
{%- assign _nav_url = __return -%}
{%- include snippets/get-nav-url.html path=page.url -%}
{%- assign _page_url = __return -%}
{%- if _nav_url == _page_url -%}
{%- assign _find_cur = true -%}
{%- elsif _find_cur and _find_next != true -%}
{%- assign _find_next = true -%}
{%- assign _next = _child -%}
{%- break -%}
{%- else -%}
{%- assign _previous = _child -%}
{%- endif -%}
{%- endfor -%}
{%- endif -%}
{%- endfor -%}
{%- endif -%}
{%- else -%}
{%- assign _previous = page.previous -%}
{%- assign _next = page.next -%}
{%- endif -%}
{%- if _next or _previous -%}
<div class="article__section-navigator clearfix">
{%- if _previous -%}
{%- include snippets/prepend-baseurl.html path=_previous.url -%}
{%- assign _href = __return -%}
{%- include snippets/get-locale-string.html key='PREVIOUS' -%}
{%- assign _locale_previous = __return -%}
<div class="previous"><span>{{ _locale_previous }}</span><a href="{{ _href }}">{{ _previous.title }}</a></div>
{%- endif -%}
{%- if _next -%}
{%- include snippets/prepend-baseurl.html path=_next.url -%}
{%- assign _href = __return -%}
{%- include snippets/get-locale-string.html key='NEXT' -%}
{%- assign _locale_next = __return -%}
<div class="next"><span>{{ _locale_next }}</span><a href="{{ _href }}">{{ _next.title }}</a></div>
{%- endif -%}
</div>
{%- endif -%}

View File

@ -1,47 +0,0 @@
{%- assign _author = include.author -%}
{%- if _author.type == 'organization' -%}
{%- assign _author_itemtype = 'http://schema.org/Organization' -%}
{%- else -%}
{%- assign _author_itemtype = 'http://schema.org/Person' -%}
{%- endif -%}
<div itemscope itemtype="{{ _author_itemtype }}" class="author-profile card card--flat item">
{%- if _author.avatar -%}
{%- if _author.url -%}
<a href="{{ _author.url }}" class="item__image">
{%- endif -%}
{%- include snippets/get-nav-url.html path=_author.avatar -%}
{%- assign _author_avatar = __return -%}
<img class="author-profile__avatar" itemprop="image" src="{{ _author_avatar }}" />
{%- if _author.url -%}
</a>
{%- endif -%}
{%- endif -%}
<div class="item__content">
{%- if _author.name -%}
<meta itemprop="name" content="{{ _author.name }}">
<p class="author-profile__name">
{%- if _author.url -%}
<meta itemprop="url" content="{{ _author.url }}">
<a href="{{ _author.url }}">
{%- endif -%}
{{ _author.name }}
{%- if _author.url -%}
</a>
{%- endif -%}
</p>
{%- endif -%}
{%- if _author.bio -%}
<p itemprop="description">{{ _author.bio }}</p>
{%- endif -%}
<div class="author-profile__links">
{%- include author-links.html author=_author -%}
</div>
</div>
</div>

View File

@ -1,3 +0,0 @@
<!-- start custom article footer snippet -->
<!-- end custom article footer snippet -->

View File

@ -1,14 +0,0 @@
{%- include snippets/get-locale-string.html key='LICENSE_ANNOUNCE' -%}
{%- assign _license_announce = __return -%}
{%- if include.license -%}
{%-assign _license_name = '<a itemprop="license" rel="license" href="[URL]">[NAME]</a>' | replace: "[URL]", include.license.url | replace: "[NAME]", include.license.name -%}
<div class="license">
<p>{{ _license_announce | replace: "[LICENSE]", _license_name }}
<a rel="license" href="{{ include.license.url }}">
<img alt="{{ include.license.name }}" src="{{ include.license.image }}" />
</a>
</p>
</div>
{%- endif -%}

View File

@ -1,6 +0,0 @@
{%- assign _paths_rss = site.paths.rss | default: site.data.variables.default.paths.rss -%}
{%- include snippets/get-nav-url.html path=_paths_rss -%}
{%- assign _paths_rss = __return -%}
{%- include snippets/get-locale-string.html key='SUBSCRIBE' -%}
{%- assign _locale_nav_subscribe = __return -%}
<div class="subscribe"><i class="fas fa-rss"></i> <a type="application/rss+xml" href="{{ _paths_rss }}">{{ _locale_nav_subscribe }}</a></div>

View File

@ -1,3 +0,0 @@
<!-- start custom article top snippet -->
<!-- end custom article top snippet -->

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