Merge branch 'main' into rtp_opus

This commit is contained in:
manisha_jajoo 2022-04-21 10:26:08 +05:30 committed by GitHub
commit f0d7d96309
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
313 changed files with 12053 additions and 4852 deletions

View file

@ -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 youre 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
View 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
View file

@ -0,0 +1 @@
blank_issues_enabled: false

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -96,7 +96,6 @@ class PlaybackService : MediaLibraryService() {
val item = MediaItemTree.getItemFromTitle(mediaTitle) ?: MediaItemTree.getRandomItem()
player.setMediaItem(item)
player.prepare()
}
override fun onSetMediaUri(

View file

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

View file

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

View file

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

View file

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

View file

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

Binary file not shown.

View file

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

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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]) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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(...) );
```

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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.");
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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