Skip to content

Conversation

@xxoo
Copy link
Contributor

@xxoo xxoo commented Apr 17, 2025

Allow removing TextTracks created by hls.js

This PR will...

  • use appendChild instead of addTextTrack to allow reverse operation
  • replace track flush and reuse workflow with simple removal
  • add trackNode field in MediaPlaylist to indicate associated TextTrack
  • fix MEDIA_DETACHED event in wrong order and sometimes not triggering

Why is this Pull Request needed?

This PR allows video elements to have the same lifecycle as hls.js instances. After detaching, you may attach them to new hls.js instances with nothing to worry about. It also fixed a bug caused by track reuse, where content overlapping occurs if a video contains multiple captions sharing the same language and label. eg: https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8

Are there any points in the code the reviewer needs to double check?

Yes. Since the changes involve more than one place, extensive testing from multiple aspects may be necessary to ensure no issues are left behind. Especially I'm not sure whether it works well with transferMedia()

Resolves issues:

#2198

Checklist

  • changes have been done against master branch, and PR does not conflict
  • new unit / functional tests have been added (whenever applicable)
  • API or design changes are documented in API.md

Copy link
Collaborator

@robwalch robwalch left a comment

Choose a reason for hiding this comment

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

Thank you for the contribution @xxoo,

The ability to remove tracks would be appreciated. However, for backwards compatibility, we need to maintain addTextTrack as the default path for adding TextTracks.

There may be JavaScript platforms with limited Web support cannot create TextTrack nodes. There are also browsers where media.textTracks from DOM created tracks do not behave the same (HTMLMediaElement controls menu issues, events, etc...). One concern is that apps may provide an element with tracks already added by another means or they may add their own side car tracks and this change would introduce problems with those integrations.

Would be possible to make this change optional via a configuration option (use addTextTrack by default, but set an option to use DOM tracks instead)?

Comment on lines 19 to 22
// To avoid an issue of the built-in captions menu in Chrome
if (navigator.userAgent.includes('Chrome/')) {
el.src = 'data:,WEBVTT';
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Interesting.

Is this to prevent loading of the current page (src="" treated like "." path)?
What other issues occur with the menu?

I recall there was some issue in Safari with DOM created tracks but it has been long enough that I don't remember the details.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So far, I haven't seen any issue related to DOM tracks on Safari. However, the issue you found on Chrome still exists. Providing a valid VTT file might help to avoid this, and the data URL approach serves exactly this purpose.

Subtitle tracks not managed by hls.js shouldn't cause problems, or at least such problems can be avoided. Because hls.js keeps a reference to each track it manages. If a user selects an unmanaged track, hls.js will leave it as is and set the current subtitle track index to -1. I think I can add some unit tests for this scenario.

According to caniuse.com, the track element and the addTextTrack method have nearly identical browser support. The only difference I've noticed is that a newly created track node might need some time to initialize its mode. I'm not sure if this behavior affects any public API usage. Perhaps we should further analyze the necessity and implications of this, as maintaining the old approach could complicate the workflow.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The only difference I've noticed is that a newly created track node might need some time to initialize its mode.

We should also have a look at whether or not these changes impact performance WRT #7093. I'd be curious to know if adding tracks this way has the same cost in Safari or not. If it resolved the issue without any changes to WebKit that would be another +1 for accepting the change.

Copy link
Collaborator

@robwalch robwalch Apr 18, 2025

Choose a reason for hiding this comment

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

performance WRT #7093

No difference. In each session that I profile, there appears to be a fixed cost for each track added in Safari that can be seen in the profiling results under "Add Track Event Dispatched" Timelines tab of the Web Inspector.

That cost does appear to be reduced when appending tracks nodes with mode disabled at once compared to using addTextTrack.

@robwalch robwalch added this to the 1.7.0 milestone Apr 17, 2025
@robwalch
Copy link
Collaborator

According to caniuse.com, the track element and the addTextTrack method have nearly identical browser support.

That does not guarantee that these changes will not break someone's integration. There could be integrations of hls.js out there that depend at some level on media.addTextTrack getting called. We can't know until releasing these changes, but don't be surprised. Someone might come out of nowhere saying that ownerDocument in undefined on their media element.

That said, I marked this under milestone v1.7. This is really nice work.

@xxoo
Copy link
Contributor Author

xxoo commented Apr 19, 2025

Thanks for the review. I will fix these issues soon. I also think removable tracks may help improve performance slightly when there are too many text tracks and more than one text group, as we can destroy tracks outside current groups. Shaka Player avoids this problem by adding only one text track, meaning the native captions UI or API isn't available as an option for users.

@xxoo
Copy link
Contributor Author

xxoo commented Apr 20, 2025

@robwalch
I just updated this PR with the following changes:

  • Replace for...of with forEach
  • Only create track nodes for current groups
  • No longer depend on event order
  • Adjusted the way hls.js handles external subtitles to fix unexpected behavior

You may reproduce the issue on master branch with the unit test "should set subtitleTrack to -1 and keep unmanagedTrack showing"

@xxoo xxoo requested a review from robwalch April 20, 2025 23:43
Comment on lines 549 to 550
if (lastTrack) {
// switch to -1
Copy link
Collaborator

Choose a reason for hiding this comment

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

SUBTITLE_TRACK_SWITCH is only expected to trigger when there is a track change. The thinking here is that if this.currentTrack (lastTrack) is null then it is not set , trackId must also be -1 already, and setting subtitleTrack to -1 is a no-op.

Copy link
Contributor Author

@xxoo xxoo Apr 21, 2025

Choose a reason for hiding this comment

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

Yes this detection was added by me. But it breaks the unit test "should trigger SUBTITLE_TRACK_SWITCH if passed -1" (which I removed before for easier debugging, but forgot to add it back).
In the past, setting to -1 when it is already -1 may still change the mode of external subtitles in toggleTrackModes(). But this behavior has changed now. Which makes setting to -1 in this situation have no effect. I removed this line only for passing this unit test. Although I totally agree with you. If this unit test is no longer necessary (I believe it is true, but maybe it is not for me to decide), I'd also like to add this detection back.

Copy link
Contributor Author

@xxoo xxoo Apr 29, 2025

Choose a reason for hiding this comment

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

@robwalch I just reverted this commit and changed some logic to avoid issues that I found.

  • Support forced subtitle on Apple webkit
  • Prefer previously selected track in change event of video.textTracks
  • Disable new text tracks before appending and show the selected one after that to avoid state out of sync issue of the Chrome built-in controls

});
});

describe('text track kind', function () {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why were the track kind tests removed?

Copy link
Contributor Author

@xxoo xxoo May 23, 2025

Choose a reason for hiding this comment

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

Because TimelineController no longer creates TextTrack for MediaPlaylist. Instead, the life cycle of every <track> element is managed by SubtitleTrackController now. There is an equivalent test in tests/unit/controller/subtitle-track-controller.ts

hlsjs-ci and others added 17 commits May 23, 2025 16:18
chore(deps): update dependency mocha to v11.3.0
chore(deps): update dependency mocha to v11.4.0
…2-8.x

chore(deps): update dependency npm-run-all2 to v8.0.2
…2-8.x

chore(deps): update dependency npm-run-all2 to v8.0.3
chore(deps): update dependency rollup to v4.41.0
chore(deps): update dependency rollup to v4.41.1
…ry for entire break (video-dev#7263)

* Fix handling of interstitials where all assets in asset-list error, and seeking into assets that errored
* Simplify asset list progression and make interstitial logging more consistent
Fixes video-dev#7180
…ELINE_DECODE_ERROR on clear to encrypted transition)
robwalch and others added 5 commits August 28, 2025 10:09
* refactor: remove usage of any for networkDetails;

* chore: use new Response in unit tests;
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
@robwalch
Copy link
Collaborator

Hi @xxoo,

Sorry for the delay in getting to this. Can you please resolve the merge conflicts by rebasing these changes?

yajin2021 and others added 17 commits August 28, 2025 14:20
…ckout-5.x

chore(deps): update actions/checkout action to v5
…b-artifact-actions

chore(deps): update actions/download-artifact action to v5
…r-139.x

chore(deps): update dependency chromedriver to v139.0.3
chore(deps): update dependency rollup to v4.47.1
- replace track flush and reuse workflow with simple removal
- add `trackNode` field in `MediaPlaylist` to indicate associated `TextTrack`
- fixed `MEDIA_DETACHED` event in wrong order and sometimes not triggering
…oo/hls.js into feature/removable-text-tracks

# Conflicts:
#	src/controller/id3-track-controller.ts
#	src/controller/subtitle-track-controller.ts
#	src/controller/timeline-controller.ts
#	src/utils/texttrack-utils.ts
#	tests/unit/controller/subtitle-track-controller.ts
@xxoo
Copy link
Contributor Author

xxoo commented Aug 29, 2025

@robwalch
Resolved. All unit tests passed locally.

@robwalch
Copy link
Collaborator

Hi @xxoo,

Take a look at the diff. I think something went wrong in your branch rebase. This PR should not include 106 file changes.

@xxoo
Copy link
Contributor Author

xxoo commented Aug 29, 2025

Sorry seems I used merge in some step. Maybe I'll have to start a new PR to fix this.

@xxoo
Copy link
Contributor Author

xxoo commented Aug 29, 2025

All changes in this PR is moved to #7515.

@xxoo xxoo closed this Aug 29, 2025
@robwalch robwalch removed this from the 1.7.0 milestone Aug 29, 2025
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.