这是indexloc提供的服务,不要输入任何密码
Skip to content

#2426 Support updating HLS Interstitials according to the HLS spec #2427

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

Conversation

TomVarga
Copy link

@TomVarga TomVarga commented May 15, 2025

Use case description

According to the latest version of the HLS spec at https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.5.1

If a Playlist contains two EXT-X-DATERANGE tags with the same ID
attribute value, then any AttributeName that appears in both tags
MUST have the same AttributeValue. A Server MAY augment a Date Range
with additional attributes by adding subsequent EXT-X-DATERANGE tags
with the same ID attribute to a Playlist. The client is responsible
for consolidating the tags. The subsequent EXT-X-DATERANGE tags can
appear in a subsequent playlist update, in the case of live or event
streams.

In a live stream scenario our system does updates to an interstitial according to the spec.

Here is simplified example stream:

https://raw.githubusercontent.com/TomVarga/HLS-test-stream/refs/heads/main/ride/audio.m3u8

Proposed solution

I am creating a PR to show my proposed solution, which is going to allow updating the interstitial, but still require the START-DATE for the first appearance of the interstitial.

This is a naive solution:

  • if an interstitial already appeared, then attempt to update it

Can use https://raw.githubusercontent.com/TomVarga/HLS-test-stream/refs/heads/main/ride/audio.m3u8 in the demo app to validate these changes

@marcbaechinger marcbaechinger self-requested a review May 15, 2025 14:21
@marcbaechinger
Copy link
Contributor

Thanks for the pull request.

I don't think we need changes in the parser or the HlsMediaPlaylist for this. This is exactly the same. In a live stream, the playlist is loaded again and then produces a playlist with the new interstitials.

The user of the playlist can then compare and update the interstitial by comparing playlistA with playlistB.

@marcbaechinger
Copy link
Contributor

The change we would need for HlsINterstitialsAdsLoader would be that we let the ads loader updated the AdPlaybackState in [1] when we get the same interstitial again with changed attributes.

What does your stream update in the interstitial in the playlist?

[1] https://github.com/androidx/media/blob/main/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsInterstitialsAdsLoader.java#L1093

@TomVarga
Copy link
Author

We need the code change because if you revert the changes in HlsPlaylistParser but run the test in HlsMediaPlaylistParserTest#parseMediaPlaylist_withInterstitialWithUpdatingDateRange you'll get:

Couldn't match START-DATE="(.+?)" in #EXT-X-DATERANGE:ID="15943",CLASS="com.apple.hls.interstitial",END-DATE="2024-09-20T15:29:49.006Z",X-PLAYOUT-LIMIT=24.953741497,X-RESUME-OFFSET=24.953741497 {contentIsMalformed=true, dataType=4}

Which is also what you get if you try and play this https://raw.githubusercontent.com/TomVarga/HLS-test-stream/refs/heads/main/ride/audio.m3u8 stream in the demo app.

Which means that the player is not handling the playlist according to the HLS spec as updating a daterange tag is allowed, meaning it's OK to not have START-DATE in a DATERANGE tag, as another DATERANGE tag can make changes to the interstitial based on the id

@TomVarga
Copy link
Author

The change we would need for HlsINterstitialsAdsLoader would be that we let the ads loader updated the AdPlaybackState in [1] when we get the same interstitial again with changed attributes.

What does your stream update in the interstitial in the playlist?

[1] https://github.com/androidx/media/blob/main/libraries/exoplayer_hls/src/main/java/androidx/media3/exoplayer/hls/HlsInterstitialsAdsLoader.java#L1093

As you can see in the mentioned example stream, and the test I have added:

END-DATE
X-PLAYOUT-LIMIT
X-RESUME-OFFSET

get added to the interstitial

@TomVarga
Copy link
Author

I have updated the PR description with the same as the issue to hopefully avoid some confusion.

@marcbaechinger
Copy link
Contributor

We need the code change because if you revert the changes in HlsPlaylistParser

The model is that each time when a playlist is fetched an HlsMediaPlaylist object is created by the parser. If the interstitials have changed, then a new HlsMediaPlaylist is created with the updated content of the playlist.

We don't merge playlist A into playlist B or parts of it, so that multiple states of the playlist are represented.
A user can listen to onTimelineChanged() to get the playlist at time A. Then when it's fetched again at time B onTimelineChanged is called again with the next playlist. If the date ranges have changed the playlist will contain the new values. They won't be merged into the next playlist. For this you will have two instances of playlists with the state at time A and at time B.

If you see a failure with parsing please file a bug. This isn't related to updateability.

@TomVarga
Copy link
Author

Sure, I can file the parsing issue as a bug, do please see #2428

Copy link
Contributor

@marcbaechinger marcbaechinger left a comment

Choose a reason for hiding this comment

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

Considering that I have misread the part 'If a Playlist contains two EXT-X-DATERANGE tags with' and haven't realized this in your unit test . I should have looked at this more closely beside the discussion around the START-DATE problems. Sorry for this.

So bseides the discussion about the spec ambiguity, this change makes totally sense in general as we want to support this according to spec at some point.

In the comment on L1321 I suggest to make an HlsMediaPlaylist.Interstitial.Builder because I think this would simplify the code and provide a good way to handle the update and validate merging the two or more tags.

Let me know what you think. It's fine to leave it to us to implement that Builder approach or convince me for another approach and why. :)

new ArrayList<>(interstitialMap.values()));
}

private static Interstitial getUpdatedInterstitial(
Copy link
Contributor

Choose a reason for hiding this comment

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

What about creating an HlsMediaPlaylist.Interstitial.Builder() that has setters for each field. When a setter is called multiple times, it asserts that it's the unset value or the same value else it throws.

Then we put the Interstitial.Builder instances in the LinkedHashMap by ID as you already do. At the very end, when we construct the HlsMediaPlaylist instance pass in the list of builder, and the constructor of Intersitital iterates over all builders, calls build() to get a ImmutableList of Interstitial instances.

I think that would make the code a bit simpler. WDYT?

// for consolidating the tags. The subsequent EXT-X-DATERANGE tags can
// appear in a subsequent playlist update, in the case of live or event
// streams.
Uri newAssetUri = (assetUri != null) ? assetUri : oldInterstitial.assetUri;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the old value has precedence as mentioned in the Builder approach above or we should actually throw if not equal.

any AttributeName that appears in both tags MUST have the same AttributeValue.

restrictions,
clientDefinedAttributes.build()));

if (interstitialMap.containsKey(id)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If you do the Builder that I suggest below in the other comment, this if is at the very beginning on L1044 or so. Then you can set each value on the builder after parsing each field. The setter of the builder can then act accordingly: a) when the field wasn't set before: set the value b) if it was already set assert that the new value is either the same or unset, else throw. These validation can then be in the setter of each field. This can include things like checking that there is only an ASSET-URI or an ASSET-LIST.

@marcbaechinger
Copy link
Contributor

@TomVarga
Copy link
Author

Considering that I have misread the part 'If a Playlist contains two EXT-X-DATERANGE tags with' and haven't realized this in your unit test . I should have looked at this more closely beside the discussion around the START-DATE problems. Sorry for this.

So bseides the discussion about the spec ambiguity, this change makes totally sense in general as we want to support this according to spec at some point.

In the comment on L1321 I suggest to make an HlsMediaPlaylist.Interstitial.Builder because I think this would simplify the code and provide a good way to handle the update and validate merging the two or more tags.

Let me know what you think. It's fine to leave it to us to implement that Builder approach or convince me for another approach and why. :)

I'll update the PR with the builder approach.

@TomVarga
Copy link
Author

I've pushed the requested changes, do please let me know if you think something is missing or needs improvement.

Copy link
Contributor

@marcbaechinger marcbaechinger left a comment

Choose a reason for hiding this comment

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

Nice! Thank you very much!

There are two or three things that we need to look at.

The rest are nits or uber-nits, as I gave you the hard way like I'd do internally. We can share these things also when leave some of them to me and I can do these nits when preparing for internal review. :)

public Builder setClientDefinedAttributes(
List<HlsMediaPlaylist.ClientDefinedAttribute> clientDefinedAttributes) {
if (clientDefinedAttributes.isEmpty()) return this;
if (!this.clientDefinedAttributes.isEmpty()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure how to interpret the spec. These attributes are not interpreted by a player by spec, so changing them would not have any implications for playback. We could choose to be a bit less strict here.

From the text of the spec, we could as an alternative to your proposal allow any new attributes, but make sure we don't override existing ones with a different value. Easiest would probably to have a Map here in the builder keyed by the attribute.name and then check for equality if an attribute with the same name is provided an additional time. Then when build() is called this is converted to a list.

Being a bit less strict would mean to just make sure to not create duplicate names and either ignore the new attribute or allow it to be overridden. So being less strict gives us the burden to decide exactly that which we may want to avoid.

So I think I'd vote for checking equality. :)

WDYT?

Copy link
Author

Choose a reason for hiding this comment

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

I think we definitely need to allow new attributes to the list.

I agree probably easiest is to have a Map as you've described.

I also agree I'd rather check value equality too.

I'll add this in a follow up commit.

Copy link
Author

Choose a reason for hiding this comment

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

I have added some validation, do let me know if it looks OK to you.

@TomVarga
Copy link
Author

I think I addressed most of your comments, I'll work on a commit for clientDefinedAttributes. Let me know if it looks OK so far.

@TomVarga TomVarga requested a review from marcbaechinger May 20, 2025 12:09
Copy link
Contributor

@marcbaechinger marcbaechinger left a comment

Choose a reason for hiding this comment

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

Very nice! Thank you very much!

I'm going to take this to send it for internal review.

Can you please let me quickly know that's fine? Looks to me you applied the changes for the client defined attributes.

@TomVarga
Copy link
Author

Very nice! Thank you very much!

I'm going to take this to send it for internal review.

Can you please let me quickly know that's fine? Looks to me you applied the changes for the client defined attributes.

Yes, that's fine. Thank you.

TomVarga and others added 5 commits May 20, 2025 23:09
… spec

- if an interstitial already appeared, then attempt to update it
- use a builder
- don't throw parse exception for missing START-DATE instead ignore the interstitial
… spec

- some code style cleanup
- fix negated expression in checkArguments and add test for it
… spec

- add validation for clientDefinedAttribute
@marcbaechinger marcbaechinger force-pushed the tv/2426-Support-updating-HLS-Interstitials-according-to-the-HLS-spec branch from 3efb6a9 to d580062 Compare May 20, 2025 21:12
@marcbaechinger
Copy link
Contributor

I'm going to send this for internal review now. You may see some more commits being added as I make changes in response to review feedback. Please refrain from pushing any more substantive changes as it will complicate the internal review - thanks!

@marcbaechinger marcbaechinger force-pushed the tv/2426-Support-updating-HLS-Interstitials-according-to-the-HLS-spec branch from 9050266 to 16b9880 Compare May 22, 2025 16:09
@copybara-service copybara-service bot merged commit b98e43f into androidx:main May 22, 2025
1 check passed
@androidx androidx locked and limited conversation to collaborators Jul 21, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants