Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allows exact live sync for HLS #703

Closed
wants to merge 212 commits into from

Conversation

stevemayhew
Copy link
Contributor

This pull request satisfies the enhancement request #702. For playback longer than a few hours the fix for SntpClient is also required, see pull request #697

Comment on lines -560 to 603
/* elapsedRealtimeEpochOffsetMs= */ C.TIME_UNSET,
SntpClient.getElapsedRealtimeOffsetMs(),
periodDurationUs,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the SntpClient is not initialized this change done nothing, so you could consider making the changes to DefaultHlsPlaylistTracker surrounded by a feature flag from the HlsPlaylistTracker.Factory (if it was not private), or simply a PSFB flag.

}

private void requestMasterPlaylist(
EventDispatcher eventDispatcher, ParsingLoadable<HlsPlaylist> multivariantPlaylistLoadable) {
long elapsedRealtime =
initialPlaylistLoader.startLoading(
multivariantPlaylistLoadable,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The requestMasterPlaylist was simply a refactoring (extract method), then call the method in the SntpClient callback. Most times this is a no-op as the SntpClient offset is up to date.

}
}

private void loadAfterTimeSync(Uri playlistRequestUri) {
ParsingLoadable.Parser<HlsPlaylist> mediaPlaylistParser =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same refactor lift method to create loadAfterTimeSync(). Here only the primary playlist load needs to possibly refresh SntpClient as the master playlist is only loaded once

@BogdanLivadariu
Copy link

@stevemayhew I think you should rebase this against main branch

MGaetan89 and others added 25 commits June 14, 2024 16:21
This method allows customizing the maximum position when using `Player.seekToPrevious()`.

This commit also adds two new methods to `TestExoPlayerBuilder`:
- `setMaxSeekToPreviousPosition(long)`
- `getMaxSeekToPreviousPosition()`
In muxer only 32-bit sample duration is supported. Earlier the duration was type
casted at different places.

PiperOrigin-RevId: 643954226
…eekToPreviousPosition

PiperOrigin-RevId: 643987403
`Objects.equals` has been available since API 19.

PiperOrigin-RevId: 644026884
This is an internal refactor with no logic changed.

There is a duplication in information and a lack of consistency around
the use of test assets in transformer androidTest. This change
refactors each asset to be defined as its own AssetInfo, with the other
relevant parts stored alongside the uri.

Once this is in place, we can consider other useful functionality, such
as having boolean flags for what tracks an asset has, helper methods
for whether an asset is local or remote, and more. This will reduce the
manual overhead necessary to use more assets in tests, and in
particular leads towards easily using new & existing assets in
parameterized tests.

PiperOrigin-RevId: 644040595
These calls have no effect because the VideoFrameReleaseControl of
CompositionPlayer is created with allowedJoiningTimeMs set to 0.

PiperOrigin-RevId: 644274524
Also adds basic support for canDecode receiving an image Format.

PiperOrigin-RevId: 644300521
There are a lot of tests for AudioFocusManager in isolation,
but almost none for the handling in ExoPlayer.

Add test coverage for all the common cases, including some
currently broken behavior that is indicated by TODOs.

PiperOrigin-RevId: 644319251
#cherrypick

PiperOrigin-RevId: 644351958
#cherrypick

PiperOrigin-RevId: 644352776
The 'player commands' returned to ExoPlayerImpl instruct the
player on how to treat the current audio focus state.

The current return value when playWhenReady==false is misleading
because it implies we are definitely not allowed to play as if
we've lost focus. Instead, we should return the actual player
command corresponding to the focus state we are in.

This has no practical effect in ExoPlayerImpl as we already
ignore the 'player command' completely  when playWhenReady=false.

To facilitate this change, we also introduce a new internal
state for FOCUS_NOT_REQUESTED to distinguish it from the state
in which we lost focus.

#cherrypick

PiperOrigin-RevId: 644416586
PiperOrigin-RevId: 644659699
Includes adjusting how effects are defined, to make it clearer in a
test that a given ItemConfig has effects associated with it.

PiperOrigin-RevId: 644684308
The Javadoc was indicating that the duration should always be set,
but it doesn't need to be set in most cases for export.

PiperOrigin-RevId: 644685827
#cherrypick

PiperOrigin-RevId: 644693533
In the case of a legacy session app providing an error
code different to `ERROR_UNKNOWN` without an error
message, a localized fallback message is provided
instead of ignoring the error.

#cherrypick

PiperOrigin-RevId: 644704680
ychaparov and others added 22 commits July 17, 2024 05:08
PiperOrigin-RevId: 653192624
-----

Context:
* Each sequence is wrapped as a single MediaSource, each being played
by an underlying ExoPlayer.
* Repeat mode is typically implemented in Players as a seek to the next
item in the playlist.

-----

This CL:

Repeat mode is triggered by listening for when the main input player
sees a play when ready change due to the end of the media item.

There is a slight delay at the end of the playback, before it repeats.
Setting repeat mode on the underlying players addresses this, but means
that the players will seek without waiting for the CompositionPlayer,
and as such previewAudioPipeline does not get the correct signals
around blocking/flushing.

PreviewAudioPipeline - The seek position can validly be C.TIME_UNSET,
however preview pipeline did not handle this case.

CompositionPlayer getContentPosition is given (through a lambda) as a
supplier to the State object, which means any comparisons between
previous/new state for this value does not work. In SimpleBasePlayer,
there is logic to use the positionDiscontinuityPositionUs for the
position change (see getPositionInfo called from
updateStateAndInformListeners), however this logic is not considered in
getMediaItemTransitionReason, so a condition needed to be added for
this case.

-----

Tests:
* Dump files clearly show the position and data is repeated.
* Assertions on the reasons for transitions or position
discontinuities.
PiperOrigin-RevId: 653210278
The change is purely video releated.

PiperOrigin-RevId: 653229546
#cherrypick

PiperOrigin-RevId: 653261278
Implement dOpsBox to provide support for Opus audio codec

PiperOrigin-RevId: 653288049
Boxes.moov() simply wraps the subboxes and this logic can be
put into main method which has all the logic of creating moov box.
This is to eventually move Mp4MoovStucture.moov() into
Boxes.java where all other box creation methods are already present.

PiperOrigin-RevId: 653319292
This replaces the existing PlaceholderSurface mode with a more
efficient solution that doesn't require a GL texture or a new
thread.

PiperOrigin-RevId: 653537596
This test is flaky at p4head, because CompositionPlayer has no logic to
ensure a specific input is used to configure the audio graph. Depending
on which sequence registers input first, it changes the output format
of the media.

By setting effects on the 2nd sequence, both inputs are the same format
and this flakiness is avoided in the test.

PiperOrigin-RevId: 653582441
Creating a moov box is same as creating any other box so
there is no particular need to have a separate class for this.
In a follow up CL the method will be moved into Boxes.java along with
other box creation methods.

Made nit changes in the final fields ordering to match with
constructor parameter ordering.

PiperOrigin-RevId: 653602558
This controller connects the audio output to the MediaCodec so
that it can automatically propagate CTA-2075 loudness metadata.

PiperOrigin-RevId: 653628503
#cherrypick

PiperOrigin-RevId: 653640574
PiperOrigin-RevId: 653644998
#cherrypick

PiperOrigin-RevId: 653654999
Introduced three `setDataSource` APIs in `MediaExtractorCompat`, enabling the use of `AssetFileDescriptor` and `FileDescriptor` to set the data source.

PiperOrigin-RevId: 653957035
All methods check if the player is currently handling the ad source
by calling isCurrentAdPlaying(). This method was missing a check
for empty timelines that throws an exception when trying to access
a non-existent period.

Also add this check to two methods that assume the current item
is the ads source, but didn't check it yet.

PiperOrigin-RevId: 653963557
According to b/292763081 the build failure
`unresolved reference: addLast` in the session-demo is caused
by a bug in the Kotlin compiler 1.9.0 to which we depend.

Bumping the Kotlin plugin version to 1.9.10 which is the next
above 1.9.0 as listed in [1], the build problem is resolved.

Further, the Kotlin extension compiler version is set to 1.5.3
to make compose work with Kotlin 1.9.10 (see [2] for the
Kotlin/Compose compatibility map).

[1] https://kotlinlang.org/docs/releases.html#release-details
[2] https://developer.android.com/jetpack/androidx/releases/compose-kotlin

PiperOrigin-RevId: 654030537
We currently wait until a previous AudioTrack from the same
DefaultAudioSink is released on a background thread before attempting
to initialize a new AudioTrack. This is done to avoid issues where
the releasing track blocks some shared audio memory, preventing a new
track from being created.

The current solution has two main shortcomings:
 - In most cases, the system can easily handle multiple AudioTracks
   and waiting for the release just causes unnecessary delays (e.g.
   when seeking).
 - It only waits for a previous track from the same DefaultAudioSink,
   not accounting for any other tracks that may be in the process of
   being released from other players.

To improve on both shortcomings, we can
 (1) move the check for "is releasing tracks and thus may block shared
 memory" to the static release infrastructure to be shared across all
 player instances.
 (2) optimistically create a new AudioTrack immediately without waiting
 for the previous one to be fully released.
 (3) extend the existing retry logic that already retries failed
 attempts for 100ms to only start the timer when ongoing releases are
 done. This ensures we really waited until we have all shared resources
 we can get before giving up completely. This also acts as a replacement
 for change (2) to handle situations where creating a second track is
 genuinely not possible. Also increase threshold to 200ms as the new
 unit test is falky on a real device with just 100ms (highlighting that
 the device needed more than 100ms to clean up internal resources).

PiperOrigin-RevId: 654053123
This was used in media1 `MediaItemDescription` to indicate the download
status of a media item. When connected to a legacy
`MediaBrowserServiceCompat` the Media3 browsers converts the legacy
media item to a Media3 `MediaItem` and converts the extras of
`MediaDescriptionCompat.extras` to `MediaMetadata.extras`.

#cherrypick

PiperOrigin-RevId: 654625502
@stevemayhew
Copy link
Contributor Author

Thanks @BogdanLivadariu no one is giving it any attention, we've already shipped it ;-)

This commit addresses the issue of inaccurate live position measurement in HLS
by introducing NTP time synchronization. Unlike DASH, HLS lacks origin time
synchronization logic, resulting in potential discrepancies in wall-clock time
when using `System.currentTimeMillis()`.

The commit utilizes the `SntpClient` in ExoPlayer to determine the clock offset
with the default NTP server (`time.android.com`) and applies this information to the `HlsMediaSource`.

With the implementation of this change, multiple devices can now synchronize playback to
a common time source, as long as the origin server also synchronizes to an NTP time source.
Enables testing the Live Offset settings with the main demo PlayerActivity.  Using an
intent like:

```
adb shell am start -n androidx.media3.demo.main/.PlayerActivity -a androidx.media3.demo.main.action.VIEW --ei live_offset_target 30 --ef live_offset_adjust_speed 35.0
```

Will start playback synced to a 30 second offset with agressive speed adjustment to achive it, note
this requires audio codec that supports speed changes (AAC for example)
@stevemayhew
Copy link
Contributor Author

Closing this mess and re-opening with base against main

@stevemayhew stevemayhew deleted the p-add-hls-live-sync branch September 24, 2024 00:36
@stevemayhew stevemayhew restored the p-add-hls-live-sync branch September 24, 2024 00:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.