mirror of
https://github.com/samsonjs/media.git
synced 2026-04-08 11:45:51 +00:00
Merge branch 'main' into rtp_opus
This commit is contained in:
commit
f0d7d96309
313 changed files with 12053 additions and 4852 deletions
42
.github/ISSUE_TEMPLATE/bug.md
vendored
42
.github/ISSUE_TEMPLATE/bug.md
vendored
|
|
@ -1,42 +0,0 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Issue template for a bug report.
|
||||
title: ''
|
||||
labels: bug, needs triage
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
We can only process bug reports that are actionable. Unclear bug reports or
|
||||
reports with insufficient information may not get attention.
|
||||
|
||||
Before filing a bug:
|
||||
-------------------------
|
||||
|
||||
- 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
|
||||
|
||||
When reporting a bug:
|
||||
-------------------------
|
||||
|
||||
Describe how the issue can be reproduced, ideally using one of the demo apps
|
||||
or a small sample app that you’re able to share as source code on GitHub. To
|
||||
increase the chance of your issue getting attention, please also include:
|
||||
|
||||
- Clear reproduction steps including observed and expected behavior
|
||||
- Output of running "adb bugreport" in the console shortly after encountering
|
||||
the issue
|
||||
- URI to test content for reproduction
|
||||
- For protected content:
|
||||
- DRM scheme and license server URL
|
||||
- Authentication HTTP headers
|
||||
|
||||
- AndroidX Media version number
|
||||
- Android version
|
||||
- Android device
|
||||
|
||||
If there's something you don't want to post publicly, please submit the issue,
|
||||
then email the link/bug report to dev.exoplayer@gmail.com using a subject in the
|
||||
format "Issue #1234", where #1234 is your issue number (we don't reply to
|
||||
emails).
|
||||
99
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
99
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
name: Bug Report
|
||||
description: Report a bug in the Media3 library
|
||||
labels: ["bug", "needs triage"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
We can only process bug reports that are actionable. Unclear bug reports or reports with insufficient information may not get attention.
|
||||
|
||||
Before filing a bug:
|
||||
-------------------------
|
||||
|
||||
- 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
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Media3 Version
|
||||
description: What version of Media3 are you using?
|
||||
options:
|
||||
- 1.0.0-alpha03
|
||||
- 1.0.0-alpha02
|
||||
- 1.0.0-alpha01
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Devices that reproduce the issue
|
||||
placeholder: |
|
||||
Example:
|
||||
* Pixel 4 running Android 12
|
||||
* Samsung S21 running Android 11
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Devices that do not reproduce the issue
|
||||
placeholder: |
|
||||
Example:
|
||||
* Pixel 3 running Android Pie
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Reproducible in the demo app?
|
||||
description: Please try and reproduce the issue in the [Media3 demo app](https://github.com/androidx/media/tree/release/demos/main).
|
||||
options:
|
||||
- "Yes"
|
||||
- "No"
|
||||
- Not tested
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Reproduction steps
|
||||
description: Clear and complete steps we can use to reproduce the problem
|
||||
placeholder: |
|
||||
Example:
|
||||
1. Play the attached media in the demo app
|
||||
2. Seek forward 10s
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected result
|
||||
placeholder: |
|
||||
Example:
|
||||
The media plays successfully
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Actual result
|
||||
placeholder: |
|
||||
Example:
|
||||
Playback crashes with the following stack trace:
|
||||
...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Media
|
||||
description: |
|
||||
Media we can use to reproduce the problem. Either:
|
||||
* Attach a file here
|
||||
* Include a media URL
|
||||
* Refer to a piece of media from the demo app (e.g. `Misc > Dizzy (MP4)`)
|
||||
* If you don't want to post media publicly please email the info to dev.exoplayer@gmail.com with subject 'Issue #\<issuenumber\>' after filing this issue, and note that you will do this here.
|
||||
* If you are certain the issue does not depend on the media being played, enter "Not applicable" here.
|
||||
|
||||
For DRM-protected media please also include the scheme and license server URL.
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Bug Report
|
||||
description: |
|
||||
After filing this issue please run `adb bugreport` shortly after reproducing the problem (ideally in the [demo app](https://github.com/androidx/media/tree/release/demos/main)) to capture a zip file, and email this to dev.exoplayer@gmail.com with subject 'Issue #\<issuenumber\>'.
|
||||
|
||||
**Note:** Logcat output is **not** the same as a full bug report, and is often missing information that's useful for diagnosing issues. Please ensure you're sending a full bug report zip file.
|
||||
options:
|
||||
- label: You will email the zip file produced by `adb bugreport` to dev.exoplayer@gmail.com after filing this issue.
|
||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
blank_issues_enabled: false
|
||||
|
|
@ -94,15 +94,13 @@ to prevent build errors.
|
|||
|
||||
Cloning the repository and depending on the modules locally is required when
|
||||
using some libraries. It's also a suitable approach if you want to make local
|
||||
changes, or if you want to use the main branch.
|
||||
changes, or if you want to use the `main` branch.
|
||||
|
||||
First, clone the repository into a local directory and checkout the desired
|
||||
branch:
|
||||
First, clone the repository into a local directory:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/androidx/media.git
|
||||
cd media
|
||||
git checkout main
|
||||
```
|
||||
|
||||
Next, add the following to your project's `settings.gradle` file, replacing
|
||||
|
|
@ -129,7 +127,7 @@ implementation project(':media-lib-ui')
|
|||
Development work happens on the `main` branch. Pull requests should normally be
|
||||
made to this branch.
|
||||
|
||||
We plan to add a release branch soon.
|
||||
The `release` branch holds the most recent stable release.
|
||||
|
||||
#### Using Android Studio
|
||||
|
||||
|
|
|
|||
393
RELEASENOTES.md
393
RELEASENOTES.md
|
|
@ -1,5 +1,398 @@
|
|||
# Release notes
|
||||
|
||||
### Unreleased changes
|
||||
|
||||
* Core library:
|
||||
* Enable support for Android platform diagnostics via
|
||||
`MediaMetricsManager`. ExoPlayer will forward playback events and
|
||||
performance data to the platform, which helps to provide system
|
||||
performance and debugging information on the device. This data may also
|
||||
be collected by Google
|
||||
[if sharing usage and diagnostics data is enabled](https://support.google.com/accounts/answer/6078260)
|
||||
by the user of the device. Apps can opt-out of contributing to platform
|
||||
diagnostics for ExoPlayer with
|
||||
`ExoPlayer.Builder.setUsePlatformDiagnostics(false)`.
|
||||
* Track selection:
|
||||
* Flatten `TrackSelectionOverrides` class into `TrackSelectionParameters`,
|
||||
and promote `TrackSelectionOverride` to a top level class.
|
||||
* Video:
|
||||
* Rename `DummySurface` to `PlaceHolderSurface`.
|
||||
* Audio:
|
||||
* Use LG AC3 audio decoder advertising non-standard MIME type.
|
||||
* Ad playback / IMA:
|
||||
* Decrease ad polling rate from every 100ms to every 200ms, to line up with
|
||||
Media Rating Council (MRC) recommendations.
|
||||
* Extractors:
|
||||
* Matroska: Parse `DiscardPadding` for Opus tracks.
|
||||
* Parse bitrates from `esds` boxes.
|
||||
* MP4: Parse initialization data from AV1 tracks.
|
||||
* UI:
|
||||
* Fix delivery of events to `OnClickListener`s set on `PlayerView` and
|
||||
`LegacyPlayerView`, in the case that `useController=false`
|
||||
([#9605](https://github.com/google/ExoPlayer/issues/9605)). Also fix
|
||||
delivery of events to `OnLongClickListener` for all view configurations.
|
||||
* Fix incorrectly treating a sequence of touch events that exit the bounds
|
||||
of `PlayerView` and `LegacyPlayerView` before `ACTION_UP` as a click
|
||||
([#9861](https://github.com/google/ExoPlayer/issues/9861)).
|
||||
* Fix `PlayerView` accessibility issue where it was not possible to
|
||||
tapping would toggle playback rather than hiding the controls
|
||||
([#8627](https://github.com/google/ExoPlayer/issues/8627)).
|
||||
* Rewrite `TrackSelectionView` and `TrackSelectionDialogBuilder` to work
|
||||
with the `Player` interface rather than `ExoPlayer`. This allows the
|
||||
views to be used with other `Player` implementations, and removes the
|
||||
dependency from the UI module to the ExoPlayer module. This is a
|
||||
breaking change.
|
||||
* Don't show forced text tracks in the `PlayerView` track selector, and
|
||||
keep a suitable forced text track selected if "None" is selected
|
||||
([#9432](https://github.com/google/ExoPlayer/issues/9432)).
|
||||
* HLS:
|
||||
* Fallback to chunkful preparation if the playlist CODECS attribute
|
||||
does not contain the audio codec
|
||||
([#10065](https://github.com/google/ExoPlayer/issues/10065)).
|
||||
* RTSP:
|
||||
* Add RTP reader for MPEG4
|
||||
([#35](https://github.com/androidx/media/pull/35))
|
||||
* Add RTP reader for HEVC
|
||||
([#36](https://github.com/androidx/media/pull/36)).
|
||||
* Add RTP reader for AMR. Currently only mono-channel, non-interleaved
|
||||
AMR streams are supported. Compound AMR RTP payload is not supported.
|
||||
([#46](https://github.com/androidx/media/pull/46))
|
||||
* Add RTP reader for VP8
|
||||
([#47](https://github.com/androidx/media/pull/47)).
|
||||
* Add RTP reader for WAV
|
||||
([#56](https://github.com/androidx/media/pull/56)).
|
||||
* Data sources:
|
||||
* Rename `DummyDataSource` to `PlaceHolderDataSource`.
|
||||
* Remove deprecated symbols:
|
||||
* Remove `Player.Listener.onTracksChanged`. Use
|
||||
`Player.Listener.onTracksInfoChanged` instead.
|
||||
* Remove `Player.getCurrentTrackGroups` and
|
||||
`Player.getCurrentTrackSelections`. Use `Player.getCurrentTracksInfo`
|
||||
instead. You can also continue to use `ExoPlayer.getCurrentTrackGroups`
|
||||
and `ExoPlayer.getCurrentTrackSelections`, although these methods remain
|
||||
deprecated.
|
||||
* Remove `DownloadHelper`
|
||||
`DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_VIEWPORT` and
|
||||
`DEFAULT_TRACK_SELECTOR_PARAMETERS` constants. Use
|
||||
`getDefaultTrackSelectorParameters(Context)` instead when possible, and
|
||||
`DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT` otherwise.
|
||||
* FFmpeg extension:
|
||||
* Update CMake version to `3.21.0+` to avoid a CMake bug causing
|
||||
AndroidStudio's gradle sync to fail
|
||||
([#9933](https://github.com/google/ExoPlayer/issues/9933)).
|
||||
|
||||
### 1.0.0-alpha03 (2022-03-14)
|
||||
|
||||
This release corresponds to the
|
||||
[ExoPlayer 2.17.1 release](https://github.com/google/ExoPlayer/releases/tag/r2.17.1).
|
||||
|
||||
* Audio:
|
||||
* Fix error checking audio capabilities for Dolby Atmos (E-AC3-JOC) in
|
||||
HLS.
|
||||
* Extractors:
|
||||
* FMP4: Fix issue where emsg sample metadata could be output in the wrong
|
||||
order for streams containing both v0 and v1 emsg atoms
|
||||
([#9996](https://github.com/google/ExoPlayer/issues/9996)).
|
||||
* Text:
|
||||
* Fix the interaction of `SingleSampleMediaSource.Factory.setTrackId` and
|
||||
`MediaItem.SubtitleConfiguration.Builder.setId` to prioritise the
|
||||
`SubtitleConfiguration` field and fall back to the `Factory` value if
|
||||
it's not set
|
||||
([#10016](https://github.com/google/ExoPlayer/issues/10016)).
|
||||
* Ad playback:
|
||||
* Fix audio underruns between ad periods in live HLS SSAI streams.
|
||||
|
||||
### 1.0.0-alpha02 (2022-03-02)
|
||||
|
||||
This release corresponds to the
|
||||
[ExoPlayer 2.17.0 release](https://github.com/google/ExoPlayer/releases/tag/r2.17.0).
|
||||
|
||||
* Core Library:
|
||||
* Add protected method `DefaultRenderersFactory.getCodecAdapterFactory()`
|
||||
so that subclasses of `DefaultRenderersFactory` that override
|
||||
`buildVideoRenderers()` or `buildAudioRenderers()` can access the codec
|
||||
adapter factory and pass it to `MediaCodecRenderer` instances they
|
||||
create.
|
||||
* Propagate ICY header fields `name` and `genre` to
|
||||
`MediaMetadata.station` and `MediaMetadata.genre` respectively so that
|
||||
they reach the app via `Player.Listener.onMediaMetadataChanged()`
|
||||
([#9677](https://github.com/google/ExoPlayer/issues/9677)).
|
||||
* Remove null keys from `DefaultHttpDataSource#getResponseHeaders`.
|
||||
* Sleep and retry when creating a `MediaCodec` instance fails. This works
|
||||
around an issue that occurs on some devices when switching a surface
|
||||
from a secure codec to another codec
|
||||
([#8696](https://github.com/google/ExoPlayer/issues/8696)).
|
||||
* Add `MediaCodecAdapter.getMetrics()` to allow users obtain metrics data
|
||||
from `MediaCodec`
|
||||
([#9766](https://github.com/google/ExoPlayer/issues/9766)).
|
||||
* Fix Maven dependency resolution
|
||||
([#8353](https://github.com/google/ExoPlayer/issues/8353)).
|
||||
* Disable automatic speed adjustment for live streams that neither have
|
||||
low-latency features nor a user request setting the speed
|
||||
([#9329](https://github.com/google/ExoPlayer/issues/9329)).
|
||||
* Rename `DecoderCounters#inputBufferCount` to `queuedInputBufferCount`.
|
||||
* Make `SimpleExoPlayer.renderers` private. Renderers can be accessed via
|
||||
`ExoPlayer.getRenderer`.
|
||||
* Updated some `AnalyticsListener.EventFlags` constant values to match
|
||||
values in `Player.EventFlags`.
|
||||
* Split `AnalyticsCollector` into an interface and default implementation
|
||||
to allow it to be stripped by R8 if an app doesn't need it.
|
||||
* Track selection:
|
||||
* Support preferred video role flags in track selection
|
||||
([#9402](https://github.com/google/ExoPlayer/issues/9402)).
|
||||
* Update video track selection logic to take preferred MIME types and role
|
||||
flags into account when selecting multiple video tracks for adaptation
|
||||
([#9519](https://github.com/google/ExoPlayer/issues/9519)).
|
||||
* Update video and audio track selection logic to only choose formats for
|
||||
adaptive selections that have the same level of decoder and hardware
|
||||
support ([#9565](https://github.com/google/ExoPlayer/issues/9565)).
|
||||
* Update video track selection logic to prefer more efficient codecs if
|
||||
multiple codecs are supported by primary, hardware-accelerated decoders
|
||||
([#4835](https://github.com/google/ExoPlayer/issues/4835)).
|
||||
* Prefer audio content preferences (for example, the "default" audio track
|
||||
or a track matching the system locale language) over technical track
|
||||
selection constraints (for example, preferred MIME type, or maximum
|
||||
channel count).
|
||||
* Fix track selection issue where overriding one track group did not
|
||||
disable other track groups of the same type
|
||||
([#9675](https://github.com/google/ExoPlayer/issues/9675)).
|
||||
* Fix track selection issue where a mixture of non-empty and empty track
|
||||
overrides is not applied correctly
|
||||
([#9649](https://github.com/google/ExoPlayer/issues/9649)).
|
||||
* Prohibit duplicate `TrackGroup`s in a `TrackGroupArray`. `TrackGroup`s
|
||||
can always be made distinguishable by setting an `id` in the
|
||||
`TrackGroup` constructor. This fixes a crash when resuming playback
|
||||
after backgrounding the app with an active track override
|
||||
([#9718](https://github.com/google/ExoPlayer/issues/9718)).
|
||||
* Amend logic in `AdaptiveTrackSelection` to allow a quality increase
|
||||
under sufficient network bandwidth even if playback is very close to the
|
||||
live edge ([#9784](https://github.com/google/ExoPlayer/issues/9784)).
|
||||
* Video:
|
||||
* Fix decoder fallback logic for Dolby Vision to use a compatible
|
||||
H264/H265 decoder if needed.
|
||||
* Audio:
|
||||
* Fix decoder fallback logic for Dolby Atmos (E-AC3-JOC) to use a
|
||||
compatible E-AC3 decoder if needed.
|
||||
* Change `AudioCapabilities` APIs to require passing explicitly
|
||||
`AudioCapabilities.DEFAULT_AUDIO_CAPABILITIES` instead of `null`.
|
||||
* Allow customization of the `AudioTrack` buffer size calculation by
|
||||
injecting an `AudioTrackBufferSizeProvider` to `DefaultAudioSink`
|
||||
([#8891](https://github.com/google/ExoPlayer/issues/8891)).
|
||||
* Retry `AudioTrack` creation if the requested buffer size was > 1MB
|
||||
([#9712](https://github.com/google/ExoPlayer/issues/9712)).
|
||||
* Extractors:
|
||||
* WAV: Add support for RF64 streams
|
||||
([#9543](https://github.com/google/ExoPlayer/issues/9543)).
|
||||
* Fix incorrect parsing of H.265 SPS NAL units
|
||||
([#9719](https://github.com/google/ExoPlayer/issues/9719)).
|
||||
* Parse Vorbis Comments (including `METADATA_BLOCK_PICTURE`) in Ogg Opus
|
||||
and Ogg Vorbis files.
|
||||
* Text:
|
||||
* Add a `MediaItem.SubtitleConfiguration.id` field which is propagated to
|
||||
the `Format.id` field of the subtitle track created from the
|
||||
configuration
|
||||
([#9673](https://github.com/google/ExoPlayer/issues/9673)).
|
||||
* Add basic support for WebVTT subtitles in Matroska containers
|
||||
([#9886](https://github.com/google/ExoPlayer/issues/9886)).
|
||||
* Prevent `Cea708Decoder` from reading more than the declared size of a
|
||||
service block.
|
||||
* DRM:
|
||||
* Remove `playbackLooper` from `DrmSessionManager.(pre)acquireSession`.
|
||||
When a `DrmSessionManager` is used by an app in a custom `MediaSource`,
|
||||
the `playbackLooper` needs to be passed to `DrmSessionManager.setPlayer`
|
||||
instead.
|
||||
* Ad playback / IMA:
|
||||
* Add support for
|
||||
[IMA Dynamic Ad Insertion (DAI)](https://support.google.com/admanager/answer/6147120)
|
||||
([#8213](https://github.com/google/ExoPlayer/issues/8213)).
|
||||
* Add a method to `AdPlaybackState` to allow resetting an ad group so that
|
||||
it can be played again
|
||||
([#9615](https://github.com/google/ExoPlayer/issues/9615)).
|
||||
* Enforce playback speed of 1.0 during ad playback
|
||||
([#9018](https://github.com/google/ExoPlayer/issues/9018)).
|
||||
* Fix issue where an ad group that failed to load caused an immediate
|
||||
playback reset
|
||||
([#9929](https://github.com/google/ExoPlayer/issues/9929)).
|
||||
* UI:
|
||||
* Fix the color of the numbers in `StyledPlayerView` rewind and
|
||||
fastforward buttons when using certain themes
|
||||
([#9765](https://github.com/google/ExoPlayer/issues/9765)).
|
||||
* Correctly translate playback speed strings
|
||||
([#9811](https://github.com/google/ExoPlayer/issues/9811)).
|
||||
* DASH:
|
||||
* Add parsed essential and supplemental properties to the `Representation`
|
||||
([#9579](https://github.com/google/ExoPlayer/issues/9579)).
|
||||
* Support the `forced-subtitle` track role
|
||||
([#9727](https://github.com/google/ExoPlayer/issues/9727)).
|
||||
* Stop interpreting the `main` track role as `C.SELECTION_FLAG_DEFAULT`.
|
||||
* Fix base URL exclusion logic for manifests that do not declare the DVB
|
||||
namespace ([#9856](https://github.com/google/ExoPlayer/issues/9856)).
|
||||
* Support relative `MPD.Location` URLs
|
||||
([#9939](https://github.com/google/ExoPlayer/issues/9939)).
|
||||
* HLS:
|
||||
* Correctly populate `Format.label` for audio only HLS streams
|
||||
([#9608](https://github.com/google/ExoPlayer/issues/9608)).
|
||||
* Use chunkless preparation by default to improve start up time. If your
|
||||
renditions contain muxed closed-caption tracks that are **not** declared
|
||||
in the master playlist, you should add them to the master playlist to be
|
||||
available for playback, or turn off chunkless preparation with
|
||||
`HlsMediaSource.Factory.setAllowChunklessPreparation(false)`.
|
||||
* Support key-frame accurate seeking in HLS
|
||||
([#2882](https://github.com/google/ExoPlayer/issues/2882)).
|
||||
* RTSP:
|
||||
* Provide a client API to override the `SocketFactory` used for any server
|
||||
connection ([#9606](https://github.com/google/ExoPlayer/pull/9606)).
|
||||
* Prefer DIGEST authentication method over BASIC if both are present
|
||||
([#9800](https://github.com/google/ExoPlayer/issues/9800)).
|
||||
* Handle when RTSP track timing is not available
|
||||
([#9775](https://github.com/google/ExoPlayer/issues/9775)).
|
||||
* Ignore invalid RTP-Info header values
|
||||
([#9619](https://github.com/google/ExoPlayer/issues/9619)).
|
||||
* Transformer:
|
||||
* Increase required min API version to 21.
|
||||
* `TransformationException` is now used to describe errors that occur
|
||||
during a transformation.
|
||||
* Add `TransformationRequest` for specifying the transformation options.
|
||||
* Allow multiple listeners to be registered.
|
||||
* Fix Transformer being stuck when the codec output is partially read.
|
||||
* Fix potential NPE in `Transformer.getProgress` when releasing the muxer
|
||||
throws.
|
||||
* Add a demo app for applying transformations.
|
||||
* MediaSession extension:
|
||||
* By default, `MediaSessionConnector` now clears the playlist on stop.
|
||||
Apps that want the playlist to be retained can call
|
||||
`setClearMediaItemsOnStop(false)` on the connector.
|
||||
* Cast extension:
|
||||
* Fix bug that prevented `CastPlayer` from calling `onIsPlayingChanged`
|
||||
correctly ([#9792](https://github.com/google/ExoPlayer/issues/9792)).
|
||||
* Support audio metadata including artwork with
|
||||
`DefaultMediaItemConverter`
|
||||
([#9663](https://github.com/google/ExoPlayer/issues/9663)).
|
||||
* FFmpeg extension:
|
||||
* Make `build_ffmpeg.sh` depend on LLVM's bin utils instead of GNU's
|
||||
([#9933](https://github.com/google/ExoPlayer/issues/9933)).
|
||||
* Android 12 compatibility:
|
||||
* Upgrade the Cast extension to depend on
|
||||
`com.google.android.gms:play-services-cast-framework:20.1.0`. Earlier
|
||||
versions of `play-services-cast-framework` are not compatible with apps
|
||||
targeting Android 12, and will crash with an `IllegalArgumentException`
|
||||
when creating `PendingIntent`s
|
||||
([#9528](https://github.com/google/ExoPlayer/issues/9528)).
|
||||
* Remove deprecated symbols:
|
||||
* Remove `Player.EventLister`. Use `Player.Listener` instead.
|
||||
* Remove `MediaSourceFactory.setDrmSessionManager`,
|
||||
`MediaSourceFactory.setDrmHttpDataSourceFactory`, and
|
||||
`MediaSourceFactory.setDrmUserAgent`. Use
|
||||
`MediaSourceFactory.setDrmSessionManagerProvider` instead.
|
||||
* Remove `MediaSourceFactory.setStreamKeys`. Use
|
||||
`MediaItem.Builder.setStreamKeys` instead.
|
||||
* Remove `MediaSourceFactory.createMediaSource(Uri)`. Use
|
||||
`MediaSourceFactory.createMediaSource(MediaItem)` instead.
|
||||
* Remove `setTag` from `DashMediaSource`, `HlsMediaSource` and
|
||||
`SsMediaSource`. Use `MediaItem.Builder.setTag` instead.
|
||||
* Remove `DashMediaSource.setLivePresentationDelayMs(long, boolean)`. Use
|
||||
`MediaItem.Builder.setLiveConfiguration` and
|
||||
`MediaItem.LiveConfiguration.Builder.setTargetOffsetMs` to override the
|
||||
manifest, or `DashMediaSource.setFallbackTargetLiveOffsetMs` to provide
|
||||
a fallback value.
|
||||
* Remove `(Simple)ExoPlayer.setThrowsWhenUsingWrongThread`. Opting out of
|
||||
the thread enforcement is no longer possible.
|
||||
* Remove `ActionFile` and `ActionFileUpgradeUtil`. Use ExoPlayer 2.16.1 or
|
||||
before to use `ActionFileUpgradeUtil` to merge legacy action files into
|
||||
`DefaultDownloadIndex`.
|
||||
* Remove `ProgressiveMediaSource.setExtractorsFactory`. Use
|
||||
`ProgressiveMediaSource.Factory(DataSource.Factory, ExtractorsFactory)`
|
||||
constructor instead.
|
||||
* Remove `ProgressiveMediaSource.Factory.setTag` and
|
||||
`ProgressiveMediaSource.Factory.setCustomCacheKey`. Use
|
||||
`MediaItem.Builder.setTag` and `MediaItem.Builder.setCustomCacheKey`
|
||||
instead.
|
||||
* Remove `DefaultRenderersFactory(Context, @ExtensionRendererMode int)`
|
||||
and `DefaultRenderersFactory(Context, @ExtensionRendererMode int, long)`
|
||||
constructors. Use the `DefaultRenderersFactory(Context)` constructor,
|
||||
`DefaultRenderersFactory.setExtensionRendererMode`, and
|
||||
`DefaultRenderersFactory.setAllowedVideoJoiningTimeMs` instead.
|
||||
* Remove all public `CronetDataSource` constructors. Use
|
||||
`CronetDataSource.Factory` instead.
|
||||
* Change the following `IntDefs` to `@Target(TYPE_USE)` only. This may break
|
||||
the compilation of usages in Kotlin, which can be fixed by moving the
|
||||
annotation to annotate the type (`Int`).
|
||||
* `@AacAudioObjectType`
|
||||
* `@Ac3Util.SyncFrameInfo.StreamType`
|
||||
* `@AdLoadException.Type`
|
||||
* `@AdtsExtractor.Flags`
|
||||
* `@AmrExtractor.Flags`
|
||||
* `@AspectRatioFrameLayout.ResizeMode`
|
||||
* `@AudioFocusManager.PlayerCommand`
|
||||
* `@AudioSink.SinkFormatSupport`
|
||||
* `@BinarySearchSeeker.TimestampSearchResult.Type`
|
||||
* `@BufferReplacementMode`
|
||||
* `@C.BufferFlags`
|
||||
* `@C.ColorRange`
|
||||
* `@C.ColorSpace`
|
||||
* `@C.ColorTransfer`
|
||||
* `@C.CryptoMode`
|
||||
* `@C.Encoding`
|
||||
* `@C.PcmEncoding`
|
||||
* `@C.Projection`
|
||||
* `@C.SelectionReason`
|
||||
* `@C.StereoMode`
|
||||
* `@C.VideoOutputMode`
|
||||
* `@CacheDataSource.Flags`
|
||||
* `@CaptionStyleCompat.EdgeType`
|
||||
* `@DataSpec.Flags`
|
||||
* `@DataSpec.HttpMethods`
|
||||
* `@DecoderDiscardReasons`
|
||||
* `@DecoderReuseResult`
|
||||
* `@DefaultAudioSink.OutputMode`
|
||||
* `@DefaultDrmSessionManager.Mode`
|
||||
* `@DefaultTrackSelector.SelectionEligibility`
|
||||
* `@DefaultTsPayloadReaderFactory.Flags`
|
||||
* `@EGLSurfaceTexture.SecureMode`
|
||||
* `@EbmlProcessor.ElementType`
|
||||
* `@ExoMediaDrm.KeyRequest.RequestType`
|
||||
* `@ExtensionRendererMode`
|
||||
* `@Extractor.ReadResult`
|
||||
* `@FileTypes.Type`
|
||||
* `@FlacExtractor.Flags` (in `com.google.android.exoplayer2.ext.flac`
|
||||
package)
|
||||
* `@FlacExtractor.Flags` (in
|
||||
`com.google.android.exoplayer2.extractor.flac` package)
|
||||
* `@FragmentedMp4Extractor.Flags`
|
||||
* `@HlsMediaPlaylist.PlaylistType`
|
||||
* `@HttpDataSourceException.Type`
|
||||
* `@IllegalClippingException.Reason`
|
||||
* `@IllegalMergeException.Reason`
|
||||
* `@LoadErrorHandlingPolicy.FallbackType`
|
||||
* `@MatroskaExtractor.Flags`
|
||||
* `@Mp3Extractor.Flags`
|
||||
* `@Mp4Extractor.Flags`
|
||||
* `@NotificationUtil.Importance`
|
||||
* `@PlaybackException.FieldNumber`
|
||||
* `@PlayerNotificationManager.Priority`
|
||||
* `@PlayerNotificationManager.Visibility`
|
||||
* `@PlayerView.ShowBuffering`
|
||||
* `@Renderer.State`
|
||||
* `@RendererCapabilities.AdaptiveSupport`
|
||||
* `@RendererCapabilities.Capabilities`
|
||||
* `@RendererCapabilities.DecoderSupport`
|
||||
* `@RendererCapabilities.FormatSupport`
|
||||
* `@RendererCapabilities.HardwareAccelerationSupport`
|
||||
* `@RendererCapabilities.TunnelingSupport`
|
||||
* `@SampleStream.ReadDataResult`
|
||||
* `@SampleStream.ReadFlags`
|
||||
* `@StyledPlayerView.ShowBuffering`
|
||||
* `@SubtitleView.ViewType`
|
||||
* `@TextAnnotation.Position`
|
||||
* `@TextEmphasisSpan.MarkFill`
|
||||
* `@TextEmphasisSpan.MarkShape`
|
||||
* `@Track.Transformation`
|
||||
* `@TrackOutput.SampleDataPart`
|
||||
* `@Transformer.ProgressState`
|
||||
* `@TsExtractor.Mode`
|
||||
* `@TsPayloadReader.Flags`
|
||||
* `@WebvttCssStyle.FontSizeUnit`
|
||||
|
||||
### 1.0.0-alpha01
|
||||
|
||||
AndroidX Media is the new home for media support libraries, including ExoPlayer.
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
project.ext {
|
||||
releaseVersion = '1.0.0-alpha01'
|
||||
releaseVersionCode = 1000000
|
||||
releaseVersion = '1.0.0-alpha03'
|
||||
releaseVersionCode = 1_000_000_0_03
|
||||
minSdkVersion = 16
|
||||
appTargetSdkVersion = 29
|
||||
// Upgrading this requires [Internal ref: b/193254928] to be fixed, or some
|
||||
|
|
@ -26,7 +26,7 @@ project.ext {
|
|||
// https://cs.android.com/android/platform/superproject/+/master:external/guava/METADATA
|
||||
guavaVersion = '31.0.1-android'
|
||||
mockitoVersion = '3.12.4'
|
||||
robolectricVersion = '4.6.1'
|
||||
robolectricVersion = '4.8-alpha-1'
|
||||
// Keep this in sync with Google's internal Checker Framework version.
|
||||
checkerframeworkVersion = '3.13.0'
|
||||
checkerframeworkCompatVersion = '2.5.5'
|
||||
|
|
|
|||
|
|
@ -87,3 +87,7 @@ include modulePrefix + 'test-data'
|
|||
project(modulePrefix + 'test-data').projectDir = new File(rootDir, 'libraries/test_data')
|
||||
include modulePrefix + 'test-utils'
|
||||
project(modulePrefix + 'test-utils').projectDir = new File(rootDir, 'libraries/test_utils')
|
||||
include modulePrefix + 'test-session-common'
|
||||
project(modulePrefix + 'test-session-common').projectDir = new File(rootDir, 'libraries/test_session_common')
|
||||
include modulePrefix + 'test-session-current'
|
||||
project(modulePrefix + 'test-session-current').projectDir = new File(rootDir, 'libraries/test_session_current')
|
||||
|
|
|
|||
|
|
@ -119,8 +119,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
|
||||
// Run the shader program.
|
||||
GlProgram program = checkNotNull(this.program);
|
||||
program.setSamplerTexIdUniform("uTexSampler0", frameTexture, /* unit= */ 0);
|
||||
program.setSamplerTexIdUniform("uTexSampler1", textures[0], /* unit= */ 1);
|
||||
program.setSamplerTexIdUniform("uTexSampler0", frameTexture, /* texUnitIndex= */ 0);
|
||||
program.setSamplerTexIdUniform("uTexSampler1", textures[0], /* texUnitIndex= */ 1);
|
||||
program.setFloatUniform("uScaleX", bitmapScaleX);
|
||||
program.setFloatUniform("uScaleY", bitmapScaleY);
|
||||
program.setFloatsUniform("uTexTransform", transformMatrix);
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ 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;
|
||||
|
|
@ -144,7 +143,7 @@ public final class MainActivity extends Activity {
|
|||
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();
|
||||
DataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory();
|
||||
HttpMediaDrmCallback drmCallback =
|
||||
new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory);
|
||||
drmSessionManager =
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import static androidx.media3.demo.main.DemoUtil.DOWNLOAD_NOTIFICATION_CHANNEL_I
|
|||
import android.app.Notification;
|
||||
import android.content.Context;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.media3.common.util.NotificationUtil;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.exoplayer.offline.Download;
|
||||
|
|
@ -32,6 +33,7 @@ import androidx.media3.exoplayer.scheduler.Scheduler;
|
|||
import java.util.List;
|
||||
|
||||
/** A service for downloading media. */
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
public class DemoDownloadService extends DownloadService {
|
||||
|
||||
private static final int JOB_ID = 1;
|
||||
|
|
|
|||
|
|
@ -16,12 +16,12 @@
|
|||
package androidx.media3.demo.main;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.OptIn;
|
||||
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;
|
||||
|
|
@ -59,7 +59,7 @@ public final class DemoUtil {
|
|||
private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
|
||||
|
||||
private static DataSource.@MonotonicNonNull Factory dataSourceFactory;
|
||||
private static HttpDataSource.@MonotonicNonNull Factory httpDataSourceFactory;
|
||||
private static DataSource.@MonotonicNonNull Factory httpDataSourceFactory;
|
||||
private static @MonotonicNonNull DatabaseProvider databaseProvider;
|
||||
private static @MonotonicNonNull File downloadDirectory;
|
||||
private static @MonotonicNonNull Cache downloadCache;
|
||||
|
|
@ -72,6 +72,7 @@ public final class DemoUtil {
|
|||
return BuildConfig.USE_DECODER_EXTENSIONS;
|
||||
}
|
||||
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
public static RenderersFactory buildRenderersFactory(
|
||||
Context context, boolean preferExtensionRenderer) {
|
||||
@DefaultRenderersFactory.ExtensionRendererMode
|
||||
|
|
@ -85,7 +86,7 @@ public final class DemoUtil {
|
|||
.setExtensionRendererMode(extensionRendererMode);
|
||||
}
|
||||
|
||||
public static synchronized HttpDataSource.Factory getHttpDataSourceFactory(Context context) {
|
||||
public static synchronized DataSource.Factory getHttpDataSourceFactory(Context context) {
|
||||
if (httpDataSourceFactory == null) {
|
||||
if (USE_CRONET_FOR_NETWORKING) {
|
||||
context = context.getApplicationContext();
|
||||
|
|
@ -117,6 +118,7 @@ public final class DemoUtil {
|
|||
return dataSourceFactory;
|
||||
}
|
||||
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
public static synchronized DownloadNotificationHelper getDownloadNotificationHelper(
|
||||
Context context) {
|
||||
if (downloadNotificationHelper == null) {
|
||||
|
|
@ -136,6 +138,7 @@ public final class DemoUtil {
|
|||
return downloadTracker;
|
||||
}
|
||||
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
private static synchronized Cache getDownloadCache(Context context) {
|
||||
if (downloadCache == null) {
|
||||
File downloadContentDirectory =
|
||||
|
|
@ -147,6 +150,7 @@ public final class DemoUtil {
|
|||
return downloadCache;
|
||||
}
|
||||
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
private static synchronized void ensureDownloadManagerInitialized(Context context) {
|
||||
if (downloadManager == null) {
|
||||
downloadManager =
|
||||
|
|
@ -161,6 +165,7 @@ public final class DemoUtil {
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
private static synchronized DatabaseProvider getDatabaseProvider(Context context) {
|
||||
if (databaseProvider == null) {
|
||||
databaseProvider = new StandaloneDatabaseProvider(context);
|
||||
|
|
@ -178,6 +183,7 @@ public final class DemoUtil {
|
|||
return downloadDirectory;
|
||||
}
|
||||
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
private static CacheDataSource.Factory buildReadOnlyCacheDataSource(
|
||||
DataSource.Factory upstreamFactory, Cache cache) {
|
||||
return new CacheDataSource.Factory()
|
||||
|
|
|
|||
|
|
@ -15,8 +15,7 @@
|
|||
*/
|
||||
package androidx.media3.demo.main;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
|
|
@ -24,16 +23,18 @@ import android.net.Uri;
|
|||
import android.os.AsyncTask;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.media3.common.DrmInitData;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.TrackSelectionParameters;
|
||||
import androidx.media3.common.TracksInfo;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.HttpDataSource;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.exoplayer.RenderersFactory;
|
||||
import androidx.media3.exoplayer.drm.DrmSession;
|
||||
import androidx.media3.exoplayer.drm.DrmSessionEventListener;
|
||||
|
|
@ -46,13 +47,14 @@ import androidx.media3.exoplayer.offline.DownloadIndex;
|
|||
import androidx.media3.exoplayer.offline.DownloadManager;
|
||||
import androidx.media3.exoplayer.offline.DownloadRequest;
|
||||
import androidx.media3.exoplayer.offline.DownloadService;
|
||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.trackselection.MappingTrackSelector.MappedTrackInfo;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
/** Tracks media that has been downloaded. */
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
public class DownloadTracker {
|
||||
|
||||
/** Listens for changes in the tracked downloads. */
|
||||
|
|
@ -65,31 +67,26 @@ public class DownloadTracker {
|
|||
private static final String TAG = "DownloadTracker";
|
||||
|
||||
private final Context context;
|
||||
private final HttpDataSource.Factory httpDataSourceFactory;
|
||||
private final DataSource.Factory dataSourceFactory;
|
||||
private final CopyOnWriteArraySet<Listener> listeners;
|
||||
private final HashMap<Uri, Download> downloads;
|
||||
private final DownloadIndex downloadIndex;
|
||||
private final DefaultTrackSelector.Parameters trackSelectorParameters;
|
||||
|
||||
@Nullable private StartDownloadDialogHelper startDownloadDialogHelper;
|
||||
|
||||
public DownloadTracker(
|
||||
Context context,
|
||||
HttpDataSource.Factory httpDataSourceFactory,
|
||||
DownloadManager downloadManager) {
|
||||
Context context, DataSource.Factory dataSourceFactory, DownloadManager downloadManager) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.httpDataSourceFactory = httpDataSourceFactory;
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
listeners = new CopyOnWriteArraySet<>();
|
||||
downloads = new HashMap<>();
|
||||
downloadIndex = downloadManager.getDownloadIndex();
|
||||
trackSelectorParameters = DownloadHelper.getDefaultTrackSelectorParameters(context);
|
||||
downloadManager.addListener(new DownloadManagerListener());
|
||||
loadDownloads();
|
||||
}
|
||||
|
||||
public void addListener(Listener listener) {
|
||||
checkNotNull(listener);
|
||||
listeners.add(listener);
|
||||
listeners.add(checkNotNull(listener));
|
||||
}
|
||||
|
||||
public void removeListener(Listener listener) {
|
||||
|
|
@ -120,8 +117,7 @@ public class DownloadTracker {
|
|||
startDownloadDialogHelper =
|
||||
new StartDownloadDialogHelper(
|
||||
fragmentManager,
|
||||
DownloadHelper.forMediaItem(
|
||||
context, mediaItem, renderersFactory, httpDataSourceFactory),
|
||||
DownloadHelper.forMediaItem(context, mediaItem, renderersFactory, dataSourceFactory),
|
||||
mediaItem);
|
||||
}
|
||||
}
|
||||
|
|
@ -159,7 +155,7 @@ public class DownloadTracker {
|
|||
|
||||
private final class StartDownloadDialogHelper
|
||||
implements DownloadHelper.Callback,
|
||||
DialogInterface.OnClickListener,
|
||||
TrackSelectionDialog.TrackSelectionListener,
|
||||
DialogInterface.OnDismissListener {
|
||||
|
||||
private final FragmentManager fragmentManager;
|
||||
|
|
@ -167,7 +163,6 @@ public class DownloadTracker {
|
|||
private final MediaItem mediaItem;
|
||||
|
||||
private TrackSelectionDialog trackSelectionDialog;
|
||||
private MappedTrackInfo mappedTrackInfo;
|
||||
private WidevineOfflineLicenseFetchTask widevineOfflineLicenseFetchTask;
|
||||
@Nullable private byte[] keySetId;
|
||||
|
||||
|
|
@ -220,7 +215,7 @@ public class DownloadTracker {
|
|||
new WidevineOfflineLicenseFetchTask(
|
||||
format,
|
||||
mediaItem.localConfiguration.drmConfiguration,
|
||||
httpDataSourceFactory,
|
||||
dataSourceFactory,
|
||||
/* dialogHelper= */ this,
|
||||
helper);
|
||||
widevineOfflineLicenseFetchTask.execute();
|
||||
|
|
@ -237,21 +232,13 @@ public class DownloadTracker {
|
|||
Log.e(TAG, logMessage, e);
|
||||
}
|
||||
|
||||
// DialogInterface.OnClickListener implementation.
|
||||
// TrackSelectionListener implementation.
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
public void onTracksSelected(TrackSelectionParameters trackSelectionParameters) {
|
||||
for (int periodIndex = 0; periodIndex < downloadHelper.getPeriodCount(); periodIndex++) {
|
||||
downloadHelper.clearTrackSelections(periodIndex);
|
||||
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
|
||||
if (!trackSelectionDialog.getIsDisabled(/* rendererIndex= */ i)) {
|
||||
downloadHelper.addTrackSelectionForSingleRenderer(
|
||||
periodIndex,
|
||||
/* rendererIndex= */ i,
|
||||
trackSelectorParameters,
|
||||
trackSelectionDialog.getOverrides(/* rendererIndex= */ i));
|
||||
}
|
||||
}
|
||||
downloadHelper.addTrackSelection(periodIndex, trackSelectionParameters);
|
||||
}
|
||||
DownloadRequest downloadRequest = buildDownloadRequest();
|
||||
if (downloadRequest.streamKeys.isEmpty()) {
|
||||
|
|
@ -316,21 +303,21 @@ public class DownloadTracker {
|
|||
return;
|
||||
}
|
||||
|
||||
mappedTrackInfo = downloadHelper.getMappedTrackInfo(/* periodIndex= */ 0);
|
||||
if (!TrackSelectionDialog.willHaveContent(mappedTrackInfo)) {
|
||||
TracksInfo tracksInfo = downloadHelper.getTracksInfo(/* periodIndex= */ 0);
|
||||
if (!TrackSelectionDialog.willHaveContent(tracksInfo)) {
|
||||
Log.d(TAG, "No dialog content. Downloading entire stream.");
|
||||
startDownload();
|
||||
downloadHelper.release();
|
||||
return;
|
||||
}
|
||||
trackSelectionDialog =
|
||||
TrackSelectionDialog.createForMappedTrackInfoAndParameters(
|
||||
TrackSelectionDialog.createForTracksInfoAndParameters(
|
||||
/* titleId= */ R.string.exo_download_description,
|
||||
mappedTrackInfo,
|
||||
trackSelectorParameters,
|
||||
tracksInfo,
|
||||
DownloadHelper.getDefaultTrackSelectorParameters(context),
|
||||
/* allowAdaptiveSelections= */ false,
|
||||
/* allowMultipleOverrides= */ true,
|
||||
/* onClickListener= */ this,
|
||||
/* onTracksSelectedListener= */ this,
|
||||
/* onDismissListener= */ this);
|
||||
trackSelectionDialog.show(fragmentManager, /* tag= */ null);
|
||||
}
|
||||
|
|
@ -371,7 +358,7 @@ public class DownloadTracker {
|
|||
|
||||
private final Format format;
|
||||
private final MediaItem.DrmConfiguration drmConfiguration;
|
||||
private final HttpDataSource.Factory httpDataSourceFactory;
|
||||
private final DataSource.Factory dataSourceFactory;
|
||||
private final StartDownloadDialogHelper dialogHelper;
|
||||
private final DownloadHelper downloadHelper;
|
||||
|
||||
|
|
@ -381,12 +368,12 @@ public class DownloadTracker {
|
|||
public WidevineOfflineLicenseFetchTask(
|
||||
Format format,
|
||||
MediaItem.DrmConfiguration drmConfiguration,
|
||||
HttpDataSource.Factory httpDataSourceFactory,
|
||||
DataSource.Factory dataSourceFactory,
|
||||
StartDownloadDialogHelper dialogHelper,
|
||||
DownloadHelper downloadHelper) {
|
||||
this.format = format;
|
||||
this.drmConfiguration = drmConfiguration;
|
||||
this.httpDataSourceFactory = httpDataSourceFactory;
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
this.dialogHelper = dialogHelper;
|
||||
this.downloadHelper = downloadHelper;
|
||||
}
|
||||
|
|
@ -397,7 +384,7 @@ public class DownloadTracker {
|
|||
OfflineLicenseHelper.newWidevineInstance(
|
||||
drmConfiguration.licenseUri.toString(),
|
||||
drmConfiguration.forceDefaultLicenseUri,
|
||||
httpDataSourceFactory,
|
||||
dataSourceFactory,
|
||||
drmConfiguration.licenseRequestHeaders,
|
||||
new DrmSessionEventListener.EventDispatcher());
|
||||
try {
|
||||
|
|
@ -415,7 +402,7 @@ public class DownloadTracker {
|
|||
if (drmSessionException != null) {
|
||||
dialogHelper.onOfflineLicenseFetchedError(drmSessionException);
|
||||
} else {
|
||||
dialogHelper.onOfflineLicenseFetched(downloadHelper, checkStateNotNull(keySetId));
|
||||
dialogHelper.onOfflineLicenseFetched(downloadHelper, checkNotNull(keySetId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,9 @@
|
|||
*/
|
||||
package androidx.media3.demo.main;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
|
@ -26,7 +27,6 @@ import androidx.media3.common.MediaItem;
|
|||
import androidx.media3.common.MediaItem.ClippingConfiguration;
|
||||
import androidx.media3.common.MediaItem.SubtitleConfiguration;
|
||||
import androidx.media3.common.MediaMetadata;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.Util;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -86,7 +86,7 @@ public class IntentUtil {
|
|||
|
||||
/** Populates the intent with the given list of {@link MediaItem media items}. */
|
||||
public static void addToIntent(List<MediaItem> mediaItems, Intent intent) {
|
||||
Assertions.checkArgument(!mediaItems.isEmpty());
|
||||
checkArgument(!mediaItems.isEmpty());
|
||||
if (mediaItems.size() == 1) {
|
||||
MediaItem mediaItem = mediaItems.get(0);
|
||||
MediaItem.LocalConfiguration localConfiguration = checkNotNull(mediaItem.localConfiguration);
|
||||
|
|
@ -241,7 +241,7 @@ public class IntentUtil {
|
|||
drmConfiguration.forcedSessionTrackTypes;
|
||||
if (!forcedDrmSessionTrackTypes.isEmpty()) {
|
||||
// Only video and audio together are supported.
|
||||
Assertions.checkState(
|
||||
checkState(
|
||||
forcedDrmSessionTrackTypes.size() == 2
|
||||
&& forcedDrmSessionTrackTypes.contains(C.TRACK_TYPE_VIDEO)
|
||||
&& forcedDrmSessionTrackTypes.contains(C.TRACK_TYPE_AUDIO));
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ package androidx.media3.demo.main;
|
|||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Pair;
|
||||
import android.view.KeyEvent;
|
||||
|
|
@ -27,6 +28,7 @@ import android.widget.LinearLayout;
|
|||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.media3.common.AudioAttributes;
|
||||
import androidx.media3.common.C;
|
||||
|
|
@ -34,6 +36,7 @@ import androidx.media3.common.ErrorMessageProvider;
|
|||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.PlaybackException;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.TrackSelectionParameters;
|
||||
import androidx.media3.common.TracksInfo;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
|
|
@ -48,7 +51,6 @@ import androidx.media3.exoplayer.offline.DownloadRequest;
|
|||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
||||
import androidx.media3.exoplayer.source.MediaSource;
|
||||
import androidx.media3.exoplayer.source.ads.AdsLoader;
|
||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
|
||||
import androidx.media3.exoplayer.util.DebugTextViewHelper;
|
||||
import androidx.media3.exoplayer.util.EventLogger;
|
||||
import androidx.media3.ui.PlayerControlView;
|
||||
|
|
@ -79,8 +81,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||
private Button selectTracksButton;
|
||||
private DataSource.Factory dataSourceFactory;
|
||||
private List<MediaItem> mediaItems;
|
||||
private DefaultTrackSelector trackSelector;
|
||||
private DefaultTrackSelector.Parameters trackSelectionParameters;
|
||||
private TrackSelectionParameters trackSelectionParameters;
|
||||
private DebugTextViewHelper debugViewHelper;
|
||||
private TracksInfo lastSeenTracksInfo;
|
||||
private boolean startAutoPlay;
|
||||
|
|
@ -113,9 +114,8 @@ public class PlayerActivity extends AppCompatActivity
|
|||
playerView.requestFocus();
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
// Restore as DefaultTrackSelector.Parameters in case ExoPlayer specific parameters were set.
|
||||
trackSelectionParameters =
|
||||
DefaultTrackSelector.Parameters.CREATOR.fromBundle(
|
||||
TrackSelectionParameters.CREATOR.fromBundle(
|
||||
savedInstanceState.getBundle(KEY_TRACK_SELECTION_PARAMETERS));
|
||||
startAutoPlay = savedInstanceState.getBoolean(KEY_AUTO_PLAY);
|
||||
startItemIndex = savedInstanceState.getInt(KEY_ITEM_INDEX);
|
||||
|
|
@ -127,8 +127,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||
adsLoaderStateBundle);
|
||||
}
|
||||
} else {
|
||||
trackSelectionParameters =
|
||||
new DefaultTrackSelector.ParametersBuilder(/* context= */ this).build();
|
||||
trackSelectionParameters = new TrackSelectionParameters.Builder(/* context= */ this).build();
|
||||
clearStartPosition();
|
||||
}
|
||||
}
|
||||
|
|
@ -145,7 +144,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
if (Util.SDK_INT > 23) {
|
||||
if (Build.VERSION.SDK_INT > 23) {
|
||||
initializePlayer();
|
||||
if (playerView != null) {
|
||||
playerView.onResume();
|
||||
|
|
@ -156,7 +155,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (Util.SDK_INT <= 23 || player == null) {
|
||||
if (Build.VERSION.SDK_INT <= 23 || player == null) {
|
||||
initializePlayer();
|
||||
if (playerView != null) {
|
||||
playerView.onResume();
|
||||
|
|
@ -167,7 +166,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (Util.SDK_INT <= 23) {
|
||||
if (Build.VERSION.SDK_INT <= 23) {
|
||||
if (playerView != null) {
|
||||
playerView.onPause();
|
||||
}
|
||||
|
|
@ -178,7 +177,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
if (Util.SDK_INT > 23) {
|
||||
if (Build.VERSION.SDK_INT > 23) {
|
||||
if (playerView != null) {
|
||||
playerView.onPause();
|
||||
}
|
||||
|
|
@ -237,11 +236,11 @@ public class PlayerActivity extends AppCompatActivity
|
|||
public void onClick(View view) {
|
||||
if (view == selectTracksButton
|
||||
&& !isShowingTrackSelectionDialog
|
||||
&& TrackSelectionDialog.willHaveContent(trackSelector)) {
|
||||
&& TrackSelectionDialog.willHaveContent(player)) {
|
||||
isShowingTrackSelectionDialog = true;
|
||||
TrackSelectionDialog trackSelectionDialog =
|
||||
TrackSelectionDialog.createForTrackSelector(
|
||||
trackSelector,
|
||||
TrackSelectionDialog.createForPlayer(
|
||||
player,
|
||||
/* onDismissListener= */ dismissedDialog -> isShowingTrackSelectionDialog = false);
|
||||
trackSelectionDialog.show(getSupportFragmentManager(), /* tag= */ null);
|
||||
}
|
||||
|
|
@ -277,13 +276,11 @@ public class PlayerActivity extends AppCompatActivity
|
|||
RenderersFactory renderersFactory =
|
||||
DemoUtil.buildRenderersFactory(/* context= */ this, preferExtensionDecoders);
|
||||
|
||||
trackSelector = new DefaultTrackSelector(/* context= */ this);
|
||||
lastSeenTracksInfo = TracksInfo.EMPTY;
|
||||
player =
|
||||
new ExoPlayer.Builder(/* context= */ this)
|
||||
.setRenderersFactory(renderersFactory)
|
||||
.setMediaSourceFactory(createMediaSourceFactory())
|
||||
.setTrackSelector(trackSelector)
|
||||
.build();
|
||||
player.setTrackSelectionParameters(trackSelectionParameters);
|
||||
player.addListener(new PlayerEventListener());
|
||||
|
|
@ -347,7 +344,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||
|
||||
MediaItem.DrmConfiguration drmConfiguration = mediaItem.localConfiguration.drmConfiguration;
|
||||
if (drmConfiguration != null) {
|
||||
if (Util.SDK_INT < 18) {
|
||||
if (Build.VERSION.SDK_INT < 18) {
|
||||
showToast(R.string.error_drm_unsupported_before_api_18);
|
||||
finish();
|
||||
return Collections.emptyList();
|
||||
|
|
@ -400,10 +397,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||
|
||||
private void updateTrackSelectorParameters() {
|
||||
if (player != null) {
|
||||
// Until the demo app is fully migrated to TrackSelectionParameters, rely on ExoPlayer to use
|
||||
// DefaultTrackSelector by default.
|
||||
trackSelectionParameters =
|
||||
(DefaultTrackSelector.Parameters) player.getTrackSelectionParameters();
|
||||
trackSelectionParameters = player.getTrackSelectionParameters();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -424,8 +418,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||
// User controls
|
||||
|
||||
private void updateButtonVisibility() {
|
||||
selectTracksButton.setEnabled(
|
||||
player != null && TrackSelectionDialog.willHaveContent(trackSelector));
|
||||
selectTracksButton.setEnabled(player != null && TrackSelectionDialog.willHaveContent(player));
|
||||
}
|
||||
|
||||
private void showControls() {
|
||||
|
|
@ -517,29 +510,32 @@ public class PlayerActivity extends AppCompatActivity
|
|||
private static List<MediaItem> createMediaItems(Intent intent, DownloadTracker downloadTracker) {
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
for (MediaItem item : IntentUtil.createMediaItemsFromIntent(intent)) {
|
||||
@Nullable
|
||||
DownloadRequest downloadRequest =
|
||||
downloadTracker.getDownloadRequest(item.localConfiguration.uri);
|
||||
if (downloadRequest != null) {
|
||||
MediaItem.Builder builder = item.buildUpon();
|
||||
builder
|
||||
.setMediaId(downloadRequest.id)
|
||||
.setUri(downloadRequest.uri)
|
||||
.setCustomCacheKey(downloadRequest.customCacheKey)
|
||||
.setMimeType(downloadRequest.mimeType)
|
||||
.setStreamKeys(downloadRequest.streamKeys);
|
||||
@Nullable
|
||||
MediaItem.DrmConfiguration drmConfiguration = item.localConfiguration.drmConfiguration;
|
||||
if (drmConfiguration != null) {
|
||||
builder.setDrmConfiguration(
|
||||
drmConfiguration.buildUpon().setKeySetId(downloadRequest.keySetId).build());
|
||||
}
|
||||
|
||||
mediaItems.add(builder.build());
|
||||
} else {
|
||||
mediaItems.add(item);
|
||||
}
|
||||
mediaItems.add(
|
||||
maybeSetDownloadProperties(
|
||||
item, downloadTracker.getDownloadRequest(item.localConfiguration.uri)));
|
||||
}
|
||||
return mediaItems;
|
||||
}
|
||||
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
private static MediaItem maybeSetDownloadProperties(
|
||||
MediaItem item, @Nullable DownloadRequest downloadRequest) {
|
||||
if (downloadRequest == null) {
|
||||
return item;
|
||||
}
|
||||
MediaItem.Builder builder = item.buildUpon();
|
||||
builder
|
||||
.setMediaId(downloadRequest.id)
|
||||
.setUri(downloadRequest.uri)
|
||||
.setCustomCacheKey(downloadRequest.customCacheKey)
|
||||
.setMimeType(downloadRequest.mimeType)
|
||||
.setStreamKeys(downloadRequest.streamKeys);
|
||||
@Nullable
|
||||
MediaItem.DrmConfiguration drmConfiguration = item.localConfiguration.drmConfiguration;
|
||||
if (drmConfiguration != null) {
|
||||
builder.setDrmConfiguration(
|
||||
drmConfiguration.buildUpon().setKeySetId(downloadRequest.keySetId).build());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@
|
|||
*/
|
||||
package androidx.media3.demo.main;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
|
@ -41,6 +41,7 @@ import android.widget.ImageButton;
|
|||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.MediaItem.ClippingConfiguration;
|
||||
|
|
@ -53,6 +54,7 @@ import androidx.media3.datasource.DataSourceUtil;
|
|||
import androidx.media3.datasource.DataSpec;
|
||||
import androidx.media3.exoplayer.RenderersFactory;
|
||||
import androidx.media3.exoplayer.offline.DownloadService;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.io.IOException;
|
||||
|
|
@ -116,8 +118,12 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||
useExtensionRenderers = DemoUtil.useExtensionRenderers();
|
||||
downloadTracker = DemoUtil.getDownloadTracker(/* context= */ this);
|
||||
loadSample();
|
||||
startDownloadService();
|
||||
}
|
||||
|
||||
// Start the download service if it should be running but it's not currently.
|
||||
/** Start the download service if it should be running but it's not currently. */
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
private void startDownloadService() {
|
||||
// Starting the service in the foreground causes notification flicker if there is no scheduled
|
||||
// action. Starting it in the background throws an exception if the app is in the background too
|
||||
// (e.g. if device screen is locked).
|
||||
|
|
@ -271,6 +277,7 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||
|
||||
private boolean sawError;
|
||||
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
@Override
|
||||
protected List<PlaylistGroup> doInBackground(String... uris) {
|
||||
List<PlaylistGroup> result = new ArrayList<>();
|
||||
|
|
@ -481,7 +488,7 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||
|
||||
private PlaylistGroup getGroup(String groupName, List<PlaylistGroup> groups) {
|
||||
for (int i = 0; i < groups.size(); i++) {
|
||||
if (Util.areEqual(groupName, groups.get(i).title)) {
|
||||
if (Objects.equal(groupName, groups.get(i).title)) {
|
||||
return groups.get(i);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,21 +31,42 @@ import androidx.fragment.app.Fragment;
|
|||
import androidx.fragment.app.FragmentManager;
|
||||
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.common.Player;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackSelectionOverride;
|
||||
import androidx.media3.common.TrackSelectionParameters;
|
||||
import androidx.media3.common.TracksInfo;
|
||||
import androidx.media3.common.TracksInfo.TrackGroupInfo;
|
||||
import androidx.media3.ui.TrackSelectionView;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** Dialog to select tracks. */
|
||||
public final class TrackSelectionDialog extends DialogFragment {
|
||||
|
||||
/** Called when tracks are selected. */
|
||||
public interface TrackSelectionListener {
|
||||
|
||||
/**
|
||||
* Called when tracks are selected.
|
||||
*
|
||||
* @param trackSelectionParameters A {@link TrackSelectionParameters} representing the selected
|
||||
* tracks. Any manual selections are defined by {@link
|
||||
* TrackSelectionParameters#disabledTrackTypes} and {@link
|
||||
* TrackSelectionParameters#overrides}.
|
||||
*/
|
||||
void onTracksSelected(TrackSelectionParameters trackSelectionParameters);
|
||||
}
|
||||
|
||||
public static final ImmutableList<Integer> SUPPORTED_TRACK_TYPES =
|
||||
ImmutableList.of(C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_TEXT);
|
||||
|
||||
private final SparseArray<TrackSelectionViewFragment> tabFragments;
|
||||
private final ArrayList<Integer> tabTrackTypes;
|
||||
|
||||
|
|
@ -55,20 +76,19 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||
|
||||
/**
|
||||
* Returns whether a track selection dialog will have content to display if initialized with the
|
||||
* specified {@link DefaultTrackSelector} in its current state.
|
||||
* specified {@link Player}.
|
||||
*/
|
||||
public static boolean willHaveContent(DefaultTrackSelector trackSelector) {
|
||||
MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo();
|
||||
return mappedTrackInfo != null && willHaveContent(mappedTrackInfo);
|
||||
public static boolean willHaveContent(Player player) {
|
||||
return willHaveContent(player.getCurrentTracksInfo());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a track selection dialog will have content to display if initialized with the
|
||||
* specified {@link MappedTrackInfo}.
|
||||
* specified {@link TracksInfo}.
|
||||
*/
|
||||
public static boolean willHaveContent(MappedTrackInfo mappedTrackInfo) {
|
||||
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
|
||||
if (showTabForRenderer(mappedTrackInfo, i)) {
|
||||
public static boolean willHaveContent(TracksInfo tracksInfo) {
|
||||
for (TrackGroupInfo trackGroupInfo : tracksInfo.getTrackGroupInfos()) {
|
||||
if (SUPPORTED_TRACK_TYPES.contains(trackGroupInfo.getTrackType())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -76,78 +96,67 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates a dialog for a given {@link DefaultTrackSelector}, whose parameters will be
|
||||
* automatically updated when tracks are selected.
|
||||
* Creates a dialog for a given {@link Player}, whose parameters will be automatically updated
|
||||
* when tracks are selected.
|
||||
*
|
||||
* @param trackSelector The {@link DefaultTrackSelector}.
|
||||
* @param player The {@link Player}.
|
||||
* @param onDismissListener A {@link DialogInterface.OnDismissListener} to call when the dialog is
|
||||
* dismissed.
|
||||
*/
|
||||
public static TrackSelectionDialog createForTrackSelector(
|
||||
DefaultTrackSelector trackSelector, DialogInterface.OnDismissListener onDismissListener) {
|
||||
MappedTrackInfo mappedTrackInfo =
|
||||
Assertions.checkNotNull(trackSelector.getCurrentMappedTrackInfo());
|
||||
TrackSelectionDialog trackSelectionDialog = new TrackSelectionDialog();
|
||||
DefaultTrackSelector.Parameters parameters = trackSelector.getParameters();
|
||||
trackSelectionDialog.init(
|
||||
/* titleId= */ R.string.track_selection_title,
|
||||
mappedTrackInfo,
|
||||
/* initialParameters = */ parameters,
|
||||
public static TrackSelectionDialog createForPlayer(
|
||||
Player player, DialogInterface.OnDismissListener onDismissListener) {
|
||||
return createForTracksInfoAndParameters(
|
||||
R.string.track_selection_title,
|
||||
player.getCurrentTracksInfo(),
|
||||
player.getTrackSelectionParameters(),
|
||||
/* allowAdaptiveSelections= */ true,
|
||||
/* allowMultipleOverrides= */ false,
|
||||
/* onClickListener= */ (dialog, which) -> {
|
||||
DefaultTrackSelector.ParametersBuilder builder = parameters.buildUpon();
|
||||
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
|
||||
builder
|
||||
.clearSelectionOverrides(/* rendererIndex= */ i)
|
||||
.setRendererDisabled(
|
||||
/* rendererIndex= */ i,
|
||||
trackSelectionDialog.getIsDisabled(/* rendererIndex= */ i));
|
||||
List<SelectionOverride> overrides =
|
||||
trackSelectionDialog.getOverrides(/* rendererIndex= */ i);
|
||||
if (!overrides.isEmpty()) {
|
||||
builder.setSelectionOverride(
|
||||
/* rendererIndex= */ i,
|
||||
mappedTrackInfo.getTrackGroups(/* rendererIndex= */ i),
|
||||
overrides.get(0));
|
||||
}
|
||||
}
|
||||
trackSelector.setParameters(builder);
|
||||
},
|
||||
player::setTrackSelectionParameters,
|
||||
onDismissListener);
|
||||
return trackSelectionDialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a dialog for given {@link MappedTrackInfo} and {@link DefaultTrackSelector.Parameters}.
|
||||
* Creates a dialog for given {@link TracksInfo} and {@link TrackSelectionParameters}.
|
||||
*
|
||||
* @param titleId The resource id of the dialog title.
|
||||
* @param mappedTrackInfo The {@link MappedTrackInfo} to display.
|
||||
* @param initialParameters The {@link DefaultTrackSelector.Parameters} describing the initial
|
||||
* track selection.
|
||||
* @param tracksInfo The {@link TracksInfo} describing the tracks to display.
|
||||
* @param trackSelectionParameters The initial {@link TrackSelectionParameters}.
|
||||
* @param allowAdaptiveSelections Whether adaptive selections (consisting of more than one track)
|
||||
* can be made.
|
||||
* @param allowMultipleOverrides Whether tracks from multiple track groups can be selected.
|
||||
* @param onClickListener {@link DialogInterface.OnClickListener} called when tracks are selected.
|
||||
* @param trackSelectionListener Called when tracks are selected.
|
||||
* @param onDismissListener {@link DialogInterface.OnDismissListener} called when the dialog is
|
||||
* dismissed.
|
||||
*/
|
||||
public static TrackSelectionDialog createForMappedTrackInfoAndParameters(
|
||||
public static TrackSelectionDialog createForTracksInfoAndParameters(
|
||||
int titleId,
|
||||
MappedTrackInfo mappedTrackInfo,
|
||||
DefaultTrackSelector.Parameters initialParameters,
|
||||
TracksInfo tracksInfo,
|
||||
TrackSelectionParameters trackSelectionParameters,
|
||||
boolean allowAdaptiveSelections,
|
||||
boolean allowMultipleOverrides,
|
||||
DialogInterface.OnClickListener onClickListener,
|
||||
TrackSelectionListener trackSelectionListener,
|
||||
DialogInterface.OnDismissListener onDismissListener) {
|
||||
TrackSelectionDialog trackSelectionDialog = new TrackSelectionDialog();
|
||||
trackSelectionDialog.init(
|
||||
tracksInfo,
|
||||
trackSelectionParameters,
|
||||
titleId,
|
||||
mappedTrackInfo,
|
||||
initialParameters,
|
||||
allowAdaptiveSelections,
|
||||
allowMultipleOverrides,
|
||||
onClickListener,
|
||||
/* onClickListener= */ (dialog, which) -> {
|
||||
TrackSelectionParameters.Builder builder = trackSelectionParameters.buildUpon();
|
||||
for (int i = 0; i < SUPPORTED_TRACK_TYPES.size(); i++) {
|
||||
int trackType = SUPPORTED_TRACK_TYPES.get(i);
|
||||
builder.setTrackTypeDisabled(trackType, trackSelectionDialog.getIsDisabled(trackType));
|
||||
builder.clearOverridesOfType(trackType);
|
||||
Map<TrackGroup, TrackSelectionOverride> overrides =
|
||||
trackSelectionDialog.getOverrides(trackType);
|
||||
for (TrackSelectionOverride override : overrides.values()) {
|
||||
builder.addOverride(override);
|
||||
}
|
||||
}
|
||||
trackSelectionListener.onTracksSelected(builder.build());
|
||||
},
|
||||
onDismissListener);
|
||||
return trackSelectionDialog;
|
||||
}
|
||||
|
|
@ -160,9 +169,9 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||
}
|
||||
|
||||
private void init(
|
||||
TracksInfo tracksInfo,
|
||||
TrackSelectionParameters trackSelectionParameters,
|
||||
int titleId,
|
||||
MappedTrackInfo mappedTrackInfo,
|
||||
DefaultTrackSelector.Parameters initialParameters,
|
||||
boolean allowAdaptiveSelections,
|
||||
boolean allowMultipleOverrides,
|
||||
DialogInterface.OnClickListener onClickListener,
|
||||
|
|
@ -170,45 +179,49 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||
this.titleId = titleId;
|
||||
this.onClickListener = onClickListener;
|
||||
this.onDismissListener = onDismissListener;
|
||||
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
|
||||
if (showTabForRenderer(mappedTrackInfo, i)) {
|
||||
int trackType = mappedTrackInfo.getRendererType(/* rendererIndex= */ i);
|
||||
TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(i);
|
||||
|
||||
for (int i = 0; i < SUPPORTED_TRACK_TYPES.size(); i++) {
|
||||
@C.TrackType int trackType = SUPPORTED_TRACK_TYPES.get(i);
|
||||
ArrayList<TrackGroupInfo> trackGroupInfos = new ArrayList<>();
|
||||
for (TrackGroupInfo trackGroupInfo : tracksInfo.getTrackGroupInfos()) {
|
||||
if (trackGroupInfo.getTrackType() == trackType) {
|
||||
trackGroupInfos.add(trackGroupInfo);
|
||||
}
|
||||
}
|
||||
if (!trackGroupInfos.isEmpty()) {
|
||||
TrackSelectionViewFragment tabFragment = new TrackSelectionViewFragment();
|
||||
tabFragment.init(
|
||||
mappedTrackInfo,
|
||||
/* rendererIndex= */ i,
|
||||
initialParameters.getRendererDisabled(/* rendererIndex= */ i),
|
||||
initialParameters.getSelectionOverride(/* rendererIndex= */ i, trackGroupArray),
|
||||
trackGroupInfos,
|
||||
trackSelectionParameters.disabledTrackTypes.contains(trackType),
|
||||
trackSelectionParameters.overrides,
|
||||
allowAdaptiveSelections,
|
||||
allowMultipleOverrides);
|
||||
tabFragments.put(i, tabFragment);
|
||||
tabFragments.put(trackType, tabFragment);
|
||||
tabTrackTypes.add(trackType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a renderer is disabled.
|
||||
* Returns whether the disabled option is selected for the specified track type.
|
||||
*
|
||||
* @param rendererIndex Renderer index.
|
||||
* @return Whether the renderer is disabled.
|
||||
* @param trackType The track type.
|
||||
* @return Whether the disabled option is selected for the track type.
|
||||
*/
|
||||
public boolean getIsDisabled(int rendererIndex) {
|
||||
TrackSelectionViewFragment rendererView = tabFragments.get(rendererIndex);
|
||||
return rendererView != null && rendererView.isDisabled;
|
||||
public boolean getIsDisabled(int trackType) {
|
||||
TrackSelectionViewFragment trackView = tabFragments.get(trackType);
|
||||
return trackView != null && trackView.isDisabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of selected track selection overrides for the specified renderer. There will
|
||||
* be at most one override for each track group.
|
||||
* Returns the selected track overrides for the specified track type.
|
||||
*
|
||||
* @param rendererIndex Renderer index.
|
||||
* @return The list of track selection overrides for this renderer.
|
||||
* @param trackType The track type.
|
||||
* @return The track overrides for the track type.
|
||||
*/
|
||||
public List<SelectionOverride> getOverrides(int rendererIndex) {
|
||||
TrackSelectionViewFragment rendererView = tabFragments.get(rendererIndex);
|
||||
return rendererView == null ? Collections.emptyList() : rendererView.overrides;
|
||||
public Map<TrackGroup, TrackSelectionOverride> getOverrides(int trackType) {
|
||||
TrackSelectionViewFragment trackView = tabFragments.get(trackType);
|
||||
return trackView == null ? Collections.emptyMap() : trackView.overrides;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -248,27 +261,7 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||
return dialogView;
|
||||
}
|
||||
|
||||
private static boolean showTabForRenderer(MappedTrackInfo mappedTrackInfo, int rendererIndex) {
|
||||
TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(rendererIndex);
|
||||
if (trackGroupArray.length == 0) {
|
||||
return false;
|
||||
}
|
||||
int trackType = mappedTrackInfo.getRendererType(rendererIndex);
|
||||
return isSupportedTrackType(trackType);
|
||||
}
|
||||
|
||||
private static boolean isSupportedTrackType(int trackType) {
|
||||
switch (trackType) {
|
||||
case C.TRACK_TYPE_VIDEO:
|
||||
case C.TRACK_TYPE_AUDIO:
|
||||
case C.TRACK_TYPE_TEXT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getTrackTypeString(Resources resources, int trackType) {
|
||||
private static String getTrackTypeString(Resources resources, @C.TrackType int trackType) {
|
||||
switch (trackType) {
|
||||
case C.TRACK_TYPE_VIDEO:
|
||||
return resources.getString(R.string.exo_track_selection_title_video);
|
||||
|
|
@ -289,12 +282,12 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
return tabFragments.valueAt(position);
|
||||
return tabFragments.get(tabTrackTypes.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return tabFragments.size();
|
||||
return tabTrackTypes.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -307,13 +300,12 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||
public static final class TrackSelectionViewFragment extends Fragment
|
||||
implements TrackSelectionView.TrackSelectionListener {
|
||||
|
||||
private MappedTrackInfo mappedTrackInfo;
|
||||
private int rendererIndex;
|
||||
private List<TrackGroupInfo> trackGroupInfos;
|
||||
private boolean allowAdaptiveSelections;
|
||||
private boolean allowMultipleOverrides;
|
||||
|
||||
/* package */ boolean isDisabled;
|
||||
/* package */ List<SelectionOverride> overrides;
|
||||
/* package */ Map<TrackGroup, TrackSelectionOverride> overrides;
|
||||
|
||||
public TrackSelectionViewFragment() {
|
||||
// Retain instance across activity re-creation to prevent losing access to init data.
|
||||
|
|
@ -321,21 +313,21 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||
}
|
||||
|
||||
public void init(
|
||||
MappedTrackInfo mappedTrackInfo,
|
||||
int rendererIndex,
|
||||
boolean initialIsDisabled,
|
||||
@Nullable SelectionOverride initialOverride,
|
||||
List<TrackGroupInfo> trackGroupInfos,
|
||||
boolean isDisabled,
|
||||
Map<TrackGroup, TrackSelectionOverride> overrides,
|
||||
boolean allowAdaptiveSelections,
|
||||
boolean allowMultipleOverrides) {
|
||||
this.mappedTrackInfo = mappedTrackInfo;
|
||||
this.rendererIndex = rendererIndex;
|
||||
this.isDisabled = initialIsDisabled;
|
||||
this.overrides =
|
||||
initialOverride == null
|
||||
? Collections.emptyList()
|
||||
: Collections.singletonList(initialOverride);
|
||||
this.trackGroupInfos = trackGroupInfos;
|
||||
this.isDisabled = isDisabled;
|
||||
this.allowAdaptiveSelections = allowAdaptiveSelections;
|
||||
this.allowMultipleOverrides = allowMultipleOverrides;
|
||||
// TrackSelectionView does this filtering internally, but we need to do it here as well to
|
||||
// handle the case where the TrackSelectionView is never created.
|
||||
this.overrides =
|
||||
new HashMap<>(
|
||||
TrackSelectionView.filterOverrides(
|
||||
overrides, trackGroupInfos, allowMultipleOverrides));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -351,8 +343,7 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||
trackSelectionView.setAllowMultipleOverrides(allowMultipleOverrides);
|
||||
trackSelectionView.setAllowAdaptiveSelections(allowAdaptiveSelections);
|
||||
trackSelectionView.init(
|
||||
mappedTrackInfo,
|
||||
rendererIndex,
|
||||
trackGroupInfos,
|
||||
isDisabled,
|
||||
overrides,
|
||||
/* trackFormatComparator= */ null,
|
||||
|
|
@ -361,7 +352,8 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onTrackSelectionChanged(boolean isDisabled, List<SelectionOverride> overrides) {
|
||||
public void onTrackSelectionChanged(
|
||||
boolean isDisabled, Map<TrackGroup, TrackSelectionOverride> overrides) {
|
||||
this.isDisabled = isDisabled;
|
||||
this.overrides = overrides;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import android.widget.ListView
|
|||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.session.MediaBrowser
|
||||
import androidx.media3.session.SessionToken
|
||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
|
|
@ -179,6 +180,9 @@ class PlayableFolderActivity : AppCompatActivity() {
|
|||
returnConvertView.findViewById<TextView>(R.id.add_button).setOnClickListener {
|
||||
val browser = this@PlayableFolderActivity.browser ?: return@setOnClickListener
|
||||
browser.addMediaItem(mediaItem)
|
||||
if (browser.playbackState == Player.STATE_IDLE) {
|
||||
browser.prepare()
|
||||
}
|
||||
Snackbar.make(
|
||||
findViewById<LinearLayout>(R.id.linear_layout),
|
||||
getString(R.string.added_media_item_format, mediaItem.mediaMetadata.title),
|
||||
|
|
|
|||
|
|
@ -96,7 +96,6 @@ class PlaybackService : MediaLibraryService() {
|
|||
|
||||
val item = MediaItemTree.getItemFromTitle(mediaTitle) ?: MediaItemTree.getRandomItem()
|
||||
player.setMediaItem(item)
|
||||
player.prepare()
|
||||
}
|
||||
|
||||
override fun onSetMediaUri(
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ 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;
|
||||
|
|
@ -189,7 +188,7 @@ public final class MainActivity extends Activity {
|
|||
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();
|
||||
DataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory();
|
||||
HttpMediaDrmCallback drmCallback =
|
||||
new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory);
|
||||
drmSessionManager =
|
||||
|
|
|
|||
|
|
@ -50,12 +50,11 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||
public static final String AUDIO_MIME_TYPE = "audio_mime_type";
|
||||
public static final String VIDEO_MIME_TYPE = "video_mime_type";
|
||||
public static final String RESOLUTION_HEIGHT = "resolution_height";
|
||||
public static final String TRANSLATE_X = "translate_x";
|
||||
public static final String TRANSLATE_Y = "translate_y";
|
||||
public static final String SCALE_X = "scale_x";
|
||||
public static final String SCALE_Y = "scale_y";
|
||||
public static final String ROTATE_DEGREES = "rotate_degrees";
|
||||
public static final String ENABLE_FALLBACK = "enable_fallback";
|
||||
public static final String ENABLE_REQUEST_SDR_TONE_MAPPING = "enable_request_sdr_tone_mapping";
|
||||
public static final String ENABLE_HDR_EDITING = "enable_hdr_editing";
|
||||
private static final String[] INPUT_URIS = {
|
||||
"https://html5demos.com/assets/dizzy.mp4",
|
||||
|
|
@ -63,6 +62,11 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||
"https://storage.googleapis.com/exoplayer-test-media-0/BigBuckBunny_320x180.mp4",
|
||||
"https://html5demos.com/assets/dizzy.webm",
|
||||
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_4k60.mp4",
|
||||
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/8k24fps_4s.mp4",
|
||||
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_avc_aac.mp4",
|
||||
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_rotated_avc_aac.mp4",
|
||||
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/slow-motion/slowMotion_stopwatch_240fps_long.mp4",
|
||||
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/samsung-hdr-hdr10.mp4",
|
||||
};
|
||||
private static final String[] URI_DESCRIPTIONS = { // same order as INPUT_URIS
|
||||
"MP4 with H264 video and AAC audio",
|
||||
|
|
@ -70,6 +74,11 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||
"Long MP4 with H264 video and AAC audio",
|
||||
"WebM with VP8 video and Vorbis audio",
|
||||
"4K 60fps MP4 with H264 video and AAC audio (portrait, timestamps always increase)",
|
||||
"8k 24fps MP4 with H265 video and AAC audio",
|
||||
"MP4 with H264 video and AAC audio (portrait, H > W, 0\u00B0)",
|
||||
"MP4 with H264 video and AAC audio (portrait, H < W, 90\u00B0)",
|
||||
"SEF slow motion with 240 fps",
|
||||
"MP4 with HDR (HDR10) H265 video (encoding may fail)",
|
||||
};
|
||||
private static final String SAME_AS_INPUT_OPTION = "same as input";
|
||||
|
||||
|
|
@ -81,10 +90,10 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||
private @MonotonicNonNull Spinner audioMimeSpinner;
|
||||
private @MonotonicNonNull Spinner videoMimeSpinner;
|
||||
private @MonotonicNonNull Spinner resolutionHeightSpinner;
|
||||
private @MonotonicNonNull Spinner translateSpinner;
|
||||
private @MonotonicNonNull Spinner scaleSpinner;
|
||||
private @MonotonicNonNull Spinner rotateSpinner;
|
||||
private @MonotonicNonNull CheckBox enableFallbackCheckBox;
|
||||
private @MonotonicNonNull CheckBox enableRequestSdrToneMappingCheckBox;
|
||||
private @MonotonicNonNull CheckBox enableHdrEditingCheckBox;
|
||||
private int inputUriPosition;
|
||||
|
||||
|
|
@ -136,14 +145,6 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||
resolutionHeightAdapter.addAll(
|
||||
SAME_AS_INPUT_OPTION, "144", "240", "360", "480", "720", "1080", "1440", "2160");
|
||||
|
||||
ArrayAdapter<String> translateAdapter =
|
||||
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
||||
translateAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
translateSpinner = findViewById(R.id.translate_spinner);
|
||||
translateSpinner.setAdapter(translateAdapter);
|
||||
translateAdapter.addAll(
|
||||
SAME_AS_INPUT_OPTION, "-.1, -.1", "0, 0", ".5, 0", "0, .5", "1, 1", "1.9, 0", "0, 1.9");
|
||||
|
||||
ArrayAdapter<String> scaleAdapter =
|
||||
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
||||
scaleAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
|
|
@ -159,6 +160,9 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||
rotateAdapter.addAll(SAME_AS_INPUT_OPTION, "0", "10", "45", "60", "90", "180");
|
||||
|
||||
enableFallbackCheckBox = findViewById(R.id.enable_fallback_checkbox);
|
||||
enableRequestSdrToneMappingCheckBox = findViewById(R.id.request_sdr_tone_mapping_checkbox);
|
||||
enableRequestSdrToneMappingCheckBox.setEnabled(isRequestSdrToneMappingSupported());
|
||||
findViewById(R.id.request_sdr_tone_mapping).setEnabled(isRequestSdrToneMappingSupported());
|
||||
enableHdrEditingCheckBox = findViewById(R.id.hdr_editing_checkbox);
|
||||
}
|
||||
|
||||
|
|
@ -185,10 +189,10 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||
"audioMimeSpinner",
|
||||
"videoMimeSpinner",
|
||||
"resolutionHeightSpinner",
|
||||
"translateSpinner",
|
||||
"scaleSpinner",
|
||||
"rotateSpinner",
|
||||
"enableFallbackCheckBox",
|
||||
"enableRequestSdrToneMappingCheckBox",
|
||||
"enableHdrEditingCheckBox"
|
||||
})
|
||||
private void startTransformation(View view) {
|
||||
|
|
@ -209,13 +213,6 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||
if (!SAME_AS_INPUT_OPTION.equals(selectedResolutionHeight)) {
|
||||
bundle.putInt(RESOLUTION_HEIGHT, Integer.parseInt(selectedResolutionHeight));
|
||||
}
|
||||
String selectedTranslate = String.valueOf(translateSpinner.getSelectedItem());
|
||||
if (!SAME_AS_INPUT_OPTION.equals(selectedTranslate)) {
|
||||
List<String> translateXY = Arrays.asList(selectedTranslate.split(", "));
|
||||
checkState(translateXY.size() == 2);
|
||||
bundle.putFloat(TRANSLATE_X, Float.parseFloat(translateXY.get(0)));
|
||||
bundle.putFloat(TRANSLATE_Y, Float.parseFloat(translateXY.get(1)));
|
||||
}
|
||||
String selectedScale = String.valueOf(scaleSpinner.getSelectedItem());
|
||||
if (!SAME_AS_INPUT_OPTION.equals(selectedScale)) {
|
||||
List<String> scaleXY = Arrays.asList(selectedScale.split(", "));
|
||||
|
|
@ -228,6 +225,8 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||
bundle.putFloat(ROTATE_DEGREES, Float.parseFloat(selectedRotate));
|
||||
}
|
||||
bundle.putBoolean(ENABLE_FALLBACK, enableFallbackCheckBox.isChecked());
|
||||
bundle.putBoolean(
|
||||
ENABLE_REQUEST_SDR_TONE_MAPPING, enableRequestSdrToneMappingCheckBox.isChecked());
|
||||
bundle.putBoolean(ENABLE_HDR_EDITING, enableHdrEditingCheckBox.isChecked());
|
||||
transformerIntent.putExtras(bundle);
|
||||
|
||||
|
|
@ -258,9 +257,9 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||
"audioMimeSpinner",
|
||||
"videoMimeSpinner",
|
||||
"resolutionHeightSpinner",
|
||||
"translateSpinner",
|
||||
"scaleSpinner",
|
||||
"rotateSpinner",
|
||||
"enableRequestSdrToneMappingCheckBox",
|
||||
"enableHdrEditingCheckBox"
|
||||
})
|
||||
private void onRemoveAudio(View view) {
|
||||
|
|
@ -277,9 +276,9 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||
"audioMimeSpinner",
|
||||
"videoMimeSpinner",
|
||||
"resolutionHeightSpinner",
|
||||
"translateSpinner",
|
||||
"scaleSpinner",
|
||||
"rotateSpinner",
|
||||
"enableRequestSdrToneMappingCheckBox",
|
||||
"enableHdrEditingCheckBox"
|
||||
})
|
||||
private void onRemoveVideo(View view) {
|
||||
|
|
@ -295,26 +294,32 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||
"audioMimeSpinner",
|
||||
"videoMimeSpinner",
|
||||
"resolutionHeightSpinner",
|
||||
"translateSpinner",
|
||||
"scaleSpinner",
|
||||
"rotateSpinner",
|
||||
"enableRequestSdrToneMappingCheckBox",
|
||||
"enableHdrEditingCheckBox"
|
||||
})
|
||||
private void enableTrackSpecificOptions(boolean isAudioEnabled, boolean isVideoEnabled) {
|
||||
audioMimeSpinner.setEnabled(isAudioEnabled);
|
||||
videoMimeSpinner.setEnabled(isVideoEnabled);
|
||||
resolutionHeightSpinner.setEnabled(isVideoEnabled);
|
||||
translateSpinner.setEnabled(isVideoEnabled);
|
||||
scaleSpinner.setEnabled(isVideoEnabled);
|
||||
rotateSpinner.setEnabled(isVideoEnabled);
|
||||
enableRequestSdrToneMappingCheckBox.setEnabled(
|
||||
isRequestSdrToneMappingSupported() && isVideoEnabled);
|
||||
enableHdrEditingCheckBox.setEnabled(isVideoEnabled);
|
||||
|
||||
findViewById(R.id.audio_mime_text_view).setEnabled(isAudioEnabled);
|
||||
findViewById(R.id.video_mime_text_view).setEnabled(isVideoEnabled);
|
||||
findViewById(R.id.resolution_height_text_view).setEnabled(isVideoEnabled);
|
||||
findViewById(R.id.translate).setEnabled(isVideoEnabled);
|
||||
findViewById(R.id.scale).setEnabled(isVideoEnabled);
|
||||
findViewById(R.id.rotate).setEnabled(isVideoEnabled);
|
||||
findViewById(R.id.request_sdr_tone_mapping)
|
||||
.setEnabled(isRequestSdrToneMappingSupported() && isVideoEnabled);
|
||||
findViewById(R.id.hdr_editing).setEnabled(isVideoEnabled);
|
||||
}
|
||||
|
||||
private static boolean isRequestSdrToneMappingSupported() {
|
||||
return Util.SDK_INT >= 31;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import static androidx.media3.common.util.Assertions.checkNotNull;
|
|||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Matrix;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
|
|
@ -217,10 +216,17 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||
if (resolutionHeight != C.LENGTH_UNSET) {
|
||||
requestBuilder.setResolution(resolutionHeight);
|
||||
}
|
||||
Matrix transformationMatrix = getTransformationMatrix(bundle);
|
||||
if (!transformationMatrix.isIdentity()) {
|
||||
requestBuilder.setTransformationMatrix(transformationMatrix);
|
||||
}
|
||||
|
||||
float scaleX = bundle.getFloat(ConfigurationActivity.SCALE_X, /* defaultValue= */ 1);
|
||||
float scaleY = bundle.getFloat(ConfigurationActivity.SCALE_Y, /* defaultValue= */ 1);
|
||||
requestBuilder.setScale(scaleX, scaleY);
|
||||
|
||||
float rotateDegrees =
|
||||
bundle.getFloat(ConfigurationActivity.ROTATE_DEGREES, /* defaultValue= */ 0);
|
||||
requestBuilder.setRotationDegrees(rotateDegrees);
|
||||
|
||||
requestBuilder.setEnableRequestSdrToneMapping(
|
||||
bundle.getBoolean(ConfigurationActivity.ENABLE_REQUEST_SDR_TONE_MAPPING));
|
||||
requestBuilder.experimental_setEnableHdrEditing(
|
||||
bundle.getBoolean(ConfigurationActivity.ENABLE_HDR_EDITING));
|
||||
transformerBuilder
|
||||
|
|
@ -251,27 +257,6 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||
.build();
|
||||
}
|
||||
|
||||
private static Matrix getTransformationMatrix(Bundle bundle) {
|
||||
Matrix transformationMatrix = new Matrix();
|
||||
|
||||
float translateX = bundle.getFloat(ConfigurationActivity.TRANSLATE_X, /* defaultValue= */ 0);
|
||||
float translateY = bundle.getFloat(ConfigurationActivity.TRANSLATE_Y, /* defaultValue= */ 0);
|
||||
// TODO(b/201293185): Implement an AdvancedFrameEditor to handle translation, as the current
|
||||
// transformationMatrix is automatically adjusted to focus on the original pixels and
|
||||
// effectively undo translations.
|
||||
transformationMatrix.postTranslate(translateX, translateY);
|
||||
|
||||
float scaleX = bundle.getFloat(ConfigurationActivity.SCALE_X, /* defaultValue= */ 1);
|
||||
float scaleY = bundle.getFloat(ConfigurationActivity.SCALE_Y, /* defaultValue= */ 1);
|
||||
transformationMatrix.postScale(scaleX, scaleY);
|
||||
|
||||
float rotateDegrees =
|
||||
bundle.getFloat(ConfigurationActivity.ROTATE_DEGREES, /* defaultValue= */ 0);
|
||||
transformationMatrix.postRotate(rotateDegrees);
|
||||
|
||||
return transformationMatrix;
|
||||
}
|
||||
|
||||
@RequiresNonNull({
|
||||
"informationTextView",
|
||||
"progressViewGroup",
|
||||
|
|
|
|||
|
|
@ -137,17 +137,6 @@
|
|||
android:layout_gravity="right|center_vertical"
|
||||
android:gravity="right" />
|
||||
</TableRow>
|
||||
<TableRow
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical" >
|
||||
<TextView
|
||||
android:id="@+id/translate"
|
||||
android:text="@string/translate"/>
|
||||
<Spinner
|
||||
android:id="@+id/translate_spinner"
|
||||
android:layout_gravity="right|center_vertical"
|
||||
android:gravity="right" />
|
||||
</TableRow>
|
||||
<TableRow
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical" >
|
||||
|
|
@ -180,6 +169,16 @@
|
|||
android:layout_gravity="right"
|
||||
android:checked="true"/>
|
||||
</TableRow>
|
||||
<TableRow
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical" >
|
||||
<TextView
|
||||
android:id="@+id/request_sdr_tone_mapping"
|
||||
android:text="@string/request_sdr_tone_mapping" />
|
||||
<CheckBox
|
||||
android:id="@+id/request_sdr_tone_mapping_checkbox"
|
||||
android:layout_gravity="right" />
|
||||
</TableRow>
|
||||
<TableRow
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical" >
|
||||
|
|
|
|||
|
|
@ -24,12 +24,12 @@
|
|||
<string name="audio_mime" translatable="false">Output audio MIME type</string>
|
||||
<string name="video_mime" translatable="false">Output video MIME type</string>
|
||||
<string name="resolution_height" translatable="false">Output video resolution</string>
|
||||
<string name="translate" translatable="false">Translate video</string>
|
||||
<string name="scale" translatable="false">Scale video</string>
|
||||
<string name="rotate" translatable="false">Rotate video (degrees)</string>
|
||||
<string name="enable_fallback" translatable="false">Enable fallback</string>
|
||||
<string name="transform" translatable="false">Transform</string>
|
||||
<string name="request_sdr_tone_mapping" translatable="false">Request SDR tone-mapping (API 31+)</string>
|
||||
<string name="hdr_editing" translatable="false">[Experimental] HDR editing</string>
|
||||
<string name="transform" translatable="false">Transform</string>
|
||||
<string name="debug_preview" translatable="false">Debug preview:</string>
|
||||
<string name="debug_preview_not_available" translatable="false">No debug preview available.</string>
|
||||
<string name="transformation_started" translatable="false">Transformation started</string>
|
||||
|
|
|
|||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -1,6 +1,5 @@
|
|||
#Wed Mar 04 12:41:50 GMT 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https://services.gradle.org/distributions/gradle-7.3.3-all.zip
|
||||
|
|
|
|||
302
gradlew
vendored
302
gradlew
vendored
|
|
@ -1,79 +1,129 @@
|
|||
#!/usr/bin/env bash
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# https://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.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn ( ) {
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die ( ) {
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >&-
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >&-
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
|
@ -82,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
|||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
|
|
@ -90,75 +140,95 @@ location of your Java installation."
|
|||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
|
|
|||
179
gradlew.bat
vendored
179
gradlew.bat
vendored
|
|
@ -1,90 +1,89 @@
|
|||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
|
|
|
|||
|
|
@ -34,17 +34,14 @@ import androidx.media3.common.DeviceInfo;
|
|||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.MediaLibraryInfo;
|
||||
import androidx.media3.common.MediaMetadata;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.PlaybackException;
|
||||
import androidx.media3.common.PlaybackParameters;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.TrackSelection;
|
||||
import androidx.media3.common.TrackSelectionArray;
|
||||
import androidx.media3.common.TrackSelectionParameters;
|
||||
import androidx.media3.common.TracksInfo;
|
||||
import androidx.media3.common.TracksInfo.TrackGroupInfo;
|
||||
import androidx.media3.common.VideoSize;
|
||||
import androidx.media3.common.text.Cue;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
|
|
@ -68,7 +65,6 @@ import com.google.android.gms.common.api.PendingResult;
|
|||
import com.google.android.gms.common.api.ResultCallback;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.List;
|
||||
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
|
||||
/**
|
||||
|
|
@ -115,13 +111,7 @@ public final class CastPlayer extends BasePlayer {
|
|||
|
||||
private static final String TAG = "CastPlayer";
|
||||
|
||||
private static final int RENDERER_COUNT = 3;
|
||||
private static final int RENDERER_INDEX_VIDEO = 0;
|
||||
private static final int RENDERER_INDEX_AUDIO = 1;
|
||||
private static final int RENDERER_INDEX_TEXT = 2;
|
||||
private static final long PROGRESS_REPORT_PERIOD_MS = 1000;
|
||||
private static final TrackSelectionArray EMPTY_TRACK_SELECTION_ARRAY =
|
||||
new TrackSelectionArray(null, null, null);
|
||||
private static final long[] EMPTY_TRACK_ID_ARRAY = new long[0];
|
||||
|
||||
private final CastContext castContext;
|
||||
|
|
@ -146,8 +136,6 @@ public final class CastPlayer extends BasePlayer {
|
|||
private final StateHolder<PlaybackParameters> playbackParameters;
|
||||
@Nullable private RemoteMediaClient remoteMediaClient;
|
||||
private CastTimeline currentTimeline;
|
||||
private TrackGroupArray currentTrackGroups;
|
||||
private TrackSelectionArray currentTrackSelection;
|
||||
private TracksInfo currentTracksInfo;
|
||||
private Commands availableCommands;
|
||||
private @Player.State int playbackState;
|
||||
|
|
@ -224,8 +212,6 @@ public final class CastPlayer extends BasePlayer {
|
|||
playbackParameters = new StateHolder<>(PlaybackParameters.DEFAULT);
|
||||
playbackState = STATE_IDLE;
|
||||
currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE;
|
||||
currentTrackGroups = TrackGroupArray.EMPTY;
|
||||
currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY;
|
||||
currentTracksInfo = TracksInfo.EMPTY;
|
||||
availableCommands = new Commands.Builder().addAll(PERMANENT_AVAILABLE_COMMANDS).build();
|
||||
pendingSeekWindowIndex = C.INDEX_UNSET;
|
||||
|
|
@ -557,16 +543,6 @@ public final class CastPlayer extends BasePlayer {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackGroupArray getCurrentTrackGroups() {
|
||||
return currentTrackGroups;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackSelectionArray getCurrentTrackSelections() {
|
||||
return currentTrackSelection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracksInfo getCurrentTracksInfo() {
|
||||
return currentTracksInfo;
|
||||
|
|
@ -841,9 +817,6 @@ public final class CastPlayer extends BasePlayer {
|
|||
getCurrentMediaItem(), MEDIA_ITEM_TRANSITION_REASON_AUTO));
|
||||
}
|
||||
if (updateTracksAndSelectionsAndNotifyIfChanged()) {
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_TRACKS_CHANGED,
|
||||
listener -> listener.onTracksChanged(currentTrackGroups, currentTrackSelection));
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksInfoChanged(currentTracksInfo));
|
||||
}
|
||||
|
|
@ -1000,55 +973,33 @@ public final class CastPlayer extends BasePlayer {
|
|||
return false;
|
||||
}
|
||||
|
||||
MediaStatus mediaStatus = getMediaStatus();
|
||||
MediaInfo mediaInfo = mediaStatus != null ? mediaStatus.getMediaInfo() : null;
|
||||
@Nullable MediaStatus mediaStatus = getMediaStatus();
|
||||
@Nullable MediaInfo mediaInfo = mediaStatus != null ? mediaStatus.getMediaInfo() : null;
|
||||
@Nullable
|
||||
List<MediaTrack> castMediaTracks = mediaInfo != null ? mediaInfo.getMediaTracks() : null;
|
||||
if (castMediaTracks == null || castMediaTracks.isEmpty()) {
|
||||
boolean hasChanged = !currentTrackGroups.isEmpty();
|
||||
currentTrackGroups = TrackGroupArray.EMPTY;
|
||||
currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY;
|
||||
boolean hasChanged = !TracksInfo.EMPTY.equals(currentTracksInfo);
|
||||
currentTracksInfo = TracksInfo.EMPTY;
|
||||
return hasChanged;
|
||||
}
|
||||
long[] activeTrackIds = mediaStatus.getActiveTrackIds();
|
||||
@Nullable long[] activeTrackIds = mediaStatus.getActiveTrackIds();
|
||||
if (activeTrackIds == null) {
|
||||
activeTrackIds = EMPTY_TRACK_ID_ARRAY;
|
||||
}
|
||||
|
||||
TrackGroup[] trackGroups = new TrackGroup[castMediaTracks.size()];
|
||||
@NullableType TrackSelection[] trackSelections = new TrackSelection[RENDERER_COUNT];
|
||||
TracksInfo.TrackGroupInfo[] trackGroupInfos =
|
||||
new TracksInfo.TrackGroupInfo[castMediaTracks.size()];
|
||||
TrackGroupInfo[] trackGroupInfos = new TrackGroupInfo[castMediaTracks.size()];
|
||||
for (int i = 0; i < castMediaTracks.size(); i++) {
|
||||
MediaTrack mediaTrack = castMediaTracks.get(i);
|
||||
trackGroups[i] =
|
||||
TrackGroup trackGroup =
|
||||
new TrackGroup(/* id= */ Integer.toString(i), CastUtils.mediaTrackToFormat(mediaTrack));
|
||||
|
||||
long id = mediaTrack.getId();
|
||||
@C.TrackType int trackType = MimeTypes.getTrackType(mediaTrack.getContentType());
|
||||
int rendererIndex = getRendererIndexForTrackType(trackType);
|
||||
boolean supported = rendererIndex != C.INDEX_UNSET;
|
||||
boolean selected =
|
||||
isTrackActive(id, activeTrackIds) && supported && trackSelections[rendererIndex] == null;
|
||||
if (selected) {
|
||||
trackSelections[rendererIndex] = new CastTrackSelection(trackGroups[i]);
|
||||
}
|
||||
@C.FormatSupport
|
||||
int[] trackSupport = new int[] {supported ? C.FORMAT_HANDLED : C.FORMAT_UNSUPPORTED_TYPE};
|
||||
final boolean[] trackSelected = new boolean[] {selected};
|
||||
@C.FormatSupport int[] trackSupport = new int[] {C.FORMAT_HANDLED};
|
||||
boolean[] trackSelected = new boolean[] {isTrackActive(mediaTrack.getId(), activeTrackIds)};
|
||||
trackGroupInfos[i] =
|
||||
new TracksInfo.TrackGroupInfo(
|
||||
trackGroups[i], /* adaptiveSupported= */ false, trackSupport, trackSelected);
|
||||
new TrackGroupInfo(
|
||||
trackGroup, /* adaptiveSupported= */ false, trackSupport, trackSelected);
|
||||
}
|
||||
TrackGroupArray newTrackGroups = new TrackGroupArray(trackGroups);
|
||||
TrackSelectionArray newTrackSelections = new TrackSelectionArray(trackSelections);
|
||||
TracksInfo newTracksInfo = new TracksInfo(ImmutableList.copyOf(trackGroupInfos));
|
||||
|
||||
if (!newTrackGroups.equals(currentTrackGroups)
|
||||
|| !newTrackSelections.equals(currentTrackSelection)
|
||||
|| !newTracksInfo.equals(currentTracksInfo)) {
|
||||
currentTrackSelection = newTrackSelections;
|
||||
currentTrackGroups = newTrackGroups;
|
||||
if (!newTracksInfo.equals(currentTracksInfo)) {
|
||||
currentTracksInfo = newTracksInfo;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1307,14 +1258,6 @@ public final class CastPlayer extends BasePlayer {
|
|||
return false;
|
||||
}
|
||||
|
||||
private static int getRendererIndexForTrackType(@C.TrackType int trackType) {
|
||||
return trackType == C.TRACK_TYPE_VIDEO
|
||||
? RENDERER_INDEX_VIDEO
|
||||
: trackType == C.TRACK_TYPE_AUDIO
|
||||
? RENDERER_INDEX_AUDIO
|
||||
: trackType == C.TRACK_TYPE_TEXT ? RENDERER_INDEX_TEXT : C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
private static int getCastRepeatMode(@RepeatMode int repeatMode) {
|
||||
switch (repeatMode) {
|
||||
case REPEAT_MODE_ONE:
|
||||
|
|
|
|||
|
|
@ -1,98 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 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.cast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackSelection;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
|
||||
/**
|
||||
* {@link TrackSelection} that only selects the first track of the provided {@link TrackGroup}.
|
||||
*
|
||||
* <p>This relies on {@link CastPlayer} track groups only having one track.
|
||||
*/
|
||||
/* package */ class CastTrackSelection implements TrackSelection {
|
||||
|
||||
private final TrackGroup trackGroup;
|
||||
|
||||
/**
|
||||
* @param trackGroup The {@link TrackGroup} from which the first track will only be selected.
|
||||
*/
|
||||
public CastTrackSelection(TrackGroup trackGroup) {
|
||||
this.trackGroup = trackGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return TYPE_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackGroup getTrackGroup() {
|
||||
return trackGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Format getFormat(int index) {
|
||||
Assertions.checkArgument(index == 0);
|
||||
return trackGroup.getFormat(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndexInTrackGroup(int index) {
|
||||
return index == 0 ? 0 : C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("ReferenceEquality")
|
||||
public int indexOf(Format format) {
|
||||
return format == trackGroup.getFormat(0) ? 0 : C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(int indexInTrackGroup) {
|
||||
return indexInTrackGroup == 0 ? 0 : C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
// Object overrides.
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return System.identityHashCode(trackGroup);
|
||||
}
|
||||
|
||||
// Track groups are compared by identity not value, as distinct groups may have the same value.
|
||||
@Override
|
||||
@SuppressWarnings({"ReferenceEquality", "EqualsGetClass"})
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
CastTrackSelection other = (CastTrackSelection) obj;
|
||||
return trackGroup == other.trackGroup;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2018 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.cast;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** Test for {@link CastTrackSelection}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class CastTrackSelectionTest {
|
||||
|
||||
private static final TrackGroup TRACK_GROUP =
|
||||
new TrackGroup(new Format.Builder().build(), new Format.Builder().build());
|
||||
|
||||
private static final CastTrackSelection SELECTION = new CastTrackSelection(TRACK_GROUP);
|
||||
|
||||
@Test
|
||||
public void length_isOne() {
|
||||
assertThat(SELECTION.length()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTrackGroup_returnsSameGroup() {
|
||||
assertThat(SELECTION.getTrackGroup()).isSameInstanceAs(TRACK_GROUP);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFormatSelectedTrack_isFirstTrack() {
|
||||
assertThat(SELECTION.getFormat(0)).isSameInstanceAs(TRACK_GROUP.getFormat(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIndexInTrackGroup_ofSelectedTrack_returnsFirstTrack() {
|
||||
assertThat(SELECTION.getIndexInTrackGroup(0)).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIndexInTrackGroup_onePastTheEnd_returnsIndexUnset() {
|
||||
assertThat(SELECTION.getIndexInTrackGroup(1)).isEqualTo(C.INDEX_UNSET);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOf_selectedTrack_returnsFirstTrack() {
|
||||
assertThat(SELECTION.indexOf(0)).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOf_onePastTheEnd_returnsIndexUnset() {
|
||||
assertThat(SELECTION.indexOf(1)).isEqualTo(C.INDEX_UNSET);
|
||||
}
|
||||
|
||||
@Test(expected = Exception.class)
|
||||
public void getFormat_outOfBound_throws() {
|
||||
CastTrackSelection selection = new CastTrackSelection(TRACK_GROUP);
|
||||
|
||||
selection.getFormat(1);
|
||||
}
|
||||
}
|
||||
|
|
@ -827,6 +827,36 @@ public final class AdPlaybackState implements Bundleable {
|
|||
adsId, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the ad playback state with the given ads ID.
|
||||
*
|
||||
* @param adsId The new ads ID.
|
||||
* @param adPlaybackState The ad playback state to copy.
|
||||
* @return The new ad playback state.
|
||||
*/
|
||||
public static AdPlaybackState fromAdPlaybackState(Object adsId, AdPlaybackState adPlaybackState) {
|
||||
AdGroup[] adGroups =
|
||||
new AdGroup[adPlaybackState.adGroupCount - adPlaybackState.removedAdGroupCount];
|
||||
for (int i = 0; i < adGroups.length; i++) {
|
||||
AdGroup adGroup = adPlaybackState.adGroups[i];
|
||||
adGroups[i] =
|
||||
new AdGroup(
|
||||
adGroup.timeUs,
|
||||
adGroup.count,
|
||||
Arrays.copyOf(adGroup.states, adGroup.states.length),
|
||||
Arrays.copyOf(adGroup.uris, adGroup.uris.length),
|
||||
Arrays.copyOf(adGroup.durationsUs, adGroup.durationsUs.length),
|
||||
adGroup.contentResumeOffsetUs,
|
||||
adGroup.isServerSideInserted);
|
||||
}
|
||||
return new AdPlaybackState(
|
||||
adsId,
|
||||
adGroups,
|
||||
adPlaybackState.adResumePositionUs,
|
||||
adPlaybackState.contentDurationUs,
|
||||
adPlaybackState.removedAdGroupCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) {
|
||||
|
|
|
|||
|
|
@ -732,17 +732,17 @@ public final class C {
|
|||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_RTSP, TYPE_OTHER})
|
||||
public @interface ContentType {}
|
||||
/** Value returned by {@link Util#inferContentType(String)} for DASH manifests. */
|
||||
/** Value returned by {@link Util#inferContentType} for DASH manifests. */
|
||||
@UnstableApi public static final int TYPE_DASH = 0;
|
||||
/** Value returned by {@link Util#inferContentType(String)} for Smooth Streaming manifests. */
|
||||
/** Value returned by {@link Util#inferContentType} for Smooth Streaming manifests. */
|
||||
@UnstableApi public static final int TYPE_SS = 1;
|
||||
/** Value returned by {@link Util#inferContentType(String)} for HLS manifests. */
|
||||
/** Value returned by {@link Util#inferContentType} for HLS manifests. */
|
||||
@UnstableApi public static final int TYPE_HLS = 2;
|
||||
/** Value returned by {@link Util#inferContentType(String)} for RTSP. */
|
||||
/** Value returned by {@link Util#inferContentType} for RTSP. */
|
||||
@UnstableApi public static final int TYPE_RTSP = 3;
|
||||
/**
|
||||
* Value returned by {@link Util#inferContentType(String)} for files other than DASH, HLS or
|
||||
* Smooth Streaming manifests, or RTSP URIs.
|
||||
* Value returned by {@link Util#inferContentType} for files other than DASH, HLS or Smooth
|
||||
* Streaming manifests, or RTSP URIs.
|
||||
*/
|
||||
@UnstableApi public static final int TYPE_OTHER = 4;
|
||||
|
||||
|
|
|
|||
|
|
@ -442,22 +442,6 @@ public class ForwardingPlayer implements Player {
|
|||
player.release();
|
||||
}
|
||||
|
||||
/** Calls {@link Player#getCurrentTrackGroups()} on the delegate and returns the result. */
|
||||
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
||||
@Deprecated
|
||||
@Override
|
||||
public TrackGroupArray getCurrentTrackGroups() {
|
||||
return player.getCurrentTrackGroups();
|
||||
}
|
||||
|
||||
/** Calls {@link Player#getCurrentTrackSelections()} on the delegate and returns the result. */
|
||||
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
||||
@Deprecated
|
||||
@Override
|
||||
public TrackSelectionArray getCurrentTrackSelections() {
|
||||
return player.getCurrentTrackSelections();
|
||||
}
|
||||
|
||||
/** Calls {@link Player#getCurrentTracksInfo()} on the delegate and returns the result. */
|
||||
@Override
|
||||
public TracksInfo getCurrentTracksInfo() {
|
||||
|
|
@ -846,12 +830,6 @@ public class ForwardingPlayer implements Player {
|
|||
listener.onMediaItemTransition(mediaItem, reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
||||
listener.onTracksChanged(trackGroups, trackSelections);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTracksInfoChanged(TracksInfo tracksInfo) {
|
||||
listener.onTracksInfoChanged(tracksInfo);
|
||||
|
|
|
|||
|
|
@ -27,23 +27,27 @@ public final class MediaLibraryInfo {
|
|||
/** A tag to use when logging library information. */
|
||||
public static final String TAG = "AndroidXMedia3";
|
||||
|
||||
/** The version of the library expressed as a string, for example "1.2.3". */
|
||||
/** The version of the library expressed as a string, for example "1.2.3" or "1.2.3-beta01". */
|
||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
|
||||
public static final String VERSION = "1.0.0-alpha01";
|
||||
public static final String VERSION = "1.0.0-alpha03";
|
||||
|
||||
/** The version of the library expressed as {@code TAG + "/" + VERSION}. */
|
||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
||||
public static final String VERSION_SLASHY = "AndroidXMedia3/1.0.0-alpha01";
|
||||
public static final String VERSION_SLASHY = "AndroidXMedia3/1.0.0-alpha03";
|
||||
|
||||
/**
|
||||
* The version of the library expressed as an integer, for example 1002003.
|
||||
* The version of the library expressed as an integer, for example 1002003300.
|
||||
*
|
||||
* <p>Three digits are used for each component of {@link #VERSION}. For example "1.2.3" has the
|
||||
* corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding
|
||||
* integer version 123045006 (123-045-006).
|
||||
* <p>Three digits are used for each of the first three components of {@link #VERSION}, then a
|
||||
* single digit represents the cycle of this version: alpha (0), beta (1), rc (2) or stable (3).
|
||||
* Finally two digits are used for the cycle number (always 00 for stable releases).
|
||||
*
|
||||
* <p>For example "1.2.3-alpha05" has the corresponding integer version 1002003005
|
||||
* (001-002-003-0-05), and "123.45.6" has the corresponding integer version 123045006300
|
||||
* (123-045-006-3-00).
|
||||
*/
|
||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
||||
public static final int VERSION_INT = 1000000;
|
||||
public static final int VERSION_INT = 1_000_000_0_03;
|
||||
|
||||
/** Whether the library was compiled with {@link Assertions} checks enabled. */
|
||||
public static final boolean ASSERTIONS_ENABLED = true;
|
||||
|
|
|
|||
|
|
@ -108,11 +108,10 @@ public final class MimeTypes {
|
|||
public static final String APPLICATION_MP4 = BASE_TYPE_APPLICATION + "/mp4";
|
||||
public static final String APPLICATION_WEBM = BASE_TYPE_APPLICATION + "/webm";
|
||||
|
||||
@UnstableApi
|
||||
public static final String APPLICATION_MATROSKA = BASE_TYPE_APPLICATION + "/x-matroska";
|
||||
|
||||
public static final String APPLICATION_MPD = BASE_TYPE_APPLICATION + "/dash+xml";
|
||||
@UnstableApi public static final String APPLICATION_M3U8 = BASE_TYPE_APPLICATION + "/x-mpegURL";
|
||||
public static final String APPLICATION_M3U8 = BASE_TYPE_APPLICATION + "/x-mpegURL";
|
||||
public static final String APPLICATION_SS = BASE_TYPE_APPLICATION + "/vnd.ms-sstr+xml";
|
||||
public static final String APPLICATION_ID3 = BASE_TYPE_APPLICATION + "/id3";
|
||||
public static final String APPLICATION_CEA608 = BASE_TYPE_APPLICATION + "/cea-608";
|
||||
|
|
@ -135,7 +134,7 @@ public final class MimeTypes {
|
|||
@UnstableApi public static final String APPLICATION_EXIF = BASE_TYPE_APPLICATION + "/x-exif";
|
||||
@UnstableApi public static final String APPLICATION_ICY = BASE_TYPE_APPLICATION + "/x-icy";
|
||||
public static final String APPLICATION_AIT = BASE_TYPE_APPLICATION + "/vnd.dvb.ait";
|
||||
@UnstableApi public static final String APPLICATION_RTSP = BASE_TYPE_APPLICATION + "/x-rtsp";
|
||||
public static final String APPLICATION_RTSP = BASE_TYPE_APPLICATION + "/x-rtsp";
|
||||
|
||||
// image/ MIME types
|
||||
|
||||
|
|
|
|||
|
|
@ -670,24 +670,6 @@ public interface Player {
|
|||
default void onMediaItemTransition(
|
||||
@Nullable MediaItem mediaItem, @MediaItemTransitionReason int reason) {}
|
||||
|
||||
/**
|
||||
* Called when the available or selected tracks change.
|
||||
*
|
||||
* <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
|
||||
* other events that happen in the same {@link Looper} message queue iteration.
|
||||
*
|
||||
* @param trackGroups The available tracks. Never null, but may be of length zero.
|
||||
* @param trackSelections The selected tracks. Never null, but may contain null elements. A
|
||||
* concrete implementation may include null elements if it has a fixed number of renderer
|
||||
* components, wishes to report a TrackSelection for each of them, and has one or more
|
||||
* renderer components that is not assigned any selected tracks.
|
||||
* @deprecated Use {@link #onTracksInfoChanged(TracksInfo)} instead.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
default void onTracksChanged(
|
||||
TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {}
|
||||
|
||||
/**
|
||||
* Called when the available or selected tracks change.
|
||||
*
|
||||
|
|
@ -701,11 +683,12 @@ public interface Player {
|
|||
/**
|
||||
* Called when the combined {@link MediaMetadata} changes.
|
||||
*
|
||||
* <p>The provided {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata}
|
||||
* and the static and dynamic metadata from the {@link TrackSelection#getFormat(int) track
|
||||
* selections' formats} and {@link Listener#onMetadata(Metadata)}. If a field is populated in
|
||||
* the {@link MediaItem#mediaMetadata}, it will be prioritised above the same field coming from
|
||||
* static or dynamic metadata.
|
||||
* <p>The provided {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata
|
||||
* MediaItem metadata}, the static metadata in the media's {@link Format#metadata Format}, and
|
||||
* any timed metadata that has been parsed from the media and output via {@link
|
||||
* Listener#onMetadata(Metadata)}. If a field is populated in the {@link
|
||||
* MediaItem#mediaMetadata}, it will be prioritised above the same field coming from static or
|
||||
* timed metadata.
|
||||
*
|
||||
* <p>This method may be called multiple times in quick succession.
|
||||
*
|
||||
|
|
@ -2105,33 +2088,9 @@ public interface Player {
|
|||
void release();
|
||||
|
||||
/**
|
||||
* Returns the available track groups.
|
||||
* Returns information about the current tracks.
|
||||
*
|
||||
* @see Listener#onTracksChanged(TrackGroupArray, TrackSelectionArray)
|
||||
* @deprecated Use {@link #getCurrentTracksInfo()}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
TrackGroupArray getCurrentTrackGroups();
|
||||
|
||||
/**
|
||||
* Returns the current track selections.
|
||||
*
|
||||
* <p>A concrete implementation may include null elements if it has a fixed number of renderer
|
||||
* components, wishes to report a TrackSelection for each of them, and has one or more renderer
|
||||
* components that is not assigned any selected tracks.
|
||||
*
|
||||
* @see Listener#onTracksChanged(TrackGroupArray, TrackSelectionArray)
|
||||
* @deprecated Use {@link #getCurrentTracksInfo()}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
TrackSelectionArray getCurrentTrackSelections();
|
||||
|
||||
/**
|
||||
* Returns the available tracks, as well as the tracks' support, type, and selection status.
|
||||
*
|
||||
* @see Listener#onTracksChanged(TrackGroupArray, TrackSelectionArray)
|
||||
* @see Listener#onTracksInfoChanged(TracksInfo)
|
||||
*/
|
||||
TracksInfo getCurrentTracksInfo();
|
||||
|
||||
|
|
@ -2165,11 +2124,11 @@ public interface Player {
|
|||
* Returns the current combined {@link MediaMetadata}, or {@link MediaMetadata#EMPTY} if not
|
||||
* supported.
|
||||
*
|
||||
* <p>This {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata} and the
|
||||
* static and dynamic metadata from the {@link TrackSelection#getFormat(int) track selections'
|
||||
* formats} and {@link Listener#onMetadata(Metadata)}. If a field is populated in the {@link
|
||||
* MediaItem#mediaMetadata}, it will be prioritised above the same field coming from static or
|
||||
* dynamic metadata.
|
||||
* <p>This {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata MediaItem
|
||||
* metadata}, the static metadata in the media's {@link Format#metadata Format}, and any timed
|
||||
* metadata that has been parsed from the media and output via {@link
|
||||
* Listener#onMetadata(Metadata)}. If a field is populated in the {@link MediaItem#mediaMetadata},
|
||||
* it will be prioritised above the same field coming from static or timed metadata.
|
||||
*/
|
||||
MediaMetadata getMediaMetadata();
|
||||
|
||||
|
|
|
|||
|
|
@ -34,17 +34,31 @@ import java.lang.annotation.Target;
|
|||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/** Defines an immutable group of tracks identified by their format identity. */
|
||||
/**
|
||||
* An immutable group of tracks. All tracks in a group present the same content, but their formats
|
||||
* may differ.
|
||||
*
|
||||
* <p>As an example of how tracks can be grouped, consider an adaptive playback where a main video
|
||||
* feed is provided in five resolutions, and an alternative video feed (e.g., a different camera
|
||||
* angle in a sports match) is provided in two resolutions. In this case there will be two video
|
||||
* track groups, one corresponding to the main video feed containing five tracks, and a second for
|
||||
* the alternative video feed containing two tracks.
|
||||
*
|
||||
* <p>Note that audio tracks whose languages differ are not grouped, because content in different
|
||||
* languages is not considered to be the same. Conversely, audio tracks in the same language that
|
||||
* only differ in properties such as bitrate, sampling rate, channel count and so on can be grouped.
|
||||
* This also applies to text tracks.
|
||||
*/
|
||||
public final class TrackGroup implements Bundleable {
|
||||
|
||||
private static final String TAG = "TrackGroup";
|
||||
|
||||
/** The number of tracks in the group. */
|
||||
public final int length;
|
||||
@UnstableApi public final int length;
|
||||
/** An identifier for the track group. */
|
||||
public final String id;
|
||||
@UnstableApi public final String id;
|
||||
/** The type of tracks in the group. */
|
||||
public final @C.TrackType int type;
|
||||
@UnstableApi public final @C.TrackType int type;
|
||||
|
||||
private final Format[] formats;
|
||||
|
||||
|
|
@ -99,6 +113,7 @@ public final class TrackGroup implements Bundleable {
|
|||
* @param index The index of the track.
|
||||
* @return The track's format.
|
||||
*/
|
||||
@UnstableApi
|
||||
public Format getFormat(int index) {
|
||||
return formats[index];
|
||||
}
|
||||
|
|
@ -112,6 +127,7 @@ public final class TrackGroup implements Bundleable {
|
|||
* @return The index of the track, or {@link C#INDEX_UNSET} if no such track exists.
|
||||
*/
|
||||
@SuppressWarnings("ReferenceEquality")
|
||||
@UnstableApi
|
||||
public int indexOf(Format format) {
|
||||
for (int i = 0; i < formats.length; i++) {
|
||||
if (format == formats[i]) {
|
||||
|
|
|
|||
|
|
@ -31,16 +31,21 @@ import java.lang.annotation.RetentionPolicy;
|
|||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Forces the selection of {@link #trackIndices} for a {@link TrackGroup}.
|
||||
* A track selection override, consisting of a {@link TrackGroup} and the indices of the tracks
|
||||
* within the group that should be selected.
|
||||
*
|
||||
* <p>If multiple tracks in {@link #trackGroup} are overridden, as many as possible will be selected
|
||||
* depending on the player capabilities.
|
||||
* <p>A track selection override is applied during playback if the media being played contains a
|
||||
* {@link TrackGroup} equal to the one in the override. If a {@link TrackSelectionParameters}
|
||||
* contains only one override of a given track type that applies to the media, this override will be
|
||||
* used to control the track selection for that type. If multiple overrides of a given track type
|
||||
* apply then the player will apply only one of them.
|
||||
*
|
||||
* <p>If {@link #trackIndices} is empty, no tracks from {@link #trackGroup} will be played. This is
|
||||
* similar to {@link TrackSelectionParameters#disabledTrackTypes}, except it will only affect the
|
||||
* playback of the associated {@link TrackGroup}. For example, if the only {@link
|
||||
* C#TRACK_TYPE_VIDEO} {@link TrackGroup} is associated with no tracks, no video will play until the
|
||||
* next video starts.
|
||||
* <p>If {@link #trackIndices} is empty then the override specifies that no tracks should be
|
||||
* selected. Adding an empty override to a {@link TrackSelectionParameters} is similar to {@link
|
||||
* TrackSelectionParameters.Builder#setTrackTypeDisabled disabling a track type}, except that an
|
||||
* empty override will only be applied if the media being played contains a {@link TrackGroup} equal
|
||||
* to the one in the override. Conversely, disabling a track type will prevent selection of tracks
|
||||
* of that type for all media.
|
||||
*/
|
||||
public final class TrackSelectionOverride implements Bundleable {
|
||||
|
||||
|
|
@ -60,14 +65,14 @@ public final class TrackSelectionOverride implements Bundleable {
|
|||
private static final int FIELD_TRACK_GROUP = 0;
|
||||
private static final int FIELD_TRACKS = 1;
|
||||
|
||||
/** Constructs an instance to force all tracks in {@code trackGroup} to be selected. */
|
||||
public TrackSelectionOverride(TrackGroup trackGroup) {
|
||||
this.trackGroup = trackGroup;
|
||||
ImmutableList.Builder<Integer> builder = new ImmutableList.Builder<>();
|
||||
for (int i = 0; i < trackGroup.length; i++) {
|
||||
builder.add(i);
|
||||
}
|
||||
this.trackIndices = builder.build();
|
||||
/**
|
||||
* Constructs an instance to force {@code trackIndex} in {@code trackGroup} to be selected.
|
||||
*
|
||||
* @param trackGroup The {@link TrackGroup} for which to override the track selection.
|
||||
* @param trackIndex The index of the track in the {@link TrackGroup} to select.
|
||||
*/
|
||||
public TrackSelectionOverride(TrackGroup trackGroup, int trackIndex) {
|
||||
this(trackGroup, ImmutableList.of(trackIndex));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -123,13 +128,9 @@ public final class TrackSelectionOverride implements Bundleable {
|
|||
@UnstableApi
|
||||
public static final Creator<TrackSelectionOverride> CREATOR =
|
||||
bundle -> {
|
||||
@Nullable Bundle trackGroupBundle = bundle.getBundle(keyForField(FIELD_TRACK_GROUP));
|
||||
checkNotNull(trackGroupBundle); // Mandatory as there are no reasonable defaults.
|
||||
Bundle trackGroupBundle = checkNotNull(bundle.getBundle(keyForField(FIELD_TRACK_GROUP)));
|
||||
TrackGroup trackGroup = TrackGroup.CREATOR.fromBundle(trackGroupBundle);
|
||||
@Nullable int[] tracks = bundle.getIntArray(keyForField(FIELD_TRACKS));
|
||||
if (tracks == null) {
|
||||
return new TrackSelectionOverride(trackGroup);
|
||||
}
|
||||
int[] tracks = checkNotNull(bundle.getIntArray(keyForField(FIELD_TRACKS)));
|
||||
return new TrackSelectionOverride(trackGroup, Ints.asList(tracks));
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import java.lang.annotation.Documented;
|
|||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
|
@ -46,10 +47,11 @@ import org.checkerframework.checker.initialization.qual.UnknownInitialization;
|
|||
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
||||
|
||||
/**
|
||||
* Constraint parameters for track selection.
|
||||
* Parameters for controlling track selection.
|
||||
*
|
||||
* <p>For example the following code modifies the parameters to restrict video track selections to
|
||||
* SD, and to select a German audio track if there is one:
|
||||
* <p>Parameters can be queried and set on a {@link Player}. For example the following code modifies
|
||||
* the parameters to restrict video track selections to SD, and to select a German audio track if
|
||||
* there is one:
|
||||
*
|
||||
* <pre>{@code
|
||||
* // Build on the current parameters.
|
||||
|
|
@ -94,12 +96,13 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
// Text
|
||||
private ImmutableList<String> preferredTextLanguages;
|
||||
private @C.RoleFlags int preferredTextRoleFlags;
|
||||
private @C.SelectionFlags int ignoredTextSelectionFlags;
|
||||
private boolean selectUndeterminedTextLanguage;
|
||||
// General
|
||||
private boolean forceLowestBitrate;
|
||||
private boolean forceHighestSupportedBitrate;
|
||||
private HashMap<TrackGroup, TrackSelectionOverride> overrides;
|
||||
private ImmutableSet<@C.TrackType Integer> disabledTrackTypes;
|
||||
private HashSet<@C.TrackType Integer> disabledTrackTypes;
|
||||
|
||||
/**
|
||||
* @deprecated {@link Context} constraints will not be set using this constructor. Use {@link
|
||||
|
|
@ -127,12 +130,13 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
// Text
|
||||
preferredTextLanguages = ImmutableList.of();
|
||||
preferredTextRoleFlags = 0;
|
||||
ignoredTextSelectionFlags = 0;
|
||||
selectUndeterminedTextLanguage = false;
|
||||
// General
|
||||
forceLowestBitrate = false;
|
||||
forceHighestSupportedBitrate = false;
|
||||
overrides = new HashMap<>();
|
||||
disabledTrackTypes = ImmutableSet.of();
|
||||
disabledTrackTypes = new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -227,6 +231,10 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
bundle.getInt(
|
||||
keyForField(FIELD_PREFERRED_TEXT_ROLE_FLAGS),
|
||||
DEFAULT_WITHOUT_CONTEXT.preferredTextRoleFlags);
|
||||
ignoredTextSelectionFlags =
|
||||
bundle.getInt(
|
||||
keyForField(FIELD_IGNORED_TEXT_SELECTION_FLAGS),
|
||||
DEFAULT_WITHOUT_CONTEXT.ignoredTextSelectionFlags);
|
||||
selectUndeterminedTextLanguage =
|
||||
bundle.getBoolean(
|
||||
keyForField(FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE),
|
||||
|
|
@ -239,21 +247,22 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
bundle.getBoolean(
|
||||
keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE),
|
||||
DEFAULT_WITHOUT_CONTEXT.forceHighestSupportedBitrate);
|
||||
overrides = new HashMap<>();
|
||||
List<TrackSelectionOverride> overrideList =
|
||||
fromBundleNullableList(
|
||||
TrackSelectionOverride.CREATOR,
|
||||
bundle.getParcelableArrayList(keyForField(FIELD_SELECTION_OVERRIDES)),
|
||||
ImmutableList.of());
|
||||
overrides = new HashMap<>();
|
||||
for (int i = 0; i < overrideList.size(); i++) {
|
||||
TrackSelectionOverride override = overrideList.get(i);
|
||||
overrides.put(override.trackGroup, override);
|
||||
}
|
||||
disabledTrackTypes =
|
||||
ImmutableSet.copyOf(
|
||||
Ints.asList(
|
||||
firstNonNull(
|
||||
bundle.getIntArray(keyForField(FIELD_DISABLED_TRACK_TYPE)), new int[0])));
|
||||
int[] disabledTrackTypeArray =
|
||||
firstNonNull(bundle.getIntArray(keyForField(FIELD_DISABLED_TRACK_TYPE)), new int[0]);
|
||||
disabledTrackTypes = new HashSet<>();
|
||||
for (@C.TrackType int disabledTrackType : disabledTrackTypeArray) {
|
||||
disabledTrackTypes.add(disabledTrackType);
|
||||
}
|
||||
}
|
||||
|
||||
/** Overrides the value of the builder with the value of {@link TrackSelectionParameters}. */
|
||||
|
|
@ -289,11 +298,12 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
// Text
|
||||
preferredTextLanguages = parameters.preferredTextLanguages;
|
||||
preferredTextRoleFlags = parameters.preferredTextRoleFlags;
|
||||
ignoredTextSelectionFlags = parameters.ignoredTextSelectionFlags;
|
||||
selectUndeterminedTextLanguage = parameters.selectUndeterminedTextLanguage;
|
||||
// General
|
||||
forceLowestBitrate = parameters.forceLowestBitrate;
|
||||
forceHighestSupportedBitrate = parameters.forceHighestSupportedBitrate;
|
||||
disabledTrackTypes = parameters.disabledTrackTypes;
|
||||
disabledTrackTypes = new HashSet<>(parameters.disabledTrackTypes);
|
||||
overrides = new HashMap<>(parameters.overrides);
|
||||
}
|
||||
|
||||
|
|
@ -612,6 +622,18 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a bitmask of selection flags that are ignored for text track selections.
|
||||
*
|
||||
* @param ignoredTextSelectionFlags A bitmask of {@link C.SelectionFlags} that are ignored for
|
||||
* text track selections.
|
||||
* @return This builder.
|
||||
*/
|
||||
public Builder setIgnoredTextSelectionFlags(@C.SelectionFlags int ignoredTextSelectionFlags) {
|
||||
this.ignoredTextSelectionFlags = ignoredTextSelectionFlags;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether a text track with undetermined language should be selected if no track with
|
||||
* {@link #setPreferredTextLanguages(String...) a preferred language} is available, or if the
|
||||
|
|
@ -654,28 +676,26 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
return this;
|
||||
}
|
||||
|
||||
/** Adds an override for the provided {@link TrackGroup}. */
|
||||
/** Adds an override, replacing any override for the same {@link TrackGroup}. */
|
||||
public Builder addOverride(TrackSelectionOverride override) {
|
||||
overrides.put(override.trackGroup, override);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Removes the override associated with the provided {@link TrackGroup} if present. */
|
||||
public Builder clearOverride(TrackGroup trackGroup) {
|
||||
overrides.remove(trackGroup);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Set the override for the type of the provided {@link TrackGroup}. */
|
||||
/** Sets an override, replacing all existing overrides with the same track type. */
|
||||
public Builder setOverrideForType(TrackSelectionOverride override) {
|
||||
clearOverridesOfType(override.getTrackType());
|
||||
overrides.put(override.trackGroup, override);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any override associated with {@link TrackGroup TrackGroups} of type {@code trackType}.
|
||||
*/
|
||||
/** Removes the override for the provided {@link TrackGroup}, if there is one. */
|
||||
public Builder clearOverride(TrackGroup trackGroup) {
|
||||
overrides.remove(trackGroup);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Removes all overrides of the provided track type. */
|
||||
public Builder clearOverridesOfType(@C.TrackType int trackType) {
|
||||
Iterator<TrackSelectionOverride> it = overrides.values().iterator();
|
||||
while (it.hasNext()) {
|
||||
|
|
@ -687,7 +707,7 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
return this;
|
||||
}
|
||||
|
||||
/** Removes all track overrides. */
|
||||
/** Removes all overrides. */
|
||||
public Builder clearOverrides() {
|
||||
overrides.clear();
|
||||
return this;
|
||||
|
|
@ -695,13 +715,34 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
|
||||
/**
|
||||
* Sets the disabled track types, preventing all tracks of those types from being selected for
|
||||
* playback.
|
||||
* playback. Any previously disabled track types are cleared.
|
||||
*
|
||||
* @param disabledTrackTypes The track types to disable.
|
||||
* @return This builder.
|
||||
* @deprecated Use {@link #setTrackTypeDisabled(int, boolean)}.
|
||||
*/
|
||||
@Deprecated
|
||||
@UnstableApi
|
||||
public Builder setDisabledTrackTypes(Set<@C.TrackType Integer> disabledTrackTypes) {
|
||||
this.disabledTrackTypes = ImmutableSet.copyOf(disabledTrackTypes);
|
||||
this.disabledTrackTypes.clear();
|
||||
this.disabledTrackTypes.addAll(disabledTrackTypes);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether a track type is disabled. If disabled, no tracks of the specified type will be
|
||||
* selected for playback.
|
||||
*
|
||||
* @param trackType The track type.
|
||||
* @param disabled Whether the track type should be disabled.
|
||||
* @return This builder.
|
||||
*/
|
||||
public Builder setTrackTypeDisabled(@C.TrackType int trackType, boolean disabled) {
|
||||
if (disabled) {
|
||||
disabledTrackTypes.add(trackType);
|
||||
} else {
|
||||
disabledTrackTypes.remove(trackType);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -878,6 +919,11 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
* is enabled.
|
||||
*/
|
||||
public final @C.RoleFlags int preferredTextRoleFlags;
|
||||
/**
|
||||
* Bitmask of selection flags that are ignored for text track selections. See {@link
|
||||
* C.SelectionFlags}. The default value is {@code 0} (i.e., no flags are ignored).
|
||||
*/
|
||||
public final @C.SelectionFlags int ignoredTextSelectionFlags;
|
||||
/**
|
||||
* Whether a text track with undetermined language should be selected if no track with {@link
|
||||
* #preferredTextLanguages} is available, or if {@link #preferredTextLanguages} is unset. The
|
||||
|
|
@ -931,12 +977,13 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
// Text
|
||||
this.preferredTextLanguages = builder.preferredTextLanguages;
|
||||
this.preferredTextRoleFlags = builder.preferredTextRoleFlags;
|
||||
this.ignoredTextSelectionFlags = builder.ignoredTextSelectionFlags;
|
||||
this.selectUndeterminedTextLanguage = builder.selectUndeterminedTextLanguage;
|
||||
// General
|
||||
this.forceLowestBitrate = builder.forceLowestBitrate;
|
||||
this.forceHighestSupportedBitrate = builder.forceHighestSupportedBitrate;
|
||||
this.overrides = ImmutableMap.copyOf(builder.overrides);
|
||||
this.disabledTrackTypes = builder.disabledTrackTypes;
|
||||
this.disabledTrackTypes = ImmutableSet.copyOf(builder.disabledTrackTypes);
|
||||
}
|
||||
|
||||
/** Creates a new {@link Builder}, copying the initial values from this instance. */
|
||||
|
|
@ -974,8 +1021,10 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
&& maxAudioChannelCount == other.maxAudioChannelCount
|
||||
&& maxAudioBitrate == other.maxAudioBitrate
|
||||
&& preferredAudioMimeTypes.equals(other.preferredAudioMimeTypes)
|
||||
// Text
|
||||
&& preferredTextLanguages.equals(other.preferredTextLanguages)
|
||||
&& preferredTextRoleFlags == other.preferredTextRoleFlags
|
||||
&& ignoredTextSelectionFlags == other.ignoredTextSelectionFlags
|
||||
&& selectUndeterminedTextLanguage == other.selectUndeterminedTextLanguage
|
||||
// General
|
||||
&& forceLowestBitrate == other.forceLowestBitrate
|
||||
|
|
@ -1010,6 +1059,7 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
// Text
|
||||
result = 31 * result + preferredTextLanguages.hashCode();
|
||||
result = 31 * result + preferredTextRoleFlags;
|
||||
result = 31 * result + ignoredTextSelectionFlags;
|
||||
result = 31 * result + (selectUndeterminedTextLanguage ? 1 : 0);
|
||||
// General
|
||||
result = 31 * result + (forceLowestBitrate ? 1 : 0);
|
||||
|
|
@ -1024,11 +1074,7 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({
|
||||
FIELD_PREFERRED_AUDIO_LANGUAGES,
|
||||
FIELD_PREFERRED_AUDIO_ROLE_FLAGS,
|
||||
FIELD_PREFERRED_TEXT_LANGUAGES,
|
||||
FIELD_PREFERRED_TEXT_ROLE_FLAGS,
|
||||
FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE,
|
||||
// Video
|
||||
FIELD_MAX_VIDEO_WIDTH,
|
||||
FIELD_MAX_VIDEO_HEIGHT,
|
||||
FIELD_MAX_VIDEO_FRAMERATE,
|
||||
|
|
@ -1041,14 +1087,23 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
FIELD_VIEWPORT_HEIGHT,
|
||||
FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE,
|
||||
FIELD_PREFERRED_VIDEO_MIMETYPES,
|
||||
FIELD_PREFERRED_VIDEO_ROLE_FLAGS,
|
||||
// Audio
|
||||
FIELD_PREFERRED_AUDIO_LANGUAGES,
|
||||
FIELD_PREFERRED_AUDIO_ROLE_FLAGS,
|
||||
FIELD_MAX_AUDIO_CHANNEL_COUNT,
|
||||
FIELD_MAX_AUDIO_BITRATE,
|
||||
FIELD_PREFERRED_AUDIO_MIME_TYPES,
|
||||
// Text
|
||||
FIELD_PREFERRED_TEXT_LANGUAGES,
|
||||
FIELD_PREFERRED_TEXT_ROLE_FLAGS,
|
||||
FIELD_IGNORED_TEXT_SELECTION_FLAGS,
|
||||
FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE,
|
||||
// General
|
||||
FIELD_FORCE_LOWEST_BITRATE,
|
||||
FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE,
|
||||
FIELD_SELECTION_OVERRIDES,
|
||||
FIELD_DISABLED_TRACK_TYPE,
|
||||
FIELD_PREFERRED_VIDEO_ROLE_FLAGS
|
||||
})
|
||||
private @interface FieldNumber {}
|
||||
|
||||
|
|
@ -1077,6 +1132,7 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
private static final int FIELD_SELECTION_OVERRIDES = 23;
|
||||
private static final int FIELD_DISABLED_TRACK_TYPE = 24;
|
||||
private static final int FIELD_PREFERRED_VIDEO_ROLE_FLAGS = 25;
|
||||
private static final int FIELD_IGNORED_TEXT_SELECTION_FLAGS = 26;
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
|
|
@ -1114,6 +1170,7 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
bundle.putStringArray(
|
||||
keyForField(FIELD_PREFERRED_TEXT_LANGUAGES), preferredTextLanguages.toArray(new String[0]));
|
||||
bundle.putInt(keyForField(FIELD_PREFERRED_TEXT_ROLE_FLAGS), preferredTextRoleFlags);
|
||||
bundle.putInt(keyForField(FIELD_IGNORED_TEXT_SELECTION_FLAGS), ignoredTextSelectionFlags);
|
||||
bundle.putBoolean(
|
||||
keyForField(FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE), selectUndeterminedTextLanguage);
|
||||
// General
|
||||
|
|
|
|||
|
|
@ -41,8 +41,8 @@ public final class TracksInfo implements Bundleable {
|
|||
|
||||
/**
|
||||
* Information about a single group of tracks, including the underlying {@link TrackGroup}, the
|
||||
* {@link C.TrackType type} of tracks it contains, and the level to which each track is supported
|
||||
* by the player.
|
||||
* level to which each track is supported by the player, and whether any of the tracks are
|
||||
* selected.
|
||||
*/
|
||||
public static final class TrackGroupInfo implements Bundleable {
|
||||
|
||||
|
|
@ -55,26 +55,26 @@ public final class TracksInfo implements Bundleable {
|
|||
private final boolean[] trackSelected;
|
||||
|
||||
/**
|
||||
* Constructs a TrackGroupInfo.
|
||||
* Constructs an instance.
|
||||
*
|
||||
* @param trackGroup The {@link TrackGroup} described.
|
||||
* @param adaptiveSupported Whether adaptive selections containing more than one track in the
|
||||
* {@code trackGroup} are supported.
|
||||
* @param trackSupport The {@link C.FormatSupport} of each track in the {@code trackGroup}.
|
||||
* @param tracksSelected Whether each track in the {@code trackGroup} is selected.
|
||||
* @param trackGroup The underlying {@link TrackGroup}.
|
||||
* @param adaptiveSupported Whether the player supports adaptive selections containing more than
|
||||
* one track in the group.
|
||||
* @param trackSupport The {@link C.FormatSupport} of each track in the group.
|
||||
* @param trackSelected Whether each track in the {@code trackGroup} is selected.
|
||||
*/
|
||||
@UnstableApi
|
||||
public TrackGroupInfo(
|
||||
TrackGroup trackGroup,
|
||||
boolean adaptiveSupported,
|
||||
@C.FormatSupport int[] trackSupport,
|
||||
boolean[] tracksSelected) {
|
||||
boolean[] trackSelected) {
|
||||
length = trackGroup.length;
|
||||
checkArgument(length == trackSupport.length && length == tracksSelected.length);
|
||||
checkArgument(length == trackSupport.length && length == trackSelected.length);
|
||||
this.trackGroup = trackGroup;
|
||||
this.adaptiveSupported = adaptiveSupported && length > 1;
|
||||
this.trackSupport = trackSupport.clone();
|
||||
this.trackSelected = tracksSelected.clone();
|
||||
this.trackSelected = trackSelected.clone();
|
||||
}
|
||||
|
||||
/** Returns the underlying {@link TrackGroup}. */
|
||||
|
|
@ -266,11 +266,11 @@ public final class TracksInfo implements Bundleable {
|
|||
}
|
||||
}
|
||||
|
||||
private final ImmutableList<TrackGroupInfo> trackGroupInfos;
|
||||
|
||||
/** An {@code TrackInfo} that contains no tracks. */
|
||||
@UnstableApi public static final TracksInfo EMPTY = new TracksInfo(ImmutableList.of());
|
||||
|
||||
private final ImmutableList<TrackGroupInfo> trackGroupInfos;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
package androidx.media3.common.util;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
|
||||
import android.util.Pair;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
|
|
@ -31,6 +33,12 @@ public final class CodecSpecificDataUtil {
|
|||
private static final String[] HEVC_GENERAL_PROFILE_SPACE_STRINGS =
|
||||
new String[] {"", "A", "B", "C"};
|
||||
|
||||
// MP4V-ES
|
||||
private static final int VISUAL_OBJECT_LAYER = 1;
|
||||
private static final int VISUAL_OBJECT_LAYER_START = 0x20;
|
||||
private static final int EXTENDED_PAR = 0x0F;
|
||||
private static final int RECTANGULAR = 0x00;
|
||||
|
||||
/**
|
||||
* Parses an ALAC AudioSpecificConfig (i.e. an <a
|
||||
* href="https://github.com/macosforge/alac/blob/master/ALACMagicCookieDescription.txt">ALACSpecificConfig</a>).
|
||||
|
|
@ -72,6 +80,87 @@ public final class CodecSpecificDataUtil {
|
|||
&& initializationData.get(0)[0] == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an MPEG-4 Visual configuration information, as defined in ISO/IEC14496-2.
|
||||
*
|
||||
* @param videoSpecificConfig A byte array containing the MPEG-4 Visual configuration information
|
||||
* to parse.
|
||||
* @return A pair of the video's width and height.
|
||||
*/
|
||||
public static Pair<Integer, Integer> getVideoResolutionFromMpeg4VideoConfig(
|
||||
byte[] videoSpecificConfig) {
|
||||
int offset = 0;
|
||||
boolean foundVOL = false;
|
||||
ParsableByteArray scratchBytes = new ParsableByteArray(videoSpecificConfig);
|
||||
while (offset + 3 < videoSpecificConfig.length) {
|
||||
if (scratchBytes.readUnsignedInt24() != VISUAL_OBJECT_LAYER
|
||||
|| (videoSpecificConfig[offset + 3] & 0xF0) != VISUAL_OBJECT_LAYER_START) {
|
||||
scratchBytes.setPosition(scratchBytes.getPosition() - 2);
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
foundVOL = true;
|
||||
break;
|
||||
}
|
||||
|
||||
checkArgument(foundVOL, "Invalid input: VOL not found.");
|
||||
|
||||
ParsableBitArray scratchBits = new ParsableBitArray(videoSpecificConfig);
|
||||
// Skip the start codecs from the bitstream
|
||||
scratchBits.skipBits((offset + 4) * 8);
|
||||
scratchBits.skipBits(1); // random_accessible_vol
|
||||
scratchBits.skipBits(8); // video_object_type_indication
|
||||
|
||||
if (scratchBits.readBit()) { // object_layer_identifier
|
||||
scratchBits.skipBits(4); // video_object_layer_verid
|
||||
scratchBits.skipBits(3); // video_object_layer_priority
|
||||
}
|
||||
|
||||
int aspectRatioInfo = scratchBits.readBits(4);
|
||||
if (aspectRatioInfo == EXTENDED_PAR) {
|
||||
scratchBits.skipBits(8); // par_width
|
||||
scratchBits.skipBits(8); // par_height
|
||||
}
|
||||
|
||||
if (scratchBits.readBit()) { // vol_control_parameters
|
||||
scratchBits.skipBits(2); // chroma_format
|
||||
scratchBits.skipBits(1); // low_delay
|
||||
if (scratchBits.readBit()) { // vbv_parameters
|
||||
scratchBits.skipBits(79);
|
||||
}
|
||||
}
|
||||
|
||||
int videoObjectLayerShape = scratchBits.readBits(2);
|
||||
checkArgument(
|
||||
videoObjectLayerShape == RECTANGULAR,
|
||||
"Only supports rectangular video object layer shape.");
|
||||
|
||||
checkArgument(scratchBits.readBit()); // marker_bit
|
||||
int vopTimeIncrementResolution = scratchBits.readBits(16);
|
||||
checkArgument(scratchBits.readBit()); // marker_bit
|
||||
|
||||
if (scratchBits.readBit()) { // fixed_vop_rate
|
||||
checkArgument(vopTimeIncrementResolution > 0);
|
||||
vopTimeIncrementResolution--;
|
||||
int numBitsToSkip = 0;
|
||||
while (vopTimeIncrementResolution > 0) {
|
||||
numBitsToSkip++;
|
||||
vopTimeIncrementResolution >>= 1;
|
||||
}
|
||||
scratchBits.skipBits(numBitsToSkip); // fixed_vop_time_increment
|
||||
}
|
||||
|
||||
checkArgument(scratchBits.readBit()); // marker_bit
|
||||
int videoObjectLayerWidth = scratchBits.readBits(13);
|
||||
checkArgument(scratchBits.readBit()); // marker_bit
|
||||
int videoObjectLayerHeight = scratchBits.readBits(13);
|
||||
checkArgument(scratchBits.readBit()); // marker_bit
|
||||
|
||||
scratchBits.skipBits(1); // interlaced
|
||||
|
||||
return Pair.create(videoObjectLayerWidth, videoObjectLayerHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an RFC 6381 AVC codec string using the provided parameters.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -147,8 +147,6 @@ public final class GlProgram {
|
|||
* <p>Call this in the rendering loop to switch between different programs.
|
||||
*/
|
||||
public void use() {
|
||||
// TODO(b/214975934): When multiple GL programs are supported by Transformer, make sure
|
||||
// to call use() to switch between programs.
|
||||
GLES20.glUseProgram(programId);
|
||||
GlUtil.checkGlError();
|
||||
}
|
||||
|
|
@ -175,9 +173,16 @@ public final class GlProgram {
|
|||
checkNotNull(attributeByName.get(name)).setBuffer(values, size);
|
||||
}
|
||||
|
||||
/** Sets a texture sampler type uniform. */
|
||||
public void setSamplerTexIdUniform(String name, int texId, int unit) {
|
||||
checkNotNull(uniformByName.get(name)).setSamplerTexId(texId, unit);
|
||||
/**
|
||||
* Sets a texture sampler type uniform.
|
||||
*
|
||||
* @param name The uniform's name.
|
||||
* @param texId The texture identifier.
|
||||
* @param texUnitIndex The texture unit index. Use a different index (0, 1, 2, ...) for each
|
||||
* texture sampler in the program.
|
||||
*/
|
||||
public void setSamplerTexIdUniform(String name, int texId, int texUnitIndex) {
|
||||
checkNotNull(uniformByName.get(name)).setSamplerTexId(texId, texUnitIndex);
|
||||
}
|
||||
|
||||
/** Sets a float type uniform. */
|
||||
|
|
@ -322,7 +327,7 @@ public final class GlProgram {
|
|||
private final float[] value;
|
||||
|
||||
private int texId;
|
||||
private int unit;
|
||||
private int texUnitIndex;
|
||||
|
||||
private Uniform(String name, int location, int type) {
|
||||
this.name = name;
|
||||
|
|
@ -335,11 +340,11 @@ public final class GlProgram {
|
|||
* Configures {@link #bind()} to use the specified {@code texId} for this sampler uniform.
|
||||
*
|
||||
* @param texId The GL texture identifier from which to sample.
|
||||
* @param unit The GL texture unit index.
|
||||
* @param texUnitIndex The GL texture unit index.
|
||||
*/
|
||||
public void setSamplerTexId(int texId, int unit) {
|
||||
public void setSamplerTexId(int texId, int texUnitIndex) {
|
||||
this.texId = texId;
|
||||
this.unit = unit;
|
||||
this.texUnitIndex = texUnitIndex;
|
||||
}
|
||||
|
||||
/** Configures {@link #bind()} to use the specified float {@code value} for this uniform. */
|
||||
|
|
@ -382,7 +387,7 @@ public final class GlProgram {
|
|||
if (texId == 0) {
|
||||
throw new IllegalStateException("No call to setSamplerTexId() before bind.");
|
||||
}
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + unit);
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + texUnitIndex);
|
||||
if (type == GLES11Ext.GL_SAMPLER_EXTERNAL_OES || type == GL_SAMPLER_EXTERNAL_2D_Y2Y_EXT) {
|
||||
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId);
|
||||
} else if (type == GLES20.GL_SAMPLER_2D) {
|
||||
|
|
@ -390,7 +395,7 @@ public final class GlProgram {
|
|||
} else {
|
||||
throw new IllegalStateException("Unexpected uniform type: " + type);
|
||||
}
|
||||
GLES20.glUniform1i(location, unit);
|
||||
GLES20.glUniform1i(location, texUnitIndex);
|
||||
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
|
||||
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
|
||||
GLES20.glTexParameteri(
|
||||
|
|
|
|||
|
|
@ -119,7 +119,8 @@ public final class GlUtil {
|
|||
|
||||
/**
|
||||
* Returns whether creating a GL context with {@value #EXTENSION_PROTECTED_CONTENT} is possible.
|
||||
* If {@code true}, the device supports a protected output path for DRM content when using GL.
|
||||
*
|
||||
* <p>If {@code true}, the device supports a protected output path for DRM content when using GL.
|
||||
*/
|
||||
public static boolean isProtectedContentExtensionSupported(Context context) {
|
||||
if (Util.SDK_INT < 24) {
|
||||
|
|
@ -146,7 +147,11 @@ public final class GlUtil {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns whether creating a GL context with {@value #EXTENSION_SURFACELESS_CONTEXT} is possible.
|
||||
* Returns whether the {@value #EXTENSION_SURFACELESS_CONTEXT} extension is supported.
|
||||
*
|
||||
* <p>This extension allows passing {@link EGL14#EGL_NO_SURFACE} for both the write and read
|
||||
* surfaces in a call to {@link EGL14#eglMakeCurrent(EGLDisplay, EGLSurface, EGLSurface,
|
||||
* EGLContext)}.
|
||||
*/
|
||||
public static boolean isSurfacelessContextExtensionSupported() {
|
||||
if (Util.SDK_INT < 17) {
|
||||
|
|
@ -206,6 +211,52 @@ public final class GlUtil {
|
|||
EGL_WINDOW_SURFACE_ATTRIBUTES_BT2020_PQ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and focuses a new {@link EGLSurface} wrapping a 1x1 pixel buffer.
|
||||
*
|
||||
* @param eglContext The {@link EGLContext} to make current.
|
||||
* @param eglDisplay The {@link EGLDisplay} to attach the surface to.
|
||||
*/
|
||||
@RequiresApi(17)
|
||||
public static void focusPlaceholderEglSurface(EGLContext eglContext, EGLDisplay eglDisplay) {
|
||||
int[] pbufferAttributes =
|
||||
new int[] {
|
||||
EGL14.EGL_WIDTH, /* width= */ 1,
|
||||
EGL14.EGL_HEIGHT, /* height= */ 1,
|
||||
EGL14.EGL_NONE
|
||||
};
|
||||
EGLSurface eglSurface =
|
||||
Api17.createEglPbufferSurface(
|
||||
eglDisplay, EGL_CONFIG_ATTRIBUTES_RGBA_8888, pbufferAttributes);
|
||||
focusEglSurface(eglDisplay, eglContext, eglSurface, /* width= */ 1, /* height= */ 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and focuses a new {@link EGLSurface} wrapping a 1x1 pixel buffer, for HDR rendering
|
||||
* with Rec. 2020 color primaries and using the PQ transfer function.
|
||||
*
|
||||
* @param eglContext The {@link EGLContext} to make current.
|
||||
* @param eglDisplay The {@link EGLDisplay} to attach the surface to.
|
||||
*/
|
||||
@RequiresApi(17)
|
||||
public static void focusPlaceholderEglSurfaceBt2020Pq(
|
||||
EGLContext eglContext, EGLDisplay eglDisplay) {
|
||||
int[] pbufferAttributes =
|
||||
new int[] {
|
||||
EGL14.EGL_WIDTH,
|
||||
/* width= */ 1,
|
||||
EGL14.EGL_HEIGHT,
|
||||
/* height= */ 1,
|
||||
EGL_GL_COLORSPACE_KHR,
|
||||
EGL_GL_COLORSPACE_BT2020_PQ_EXT,
|
||||
EGL14.EGL_NONE
|
||||
};
|
||||
EGLSurface eglSurface =
|
||||
Api17.createEglPbufferSurface(
|
||||
eglDisplay, EGL_CONFIG_ATTRIBUTES_RGBA_1010102, pbufferAttributes);
|
||||
focusEglSurface(eglDisplay, eglContext, eglSurface, /* width= */ 1, /* height= */ 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is an OpenGl error, logs the error and if {@link #glAssertionsEnabled} is true throws
|
||||
* a {@link GlException}.
|
||||
|
|
@ -222,6 +273,30 @@ public final class GlUtil {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the texture size is valid.
|
||||
*
|
||||
* @param width The width for a texture.
|
||||
* @param height The height for a texture.
|
||||
* @throws GlException If the texture width or height is invalid.
|
||||
*/
|
||||
public static void assertValidTextureSize(int width, int height) {
|
||||
// TODO(b/201293185): Consider handling adjustments for sizes > GL_MAX_TEXTURE_SIZE
|
||||
// (ex. downscaling appropriately) in a FrameProcessor instead of asserting incorrect values.
|
||||
|
||||
// For valid GL sizes, see:
|
||||
// https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glTexImage2D.xml
|
||||
int[] maxTextureSizeBuffer = new int[1];
|
||||
GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSizeBuffer, 0);
|
||||
int maxTextureSize = maxTextureSizeBuffer[0];
|
||||
if (width < 0 || height < 0) {
|
||||
throwGlException("width or height is less than 0");
|
||||
}
|
||||
if (width > maxTextureSize || height > maxTextureSize) {
|
||||
throwGlException("width or height is greater than GL_MAX_TEXTURE_SIZE " + maxTextureSize);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the specified {@code eglSurface} the render target, using a viewport of {@code width} by
|
||||
* {@code height} pixels.
|
||||
|
|
@ -320,6 +395,7 @@ public final class GlUtil {
|
|||
* @param height of the new texture in pixels
|
||||
*/
|
||||
public static int createTexture(int width, int height) {
|
||||
assertValidTextureSize(width, height);
|
||||
int texId = generateAndBindTexture(GLES20.GL_TEXTURE_2D);
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(width * height * 4);
|
||||
GLES20.glTexImage2D(
|
||||
|
|
@ -345,6 +421,9 @@ public final class GlUtil {
|
|||
* GLES11Ext#GL_TEXTURE_EXTERNAL_OES} for an external texture.
|
||||
*/
|
||||
private static int generateAndBindTexture(int textureTarget) {
|
||||
checkEglException(
|
||||
!Util.areEqual(EGL14.eglGetCurrentContext(), EGL14.EGL_NO_CONTEXT), "No current context");
|
||||
|
||||
int[] texId = new int[1];
|
||||
GLES20.glGenTextures(/* n= */ 1, texId, /* offset= */ 0);
|
||||
checkGlError();
|
||||
|
|
@ -365,6 +444,9 @@ public final class GlUtil {
|
|||
* @param texId The identifier of the texture to attach to the framebuffer.
|
||||
*/
|
||||
public static int createFboForTexture(int texId) {
|
||||
checkEglException(
|
||||
!Util.areEqual(EGL14.eglGetCurrentContext(), EGL14.EGL_NO_CONTEXT), "No current context");
|
||||
|
||||
int[] fboId = new int[1];
|
||||
GLES20.glGenFramebuffers(/* n= */ 1, fboId, /* offset= */ 0);
|
||||
checkGlError();
|
||||
|
|
@ -377,9 +459,10 @@ public final class GlUtil {
|
|||
}
|
||||
|
||||
/* package */ static void throwGlException(String errorMsg) {
|
||||
Log.e(TAG, errorMsg);
|
||||
if (glAssertionsEnabled) {
|
||||
throw new GlException(errorMsg);
|
||||
} else {
|
||||
Log.e(TAG, errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -389,6 +472,11 @@ public final class GlUtil {
|
|||
}
|
||||
}
|
||||
|
||||
private static void checkEglException(String errorMessage) {
|
||||
int error = EGL14.eglGetError();
|
||||
checkEglException(error == EGL14.EGL_SUCCESS, errorMessage + ", error code: " + error);
|
||||
}
|
||||
|
||||
@RequiresApi(17)
|
||||
private static final class Api17 {
|
||||
private Api17() {}
|
||||
|
|
@ -437,12 +525,28 @@ public final class GlUtil {
|
|||
Object surface,
|
||||
int[] configAttributes,
|
||||
int[] windowSurfaceAttributes) {
|
||||
return EGL14.eglCreateWindowSurface(
|
||||
eglDisplay,
|
||||
getEglConfig(eglDisplay, configAttributes),
|
||||
surface,
|
||||
windowSurfaceAttributes,
|
||||
/* offset= */ 0);
|
||||
EGLSurface eglSurface =
|
||||
EGL14.eglCreateWindowSurface(
|
||||
eglDisplay,
|
||||
getEglConfig(eglDisplay, configAttributes),
|
||||
surface,
|
||||
windowSurfaceAttributes,
|
||||
/* offset= */ 0);
|
||||
checkEglException("Error creating surface");
|
||||
return eglSurface;
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
public static EGLSurface createEglPbufferSurface(
|
||||
EGLDisplay eglDisplay, int[] configAttributes, int[] pbufferAttributes) {
|
||||
EGLSurface eglSurface =
|
||||
EGL14.eglCreatePbufferSurface(
|
||||
eglDisplay,
|
||||
getEglConfig(eglDisplay, configAttributes),
|
||||
pbufferAttributes,
|
||||
/* offset= */ 0);
|
||||
checkEglException("Error creating surface");
|
||||
return eglSurface;
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
|
|
@ -458,8 +562,11 @@ public final class GlUtil {
|
|||
if (boundFramebuffer[0] != framebuffer) {
|
||||
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebuffer);
|
||||
}
|
||||
checkGlError();
|
||||
EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
|
||||
checkEglException("Error making context current");
|
||||
GLES20.glViewport(/* x= */ 0, /* y= */ 0, width, height);
|
||||
checkGlError();
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
|
|
@ -470,19 +577,15 @@ public final class GlUtil {
|
|||
}
|
||||
EGL14.eglMakeCurrent(
|
||||
eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
|
||||
int error = EGL14.eglGetError();
|
||||
checkEglException(error == EGL14.EGL_SUCCESS, "Error releasing context: " + error);
|
||||
checkEglException("Error releasing context");
|
||||
if (eglContext != null) {
|
||||
EGL14.eglDestroyContext(eglDisplay, eglContext);
|
||||
error = EGL14.eglGetError();
|
||||
checkEglException(error == EGL14.EGL_SUCCESS, "Error destroying context: " + error);
|
||||
checkEglException("Error destroying context");
|
||||
}
|
||||
EGL14.eglReleaseThread();
|
||||
error = EGL14.eglGetError();
|
||||
checkEglException(error == EGL14.EGL_SUCCESS, "Error releasing thread: " + error);
|
||||
checkEglException("Error releasing thread");
|
||||
EGL14.eglTerminate(eglDisplay);
|
||||
error = EGL14.eglGetError();
|
||||
checkEglException(error == EGL14.EGL_SUCCESS, "Error terminating display: " + error);
|
||||
checkEglException("Error terminating display");
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
|
|
|
|||
|
|
@ -47,6 +47,19 @@ public final class MediaFormatUtil {
|
|||
// The constant value must not be changed, because it's also set by the framework MediaParser API.
|
||||
public static final String KEY_PCM_ENCODING_EXTENDED = "exo-pcm-encoding-int";
|
||||
|
||||
/**
|
||||
* The {@link MediaFormat} key for the maximum bitrate in bits per second.
|
||||
*
|
||||
* <p>The associated value is an integer.
|
||||
*
|
||||
* <p>The key string constant is the same as {@code MediaFormat#KEY_MAX_BITRATE}. Values for it
|
||||
* are already returned by the framework MediaExtractor; the key is a hidden field in {@code
|
||||
* MediaFormat} though, which is why it's being replicated here.
|
||||
*/
|
||||
// The constant value must not be changed, because it's also set by the framework MediaParser and
|
||||
// MediaExtractor APIs.
|
||||
public static final String KEY_MAX_BIT_RATE = "max-bitrate";
|
||||
|
||||
private static final int MAX_POWER_OF_TWO_INT = 1 << 30;
|
||||
|
||||
/**
|
||||
|
|
@ -63,6 +76,7 @@ public final class MediaFormatUtil {
|
|||
public static MediaFormat createMediaFormatFromFormat(Format format) {
|
||||
MediaFormat result = new MediaFormat();
|
||||
maybeSetInteger(result, MediaFormat.KEY_BIT_RATE, format.bitrate);
|
||||
maybeSetInteger(result, KEY_MAX_BIT_RATE, format.peakBitrate);
|
||||
maybeSetInteger(result, MediaFormat.KEY_CHANNEL_COUNT, format.channelCount);
|
||||
|
||||
maybeSetColorInfo(result, format.colorInfo);
|
||||
|
|
|
|||
|
|
@ -47,8 +47,27 @@ import java.lang.annotation.Target;
|
|||
* Android Studio, in order to alert developers to the risk of breaking changes.
|
||||
*
|
||||
* <p>Individual usage sites can be opted-in to suppress the lint error by using the {@link
|
||||
* androidx.annotation.OptIn} annotation: {@code @androidx.annotation.OptIn(markerClass =
|
||||
* androidx.media3.common.util.UnstableApi.class)}.
|
||||
* androidx.annotation.OptIn} annotation.
|
||||
*
|
||||
* <p>In Java:
|
||||
*
|
||||
* <pre>{@code
|
||||
* import androidx.annotation.OptIn;
|
||||
* import androidx.media3.common.util.UnstableApi;
|
||||
* ...
|
||||
* @OptIn(markerClass = UnstableApi.class)
|
||||
* private void methodUsingUnstableApis() { ... }
|
||||
* }</pre>
|
||||
*
|
||||
* <p>In Kotlin:
|
||||
*
|
||||
* <pre>{@code
|
||||
* import androidx.annotation.OptIn
|
||||
* import androidx.media3.common.util.UnstableApi
|
||||
* ...
|
||||
* @OptIn(UnstableApi::class)
|
||||
* private fun methodUsingUnstableApis() { ... }
|
||||
* }</pre>
|
||||
*
|
||||
* <p>Whole projects can be opted-in by suppressing the specific lint error in their <a
|
||||
* href="https://developer.android.com/studio/write/lint#pref">{@code lint.xml} file</a>:
|
||||
|
|
|
|||
|
|
@ -1121,6 +1121,25 @@ public final class Util {
|
|||
return min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum value in the given {@link SparseLongArray}.
|
||||
*
|
||||
* @param sparseLongArray The {@link SparseLongArray}.
|
||||
* @return The maximum value.
|
||||
* @throws NoSuchElementException If the array is empty.
|
||||
*/
|
||||
@RequiresApi(18)
|
||||
public static long maxValue(SparseLongArray sparseLongArray) {
|
||||
if (sparseLongArray.size() == 0) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
long max = Long.MIN_VALUE;
|
||||
for (int i = 0; i < sparseLongArray.size(); i++) {
|
||||
max = max(max, sparseLongArray.valueAt(i));
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a time in microseconds to the corresponding time in milliseconds, preserving {@link
|
||||
* C#TIME_UNSET} and {@link C#TIME_END_OF_SOURCE} values.
|
||||
|
|
@ -1143,18 +1162,6 @@ public final class Util {
|
|||
return (timeMs == C.TIME_UNSET || timeMs == C.TIME_END_OF_SOURCE) ? timeMs : (timeMs * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a time in seconds to the corresponding time in microseconds.
|
||||
*
|
||||
* @param timeSec The time in seconds.
|
||||
* @return The corresponding time in microseconds.
|
||||
*/
|
||||
public static long secToUs(double timeSec) {
|
||||
return BigDecimal.valueOf(timeSec)
|
||||
.multiply(BigDecimal.valueOf(C.MICROS_PER_SECOND))
|
||||
.longValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an xs:duration attribute value, returning the parsed duration in milliseconds.
|
||||
*
|
||||
|
|
@ -1897,10 +1904,10 @@ public final class Util {
|
|||
|
||||
/**
|
||||
* Returns the MIME type corresponding to the given adaptive {@link ContentType}, or {@code null}
|
||||
* if the content type is {@link C#TYPE_OTHER}.
|
||||
* if the content type is not adaptive.
|
||||
*/
|
||||
@Nullable
|
||||
public static String getAdaptiveMimeTypeForContentType(int contentType) {
|
||||
public static String getAdaptiveMimeTypeForContentType(@ContentType int contentType) {
|
||||
switch (contentType) {
|
||||
case C.TYPE_DASH:
|
||||
return MimeTypes.APPLICATION_MPD;
|
||||
|
|
@ -1908,6 +1915,7 @@ public final class Util {
|
|||
return MimeTypes.APPLICATION_M3U8;
|
||||
case C.TYPE_SS:
|
||||
return MimeTypes.APPLICATION_SS;
|
||||
case C.TYPE_RTSP:
|
||||
case C.TYPE_OTHER:
|
||||
default:
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -29,12 +29,12 @@ import org.junit.runner.RunWith;
|
|||
public final class TrackSelectionOverrideTest {
|
||||
|
||||
@Test
|
||||
public void newTrackSelectionOverride_withJustTrackGroup_selectsAllTracks() {
|
||||
public void newTrackSelectionOverride_withOneTrack_selectsOneTrack() {
|
||||
TrackSelectionOverride trackSelectionOverride =
|
||||
new TrackSelectionOverride(newTrackGroupWithIds(1, 2));
|
||||
new TrackSelectionOverride(newTrackGroupWithIds(1, 2), /* trackIndex= */ 1);
|
||||
|
||||
assertThat(trackSelectionOverride.trackGroup).isEqualTo(newTrackGroupWithIds(1, 2));
|
||||
assertThat(trackSelectionOverride.trackIndices).containsExactly(0, 1).inOrder();
|
||||
assertThat(trackSelectionOverride.trackIndices).containsExactly(1).inOrder();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import static com.google.common.truth.Truth.assertThat;
|
|||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
|
|
@ -57,6 +56,7 @@ public final class TrackSelectionParametersTest {
|
|||
assertThat(parameters.preferredAudioMimeTypes).isEmpty();
|
||||
assertThat(parameters.preferredTextLanguages).isEmpty();
|
||||
assertThat(parameters.preferredTextRoleFlags).isEqualTo(0);
|
||||
assertThat(parameters.ignoredTextSelectionFlags).isEqualTo(0);
|
||||
assertThat(parameters.selectUndeterminedTextLanguage).isFalse();
|
||||
// General
|
||||
assertThat(parameters.forceLowestBitrate).isFalse();
|
||||
|
|
@ -68,7 +68,8 @@ public final class TrackSelectionParametersTest {
|
|||
@Test
|
||||
public void parametersSet_fromDefault_isAsExpected() {
|
||||
TrackSelectionOverride override1 =
|
||||
new TrackSelectionOverride(new TrackGroup(new Format.Builder().build()));
|
||||
new TrackSelectionOverride(
|
||||
new TrackGroup(new Format.Builder().build()), /* trackIndex= */ 0);
|
||||
TrackSelectionOverride override2 =
|
||||
new TrackSelectionOverride(
|
||||
new TrackGroup(
|
||||
|
|
@ -98,18 +99,22 @@ public final class TrackSelectionParametersTest {
|
|||
// Text
|
||||
.setPreferredTextLanguages("de", "en")
|
||||
.setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION)
|
||||
.setIgnoredTextSelectionFlags(C.SELECTION_FLAG_AUTOSELECT)
|
||||
.setSelectUndeterminedTextLanguage(true)
|
||||
// General
|
||||
.setForceLowestBitrate(false)
|
||||
.setForceHighestSupportedBitrate(true)
|
||||
.addOverride(new TrackSelectionOverride(new TrackGroup(new Format.Builder().build())))
|
||||
.addOverride(
|
||||
new TrackSelectionOverride(
|
||||
new TrackGroup(new Format.Builder().build()), /* trackIndex= */ 0))
|
||||
.addOverride(
|
||||
new TrackSelectionOverride(
|
||||
new TrackGroup(
|
||||
new Format.Builder().setId(4).build(),
|
||||
new Format.Builder().setId(5).build()),
|
||||
/* trackIndices= */ ImmutableList.of(1)))
|
||||
.setDisabledTrackTypes(ImmutableSet.of(C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_TEXT))
|
||||
.setTrackTypeDisabled(C.TRACK_TYPE_AUDIO, /* disabled= */ true)
|
||||
.setTrackTypeDisabled(C.TRACK_TYPE_TEXT, /* disabled= */ true)
|
||||
.build();
|
||||
|
||||
// Video
|
||||
|
|
@ -138,6 +143,7 @@ public final class TrackSelectionParametersTest {
|
|||
// Text
|
||||
assertThat(parameters.preferredTextLanguages).containsExactly("de", "en").inOrder();
|
||||
assertThat(parameters.preferredTextRoleFlags).isEqualTo(C.ROLE_FLAG_CAPTION);
|
||||
assertThat(parameters.ignoredTextSelectionFlags).isEqualTo(C.SELECTION_FLAG_AUTOSELECT);
|
||||
assertThat(parameters.selectUndeterminedTextLanguage).isTrue();
|
||||
// General
|
||||
assertThat(parameters.forceLowestBitrate).isFalse();
|
||||
|
|
@ -202,8 +208,10 @@ public final class TrackSelectionParametersTest {
|
|||
|
||||
@Test
|
||||
public void addOverride_onDifferentGroups_addsOverride() {
|
||||
TrackSelectionOverride override1 = new TrackSelectionOverride(newTrackGroupWithIds(1));
|
||||
TrackSelectionOverride override2 = new TrackSelectionOverride(newTrackGroupWithIds(2));
|
||||
TrackSelectionOverride override1 =
|
||||
new TrackSelectionOverride(newTrackGroupWithIds(1), /* trackIndex= */ 0);
|
||||
TrackSelectionOverride override2 =
|
||||
new TrackSelectionOverride(newTrackGroupWithIds(2), /* trackIndex= */ 0);
|
||||
|
||||
TrackSelectionParameters trackSelectionParameters =
|
||||
new TrackSelectionParameters.Builder(getApplicationContext())
|
||||
|
|
@ -234,8 +242,10 @@ public final class TrackSelectionParametersTest {
|
|||
|
||||
@Test
|
||||
public void setOverrideForType_onSameType_replacesOverride() {
|
||||
TrackSelectionOverride override1 = new TrackSelectionOverride(newTrackGroupWithIds(1));
|
||||
TrackSelectionOverride override2 = new TrackSelectionOverride(newTrackGroupWithIds(2));
|
||||
TrackSelectionOverride override1 =
|
||||
new TrackSelectionOverride(newTrackGroupWithIds(1), /* trackIndex= */ 0);
|
||||
TrackSelectionOverride override2 =
|
||||
new TrackSelectionOverride(newTrackGroupWithIds(2), /* trackIndex= */ 0);
|
||||
|
||||
TrackSelectionParameters trackSelectionParameters =
|
||||
new TrackSelectionParameters.Builder(getApplicationContext())
|
||||
|
|
@ -248,8 +258,10 @@ public final class TrackSelectionParametersTest {
|
|||
|
||||
@Test
|
||||
public void clearOverridesOfType_ofTypeAudio_removesAudioOverride() {
|
||||
TrackSelectionOverride override1 = new TrackSelectionOverride(AAC_TRACK_GROUP);
|
||||
TrackSelectionOverride override2 = new TrackSelectionOverride(newTrackGroupWithIds(1));
|
||||
TrackSelectionOverride override1 =
|
||||
new TrackSelectionOverride(AAC_TRACK_GROUP, /* trackIndex= */ 0);
|
||||
TrackSelectionOverride override2 =
|
||||
new TrackSelectionOverride(newTrackGroupWithIds(1), /* trackIndex= */ 0);
|
||||
TrackSelectionParameters trackSelectionParameters =
|
||||
new TrackSelectionParameters.Builder(getApplicationContext())
|
||||
.addOverride(override1)
|
||||
|
|
@ -262,8 +274,10 @@ public final class TrackSelectionParametersTest {
|
|||
|
||||
@Test
|
||||
public void clearOverride_ofTypeGroup_removesOverride() {
|
||||
TrackSelectionOverride override1 = new TrackSelectionOverride(AAC_TRACK_GROUP);
|
||||
TrackSelectionOverride override2 = new TrackSelectionOverride(newTrackGroupWithIds(1));
|
||||
TrackSelectionOverride override1 =
|
||||
new TrackSelectionOverride(AAC_TRACK_GROUP, /* trackIndex= */ 0);
|
||||
TrackSelectionOverride override2 =
|
||||
new TrackSelectionOverride(newTrackGroupWithIds(1), /* trackIndex= */ 0);
|
||||
TrackSelectionParameters trackSelectionParameters =
|
||||
new TrackSelectionParameters.Builder(getApplicationContext())
|
||||
.addOverride(override1)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import static android.graphics.Color.argb;
|
|||
import static android.graphics.Color.parseColor;
|
||||
import static androidx.media3.common.util.ColorParser.parseTtmlColor;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import android.graphics.Color;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
|
@ -34,24 +35,26 @@ public final class ColorParserTest {
|
|||
|
||||
// Negative tests.
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test
|
||||
public void parseUnknownColor() {
|
||||
ColorParser.parseTtmlColor("colorOfAnElectron");
|
||||
assertThrows(
|
||||
IllegalArgumentException.class, () -> ColorParser.parseTtmlColor("colorOfAnElectron"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test
|
||||
public void parseNull() {
|
||||
ColorParser.parseTtmlColor(null);
|
||||
assertThrows(IllegalArgumentException.class, () -> ColorParser.parseTtmlColor(null));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test
|
||||
public void parseEmpty() {
|
||||
ColorParser.parseTtmlColor("");
|
||||
assertThrows(IllegalArgumentException.class, () -> ColorParser.parseTtmlColor(""));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test
|
||||
public void rgbColorParsingRgbValuesNegative() {
|
||||
ColorParser.parseTtmlColor("rgb(-4, 55, 209)");
|
||||
assertThrows(
|
||||
IllegalArgumentException.class, () -> ColorParser.parseTtmlColor("rgb(-4, 55, 209)"));
|
||||
}
|
||||
|
||||
// Positive tests.
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import static androidx.media3.common.util.Util.escapeFileName;
|
|||
import static androidx.media3.common.util.Util.getCodecsOfType;
|
||||
import static androidx.media3.common.util.Util.getStringForTime;
|
||||
import static androidx.media3.common.util.Util.gzip;
|
||||
import static androidx.media3.common.util.Util.maxValue;
|
||||
import static androidx.media3.common.util.Util.minValue;
|
||||
import static androidx.media3.common.util.Util.parseXsDateTime;
|
||||
import static androidx.media3.common.util.Util.parseXsDuration;
|
||||
|
|
@ -747,6 +748,21 @@ public class UtilTest {
|
|||
assertThrows(NoSuchElementException.class, () -> minValue(new SparseLongArray()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sparseLongArrayMaxValue_returnsMaxValue() {
|
||||
SparseLongArray sparseLongArray = new SparseLongArray();
|
||||
sparseLongArray.put(0, 2);
|
||||
sparseLongArray.put(25, 10);
|
||||
sparseLongArray.put(42, 1);
|
||||
|
||||
assertThat(maxValue(sparseLongArray)).isEqualTo(10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sparseLongArrayMaxValue_emptyArray_throws() {
|
||||
assertThrows(NoSuchElementException.class, () -> maxValue(new SparseLongArray()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseXsDuration_returnsParsedDurationInMillis() {
|
||||
assertThat(parseXsDuration("PT150.279S")).isEqualTo(150279L);
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ public abstract class BaseDataSource implements DataSource {
|
|||
this.listeners = new ArrayList<>(/* initialCapacity= */ 1);
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public final void addTransferListener(TransferListener transferListener) {
|
||||
checkNotNull(transferListener);
|
||||
|
|
|
|||
|
|
@ -26,13 +26,13 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
|
||||
/** Reads data from URI-identified resources. */
|
||||
@UnstableApi
|
||||
public interface DataSource extends DataReader {
|
||||
|
||||
/** A factory for {@link DataSource} instances. */
|
||||
interface Factory {
|
||||
|
||||
/** Creates a {@link DataSource} instance. */
|
||||
@UnstableApi
|
||||
DataSource createDataSource();
|
||||
}
|
||||
|
||||
|
|
@ -41,6 +41,7 @@ public interface DataSource extends DataReader {
|
|||
*
|
||||
* @param transferListener A {@link TransferListener}.
|
||||
*/
|
||||
@UnstableApi
|
||||
void addTransferListener(TransferListener transferListener);
|
||||
|
||||
/**
|
||||
|
|
@ -72,6 +73,7 @@ public interface DataSource extends DataReader {
|
|||
* unresolved. For all other requests, the value returned will be equal to the request's
|
||||
* {@link DataSpec#length}.
|
||||
*/
|
||||
@UnstableApi
|
||||
long open(DataSpec dataSpec) throws IOException;
|
||||
|
||||
/**
|
||||
|
|
@ -82,6 +84,7 @@ public interface DataSource extends DataReader {
|
|||
*
|
||||
* @return The {@link Uri} from which data is being read, or null if the source is not open.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Nullable
|
||||
Uri getUri();
|
||||
|
||||
|
|
@ -91,6 +94,7 @@ public interface DataSource extends DataReader {
|
|||
*
|
||||
* <p>Key look-up in the returned map is case-insensitive.
|
||||
*/
|
||||
@UnstableApi
|
||||
default Map<String, List<String>> getResponseHeaders() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
|
@ -101,5 +105,6 @@ public interface DataSource extends DataReader {
|
|||
*
|
||||
* @throws IOException If an error occurs closing the source.
|
||||
*/
|
||||
@UnstableApi
|
||||
void close() throws IOException;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import androidx.media3.common.util.UnstableApi;
|
|||
import java.io.IOException;
|
||||
|
||||
/** Used to specify reason of a DataSource error. */
|
||||
@UnstableApi
|
||||
public class DataSourceException extends IOException {
|
||||
|
||||
/**
|
||||
|
|
@ -29,6 +28,7 @@ public class DataSourceException extends IOException {
|
|||
* {@link #reason} is {@link PlaybackException#ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE} in its
|
||||
* cause stack.
|
||||
*/
|
||||
@UnstableApi
|
||||
public static boolean isCausedByPositionOutOfRange(IOException e) {
|
||||
@Nullable Throwable cause = e;
|
||||
while (cause != null) {
|
||||
|
|
@ -49,7 +49,7 @@ public class DataSourceException extends IOException {
|
|||
*
|
||||
* @deprecated Use {@link PlaybackException#ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE}.
|
||||
*/
|
||||
@Deprecated
|
||||
@UnstableApi @Deprecated
|
||||
public static final int POSITION_OUT_OF_RANGE =
|
||||
PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE;
|
||||
|
||||
|
|
@ -65,6 +65,7 @@ public class DataSourceException extends IOException {
|
|||
* @param reason Reason of the error, should be one of the {@code ERROR_CODE_IO_*} in {@link
|
||||
* PlaybackException.ErrorCode}.
|
||||
*/
|
||||
@UnstableApi
|
||||
public DataSourceException(@PlaybackException.ErrorCode int reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
|
@ -76,6 +77,7 @@ public class DataSourceException extends IOException {
|
|||
* @param reason Reason of the error, should be one of the {@code ERROR_CODE_IO_*} in {@link
|
||||
* PlaybackException.ErrorCode}.
|
||||
*/
|
||||
@UnstableApi
|
||||
public DataSourceException(@Nullable Throwable cause, @PlaybackException.ErrorCode int reason) {
|
||||
super(cause);
|
||||
this.reason = reason;
|
||||
|
|
@ -88,6 +90,7 @@ public class DataSourceException extends IOException {
|
|||
* @param reason Reason of the error, should be one of the {@code ERROR_CODE_IO_*} in {@link
|
||||
* PlaybackException.ErrorCode}.
|
||||
*/
|
||||
@UnstableApi
|
||||
public DataSourceException(@Nullable String message, @PlaybackException.ErrorCode int reason) {
|
||||
super(message);
|
||||
this.reason = reason;
|
||||
|
|
@ -101,6 +104,7 @@ public class DataSourceException extends IOException {
|
|||
* @param reason Reason of the error, should be one of the {@code ERROR_CODE_IO_*} in {@link
|
||||
* PlaybackException.ErrorCode}.
|
||||
*/
|
||||
@UnstableApi
|
||||
public DataSourceException(
|
||||
@Nullable String message,
|
||||
@Nullable Throwable cause,
|
||||
|
|
|
|||
|
|
@ -54,7 +54,6 @@ import java.util.Map;
|
|||
* #DefaultDataSource(Context, DataSource)}.
|
||||
* </ul>
|
||||
*/
|
||||
@UnstableApi
|
||||
public final class DefaultDataSource implements DataSource {
|
||||
|
||||
/** {@link DataSource.Factory} for {@link DefaultDataSource} instances. */
|
||||
|
|
@ -98,11 +97,13 @@ public final class DefaultDataSource implements DataSource {
|
|||
* @param transferListener The listener that will be used.
|
||||
* @return This factory.
|
||||
*/
|
||||
@UnstableApi
|
||||
public Factory setTransferListener(@Nullable TransferListener transferListener) {
|
||||
this.transferListener = transferListener;
|
||||
return this;
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public DefaultDataSource createDataSource() {
|
||||
DefaultDataSource dataSource =
|
||||
|
|
@ -144,6 +145,7 @@ public final class DefaultDataSource implements DataSource {
|
|||
*
|
||||
* @param context A context.
|
||||
*/
|
||||
@UnstableApi
|
||||
public DefaultDataSource(Context context, boolean allowCrossProtocolRedirects) {
|
||||
this(
|
||||
context,
|
||||
|
|
@ -162,6 +164,7 @@ public final class DefaultDataSource implements DataSource {
|
|||
* @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP
|
||||
* to HTTPS and vice versa) are enabled when fetching remote data.
|
||||
*/
|
||||
@UnstableApi
|
||||
public DefaultDataSource(
|
||||
Context context, @Nullable String userAgent, boolean allowCrossProtocolRedirects) {
|
||||
this(
|
||||
|
|
@ -185,6 +188,7 @@ public final class DefaultDataSource implements DataSource {
|
|||
* @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP
|
||||
* to HTTPS and vice versa) are enabled when fetching remote data.
|
||||
*/
|
||||
@UnstableApi
|
||||
public DefaultDataSource(
|
||||
Context context,
|
||||
@Nullable String userAgent,
|
||||
|
|
@ -209,12 +213,14 @@ public final class DefaultDataSource implements DataSource {
|
|||
* @param baseDataSource A {@link DataSource} to use for URI schemes other than file, asset and
|
||||
* content. This {@link DataSource} should normally support at least http(s).
|
||||
*/
|
||||
@UnstableApi
|
||||
public DefaultDataSource(Context context, DataSource baseDataSource) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.baseDataSource = Assertions.checkNotNull(baseDataSource);
|
||||
transferListeners = new ArrayList<>();
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public void addTransferListener(TransferListener transferListener) {
|
||||
Assertions.checkNotNull(transferListener);
|
||||
|
|
@ -229,6 +235,7 @@ public final class DefaultDataSource implements DataSource {
|
|||
maybeAddListenerToDataSource(rawResourceDataSource, transferListener);
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public long open(DataSpec dataSpec) throws IOException {
|
||||
Assertions.checkState(dataSource == null);
|
||||
|
|
@ -260,22 +267,26 @@ public final class DefaultDataSource implements DataSource {
|
|||
return dataSource.open(dataSpec);
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public int read(byte[] buffer, int offset, int length) throws IOException {
|
||||
return Assertions.checkNotNull(dataSource).read(buffer, offset, length);
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
@Nullable
|
||||
public Uri getUri() {
|
||||
return dataSource == null ? null : dataSource.getUri();
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public Map<String, List<String>> getResponseHeaders() {
|
||||
return dataSource == null ? Collections.emptyMap() : dataSource.getResponseHeaders();
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (dataSource != null) {
|
||||
|
|
|
|||
|
|
@ -60,7 +60,6 @@ import java.util.zip.GZIPInputStream;
|
|||
* priority) the {@code dataSpec}, {@link #setRequestProperty} and the default properties that can
|
||||
* be passed to {@link HttpDataSource.Factory#setDefaultRequestProperties(Map)}.
|
||||
*/
|
||||
@UnstableApi
|
||||
public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSource {
|
||||
|
||||
/** {@link DataSource.Factory} for {@link DefaultHttpDataSource} instances. */
|
||||
|
|
@ -83,6 +82,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
|||
readTimeoutMs = DEFAULT_READ_TIMEOUT_MILLIS;
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public final Factory setDefaultRequestProperties(Map<String, String> defaultRequestProperties) {
|
||||
this.defaultRequestProperties.clearAndSet(defaultRequestProperties);
|
||||
|
|
@ -99,6 +99,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
|||
* agent of the underlying platform.
|
||||
* @return This factory.
|
||||
*/
|
||||
@UnstableApi
|
||||
public Factory setUserAgent(@Nullable String userAgent) {
|
||||
this.userAgent = userAgent;
|
||||
return this;
|
||||
|
|
@ -112,6 +113,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
|||
* @param connectTimeoutMs The connect timeout, in milliseconds, that will be used.
|
||||
* @return This factory.
|
||||
*/
|
||||
@UnstableApi
|
||||
public Factory setConnectTimeoutMs(int connectTimeoutMs) {
|
||||
this.connectTimeoutMs = connectTimeoutMs;
|
||||
return this;
|
||||
|
|
@ -125,6 +127,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
|||
* @param readTimeoutMs The connect timeout, in milliseconds, that will be used.
|
||||
* @return This factory.
|
||||
*/
|
||||
@UnstableApi
|
||||
public Factory setReadTimeoutMs(int readTimeoutMs) {
|
||||
this.readTimeoutMs = readTimeoutMs;
|
||||
return this;
|
||||
|
|
@ -138,6 +141,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
|||
* @param allowCrossProtocolRedirects Whether to allow cross protocol redirects.
|
||||
* @return This factory.
|
||||
*/
|
||||
@UnstableApi
|
||||
public Factory setAllowCrossProtocolRedirects(boolean allowCrossProtocolRedirects) {
|
||||
this.allowCrossProtocolRedirects = allowCrossProtocolRedirects;
|
||||
return this;
|
||||
|
|
@ -154,6 +158,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
|||
* predicate that was previously set.
|
||||
* @return This factory.
|
||||
*/
|
||||
@UnstableApi
|
||||
public Factory setContentTypePredicate(@Nullable Predicate<String> contentTypePredicate) {
|
||||
this.contentTypePredicate = contentTypePredicate;
|
||||
return this;
|
||||
|
|
@ -169,6 +174,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
|||
* @param transferListener The listener that will be used.
|
||||
* @return This factory.
|
||||
*/
|
||||
@UnstableApi
|
||||
public Factory setTransferListener(@Nullable TransferListener transferListener) {
|
||||
this.transferListener = transferListener;
|
||||
return this;
|
||||
|
|
@ -178,11 +184,13 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
|||
* Sets whether we should keep the POST method and body when we have HTTP 302 redirects for a
|
||||
* POST request.
|
||||
*/
|
||||
@UnstableApi
|
||||
public Factory setKeepPostFor302Redirects(boolean keepPostFor302Redirects) {
|
||||
this.keepPostFor302Redirects = keepPostFor302Redirects;
|
||||
return this;
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public DefaultHttpDataSource createDataSource() {
|
||||
DefaultHttpDataSource dataSource =
|
||||
|
|
@ -202,9 +210,9 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
|||
}
|
||||
|
||||
/** The default connection timeout, in milliseconds. */
|
||||
public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 8 * 1000;
|
||||
@UnstableApi public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 8 * 1000;
|
||||
/** The default read timeout, in milliseconds. */
|
||||
public static final int DEFAULT_READ_TIMEOUT_MILLIS = 8 * 1000;
|
||||
@UnstableApi public static final int DEFAULT_READ_TIMEOUT_MILLIS = 8 * 1000;
|
||||
|
||||
private static final String TAG = "DefaultHttpDataSource";
|
||||
private static final int MAX_REDIRECTS = 20; // Same limit as okhttp.
|
||||
|
|
@ -232,6 +240,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
|||
/**
|
||||
* @deprecated Use {@link DefaultHttpDataSource.Factory} instead.
|
||||
*/
|
||||
@UnstableApi
|
||||
@SuppressWarnings("deprecation")
|
||||
@Deprecated
|
||||
public DefaultHttpDataSource() {
|
||||
|
|
@ -241,6 +250,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
|||
/**
|
||||
* @deprecated Use {@link DefaultHttpDataSource.Factory} instead.
|
||||
*/
|
||||
@UnstableApi
|
||||
@SuppressWarnings("deprecation")
|
||||
@Deprecated
|
||||
public DefaultHttpDataSource(@Nullable String userAgent) {
|
||||
|
|
@ -250,6 +260,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
|||
/**
|
||||
* @deprecated Use {@link DefaultHttpDataSource.Factory} instead.
|
||||
*/
|
||||
@UnstableApi
|
||||
@SuppressWarnings("deprecation")
|
||||
@Deprecated
|
||||
public DefaultHttpDataSource(
|
||||
|
|
@ -265,6 +276,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
|||
/**
|
||||
* @deprecated Use {@link DefaultHttpDataSource.Factory} instead.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public DefaultHttpDataSource(
|
||||
@Nullable String userAgent,
|
||||
|
|
@ -305,22 +317,26 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
|||
* @deprecated Use {@link DefaultHttpDataSource.Factory#setContentTypePredicate(Predicate)}
|
||||
* instead.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public void setContentTypePredicate(@Nullable Predicate<String> contentTypePredicate) {
|
||||
this.contentTypePredicate = contentTypePredicate;
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
@Nullable
|
||||
public Uri getUri() {
|
||||
return connection == null ? null : Uri.parse(connection.getURL().toString());
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public int getResponseCode() {
|
||||
return connection == null || responseCode <= 0 ? -1 : responseCode;
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public Map<String, List<String>> getResponseHeaders() {
|
||||
if (connection == null) {
|
||||
|
|
@ -337,6 +353,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
|||
return new NullFilteringHeadersMap(connection.getHeaderFields());
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public void setRequestProperty(String name, String value) {
|
||||
checkNotNull(name);
|
||||
|
|
@ -344,18 +361,21 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
|||
requestProperties.set(name, value);
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public void clearRequestProperty(String name) {
|
||||
checkNotNull(name);
|
||||
requestProperties.remove(name);
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public void clearAllRequestProperties() {
|
||||
requestProperties.clear();
|
||||
}
|
||||
|
||||
/** Opens the source to read the specified data. */
|
||||
@UnstableApi
|
||||
@Override
|
||||
public long open(DataSpec dataSpec) throws HttpDataSourceException {
|
||||
this.dataSpec = dataSpec;
|
||||
|
|
@ -474,6 +494,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
|||
return bytesToRead;
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public int read(byte[] buffer, int offset, int length) throws HttpDataSourceException {
|
||||
try {
|
||||
|
|
@ -484,6 +505,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
|||
}
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public void close() throws HttpDataSourceException {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -38,12 +38,12 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
|
||||
/** An HTTP {@link DataSource}. */
|
||||
@UnstableApi
|
||||
public interface HttpDataSource extends DataSource {
|
||||
|
||||
/** A factory for {@link HttpDataSource} instances. */
|
||||
interface Factory extends DataSource.Factory {
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
HttpDataSource createDataSource();
|
||||
|
||||
|
|
@ -59,6 +59,7 @@ public interface HttpDataSource extends DataSource {
|
|||
* @param defaultRequestProperties The default request properties.
|
||||
* @return This factory.
|
||||
*/
|
||||
@UnstableApi
|
||||
Factory setDefaultRequestProperties(Map<String, String> defaultRequestProperties);
|
||||
}
|
||||
|
||||
|
|
@ -67,6 +68,7 @@ public interface HttpDataSource extends DataSource {
|
|||
* a thread safe way to avoid the potential of creating snapshots of an inconsistent or unintended
|
||||
* state.
|
||||
*/
|
||||
@UnstableApi
|
||||
final class RequestProperties {
|
||||
|
||||
private final Map<String, String> requestProperties;
|
||||
|
|
@ -141,6 +143,7 @@ public interface HttpDataSource extends DataSource {
|
|||
}
|
||||
|
||||
/** Base implementation of {@link Factory} that sets default request properties. */
|
||||
@UnstableApi
|
||||
abstract class BaseFactory implements Factory {
|
||||
|
||||
private final RequestProperties defaultRequestProperties;
|
||||
|
|
@ -172,6 +175,7 @@ public interface HttpDataSource extends DataSource {
|
|||
}
|
||||
|
||||
/** A {@link Predicate} that rejects content types often used for pay-walls. */
|
||||
@UnstableApi
|
||||
Predicate<String> REJECT_PAYWALL_TYPES =
|
||||
contentType -> {
|
||||
if (contentType == null) {
|
||||
|
|
@ -208,6 +212,7 @@ public interface HttpDataSource extends DataSource {
|
|||
* Returns a {@code HttpDataSourceException} whose error code is assigned according to the cause
|
||||
* and type.
|
||||
*/
|
||||
@UnstableApi
|
||||
public static HttpDataSourceException createForIOException(
|
||||
IOException cause, DataSpec dataSpec, @Type int type) {
|
||||
@PlaybackException.ErrorCode int errorCode;
|
||||
|
|
@ -231,7 +236,7 @@ public interface HttpDataSource extends DataSource {
|
|||
}
|
||||
|
||||
/** The {@link DataSpec} associated with the current connection. */
|
||||
public final DataSpec dataSpec;
|
||||
@UnstableApi public final DataSpec dataSpec;
|
||||
|
||||
public final @Type int type;
|
||||
|
||||
|
|
@ -239,6 +244,7 @@ public interface HttpDataSource extends DataSource {
|
|||
* @deprecated Use {@link #HttpDataSourceException(DataSpec, int, int)
|
||||
* HttpDataSourceException(DataSpec, PlaybackException.ERROR_CODE_IO_UNSPECIFIED, int)}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public HttpDataSourceException(DataSpec dataSpec, @Type int type) {
|
||||
this(dataSpec, PlaybackException.ERROR_CODE_IO_UNSPECIFIED, type);
|
||||
|
|
@ -252,6 +258,7 @@ public interface HttpDataSource extends DataSource {
|
|||
* PlaybackException.ErrorCode}.
|
||||
* @param type See {@link Type}.
|
||||
*/
|
||||
@UnstableApi
|
||||
public HttpDataSourceException(
|
||||
DataSpec dataSpec, @PlaybackException.ErrorCode int errorCode, @Type int type) {
|
||||
super(assignErrorCode(errorCode, type));
|
||||
|
|
@ -264,6 +271,7 @@ public interface HttpDataSource extends DataSource {
|
|||
* HttpDataSourceException(String, DataSpec, PlaybackException.ERROR_CODE_IO_UNSPECIFIED,
|
||||
* int)}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public HttpDataSourceException(String message, DataSpec dataSpec, @Type int type) {
|
||||
this(message, dataSpec, PlaybackException.ERROR_CODE_IO_UNSPECIFIED, type);
|
||||
|
|
@ -278,6 +286,7 @@ public interface HttpDataSource extends DataSource {
|
|||
* PlaybackException.ErrorCode}.
|
||||
* @param type See {@link Type}.
|
||||
*/
|
||||
@UnstableApi
|
||||
public HttpDataSourceException(
|
||||
String message,
|
||||
DataSpec dataSpec,
|
||||
|
|
@ -293,6 +302,7 @@ public interface HttpDataSource extends DataSource {
|
|||
* HttpDataSourceException(IOException, DataSpec,
|
||||
* PlaybackException.ERROR_CODE_IO_UNSPECIFIED, int)}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public HttpDataSourceException(IOException cause, DataSpec dataSpec, @Type int type) {
|
||||
this(cause, dataSpec, PlaybackException.ERROR_CODE_IO_UNSPECIFIED, type);
|
||||
|
|
@ -307,6 +317,7 @@ public interface HttpDataSource extends DataSource {
|
|||
* PlaybackException.ErrorCode}.
|
||||
* @param type See {@link Type}.
|
||||
*/
|
||||
@UnstableApi
|
||||
public HttpDataSourceException(
|
||||
IOException cause,
|
||||
DataSpec dataSpec,
|
||||
|
|
@ -322,6 +333,7 @@ public interface HttpDataSource extends DataSource {
|
|||
* HttpDataSourceException(String, IOException, DataSpec,
|
||||
* PlaybackException.ERROR_CODE_IO_UNSPECIFIED, int)}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public HttpDataSourceException(
|
||||
String message, IOException cause, DataSpec dataSpec, @Type int type) {
|
||||
|
|
@ -338,6 +350,7 @@ public interface HttpDataSource extends DataSource {
|
|||
* PlaybackException.ErrorCode}.
|
||||
* @param type See {@link Type}.
|
||||
*/
|
||||
@UnstableApi
|
||||
public HttpDataSourceException(
|
||||
String message,
|
||||
@Nullable IOException cause,
|
||||
|
|
@ -365,6 +378,7 @@ public interface HttpDataSource extends DataSource {
|
|||
*/
|
||||
final class CleartextNotPermittedException extends HttpDataSourceException {
|
||||
|
||||
@UnstableApi
|
||||
public CleartextNotPermittedException(IOException cause, DataSpec dataSpec) {
|
||||
super(
|
||||
"Cleartext HTTP traffic not permitted. See"
|
||||
|
|
@ -381,6 +395,7 @@ public interface HttpDataSource extends DataSource {
|
|||
|
||||
public final String contentType;
|
||||
|
||||
@UnstableApi
|
||||
public InvalidContentTypeException(String contentType, DataSpec dataSpec) {
|
||||
super(
|
||||
"Invalid content type: " + contentType,
|
||||
|
|
@ -412,6 +427,7 @@ public interface HttpDataSource extends DataSource {
|
|||
* @deprecated Use {@link #InvalidResponseCodeException(int, String, IOException, Map, DataSpec,
|
||||
* byte[])}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public InvalidResponseCodeException(
|
||||
int responseCode, Map<String, List<String>> headerFields, DataSpec dataSpec) {
|
||||
|
|
@ -428,6 +444,7 @@ public interface HttpDataSource extends DataSource {
|
|||
* @deprecated Use {@link #InvalidResponseCodeException(int, String, IOException, Map, DataSpec,
|
||||
* byte[])}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public InvalidResponseCodeException(
|
||||
int responseCode,
|
||||
|
|
@ -443,6 +460,7 @@ public interface HttpDataSource extends DataSource {
|
|||
/* responseBody= */ Util.EMPTY_BYTE_ARRAY);
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
public InvalidResponseCodeException(
|
||||
int responseCode,
|
||||
@Nullable String responseMessage,
|
||||
|
|
@ -470,12 +488,15 @@ public interface HttpDataSource extends DataSource {
|
|||
* (in order of decreasing priority) the {@code dataSpec}, {@link #setRequestProperty} and the
|
||||
* default parameters set in the {@link Factory}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Override
|
||||
long open(DataSpec dataSpec) throws HttpDataSourceException;
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
void close() throws HttpDataSourceException;
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
int read(byte[] buffer, int offset, int length) throws HttpDataSourceException;
|
||||
|
||||
|
|
@ -490,6 +511,7 @@ public interface HttpDataSource extends DataSource {
|
|||
* @param name The name of the header field.
|
||||
* @param value The value of the field.
|
||||
*/
|
||||
@UnstableApi
|
||||
void setRequestProperty(String name, String value);
|
||||
|
||||
/**
|
||||
|
|
@ -498,17 +520,21 @@ public interface HttpDataSource extends DataSource {
|
|||
*
|
||||
* @param name The name of the header field.
|
||||
*/
|
||||
@UnstableApi
|
||||
void clearRequestProperty(String name);
|
||||
|
||||
/** Clears all request headers that were set by {@link #setRequestProperty(String, String)}. */
|
||||
@UnstableApi
|
||||
void clearAllRequestProperties();
|
||||
|
||||
/**
|
||||
* When the source is open, returns the HTTP response status code associated with the last {@link
|
||||
* #open} call. Otherwise, returns a negative value.
|
||||
*/
|
||||
@UnstableApi
|
||||
int getResponseCode();
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
Map<String, List<String>> getResponseHeaders();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,14 +22,14 @@ import java.io.IOException;
|
|||
|
||||
/** A DataSource which provides no data. {@link #open(DataSpec)} throws {@link IOException}. */
|
||||
@UnstableApi
|
||||
public final class DummyDataSource implements DataSource {
|
||||
public final class PlaceholderDataSource implements DataSource {
|
||||
|
||||
public static final DummyDataSource INSTANCE = new DummyDataSource();
|
||||
public static final PlaceholderDataSource INSTANCE = new PlaceholderDataSource();
|
||||
|
||||
/** A factory that produces {@link DummyDataSource}. */
|
||||
public static final Factory FACTORY = DummyDataSource::new;
|
||||
/** A factory that produces {@link PlaceholderDataSource}. */
|
||||
public static final Factory FACTORY = PlaceholderDataSource::new;
|
||||
|
||||
private DummyDataSource() {}
|
||||
private PlaceholderDataSource() {}
|
||||
|
||||
@Override
|
||||
public void addTransferListener(TransferListener transferListener) {
|
||||
|
|
@ -38,7 +38,7 @@ public final class DummyDataSource implements DataSource {
|
|||
|
||||
@Override
|
||||
public long open(DataSpec dataSpec) throws IOException {
|
||||
throw new IOException("DummyDataSource cannot be opened");
|
||||
throw new IOException("PlaceholderDataSource cannot be opened");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -36,8 +36,8 @@ import androidx.media3.datasource.DataSink;
|
|||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.datasource.DataSourceException;
|
||||
import androidx.media3.datasource.DataSpec;
|
||||
import androidx.media3.datasource.DummyDataSource;
|
||||
import androidx.media3.datasource.FileDataSource;
|
||||
import androidx.media3.datasource.PlaceholderDataSource;
|
||||
import androidx.media3.datasource.PriorityDataSource;
|
||||
import androidx.media3.datasource.TeeDataSource;
|
||||
import androidx.media3.datasource.TransferListener;
|
||||
|
|
@ -541,7 +541,7 @@ public final class CacheDataSource implements DataSource {
|
|||
? new TeeDataSource(upstreamDataSource, cacheWriteDataSink)
|
||||
: null;
|
||||
} else {
|
||||
this.upstreamDataSource = DummyDataSource.INSTANCE;
|
||||
this.upstreamDataSource = PlaceholderDataSource.INSTANCE;
|
||||
this.cacheWriteDataSource = null;
|
||||
}
|
||||
this.eventListener = eventListener;
|
||||
|
|
|
|||
|
|
@ -38,8 +38,9 @@ If your application only needs to play http(s) content, using the Cronet
|
|||
extension is as simple as updating `DataSource.Factory` instantiations in your
|
||||
application code to use `CronetDataSource.Factory`. If your application also
|
||||
needs to play non-http(s) content such as local files, use:
|
||||
|
||||
```
|
||||
new DefaultDataSourceFactory(
|
||||
new DefaultDataSource.Factory(
|
||||
...
|
||||
/* baseDataSourceFactory= */ new CronetDataSource.Factory(...) );
|
||||
```
|
||||
|
|
|
|||
|
|
@ -68,7 +68,6 @@ import org.chromium.net.UrlResponseInfo;
|
|||
* priority) the {@code dataSpec}, {@link #setRequestProperty} and the default parameters used to
|
||||
* construct the instance.
|
||||
*/
|
||||
@UnstableApi
|
||||
public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
||||
|
||||
static {
|
||||
|
|
@ -132,6 +131,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
* CronetEngine}, or {@link DefaultHttpDataSource} for cases where {@link
|
||||
* CronetEngineWrapper#getCronetEngine()} would have returned {@code null}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public Factory(CronetEngineWrapper cronetEngineWrapper, Executor executor) {
|
||||
this.cronetEngine = cronetEngineWrapper.getCronetEngine();
|
||||
|
|
@ -142,6 +142,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
readTimeoutMs = DEFAULT_READ_TIMEOUT_MILLIS;
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public final Factory setDefaultRequestProperties(Map<String, String> defaultRequestProperties) {
|
||||
this.defaultRequestProperties.clearAndSet(defaultRequestProperties);
|
||||
|
|
@ -161,6 +162,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
* agent of the underlying {@link CronetEngine}.
|
||||
* @return This factory.
|
||||
*/
|
||||
@UnstableApi
|
||||
public Factory setUserAgent(@Nullable String userAgent) {
|
||||
this.userAgent = userAgent;
|
||||
if (internalFallbackFactory != null) {
|
||||
|
|
@ -179,6 +181,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
* UrlRequest.Builder#REQUEST_PRIORITY_*} constants.
|
||||
* @return This factory.
|
||||
*/
|
||||
@UnstableApi
|
||||
public Factory setRequestPriority(int requestPriority) {
|
||||
this.requestPriority = requestPriority;
|
||||
return this;
|
||||
|
|
@ -192,6 +195,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
* @param connectTimeoutMs The connect timeout, in milliseconds, that will be used.
|
||||
* @return This factory.
|
||||
*/
|
||||
@UnstableApi
|
||||
public Factory setConnectionTimeoutMs(int connectTimeoutMs) {
|
||||
this.connectTimeoutMs = connectTimeoutMs;
|
||||
if (internalFallbackFactory != null) {
|
||||
|
|
@ -208,6 +212,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
* @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs.
|
||||
* @return This factory.
|
||||
*/
|
||||
@UnstableApi
|
||||
public Factory setResetTimeoutOnRedirects(boolean resetTimeoutOnRedirects) {
|
||||
this.resetTimeoutOnRedirects = resetTimeoutOnRedirects;
|
||||
return this;
|
||||
|
|
@ -223,6 +228,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
* to the redirect url in the "Cookie" header.
|
||||
* @return This factory.
|
||||
*/
|
||||
@UnstableApi
|
||||
public Factory setHandleSetCookieRequests(boolean handleSetCookieRequests) {
|
||||
this.handleSetCookieRequests = handleSetCookieRequests;
|
||||
return this;
|
||||
|
|
@ -236,6 +242,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
* @param readTimeoutMs The connect timeout, in milliseconds, that will be used.
|
||||
* @return This factory.
|
||||
*/
|
||||
@UnstableApi
|
||||
public Factory setReadTimeoutMs(int readTimeoutMs) {
|
||||
this.readTimeoutMs = readTimeoutMs;
|
||||
if (internalFallbackFactory != null) {
|
||||
|
|
@ -254,6 +261,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
* predicate that was previously set.
|
||||
* @return This factory.
|
||||
*/
|
||||
@UnstableApi
|
||||
public Factory setContentTypePredicate(@Nullable Predicate<String> contentTypePredicate) {
|
||||
this.contentTypePredicate = contentTypePredicate;
|
||||
if (internalFallbackFactory != null) {
|
||||
|
|
@ -266,6 +274,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
* Sets whether we should keep the POST method and body when we have HTTP 302 redirects for a
|
||||
* POST request.
|
||||
*/
|
||||
@UnstableApi
|
||||
public Factory setKeepPostFor302Redirects(boolean keepPostFor302Redirects) {
|
||||
this.keepPostFor302Redirects = keepPostFor302Redirects;
|
||||
if (internalFallbackFactory != null) {
|
||||
|
|
@ -284,6 +293,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
* @param transferListener The listener that will be used.
|
||||
* @return This factory.
|
||||
*/
|
||||
@UnstableApi
|
||||
public Factory setTransferListener(@Nullable TransferListener transferListener) {
|
||||
this.transferListener = transferListener;
|
||||
if (internalFallbackFactory != null) {
|
||||
|
|
@ -303,12 +313,14 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
* @deprecated Do not use {@link CronetDataSource} or its factory in cases where a suitable
|
||||
* {@link CronetEngine} is not available. Use the fallback factory directly in such cases.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public Factory setFallbackFactory(@Nullable HttpDataSource.Factory fallbackFactory) {
|
||||
this.fallbackFactory = fallbackFactory;
|
||||
return this;
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public HttpDataSource createDataSource() {
|
||||
if (cronetEngine == null) {
|
||||
|
|
@ -337,6 +349,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
}
|
||||
|
||||
/** Thrown when an error is encountered when trying to open a {@link CronetDataSource}. */
|
||||
@UnstableApi
|
||||
public static final class OpenException extends HttpDataSourceException {
|
||||
|
||||
/**
|
||||
|
|
@ -389,9 +402,9 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
}
|
||||
|
||||
/** The default connection timeout, in milliseconds. */
|
||||
public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 8 * 1000;
|
||||
@UnstableApi public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 8 * 1000;
|
||||
/** The default read timeout, in milliseconds. */
|
||||
public static final int DEFAULT_READ_TIMEOUT_MILLIS = 8 * 1000;
|
||||
@UnstableApi public static final int DEFAULT_READ_TIMEOUT_MILLIS = 8 * 1000;
|
||||
|
||||
/* package */ final UrlRequest.Callback urlRequestCallback;
|
||||
|
||||
|
|
@ -436,6 +449,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
|
||||
private volatile long currentConnectTimeoutMs;
|
||||
|
||||
@UnstableApi
|
||||
protected CronetDataSource(
|
||||
CronetEngine cronetEngine,
|
||||
Executor executor,
|
||||
|
|
@ -473,6 +487,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
* @param contentTypePredicate The content type {@link Predicate}, or {@code null} to clear a
|
||||
* predicate that was previously set.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public void setContentTypePredicate(@Nullable Predicate<String> contentTypePredicate) {
|
||||
this.contentTypePredicate = contentTypePredicate;
|
||||
|
|
@ -480,21 +495,25 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
|
||||
// HttpDataSource implementation.
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public void setRequestProperty(String name, String value) {
|
||||
requestProperties.set(name, value);
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public void clearRequestProperty(String name) {
|
||||
requestProperties.remove(name);
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public void clearAllRequestProperties() {
|
||||
requestProperties.clear();
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public int getResponseCode() {
|
||||
return responseInfo == null || responseInfo.getHttpStatusCode() <= 0
|
||||
|
|
@ -502,17 +521,20 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
: responseInfo.getHttpStatusCode();
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public Map<String, List<String>> getResponseHeaders() {
|
||||
return responseInfo == null ? Collections.emptyMap() : responseInfo.getAllHeaders();
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
@Nullable
|
||||
public Uri getUri() {
|
||||
return responseInfo == null ? null : Uri.parse(responseInfo.getUrl());
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public long open(DataSpec dataSpec) throws HttpDataSourceException {
|
||||
Assertions.checkNotNull(dataSpec);
|
||||
|
|
@ -644,6 +666,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
return bytesRemaining;
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public int read(byte[] buffer, int offset, int length) throws HttpDataSourceException {
|
||||
Assertions.checkState(opened);
|
||||
|
|
@ -715,6 +738,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
* @throws HttpDataSourceException If an error occurs reading from the source.
|
||||
* @throws IllegalArgumentException If {@code buffer} is not a direct ByteBuffer.
|
||||
*/
|
||||
@UnstableApi
|
||||
public int read(ByteBuffer buffer) throws HttpDataSourceException {
|
||||
Assertions.checkState(opened);
|
||||
|
||||
|
|
@ -759,6 +783,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
return bytesRead;
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (currentUrlRequest != null) {
|
||||
|
|
@ -779,17 +804,20 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
}
|
||||
|
||||
/** Returns current {@link UrlRequest}. May be null if the data source is not opened. */
|
||||
@UnstableApi
|
||||
@Nullable
|
||||
protected UrlRequest getCurrentUrlRequest() {
|
||||
return currentUrlRequest;
|
||||
}
|
||||
|
||||
/** Returns current {@link UrlResponseInfo}. May be null if the data source is not opened. */
|
||||
@UnstableApi
|
||||
@Nullable
|
||||
protected UrlResponseInfo getCurrentUrlResponseInfo() {
|
||||
return responseInfo;
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
protected UrlRequest.Builder buildRequestBuilder(DataSpec dataSpec) throws IOException {
|
||||
UrlRequest.Builder requestBuilder =
|
||||
cronetEngine
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ import org.chromium.net.CronetEngine;
|
|||
import org.chromium.net.CronetProvider;
|
||||
|
||||
/** Cronet utility methods. */
|
||||
@UnstableApi
|
||||
public final class CronetUtil {
|
||||
|
||||
private static final String TAG = "CronetUtil";
|
||||
|
|
@ -77,6 +76,7 @@ public final class CronetUtil {
|
|||
* over Cronet Embedded, if both are available.
|
||||
* @return The {@link CronetEngine}, or {@code null} if no suitable engine could be built.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Nullable
|
||||
public static CronetEngine buildCronetEngine(
|
||||
Context context, @Nullable String userAgent, boolean preferGooglePlayServices) {
|
||||
|
|
|
|||
|
|
@ -60,7 +60,6 @@ import okhttp3.ResponseBody;
|
|||
* priority) the {@code dataSpec}, {@link #setRequestProperty} and the default parameters used to
|
||||
* construct the instance.
|
||||
*/
|
||||
@UnstableApi
|
||||
public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
|
||||
|
||||
static {
|
||||
|
|
@ -89,6 +88,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
|
|||
defaultRequestProperties = new RequestProperties();
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public final Factory setDefaultRequestProperties(Map<String, String> defaultRequestProperties) {
|
||||
this.defaultRequestProperties.clearAndSet(defaultRequestProperties);
|
||||
|
|
@ -105,6 +105,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
|
|||
* agent of the underlying {@link OkHttpClient}.
|
||||
* @return This factory.
|
||||
*/
|
||||
@UnstableApi
|
||||
public Factory setUserAgent(@Nullable String userAgent) {
|
||||
this.userAgent = userAgent;
|
||||
return this;
|
||||
|
|
@ -118,6 +119,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
|
|||
* @param cacheControl The cache control that will be used.
|
||||
* @return This factory.
|
||||
*/
|
||||
@UnstableApi
|
||||
public Factory setCacheControl(@Nullable CacheControl cacheControl) {
|
||||
this.cacheControl = cacheControl;
|
||||
return this;
|
||||
|
|
@ -134,6 +136,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
|
|||
* predicate that was previously set.
|
||||
* @return This factory.
|
||||
*/
|
||||
@UnstableApi
|
||||
public Factory setContentTypePredicate(@Nullable Predicate<String> contentTypePredicate) {
|
||||
this.contentTypePredicate = contentTypePredicate;
|
||||
return this;
|
||||
|
|
@ -149,11 +152,13 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
|
|||
* @param transferListener The listener that will be used.
|
||||
* @return This factory.
|
||||
*/
|
||||
@UnstableApi
|
||||
public Factory setTransferListener(@Nullable TransferListener transferListener) {
|
||||
this.transferListener = transferListener;
|
||||
return this;
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public OkHttpDataSource createDataSource() {
|
||||
OkHttpDataSource dataSource =
|
||||
|
|
@ -185,6 +190,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
|
|||
* @deprecated Use {@link OkHttpDataSource.Factory} instead.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public OkHttpDataSource(Call.Factory callFactory) {
|
||||
this(callFactory, /* userAgent= */ null);
|
||||
|
|
@ -194,6 +200,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
|
|||
* @deprecated Use {@link OkHttpDataSource.Factory} instead.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public OkHttpDataSource(Call.Factory callFactory, @Nullable String userAgent) {
|
||||
this(callFactory, userAgent, /* cacheControl= */ null, /* defaultRequestProperties= */ null);
|
||||
|
|
@ -202,6 +209,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
|
|||
/**
|
||||
* @deprecated Use {@link OkHttpDataSource.Factory} instead.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public OkHttpDataSource(
|
||||
Call.Factory callFactory,
|
||||
|
|
@ -234,27 +242,32 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
|
|||
/**
|
||||
* @deprecated Use {@link OkHttpDataSource.Factory#setContentTypePredicate(Predicate)} instead.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
public void setContentTypePredicate(@Nullable Predicate<String> contentTypePredicate) {
|
||||
this.contentTypePredicate = contentTypePredicate;
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
@Nullable
|
||||
public Uri getUri() {
|
||||
return response == null ? null : Uri.parse(response.request().url().toString());
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public int getResponseCode() {
|
||||
return response == null ? -1 : response.code();
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public Map<String, List<String>> getResponseHeaders() {
|
||||
return response == null ? Collections.emptyMap() : response.headers().toMultimap();
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public void setRequestProperty(String name, String value) {
|
||||
Assertions.checkNotNull(name);
|
||||
|
|
@ -262,17 +275,20 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
|
|||
requestProperties.set(name, value);
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public void clearRequestProperty(String name) {
|
||||
Assertions.checkNotNull(name);
|
||||
requestProperties.remove(name);
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public void clearAllRequestProperties() {
|
||||
requestProperties.clear();
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public long open(DataSpec dataSpec) throws HttpDataSourceException {
|
||||
this.dataSpec = dataSpec;
|
||||
|
|
@ -358,6 +374,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
|
|||
return bytesToRead;
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public int read(byte[] buffer, int offset, int length) throws HttpDataSourceException {
|
||||
try {
|
||||
|
|
@ -368,6 +385,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
|
|||
}
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Override
|
||||
public void close() {
|
||||
if (opened) {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ injected from application code.
|
|||
|
||||
`DefaultDataSource` will automatically use the RTMP extension whenever it's
|
||||
available. Hence if your application is using `DefaultDataSource` or
|
||||
`DefaultDataSourceFactory`, adding support for RTMP streams is as simple as
|
||||
`DefaultDataSource.Factory`, adding support for RTMP streams is as simple as
|
||||
adding a dependency to the RTMP extension as described above. No changes to your
|
||||
application code are required. Alternatively, if you know that your application
|
||||
doesn't need to handle any other protocols, you can update any
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ apply from: "$gradle.ext.androidxMediaSettingsDir/common_library_config.gradle"
|
|||
// failures if ffmpeg hasn't been built according to the README instructions.
|
||||
if (project.file('src/main/jni/ffmpeg').exists()) {
|
||||
android.externalNativeBuild.cmake.path = 'src/main/jni/CMakeLists.txt'
|
||||
android.externalNativeBuild.cmake.version = '3.7.1+'
|
||||
// Should match cmake_minimum_required.
|
||||
android.externalNativeBuild.cmake.version = '3.21.0+'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
# limitations under the License.
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 3.7.1 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.21.0 FATAL_ERROR)
|
||||
|
||||
# Enable C++11 features.
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
|
|
|
|||
|
|
@ -21,11 +21,11 @@ import static java.lang.Math.min;
|
|||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||
import androidx.media3.exoplayer.upstream.Allocator;
|
||||
import androidx.media3.exoplayer.upstream.DefaultAllocator;
|
||||
|
|
|
|||
|
|
@ -171,25 +171,6 @@ public class DefaultRenderersFactory implements RenderersFactory {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable calling {@link MediaCodec#start} immediately after {@link MediaCodec#flush} on the
|
||||
* playback thread, when operating the codec in asynchronous mode. If disabled, {@link
|
||||
* MediaCodec#start} will be called by the callback thread after pending callbacks are handled.
|
||||
*
|
||||
* <p>By default, this feature is disabled.
|
||||
*
|
||||
* <p>This method is experimental, and will be renamed or removed in a future release.
|
||||
*
|
||||
* @param enabled Whether {@link MediaCodec#start} will be called on the playback thread
|
||||
* immediately after {@link MediaCodec#flush}.
|
||||
* @return This factory, for convenience.
|
||||
*/
|
||||
public DefaultRenderersFactory experimentalSetImmediateCodecStartAfterFlushEnabled(
|
||||
boolean enabled) {
|
||||
codecAdapterFactory.experimentalSetImmediateCodecStartAfterFlushEnabled(enabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to enable fallback to lower-priority decoders if decoder initialization fails.
|
||||
* This may result in using a decoder that is less efficient or slower than the primary decoder.
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import androidx.media3.common.MediaItem;
|
|||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.PriorityTaskManager;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TracksInfo;
|
||||
import androidx.media3.common.VideoSize;
|
||||
import androidx.media3.common.text.Cue;
|
||||
import androidx.media3.common.util.Clock;
|
||||
|
|
@ -54,8 +55,10 @@ import androidx.media3.exoplayer.metadata.MetadataRenderer;
|
|||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
||||
import androidx.media3.exoplayer.source.MediaSource;
|
||||
import androidx.media3.exoplayer.source.ShuffleOrder;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.text.TextRenderer;
|
||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
|
||||
import androidx.media3.exoplayer.trackselection.TrackSelectionArray;
|
||||
import androidx.media3.exoplayer.trackselection.TrackSelector;
|
||||
import androidx.media3.exoplayer.upstream.BandwidthMeter;
|
||||
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter;
|
||||
|
|
@ -1216,6 +1219,27 @@ public interface ExoPlayer extends Player {
|
|||
@Nullable
|
||||
TrackSelector getTrackSelector();
|
||||
|
||||
/**
|
||||
* Returns the available track groups.
|
||||
*
|
||||
* @see Listener#onTracksInfoChanged(TracksInfo)
|
||||
* @deprecated Use {@link #getCurrentTracksInfo()}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
TrackGroupArray getCurrentTrackGroups();
|
||||
|
||||
/**
|
||||
* Returns the current track selections for each renderer, which may include {@code null} elements
|
||||
* if some renderers do not have any selected tracks.
|
||||
*
|
||||
* @see Listener#onTracksInfoChanged(TracksInfo)
|
||||
* @deprecated Use {@link #getCurrentTracksInfo()}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
TrackSelectionArray getCurrentTrackSelections();
|
||||
|
||||
/** Returns the {@link Looper} associated with the playback thread. */
|
||||
@UnstableApi
|
||||
Looper getPlaybackLooper();
|
||||
|
|
|
|||
|
|
@ -69,8 +69,6 @@ import androidx.media3.common.Player;
|
|||
import androidx.media3.common.PriorityTaskManager;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.TrackSelectionArray;
|
||||
import androidx.media3.common.TrackSelectionParameters;
|
||||
import androidx.media3.common.TracksInfo;
|
||||
import androidx.media3.common.VideoSize;
|
||||
|
|
@ -93,8 +91,10 @@ import androidx.media3.exoplayer.metadata.MetadataOutput;
|
|||
import androidx.media3.exoplayer.source.MediaSource;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||
import androidx.media3.exoplayer.source.ShuffleOrder;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.text.TextOutput;
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||
import androidx.media3.exoplayer.trackselection.TrackSelectionArray;
|
||||
import androidx.media3.exoplayer.trackselection.TrackSelector;
|
||||
import androidx.media3.exoplayer.trackselection.TrackSelectorResult;
|
||||
import androidx.media3.exoplayer.upstream.BandwidthMeter;
|
||||
|
|
@ -1896,11 +1896,6 @@ import java.util.concurrent.TimeoutException;
|
|||
}
|
||||
if (previousPlaybackInfo.trackSelectorResult != newPlaybackInfo.trackSelectorResult) {
|
||||
trackSelector.onSelectionActivated(newPlaybackInfo.trackSelectorResult.info);
|
||||
TrackSelectionArray newSelection =
|
||||
new TrackSelectionArray(newPlaybackInfo.trackSelectorResult.selections);
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_TRACKS_CHANGED,
|
||||
listener -> listener.onTracksChanged(newPlaybackInfo.trackGroups, newSelection));
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_TRACKS_CHANGED,
|
||||
listener -> listener.onTracksInfoChanged(newPlaybackInfo.trackSelectorResult.tracksInfo));
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ import androidx.media3.common.Player.PlayWhenReadyChangeReason;
|
|||
import androidx.media3.common.Player.PlaybackSuppressionReason;
|
||||
import androidx.media3.common.Player.RepeatMode;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.Clock;
|
||||
import androidx.media3.common.util.HandlerWrapper;
|
||||
|
|
@ -56,11 +55,13 @@ import androidx.media3.exoplayer.DefaultMediaClock.PlaybackParametersListener;
|
|||
import androidx.media3.exoplayer.analytics.AnalyticsCollector;
|
||||
import androidx.media3.exoplayer.analytics.PlayerId;
|
||||
import androidx.media3.exoplayer.drm.DrmSession;
|
||||
import androidx.media3.exoplayer.metadata.MetadataRenderer;
|
||||
import androidx.media3.exoplayer.source.BehindLiveWindowException;
|
||||
import androidx.media3.exoplayer.source.MediaPeriod;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||
import androidx.media3.exoplayer.source.SampleStream;
|
||||
import androidx.media3.exoplayer.source.ShuffleOrder;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.text.TextRenderer;
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||
import androidx.media3.exoplayer.trackselection.TrackSelector;
|
||||
|
|
@ -2228,6 +2229,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||
return reading.info.isFollowedByTransitionToSameStream
|
||||
&& nextPeriod.prepared
|
||||
&& (renderer instanceof TextRenderer // [internal: b/181312195]
|
||||
|| renderer instanceof MetadataRenderer
|
||||
|| renderer.getReadingPositionUs() >= nextPeriod.getStartPositionRendererTime());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ package androidx.media3.exoplayer;
|
|||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||
import androidx.media3.exoplayer.upstream.Allocator;
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import androidx.annotation.Nullable;
|
|||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.exoplayer.source.ClippingMediaPeriod;
|
||||
|
|
@ -29,6 +28,7 @@ import androidx.media3.exoplayer.source.EmptySampleStream;
|
|||
import androidx.media3.exoplayer.source.MediaPeriod;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||
import androidx.media3.exoplayer.source.SampleStream;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||
import androidx.media3.exoplayer.trackselection.TrackSelector;
|
||||
import androidx.media3.exoplayer.trackselection.TrackSelectorResult;
|
||||
|
|
|
|||
|
|
@ -925,7 +925,8 @@ import com.google.common.collect.ImmutableList;
|
|||
: endPositionUs;
|
||||
if (durationUs != C.TIME_UNSET && startPositionUs >= durationUs) {
|
||||
// Ensure start position doesn't exceed duration.
|
||||
startPositionUs = max(0, durationUs - 1);
|
||||
boolean endAtLastFrame = isLastInTimeline || !clipPeriodAtContentDuration;
|
||||
startPositionUs = max(0, durationUs - (endAtLastFrame ? 1 : 0));
|
||||
}
|
||||
return new MediaPeriodInfo(
|
||||
id,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import androidx.annotation.VisibleForTesting;
|
|||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.util.Clock;
|
||||
import androidx.media3.common.util.HandlerWrapper;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
|
@ -34,6 +33,7 @@ import androidx.media3.exoplayer.analytics.PlayerId;
|
|||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
||||
import androidx.media3.exoplayer.source.MediaPeriod;
|
||||
import androidx.media3.exoplayer.source.MediaSource;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.upstream.Allocator;
|
||||
import androidx.media3.exoplayer.upstream.DefaultAllocator;
|
||||
import androidx.media3.extractor.DefaultExtractorsFactory;
|
||||
|
|
@ -157,7 +157,7 @@ public final class MetadataRetriever {
|
|||
mediaPeriod.maybeThrowPrepareError();
|
||||
}
|
||||
mediaSourceHandler.sendEmptyMessageDelayed(
|
||||
MESSAGE_CHECK_FOR_FAILURE, /* delayMillis= */ ERROR_POLL_INTERVAL_MS);
|
||||
MESSAGE_CHECK_FOR_FAILURE, /* delayMs= */ ERROR_POLL_INTERVAL_MS);
|
||||
} catch (Exception e) {
|
||||
trackGroupsFuture.setException(e);
|
||||
mediaSourceHandler.obtainMessage(MESSAGE_RELEASE).sendToTarget();
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ import androidx.media3.common.PlaybackParameters;
|
|||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.Player.PlaybackSuppressionReason;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.trackselection.TrackSelectorResult;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.List;
|
||||
|
|
|
|||
|
|
@ -35,8 +35,6 @@ import androidx.media3.common.MediaMetadata;
|
|||
import androidx.media3.common.PlaybackParameters;
|
||||
import androidx.media3.common.PriorityTaskManager;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.TrackSelectionArray;
|
||||
import androidx.media3.common.TrackSelectionParameters;
|
||||
import androidx.media3.common.TracksInfo;
|
||||
import androidx.media3.common.VideoSize;
|
||||
|
|
@ -49,6 +47,8 @@ import androidx.media3.exoplayer.analytics.AnalyticsListener;
|
|||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
||||
import androidx.media3.exoplayer.source.MediaSource;
|
||||
import androidx.media3.exoplayer.source.ShuffleOrder;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.trackselection.TrackSelectionArray;
|
||||
import androidx.media3.exoplayer.trackselection.TrackSelector;
|
||||
import androidx.media3.exoplayer.upstream.BandwidthMeter;
|
||||
import androidx.media3.exoplayer.video.VideoFrameMetadataListener;
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ import androidx.media3.common.Player.DiscontinuityReason;
|
|||
import androidx.media3.common.Player.PlaybackSuppressionReason;
|
||||
import androidx.media3.common.Player.TimelineChangeReason;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackSelection;
|
||||
import androidx.media3.common.TrackSelectionParameters;
|
||||
import androidx.media3.common.TracksInfo;
|
||||
import androidx.media3.common.VideoSize;
|
||||
|
|
@ -60,6 +59,7 @@ import androidx.media3.exoplayer.metadata.MetadataOutput;
|
|||
import androidx.media3.exoplayer.source.LoadEventInfo;
|
||||
import androidx.media3.exoplayer.source.MediaLoadData;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||
import androidx.media3.exoplayer.trackselection.TrackSelection;
|
||||
import androidx.media3.exoplayer.video.VideoDecoderOutputBufferRenderer;
|
||||
import com.google.common.base.Objects;
|
||||
import java.io.IOException;
|
||||
|
|
|
|||
|
|
@ -38,8 +38,6 @@ import androidx.media3.common.Player.PlaybackSuppressionReason;
|
|||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.Timeline.Period;
|
||||
import androidx.media3.common.Timeline.Window;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.TrackSelectionArray;
|
||||
import androidx.media3.common.TrackSelectionParameters;
|
||||
import androidx.media3.common.TracksInfo;
|
||||
import androidx.media3.common.VideoSize;
|
||||
|
|
@ -484,13 +482,6 @@ public class DefaultAnalyticsCollector implements AnalyticsCollector {
|
|||
listener -> listener.onMediaItemTransition(eventTime, mediaItem, reason));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Implementing deprecated method.
|
||||
@Override
|
||||
public final void onTracksChanged(
|
||||
TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
||||
// Do nothing. Handled by non-deprecated onTracksInfoChanged.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTracksInfoChanged(TracksInfo tracksInfo) {
|
||||
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
||||
|
|
|
|||
|
|
@ -1745,8 +1745,11 @@ public final class DefaultAudioSink implements AudioSink {
|
|||
// the channel count for this encoding, but before then there is no way to query it so we
|
||||
// assume 6 channel audio is supported.
|
||||
if (Util.SDK_INT >= 29) {
|
||||
// Default to 48 kHz if the format doesn't have a sample rate (for example, for chunkless
|
||||
// HLS preparation). See [Internal: b/222127949].
|
||||
int sampleRate = format.sampleRate != Format.NO_VALUE ? format.sampleRate : 48000;
|
||||
channelCount =
|
||||
getMaxSupportedChannelCountForPassthroughV29(C.ENCODING_E_AC3_JOC, format.sampleRate);
|
||||
getMaxSupportedChannelCountForPassthroughV29(C.ENCODING_E_AC3_JOC, sampleRate);
|
||||
if (channelCount == 0) {
|
||||
Log.w(TAG, "E-AC3 JOC encoding supported but no channel count supported");
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ import androidx.annotation.RequiresApi;
|
|||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.datasource.DefaultHttpDataSource;
|
||||
import androidx.media3.datasource.HttpDataSource;
|
||||
import com.google.common.primitives.Ints;
|
||||
import java.util.Map;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
|
@ -42,7 +42,7 @@ public final class DefaultDrmSessionManagerProvider implements DrmSessionManager
|
|||
@GuardedBy("lock")
|
||||
private @MonotonicNonNull DrmSessionManager manager;
|
||||
|
||||
@Nullable private HttpDataSource.Factory drmHttpDataSourceFactory;
|
||||
@Nullable private DataSource.Factory drmHttpDataSourceFactory;
|
||||
@Nullable private String userAgent;
|
||||
|
||||
public DefaultDrmSessionManagerProvider() {
|
||||
|
|
@ -50,26 +50,22 @@ public final class DefaultDrmSessionManagerProvider implements DrmSessionManager
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link HttpDataSource.Factory} to be used for creating {@link HttpMediaDrmCallback
|
||||
* HttpMediaDrmCallbacks} which executes key and provisioning requests over HTTP. If {@code null}
|
||||
* is passed the {@link DefaultHttpDataSource.Factory} is used.
|
||||
* Sets the {@link DataSource.Factory} which is used to create {@link HttpMediaDrmCallback}
|
||||
* instances. If {@code null} is passed a {@link DefaultHttpDataSource.Factory} is used.
|
||||
*
|
||||
* @param drmHttpDataSourceFactory The HTTP data source factory or {@code null} to use {@link
|
||||
* @param drmDataSourceFactory The data source factory or {@code null} to use {@link
|
||||
* DefaultHttpDataSource.Factory}.
|
||||
*/
|
||||
public void setDrmHttpDataSourceFactory(
|
||||
@Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
|
||||
this.drmHttpDataSourceFactory = drmHttpDataSourceFactory;
|
||||
public void setDrmHttpDataSourceFactory(@Nullable DataSource.Factory drmDataSourceFactory) {
|
||||
this.drmHttpDataSourceFactory = drmDataSourceFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the optional user agent to be used for DRM requests.
|
||||
*
|
||||
* <p>In case a factory has been set by {@link
|
||||
* #setDrmHttpDataSourceFactory(HttpDataSource.Factory)}, this user agent is ignored.
|
||||
*
|
||||
* @param userAgent The user agent to be used for DRM requests.
|
||||
* @deprecated Pass a custom {@link DataSource.Factory} to {@link
|
||||
* #setDrmHttpDataSourceFactory(DataSource.Factory)} which sets the desired user agent on
|
||||
* outgoing requests.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setDrmUserAgent(@Nullable String userAgent) {
|
||||
this.userAgent = userAgent;
|
||||
}
|
||||
|
|
@ -94,7 +90,7 @@ public final class DefaultDrmSessionManagerProvider implements DrmSessionManager
|
|||
|
||||
@RequiresApi(18)
|
||||
private DrmSessionManager createManager(MediaItem.DrmConfiguration drmConfiguration) {
|
||||
HttpDataSource.Factory dataSourceFactory =
|
||||
DataSource.Factory dataSourceFactory =
|
||||
drmHttpDataSourceFactory != null
|
||||
? drmHttpDataSourceFactory
|
||||
: new DefaultHttpDataSource.Factory().setUserAgent(userAgent);
|
||||
|
|
|
|||
|
|
@ -192,7 +192,11 @@ public final class FrameworkMediaDrm implements ExoMediaDrm {
|
|||
@Override
|
||||
public void setPlayerIdForSession(byte[] sessionId, PlayerId playerId) {
|
||||
if (Util.SDK_INT >= 31) {
|
||||
Api31.setLogSessionIdOnMediaDrmSession(mediaDrm, sessionId, playerId);
|
||||
try {
|
||||
Api31.setLogSessionIdOnMediaDrmSession(mediaDrm, sessionId, playerId);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
Log.w(TAG, "setLogSessionId failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ import androidx.media3.common.C;
|
|||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.datasource.DataSourceInputStream;
|
||||
import androidx.media3.datasource.DataSpec;
|
||||
import androidx.media3.datasource.HttpDataSource;
|
||||
import androidx.media3.datasource.HttpDataSource.InvalidResponseCodeException;
|
||||
import androidx.media3.datasource.StatsDataSource;
|
||||
import androidx.media3.exoplayer.drm.ExoMediaDrm.KeyRequest;
|
||||
|
|
@ -36,41 +36,47 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/** A {@link MediaDrmCallback} that makes requests using {@link HttpDataSource} instances. */
|
||||
/** A {@link MediaDrmCallback} that makes requests using {@link DataSource} instances. */
|
||||
@UnstableApi
|
||||
public final class HttpMediaDrmCallback implements MediaDrmCallback {
|
||||
|
||||
private static final int MAX_MANUAL_REDIRECTS = 5;
|
||||
|
||||
private final HttpDataSource.Factory dataSourceFactory;
|
||||
private final DataSource.Factory dataSourceFactory;
|
||||
@Nullable private final String defaultLicenseUrl;
|
||||
private final boolean forceDefaultLicenseUrl;
|
||||
private final Map<String, String> keyRequestProperties;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*
|
||||
* @param defaultLicenseUrl The default license URL. Used for key requests that do not specify
|
||||
* their own license URL. May be {@code null} if it's known that all key requests will specify
|
||||
* their own URLs.
|
||||
* @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
|
||||
* @param dataSourceFactory A factory from which to obtain {@link DataSource} instances. This will
|
||||
* usually be an HTTP-based {@link DataSource}.
|
||||
*/
|
||||
public HttpMediaDrmCallback(
|
||||
@Nullable String defaultLicenseUrl, HttpDataSource.Factory dataSourceFactory) {
|
||||
@Nullable String defaultLicenseUrl, DataSource.Factory dataSourceFactory) {
|
||||
this(defaultLicenseUrl, /* forceDefaultLicenseUrl= */ false, dataSourceFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*
|
||||
* @param defaultLicenseUrl The default license URL. Used for key requests that do not specify
|
||||
* their own license URL, or for all key requests if {@code forceDefaultLicenseUrl} is set to
|
||||
* true. May be {@code null} if {@code forceDefaultLicenseUrl} is {@code false} and if it's
|
||||
* known that all key requests will specify their own URLs.
|
||||
* @param forceDefaultLicenseUrl Whether to force use of {@code defaultLicenseUrl} for key
|
||||
* requests that include their own license URL.
|
||||
* @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
|
||||
* @param dataSourceFactory A factory from which to obtain {@link DataSource} instances. This will
|
||||
* * usually be an HTTP-based {@link DataSource}.
|
||||
*/
|
||||
public HttpMediaDrmCallback(
|
||||
@Nullable String defaultLicenseUrl,
|
||||
boolean forceDefaultLicenseUrl,
|
||||
HttpDataSource.Factory dataSourceFactory) {
|
||||
DataSource.Factory dataSourceFactory) {
|
||||
Assertions.checkArgument(!(forceDefaultLicenseUrl && TextUtils.isEmpty(defaultLicenseUrl)));
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
this.defaultLicenseUrl = defaultLicenseUrl;
|
||||
|
|
@ -156,7 +162,7 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback {
|
|||
}
|
||||
|
||||
private static byte[] executePost(
|
||||
HttpDataSource.Factory dataSourceFactory,
|
||||
DataSource.Factory dataSourceFactory,
|
||||
String url,
|
||||
@Nullable byte[] httpBody,
|
||||
Map<String, String> requestProperties)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import androidx.media3.common.DrmInitData;
|
|||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.datasource.HttpDataSource;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.exoplayer.analytics.PlayerId;
|
||||
import androidx.media3.exoplayer.drm.DefaultDrmSessionManager.Mode;
|
||||
import androidx.media3.exoplayer.drm.DrmSession.DrmSessionException;
|
||||
|
|
@ -53,20 +53,17 @@ public final class OfflineLicenseHelper {
|
|||
*
|
||||
* @param defaultLicenseUrl The default license URL. Used for key requests that do not specify
|
||||
* their own license URL.
|
||||
* @param httpDataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
|
||||
* @param dataSourceFactory A factory from which to obtain {@link DataSource} instances.
|
||||
* @param eventDispatcher A {@link DrmSessionEventListener.EventDispatcher} used to distribute
|
||||
* DRM-related events.
|
||||
* @return A new instance which uses Widevine CDM.
|
||||
*/
|
||||
public static OfflineLicenseHelper newWidevineInstance(
|
||||
String defaultLicenseUrl,
|
||||
HttpDataSource.Factory httpDataSourceFactory,
|
||||
DataSource.Factory dataSourceFactory,
|
||||
DrmSessionEventListener.EventDispatcher eventDispatcher) {
|
||||
return newWidevineInstance(
|
||||
defaultLicenseUrl,
|
||||
/* forceDefaultLicenseUrl= */ false,
|
||||
httpDataSourceFactory,
|
||||
eventDispatcher);
|
||||
defaultLicenseUrl, /* forceDefaultLicenseUrl= */ false, dataSourceFactory, eventDispatcher);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -77,7 +74,7 @@ public final class OfflineLicenseHelper {
|
|||
* their own license URL.
|
||||
* @param forceDefaultLicenseUrl Whether to use {@code defaultLicenseUrl} for key requests that
|
||||
* include their own license URL.
|
||||
* @param httpDataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
|
||||
* @param dataSourceFactory A factory from which to obtain {@link DataSource} instances.
|
||||
* @param eventDispatcher A {@link DrmSessionEventListener.EventDispatcher} used to distribute
|
||||
* DRM-related events.
|
||||
* @return A new instance which uses Widevine CDM.
|
||||
|
|
@ -85,12 +82,12 @@ public final class OfflineLicenseHelper {
|
|||
public static OfflineLicenseHelper newWidevineInstance(
|
||||
String defaultLicenseUrl,
|
||||
boolean forceDefaultLicenseUrl,
|
||||
HttpDataSource.Factory httpDataSourceFactory,
|
||||
DataSource.Factory dataSourceFactory,
|
||||
DrmSessionEventListener.EventDispatcher eventDispatcher) {
|
||||
return newWidevineInstance(
|
||||
defaultLicenseUrl,
|
||||
forceDefaultLicenseUrl,
|
||||
httpDataSourceFactory,
|
||||
dataSourceFactory,
|
||||
/* optionalKeyRequestParameters= */ null,
|
||||
eventDispatcher);
|
||||
}
|
||||
|
|
@ -113,7 +110,7 @@ public final class OfflineLicenseHelper {
|
|||
public static OfflineLicenseHelper newWidevineInstance(
|
||||
String defaultLicenseUrl,
|
||||
boolean forceDefaultLicenseUrl,
|
||||
HttpDataSource.Factory httpDataSourceFactory,
|
||||
DataSource.Factory dataSourceFactory,
|
||||
@Nullable Map<String, String> optionalKeyRequestParameters,
|
||||
DrmSessionEventListener.EventDispatcher eventDispatcher) {
|
||||
return new OfflineLicenseHelper(
|
||||
|
|
@ -121,7 +118,7 @@ public final class OfflineLicenseHelper {
|
|||
.setKeyRequestParameters(optionalKeyRequestParameters)
|
||||
.build(
|
||||
new HttpMediaDrmCallback(
|
||||
defaultLicenseUrl, forceDefaultLicenseUrl, httpDataSourceFactory)),
|
||||
defaultLicenseUrl, forceDefaultLicenseUrl, dataSourceFactory)),
|
||||
eventDispatcher);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ import static java.lang.annotation.ElementType.TYPE_USE;
|
|||
import androidx.annotation.IntDef;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/** Thrown when the requested DRM scheme is not supported. */
|
||||
|
|
@ -35,8 +37,9 @@ public final class UnsupportedDrmException extends Exception {
|
|||
* #REASON_INSTANTIATION_ERROR}.
|
||||
*/
|
||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||
// with Kotlin usages from before TYPE_USE was added. @Retention(RetentionPolicy.SOURCE)
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@IntDef({REASON_UNSUPPORTED_SCHEME, REASON_INSTANTIATION_ERROR})
|
||||
public @interface Reason {}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,6 @@ import java.nio.ByteBuffer;
|
|||
private final Supplier<HandlerThread> callbackThreadSupplier;
|
||||
private final Supplier<HandlerThread> queueingThreadSupplier;
|
||||
private final boolean synchronizeCodecInteractionsWithQueueing;
|
||||
private final boolean enableImmediateCodecStartAfterFlush;
|
||||
|
||||
/**
|
||||
* Creates an factory for {@link AsynchronousMediaCodecAdapter} instances.
|
||||
|
|
@ -66,29 +65,23 @@ import java.nio.ByteBuffer;
|
|||
* interactions will wait until all input buffers pending queueing wil be submitted to the
|
||||
* {@link MediaCodec}.
|
||||
*/
|
||||
public Factory(
|
||||
@C.TrackType int trackType,
|
||||
boolean synchronizeCodecInteractionsWithQueueing,
|
||||
boolean enableImmediateCodecStartAfterFlush) {
|
||||
public Factory(@C.TrackType int trackType, boolean synchronizeCodecInteractionsWithQueueing) {
|
||||
this(
|
||||
/* callbackThreadSupplier= */ () ->
|
||||
new HandlerThread(createCallbackThreadLabel(trackType)),
|
||||
/* queueingThreadSupplier= */ () ->
|
||||
new HandlerThread(createQueueingThreadLabel(trackType)),
|
||||
synchronizeCodecInteractionsWithQueueing,
|
||||
enableImmediateCodecStartAfterFlush);
|
||||
synchronizeCodecInteractionsWithQueueing);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
/* package */ Factory(
|
||||
Supplier<HandlerThread> callbackThreadSupplier,
|
||||
Supplier<HandlerThread> queueingThreadSupplier,
|
||||
boolean synchronizeCodecInteractionsWithQueueing,
|
||||
boolean enableImmediateCodecStartAfterFlush) {
|
||||
boolean synchronizeCodecInteractionsWithQueueing) {
|
||||
this.callbackThreadSupplier = callbackThreadSupplier;
|
||||
this.queueingThreadSupplier = queueingThreadSupplier;
|
||||
this.synchronizeCodecInteractionsWithQueueing = synchronizeCodecInteractionsWithQueueing;
|
||||
this.enableImmediateCodecStartAfterFlush = enableImmediateCodecStartAfterFlush;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -105,8 +98,7 @@ import java.nio.ByteBuffer;
|
|||
codec,
|
||||
callbackThreadSupplier.get(),
|
||||
queueingThreadSupplier.get(),
|
||||
synchronizeCodecInteractionsWithQueueing,
|
||||
enableImmediateCodecStartAfterFlush);
|
||||
synchronizeCodecInteractionsWithQueueing);
|
||||
TraceUtil.endSection();
|
||||
codecAdapter.initialize(
|
||||
configuration.mediaFormat,
|
||||
|
|
@ -139,7 +131,6 @@ import java.nio.ByteBuffer;
|
|||
private final AsynchronousMediaCodecCallback asynchronousMediaCodecCallback;
|
||||
private final AsynchronousMediaCodecBufferEnqueuer bufferEnqueuer;
|
||||
private final boolean synchronizeCodecInteractionsWithQueueing;
|
||||
private final boolean enableImmediateCodecStartAfterFlush;
|
||||
private boolean codecReleased;
|
||||
private @State int state;
|
||||
|
||||
|
|
@ -147,13 +138,11 @@ import java.nio.ByteBuffer;
|
|||
MediaCodec codec,
|
||||
HandlerThread callbackThread,
|
||||
HandlerThread enqueueingThread,
|
||||
boolean synchronizeCodecInteractionsWithQueueing,
|
||||
boolean enableImmediateCodecStartAfterFlush) {
|
||||
boolean synchronizeCodecInteractionsWithQueueing) {
|
||||
this.codec = codec;
|
||||
this.asynchronousMediaCodecCallback = new AsynchronousMediaCodecCallback(callbackThread);
|
||||
this.bufferEnqueuer = new AsynchronousMediaCodecBufferEnqueuer(codec, enqueueingThread);
|
||||
this.synchronizeCodecInteractionsWithQueueing = synchronizeCodecInteractionsWithQueueing;
|
||||
this.enableImmediateCodecStartAfterFlush = enableImmediateCodecStartAfterFlush;
|
||||
this.state = STATE_CREATED;
|
||||
}
|
||||
|
||||
|
|
@ -232,18 +221,13 @@ import java.nio.ByteBuffer;
|
|||
// The order of calls is important:
|
||||
// 1. Flush the bufferEnqueuer to stop queueing input buffers.
|
||||
// 2. Flush the codec to stop producing available input/output buffers.
|
||||
// 3. Flush the callback after flushing the codec so that in-flight callbacks are discarded.
|
||||
// 3. Flush the callback so that in-flight callbacks are discarded.
|
||||
// 4. Start the codec. The asynchronous callback will drop pending callbacks and we can start
|
||||
// the codec now.
|
||||
bufferEnqueuer.flush();
|
||||
codec.flush();
|
||||
if (enableImmediateCodecStartAfterFlush) {
|
||||
// The asynchronous callback will drop pending callbacks but we can start the codec now.
|
||||
asynchronousMediaCodecCallback.flush(/* codec= */ null);
|
||||
codec.start();
|
||||
} else {
|
||||
// Let the asynchronous callback start the codec in the callback thread after pending
|
||||
// callbacks are handled.
|
||||
asynchronousMediaCodecCallback.flush(codec);
|
||||
}
|
||||
asynchronousMediaCodecCallback.flush();
|
||||
codec.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -191,14 +191,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
/**
|
||||
* Initiates a flush asynchronously, which will be completed on the callback thread. When the
|
||||
* flush is complete, it will trigger {@code onFlushCompleted} from the callback thread.
|
||||
*
|
||||
* @param codec A {@link MediaCodec} to {@link MediaCodec#start start} after all pending callbacks
|
||||
* are handled, or {@code null} if starting the {@link MediaCodec} is performed elsewhere.
|
||||
*/
|
||||
public void flush(@Nullable MediaCodec codec) {
|
||||
public void flush() {
|
||||
synchronized (lock) {
|
||||
++pendingFlushCount;
|
||||
Util.castNonNull(handler).post(() -> this.onFlushCompleted(codec));
|
||||
Util.castNonNull(handler).post(this::onFlushCompleted);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -238,7 +235,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
}
|
||||
}
|
||||
|
||||
private void onFlushCompleted(@Nullable MediaCodec codec) {
|
||||
private void onFlushCompleted() {
|
||||
synchronized (lock) {
|
||||
if (shutDown) {
|
||||
return;
|
||||
|
|
@ -254,15 +251,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
return;
|
||||
}
|
||||
flushInternal();
|
||||
if (codec != null) {
|
||||
try {
|
||||
codec.start();
|
||||
} catch (IllegalStateException e) {
|
||||
setInternalException(e);
|
||||
} catch (Exception e) {
|
||||
setInternalException(new IllegalStateException(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ package androidx.media3.exoplayer.mediacodec;
|
|||
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.util.Log;
|
||||
|
|
@ -55,11 +54,9 @@ public final class DefaultMediaCodecAdapterFactory implements MediaCodecAdapter.
|
|||
|
||||
private @Mode int asynchronousMode;
|
||||
private boolean enableSynchronizeCodecInteractionsWithQueueing;
|
||||
private boolean enableImmediateCodecStartAfterFlush;
|
||||
|
||||
public DefaultMediaCodecAdapterFactory() {
|
||||
asynchronousMode = MODE_DEFAULT;
|
||||
enableImmediateCodecStartAfterFlush = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -96,27 +93,12 @@ public final class DefaultMediaCodecAdapterFactory implements MediaCodecAdapter.
|
|||
enableSynchronizeCodecInteractionsWithQueueing = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable calling {@link MediaCodec#start} immediately after {@link MediaCodec#flush} on the
|
||||
* playback thread, when operating the codec in asynchronous mode. If disabled, {@link
|
||||
* MediaCodec#start} will be called by the callback thread after pending callbacks are handled.
|
||||
*
|
||||
* <p>By default, this feature is enabled.
|
||||
*
|
||||
* <p>This method is experimental, and will be renamed or removed in a future release.
|
||||
*
|
||||
* @param enabled Whether {@link MediaCodec#start()} will be called on the playback thread
|
||||
* immediately after {@link MediaCodec#flush}.
|
||||
*/
|
||||
public void experimentalSetImmediateCodecStartAfterFlushEnabled(boolean enabled) {
|
||||
enableImmediateCodecStartAfterFlush = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaCodecAdapter createAdapter(MediaCodecAdapter.Configuration configuration)
|
||||
throws IOException {
|
||||
if ((asynchronousMode == MODE_ENABLED && Util.SDK_INT >= 23)
|
||||
|| (asynchronousMode == MODE_DEFAULT && Util.SDK_INT >= 31)) {
|
||||
if (Util.SDK_INT >= 23
|
||||
&& (asynchronousMode == MODE_ENABLED
|
||||
|| (asynchronousMode == MODE_DEFAULT && Util.SDK_INT >= 31))) {
|
||||
int trackType = MimeTypes.getTrackType(configuration.format.sampleMimeType);
|
||||
Log.i(
|
||||
TAG,
|
||||
|
|
@ -124,9 +106,7 @@ public final class DefaultMediaCodecAdapterFactory implements MediaCodecAdapter.
|
|||
+ Util.getTrackTypeString(trackType));
|
||||
AsynchronousMediaCodecAdapter.Factory factory =
|
||||
new AsynchronousMediaCodecAdapter.Factory(
|
||||
trackType,
|
||||
enableSynchronizeCodecInteractionsWithQueueing,
|
||||
enableImmediateCodecStartAfterFlush);
|
||||
trackType, enableSynchronizeCodecInteractionsWithQueueing);
|
||||
return factory.createAdapter(configuration);
|
||||
}
|
||||
return new SynchronousMediaCodecAdapter.Factory().createAdapter(configuration);
|
||||
|
|
|
|||
|
|
@ -443,6 +443,8 @@ public final class MediaCodecUtil {
|
|||
return "audio/x-lg-alac";
|
||||
} else if (mimeType.equals(MimeTypes.AUDIO_FLAC) && "OMX.lge.flac.decoder".equals(name)) {
|
||||
return "audio/x-lg-flac";
|
||||
} else if (mimeType.equals(MimeTypes.AUDIO_AC3) && "OMX.lge.ac3.decoder".equals(name)) {
|
||||
return "audio/lg-ac3";
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -31,7 +31,9 @@ import androidx.media3.common.MimeTypes;
|
|||
import androidx.media3.common.StreamKey;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.TrackSelectionOverride;
|
||||
import androidx.media3.common.TrackSelectionParameters;
|
||||
import androidx.media3.common.TracksInfo;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
|
|
@ -49,14 +51,15 @@ import androidx.media3.exoplayer.source.MediaPeriod;
|
|||
import androidx.media3.exoplayer.source.MediaSource;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaSourceCaller;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.source.chunk.MediaChunk;
|
||||
import androidx.media3.exoplayer.source.chunk.MediaChunkIterator;
|
||||
import androidx.media3.exoplayer.trackselection.BaseTrackSelection;
|
||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
|
||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector.Parameters;
|
||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector.SelectionOverride;
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||
import androidx.media3.exoplayer.trackselection.MappingTrackSelector.MappedTrackInfo;
|
||||
import androidx.media3.exoplayer.trackselection.TrackSelectionUtil;
|
||||
import androidx.media3.exoplayer.trackselection.TrackSelectorResult;
|
||||
import androidx.media3.exoplayer.upstream.Allocator;
|
||||
import androidx.media3.exoplayer.upstream.BandwidthMeter;
|
||||
|
|
@ -85,8 +88,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||
* <li>Prepare the helper using {@link #prepare(Callback)} and wait for the callback.
|
||||
* <li>Optional: Inspect the selected tracks using {@link #getMappedTrackInfo(int)} and {@link
|
||||
* #getTrackSelections(int, int)}, and make adjustments using {@link
|
||||
* #clearTrackSelections(int)}, {@link #replaceTrackSelections(int, Parameters)} and {@link
|
||||
* #addTrackSelection(int, Parameters)}.
|
||||
* #clearTrackSelections(int)}, {@link #replaceTrackSelections(int, TrackSelectionParameters)}
|
||||
* and {@link #addTrackSelection(int, TrackSelectionParameters)}.
|
||||
* <li>Create a download request for the selected track using {@link #getDownloadRequest(byte[])}.
|
||||
* <li>Release the helper using {@link #release()}.
|
||||
* </ol>
|
||||
|
|
@ -100,30 +103,18 @@ public final class DownloadHelper {
|
|||
*
|
||||
* <p>If possible, use {@link #getDefaultTrackSelectorParameters(Context)} instead.
|
||||
*
|
||||
* @see Parameters#DEFAULT_WITHOUT_CONTEXT
|
||||
* @see DefaultTrackSelector.Parameters#DEFAULT_WITHOUT_CONTEXT
|
||||
*/
|
||||
public static final Parameters DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT =
|
||||
Parameters.DEFAULT_WITHOUT_CONTEXT.buildUpon().setForceHighestSupportedBitrate(true).build();
|
||||
|
||||
/**
|
||||
* @deprecated This instance does not have {@link Context} constraints. Use {@link
|
||||
* #getDefaultTrackSelectorParameters(Context)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static final Parameters DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_VIEWPORT =
|
||||
DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT;
|
||||
|
||||
/**
|
||||
* @deprecated This instance does not have {@link Context} constraints. Use {@link
|
||||
* #getDefaultTrackSelectorParameters(Context)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static final DefaultTrackSelector.Parameters DEFAULT_TRACK_SELECTOR_PARAMETERS =
|
||||
DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT;
|
||||
public static final DefaultTrackSelector.Parameters
|
||||
DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT =
|
||||
DefaultTrackSelector.Parameters.DEFAULT_WITHOUT_CONTEXT
|
||||
.buildUpon()
|
||||
.setForceHighestSupportedBitrate(true)
|
||||
.build();
|
||||
|
||||
/** Returns the default parameters used for track selection for downloading. */
|
||||
public static DefaultTrackSelector.Parameters getDefaultTrackSelectorParameters(Context context) {
|
||||
return Parameters.getDefaults(context)
|
||||
return DefaultTrackSelector.Parameters.getDefaults(context)
|
||||
.buildUpon()
|
||||
.setForceHighestSupportedBitrate(true)
|
||||
.build();
|
||||
|
|
@ -191,7 +182,7 @@ public final class DownloadHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #forMediaItem(MediaItem, Parameters, RenderersFactory,
|
||||
* @deprecated Use {@link #forMediaItem(MediaItem, TrackSelectionParameters, RenderersFactory,
|
||||
* DataSource.Factory)} instead.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
|
|
@ -210,7 +201,7 @@ public final class DownloadHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #forMediaItem(MediaItem, Parameters, RenderersFactory,
|
||||
* @deprecated Use {@link #forMediaItem(MediaItem, TrackSelectionParameters, RenderersFactory,
|
||||
* DataSource.Factory, DrmSessionManager)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
|
|
@ -219,17 +210,17 @@ public final class DownloadHelper {
|
|||
DataSource.Factory dataSourceFactory,
|
||||
RenderersFactory renderersFactory,
|
||||
@Nullable DrmSessionManager drmSessionManager,
|
||||
DefaultTrackSelector.Parameters trackSelectorParameters) {
|
||||
TrackSelectionParameters trackSelectionParameters) {
|
||||
return forMediaItem(
|
||||
new MediaItem.Builder().setUri(uri).setMimeType(MimeTypes.APPLICATION_MPD).build(),
|
||||
trackSelectorParameters,
|
||||
trackSelectionParameters,
|
||||
renderersFactory,
|
||||
dataSourceFactory,
|
||||
drmSessionManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #forMediaItem(MediaItem, Parameters, RenderersFactory,
|
||||
* @deprecated Use {@link #forMediaItem(MediaItem, TrackSelectionParameters, RenderersFactory,
|
||||
* DataSource.Factory)} instead.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
|
|
@ -248,7 +239,7 @@ public final class DownloadHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #forMediaItem(MediaItem, Parameters, RenderersFactory,
|
||||
* @deprecated Use {@link #forMediaItem(MediaItem, TrackSelectionParameters, RenderersFactory,
|
||||
* DataSource.Factory, DrmSessionManager)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
|
|
@ -257,17 +248,17 @@ public final class DownloadHelper {
|
|||
DataSource.Factory dataSourceFactory,
|
||||
RenderersFactory renderersFactory,
|
||||
@Nullable DrmSessionManager drmSessionManager,
|
||||
DefaultTrackSelector.Parameters trackSelectorParameters) {
|
||||
TrackSelectionParameters trackSelectionParameters) {
|
||||
return forMediaItem(
|
||||
new MediaItem.Builder().setUri(uri).setMimeType(MimeTypes.APPLICATION_M3U8).build(),
|
||||
trackSelectorParameters,
|
||||
trackSelectionParameters,
|
||||
renderersFactory,
|
||||
dataSourceFactory,
|
||||
drmSessionManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #forMediaItem(MediaItem, Parameters, RenderersFactory,
|
||||
* @deprecated Use {@link #forMediaItem(MediaItem, TrackSelectionParameters, RenderersFactory,
|
||||
* DataSource.Factory)} instead.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
|
|
@ -283,7 +274,7 @@ public final class DownloadHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #forMediaItem(MediaItem, Parameters, RenderersFactory,
|
||||
* @deprecated Use {@link #forMediaItem(MediaItem, TrackSelectionParameters, RenderersFactory,
|
||||
* DataSource.Factory)} instead.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
|
|
@ -302,7 +293,7 @@ public final class DownloadHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #forMediaItem(MediaItem, Parameters, RenderersFactory,
|
||||
* @deprecated Use {@link #forMediaItem(MediaItem, TrackSelectionParameters, RenderersFactory,
|
||||
* DataSource.Factory, DrmSessionManager)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
|
|
@ -311,10 +302,10 @@ public final class DownloadHelper {
|
|||
DataSource.Factory dataSourceFactory,
|
||||
RenderersFactory renderersFactory,
|
||||
@Nullable DrmSessionManager drmSessionManager,
|
||||
DefaultTrackSelector.Parameters trackSelectorParameters) {
|
||||
TrackSelectionParameters trackSelectionParameters) {
|
||||
return forMediaItem(
|
||||
new MediaItem.Builder().setUri(uri).setMimeType(MimeTypes.APPLICATION_SS).build(),
|
||||
trackSelectorParameters,
|
||||
trackSelectionParameters,
|
||||
renderersFactory,
|
||||
dataSourceFactory,
|
||||
drmSessionManager);
|
||||
|
|
@ -372,7 +363,7 @@ public final class DownloadHelper {
|
|||
* @param mediaItem A {@link MediaItem}.
|
||||
* @param renderersFactory A {@link RenderersFactory} creating the renderers for which tracks are
|
||||
* selected.
|
||||
* @param trackSelectorParameters {@link DefaultTrackSelector.Parameters} for selecting tracks for
|
||||
* @param trackSelectionParameters {@link TrackSelectionParameters} for selecting tracks for
|
||||
* downloading.
|
||||
* @param dataSourceFactory A {@link DataSource.Factory} used to load the manifest for adaptive
|
||||
* streams. This argument is required for adaptive streams and ignored for progressive
|
||||
|
|
@ -384,12 +375,12 @@ public final class DownloadHelper {
|
|||
*/
|
||||
public static DownloadHelper forMediaItem(
|
||||
MediaItem mediaItem,
|
||||
DefaultTrackSelector.Parameters trackSelectorParameters,
|
||||
TrackSelectionParameters trackSelectionParameters,
|
||||
@Nullable RenderersFactory renderersFactory,
|
||||
@Nullable DataSource.Factory dataSourceFactory) {
|
||||
return forMediaItem(
|
||||
mediaItem,
|
||||
trackSelectorParameters,
|
||||
trackSelectionParameters,
|
||||
renderersFactory,
|
||||
dataSourceFactory,
|
||||
/* drmSessionManager= */ null);
|
||||
|
|
@ -401,7 +392,7 @@ public final class DownloadHelper {
|
|||
* @param mediaItem A {@link MediaItem}.
|
||||
* @param renderersFactory A {@link RenderersFactory} creating the renderers for which tracks are
|
||||
* selected.
|
||||
* @param trackSelectorParameters {@link DefaultTrackSelector.Parameters} for selecting tracks for
|
||||
* @param trackSelectionParameters {@link TrackSelectionParameters} for selecting tracks for
|
||||
* downloading.
|
||||
* @param dataSourceFactory A {@link DataSource.Factory} used to load the manifest for adaptive
|
||||
* streams. This argument is required for adaptive streams and ignored for progressive
|
||||
|
|
@ -415,7 +406,7 @@ public final class DownloadHelper {
|
|||
*/
|
||||
public static DownloadHelper forMediaItem(
|
||||
MediaItem mediaItem,
|
||||
DefaultTrackSelector.Parameters trackSelectorParameters,
|
||||
TrackSelectionParameters trackSelectionParameters,
|
||||
@Nullable RenderersFactory renderersFactory,
|
||||
@Nullable DataSource.Factory dataSourceFactory,
|
||||
@Nullable DrmSessionManager drmSessionManager) {
|
||||
|
|
@ -427,7 +418,7 @@ public final class DownloadHelper {
|
|||
? null
|
||||
: createMediaSourceInternal(
|
||||
mediaItem, castNonNull(dataSourceFactory), drmSessionManager),
|
||||
trackSelectorParameters,
|
||||
trackSelectionParameters,
|
||||
renderersFactory != null
|
||||
? getRendererCapabilities(renderersFactory)
|
||||
: new RendererCapabilities[0]);
|
||||
|
|
@ -483,7 +474,7 @@ public final class DownloadHelper {
|
|||
* @param mediaItem The media item.
|
||||
* @param mediaSource A {@link MediaSource} for which tracks are selected, or null if no track
|
||||
* selection needs to be made.
|
||||
* @param trackSelectorParameters {@link DefaultTrackSelector.Parameters} for selecting tracks for
|
||||
* @param trackSelectionParameters {@link TrackSelectionParameters} for selecting tracks for
|
||||
* downloading.
|
||||
* @param rendererCapabilities The {@link RendererCapabilities} of the renderers for which tracks
|
||||
* are selected.
|
||||
|
|
@ -491,12 +482,12 @@ public final class DownloadHelper {
|
|||
public DownloadHelper(
|
||||
MediaItem mediaItem,
|
||||
@Nullable MediaSource mediaSource,
|
||||
DefaultTrackSelector.Parameters trackSelectorParameters,
|
||||
TrackSelectionParameters trackSelectionParameters,
|
||||
RendererCapabilities[] rendererCapabilities) {
|
||||
this.localConfiguration = checkNotNull(mediaItem.localConfiguration);
|
||||
this.mediaSource = mediaSource;
|
||||
this.trackSelector =
|
||||
new DefaultTrackSelector(trackSelectorParameters, new DownloadTrackSelection.Factory());
|
||||
new DefaultTrackSelector(trackSelectionParameters, new DownloadTrackSelection.Factory());
|
||||
this.rendererCapabilities = rendererCapabilities;
|
||||
this.scratchSet = new SparseIntArray();
|
||||
trackSelector.init(/* listener= */ () -> {}, new FakeBandwidthMeter());
|
||||
|
|
@ -554,6 +545,20 @@ public final class DownloadHelper {
|
|||
return trackGroupArrays.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link TracksInfo} for the given period. Must not be called until after preparation
|
||||
* completes.
|
||||
*
|
||||
* @param periodIndex The period index.
|
||||
* @return The {@link TracksInfo} for the period. May be {@link TracksInfo#EMPTY} for single
|
||||
* stream content.
|
||||
*/
|
||||
public TracksInfo getTracksInfo(int periodIndex) {
|
||||
assertPreparedWithMedia();
|
||||
return TrackSelectionUtil.buildTracksInfo(
|
||||
mappedTrackInfos[periodIndex], immutableTrackSelectionsByPeriodAndRenderer[periodIndex]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the track groups for the given period. Must not be called until after preparation
|
||||
* completes.
|
||||
|
|
@ -612,13 +617,14 @@ public final class DownloadHelper {
|
|||
* completes.
|
||||
*
|
||||
* @param periodIndex The period index for which the track selection is replaced.
|
||||
* @param trackSelectorParameters The {@link DefaultTrackSelector.Parameters} to obtain the new
|
||||
* @param trackSelectionParameters The {@link TrackSelectionParameters} to obtain the new
|
||||
* selection of tracks.
|
||||
*/
|
||||
public void replaceTrackSelections(
|
||||
int periodIndex, DefaultTrackSelector.Parameters trackSelectorParameters) {
|
||||
int periodIndex, TrackSelectionParameters trackSelectionParameters) {
|
||||
assertPreparedWithMedia();
|
||||
clearTrackSelections(periodIndex);
|
||||
addTrackSelection(periodIndex, trackSelectorParameters);
|
||||
addTrackSelectionInternal(periodIndex, trackSelectionParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -626,14 +632,13 @@ public final class DownloadHelper {
|
|||
* completes.
|
||||
*
|
||||
* @param periodIndex The period index this track selection is added for.
|
||||
* @param trackSelectorParameters The {@link DefaultTrackSelector.Parameters} to obtain the new
|
||||
* @param trackSelectionParameters The {@link TrackSelectionParameters} to obtain the new
|
||||
* selection of tracks.
|
||||
*/
|
||||
public void addTrackSelection(
|
||||
int periodIndex, DefaultTrackSelector.Parameters trackSelectorParameters) {
|
||||
int periodIndex, TrackSelectionParameters trackSelectionParameters) {
|
||||
assertPreparedWithMedia();
|
||||
trackSelector.setParameters(trackSelectorParameters);
|
||||
runTrackSelection(periodIndex);
|
||||
addTrackSelectionInternal(periodIndex, trackSelectionParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -646,19 +651,25 @@ public final class DownloadHelper {
|
|||
*/
|
||||
public void addAudioLanguagesToSelection(String... languages) {
|
||||
assertPreparedWithMedia();
|
||||
for (int periodIndex = 0; periodIndex < mappedTrackInfos.length; periodIndex++) {
|
||||
DefaultTrackSelector.ParametersBuilder parametersBuilder =
|
||||
DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT.buildUpon();
|
||||
MappedTrackInfo mappedTrackInfo = mappedTrackInfos[periodIndex];
|
||||
int rendererCount = mappedTrackInfo.getRendererCount();
|
||||
for (int rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++) {
|
||||
if (mappedTrackInfo.getRendererType(rendererIndex) != C.TRACK_TYPE_AUDIO) {
|
||||
parametersBuilder.setRendererDisabled(rendererIndex, /* disabled= */ true);
|
||||
}
|
||||
}
|
||||
for (String language : languages) {
|
||||
parametersBuilder.setPreferredAudioLanguage(language);
|
||||
addTrackSelection(periodIndex, parametersBuilder.build());
|
||||
|
||||
TrackSelectionParameters.Builder parametersBuilder =
|
||||
DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT.buildUpon();
|
||||
// Prefer highest supported bitrate for downloads.
|
||||
parametersBuilder.setForceHighestSupportedBitrate(true);
|
||||
// Disable all non-audio track types supported by the renderers.
|
||||
for (RendererCapabilities capabilities : rendererCapabilities) {
|
||||
@C.TrackType int trackType = capabilities.getTrackType();
|
||||
parametersBuilder.setTrackTypeDisabled(
|
||||
trackType, /* disabled= */ trackType != C.TRACK_TYPE_AUDIO);
|
||||
}
|
||||
|
||||
// Add a track selection to each period for each of the languages.
|
||||
int periodCount = getPeriodCount();
|
||||
for (String language : languages) {
|
||||
TrackSelectionParameters parameters =
|
||||
parametersBuilder.setPreferredAudioLanguage(language).build();
|
||||
for (int periodIndex = 0; periodIndex < periodCount; periodIndex++) {
|
||||
addTrackSelectionInternal(periodIndex, parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -676,20 +687,26 @@ public final class DownloadHelper {
|
|||
public void addTextLanguagesToSelection(
|
||||
boolean selectUndeterminedTextLanguage, String... languages) {
|
||||
assertPreparedWithMedia();
|
||||
for (int periodIndex = 0; periodIndex < mappedTrackInfos.length; periodIndex++) {
|
||||
DefaultTrackSelector.ParametersBuilder parametersBuilder =
|
||||
DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT.buildUpon();
|
||||
MappedTrackInfo mappedTrackInfo = mappedTrackInfos[periodIndex];
|
||||
int rendererCount = mappedTrackInfo.getRendererCount();
|
||||
for (int rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++) {
|
||||
if (mappedTrackInfo.getRendererType(rendererIndex) != C.TRACK_TYPE_TEXT) {
|
||||
parametersBuilder.setRendererDisabled(rendererIndex, /* disabled= */ true);
|
||||
}
|
||||
}
|
||||
parametersBuilder.setSelectUndeterminedTextLanguage(selectUndeterminedTextLanguage);
|
||||
for (String language : languages) {
|
||||
parametersBuilder.setPreferredTextLanguage(language);
|
||||
addTrackSelection(periodIndex, parametersBuilder.build());
|
||||
|
||||
TrackSelectionParameters.Builder parametersBuilder =
|
||||
DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT.buildUpon();
|
||||
parametersBuilder.setSelectUndeterminedTextLanguage(selectUndeterminedTextLanguage);
|
||||
// Prefer highest supported bitrate for downloads.
|
||||
parametersBuilder.setForceHighestSupportedBitrate(true);
|
||||
// Disable all non-text track types supported by the renderers.
|
||||
for (RendererCapabilities capabilities : rendererCapabilities) {
|
||||
@C.TrackType int trackType = capabilities.getTrackType();
|
||||
parametersBuilder.setTrackTypeDisabled(
|
||||
trackType, /* disabled= */ trackType != C.TRACK_TYPE_TEXT);
|
||||
}
|
||||
|
||||
// Add a track selection to each period for each of the languages.
|
||||
int periodCount = getPeriodCount();
|
||||
for (String language : languages) {
|
||||
TrackSelectionParameters parameters =
|
||||
parametersBuilder.setPreferredTextLanguage(language).build();
|
||||
for (int periodIndex = 0; periodIndex < periodCount; periodIndex++) {
|
||||
addTrackSelectionInternal(periodIndex, parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -716,12 +733,12 @@ public final class DownloadHelper {
|
|||
builder.setRendererDisabled(/* rendererIndex= */ i, /* disabled= */ i != rendererIndex);
|
||||
}
|
||||
if (overrides.isEmpty()) {
|
||||
addTrackSelection(periodIndex, builder.build());
|
||||
addTrackSelectionInternal(periodIndex, builder.build());
|
||||
} else {
|
||||
TrackGroupArray trackGroupArray = mappedTrackInfos[periodIndex].getTrackGroups(rendererIndex);
|
||||
for (int i = 0; i < overrides.size(); i++) {
|
||||
builder.setSelectionOverride(rendererIndex, trackGroupArray, overrides.get(i));
|
||||
addTrackSelection(periodIndex, builder.build());
|
||||
addTrackSelectionInternal(periodIndex, builder.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -773,8 +790,28 @@ public final class DownloadHelper {
|
|||
return requestBuilder.setStreamKeys(streamKeys).build();
|
||||
}
|
||||
|
||||
// Initialization of array of Lists.
|
||||
@SuppressWarnings("unchecked")
|
||||
@RequiresNonNull({
|
||||
"trackGroupArrays",
|
||||
"trackSelectionsByPeriodAndRenderer",
|
||||
"mediaPreparer",
|
||||
"mediaPreparer.timeline"
|
||||
})
|
||||
private void addTrackSelectionInternal(
|
||||
int periodIndex, TrackSelectionParameters trackSelectionParameters) {
|
||||
trackSelector.setParameters(trackSelectionParameters);
|
||||
runTrackSelection(periodIndex);
|
||||
// TrackSelectionParameters can contain multiple overrides for each track type. The track
|
||||
// selector will only use one of them (because it's designed for playback), but for downloads we
|
||||
// want to use all of them. Run selection again with each override being the only one of its
|
||||
// type, to ensure that all of the desired tracks are included.
|
||||
for (TrackSelectionOverride override : trackSelectionParameters.overrides.values()) {
|
||||
trackSelector.setParameters(
|
||||
trackSelectionParameters.buildUpon().setOverrideForType(override).build());
|
||||
runTrackSelection(periodIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked") // Initialization of array of Lists.
|
||||
private void onMediaPrepared() {
|
||||
checkNotNull(mediaPreparer);
|
||||
checkNotNull(mediaPreparer.mediaPeriods);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import androidx.annotation.Nullable;
|
|||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
|
|
|
|||
|
|
@ -147,7 +147,6 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
|||
* @param dataSourceFactory A {@link DataSource.Factory} to create {@link DataSource} instances
|
||||
* for requesting media data.
|
||||
*/
|
||||
@UnstableApi
|
||||
public DefaultMediaSourceFactory(DataSource.Factory dataSourceFactory) {
|
||||
this(dataSourceFactory, new DefaultExtractorsFactory());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import static androidx.media3.common.util.Util.castNonNull;
|
|||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.exoplayer.SeekParameters;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import androidx.media3.common.C;
|
|||
import androidx.media3.common.StreamKey;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.exoplayer.SeekParameters;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import androidx.media3.common.C;
|
|||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.StreamKey;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.decoder.DecoderInputBuffer;
|
||||
import androidx.media3.exoplayer.FormatHolder;
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import androidx.media3.common.Metadata;
|
|||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.ParserException;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.ConditionVariable;
|
||||
import androidx.media3.common.util.ParsableByteArray;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import androidx.media3.common.MediaItem;
|
|||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import androidx.media3.common.C;
|
|||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.Util;
|
||||
|
|
|
|||
|
|
@ -74,11 +74,12 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets an optional track id to be used.
|
||||
*
|
||||
* @param trackId An optional track id.
|
||||
* @return This factory, for convenience.
|
||||
* @deprecated Use {@link MediaItem.SubtitleConfiguration.Builder#setId(String)} instead (on the
|
||||
* {@link MediaItem.SubtitleConfiguration} passed to {@link
|
||||
* #createMediaSource(MediaItem.SubtitleConfiguration, long)}). {@code trackId} will only be
|
||||
* used if {@link MediaItem.SubtitleConfiguration#id} is {@code null}.
|
||||
*/
|
||||
@Deprecated
|
||||
public Factory setTrackId(@Nullable String trackId) {
|
||||
this.trackId = trackId;
|
||||
return this;
|
||||
|
|
@ -157,29 +158,28 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
|
|||
this.durationUs = durationUs;
|
||||
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
|
||||
this.treatLoadErrorsAsEndOfStream = treatLoadErrorsAsEndOfStream;
|
||||
mediaItem =
|
||||
this.mediaItem =
|
||||
new MediaItem.Builder()
|
||||
.setUri(Uri.EMPTY)
|
||||
.setMediaId(subtitleConfiguration.uri.toString())
|
||||
.setSubtitleConfigurations(ImmutableList.of(subtitleConfiguration))
|
||||
.setTag(tag)
|
||||
.build();
|
||||
format =
|
||||
this.format =
|
||||
new Format.Builder()
|
||||
.setId(trackId)
|
||||
.setSampleMimeType(firstNonNull(subtitleConfiguration.mimeType, MimeTypes.TEXT_UNKNOWN))
|
||||
.setLanguage(subtitleConfiguration.language)
|
||||
.setSelectionFlags(subtitleConfiguration.selectionFlags)
|
||||
.setRoleFlags(subtitleConfiguration.roleFlags)
|
||||
.setLabel(subtitleConfiguration.label)
|
||||
.setId(subtitleConfiguration.id)
|
||||
.setId(subtitleConfiguration.id != null ? subtitleConfiguration.id : trackId)
|
||||
.build();
|
||||
dataSpec =
|
||||
this.dataSpec =
|
||||
new DataSpec.Builder()
|
||||
.setUri(subtitleConfiguration.uri)
|
||||
.setFlags(DataSpec.FLAG_ALLOW_GZIP)
|
||||
.build();
|
||||
timeline =
|
||||
this.timeline =
|
||||
new SinglePeriodTimeline(
|
||||
durationUs,
|
||||
/* isSeekable= */ true,
|
||||
|
|
|
|||
|
|
@ -13,13 +13,16 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.common;
|
||||
package androidx.media3.exoplayer.source;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.Bundleable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.util.BundleableUtil;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
|
@ -30,7 +33,16 @@ import java.lang.annotation.RetentionPolicy;
|
|||
import java.lang.annotation.Target;
|
||||
import java.util.List;
|
||||
|
||||
/** An immutable array of {@link TrackGroup}s. */
|
||||
/**
|
||||
* An immutable array of {@link TrackGroup}s.
|
||||
*
|
||||
* <p>This class is typically used to represent all of the tracks available in a piece of media.
|
||||
* Tracks that are known to present the same content are grouped together (e.g., the same video feed
|
||||
* provided at different resolutions in an adaptive stream). Tracks that are known to present
|
||||
* different content are in separate track groups (e.g., an audio track will not be in the same
|
||||
* group as a video track, and an audio track in one language will be in a different group to an
|
||||
* audio track in another language).
|
||||
*/
|
||||
@UnstableApi
|
||||
public final class TrackGroupArray implements Bundleable {
|
||||
|
||||
|
|
@ -36,7 +36,6 @@ import androidx.media3.common.MediaItem;
|
|||
import androidx.media3.common.StreamKey;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.TransferListener;
|
||||
|
|
@ -54,6 +53,7 @@ import androidx.media3.exoplayer.source.MediaPeriod;
|
|||
import androidx.media3.exoplayer.source.MediaSource;
|
||||
import androidx.media3.exoplayer.source.MediaSourceEventListener;
|
||||
import androidx.media3.exoplayer.source.SampleStream;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||
import androidx.media3.exoplayer.upstream.Allocator;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue