Skip to content

Commit

Permalink
Fix regression where subtitle options with AUTOSELECT and FORCED are …
Browse files Browse the repository at this point in the history
…enabled at start (#6094)

* Do not enable subtitle options with AUTOSELECT=YES attribute
* Update and add initial selection tests for subtitle-controller
* Only pick forced subtitle option if it is the only one
Add default field to audio and subtitle selection options and forced field to subtitle selection option
* Address TextTrack change event overriding subtitle preference
Fix _TRACKS_UPDATED and _TRACK_SWITCH event order when preference is selected
* Do not auto select subtitle options with FORCED=YES attribute
  • Loading branch information
robwalch committed Jan 10, 2024
1 parent e7ed554 commit cd12094
Show file tree
Hide file tree
Showing 7 changed files with 638 additions and 395 deletions.
3 changes: 3 additions & 0 deletions api-extractor/report/hls.js.api.md
Expand Up @@ -129,6 +129,7 @@ export type AudioSelectionOption = {
name?: string;
audioCodec?: string;
groupId?: string;
default?: boolean;
};

// Warning: (ae-missing-release-tag) "AudioStreamController" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
Expand Down Expand Up @@ -3164,6 +3165,8 @@ export type SubtitleSelectionOption = {
characteristics?: string;
name?: string;
groupId?: string;
default?: boolean;
forced?: boolean;
};

// Warning: (ae-missing-release-tag) "SubtitleStreamController" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
Expand Down
20 changes: 17 additions & 3 deletions src/controller/audio-track-controller.ts
Expand Up @@ -157,17 +157,31 @@ class AudioTrackController extends BasePlaylistController {
// Do not dispatch AUDIO_TRACKS_UPDATED when there were and are no tracks
return;
}
this.tracksInGroup = audioTracks;

if (!currentTrack) {
currentTrack = this.setAudioOption(this.hls.config.audioPreference);
// Find preferred track
const audioPreference = this.hls.config.audioPreference;
if (!currentTrack && audioPreference) {
const groupIndex = findMatchingOption(
audioPreference,
audioTracks,
audioMatchPredicate,
);
if (groupIndex > -1) {
currentTrack = audioTracks[groupIndex];
} else {
const allIndex = findMatchingOption(audioPreference, this.tracks);
currentTrack = this.tracks[allIndex];
}
}

this.tracksInGroup = audioTracks;
// Select initial track
let trackId = this.findTrackId(currentTrack);
if (trackId === -1 && currentTrack) {
trackId = this.findTrackId(null);
}

// Dispatch events and load track if needed
const audioTracksUpdated: AudioTracksUpdatedData = { audioTracks };
this.log(
`Updating audio tracks, ${
Expand Down
44 changes: 25 additions & 19 deletions src/controller/subtitle-track-controller.ts
Expand Up @@ -35,7 +35,6 @@ class SubtitleTrackController extends BasePlaylistController {
private currentTrack: MediaPlaylist | null = null;
private selectDefaultTrack: boolean = true;
private queuedDefaultTrack: number = -1;
private trackChangeListener: () => void = () => this.onTextTracksChanged();
private asyncPollTrackChange: () => void = () => this.pollTrackChange(0);
private useTextTrackPolling: boolean = false;
private subtitlePollingInterval: number = -1;
Expand All @@ -51,7 +50,7 @@ class SubtitleTrackController extends BasePlaylistController {
this.tracks.length = 0;
this.tracksInGroup.length = 0;
this.currentTrack = null;
this.trackChangeListener = this.asyncPollTrackChange = null as any;
this.onTextTracksChanged = this.asyncPollTrackChange = null as any;
super.destroy();
}

Expand Down Expand Up @@ -121,7 +120,7 @@ class SubtitleTrackController extends BasePlaylistController {
private pollTrackChange(timeout: number) {
self.clearInterval(this.subtitlePollingInterval);
this.subtitlePollingInterval = self.setInterval(
this.trackChangeListener,
this.onTextTracksChanged,
timeout,
);
}
Expand Down Expand Up @@ -231,12 +230,10 @@ class SubtitleTrackController extends BasePlaylistController {
!subtitleGroups || subtitleGroups.indexOf(track.groupId) !== -1,
);
if (subtitleTracks.length) {
// Disable selectDefaultTrack if there are no default or forced tracks
// Disable selectDefaultTrack if there are no default tracks
if (
this.selectDefaultTrack &&
!subtitleTracks.some(
(track) => track.default || track.forced || track.autoselect,
)
!subtitleTracks.some((track) => track.default)
) {
this.selectDefaultTrack = false;
}
Expand All @@ -248,19 +245,31 @@ class SubtitleTrackController extends BasePlaylistController {
// Do not dispatch SUBTITLE_TRACKS_UPDATED when there were and are no tracks
return;
}
this.tracksInGroup = subtitleTracks;

if (!currentTrack) {
currentTrack = this.setSubtitleOption(
this.hls.config.subtitlePreference,
// Find preferred track
const subtitlePreference = this.hls.config.subtitlePreference;
if (!currentTrack && subtitlePreference) {
this.selectDefaultTrack = false;
const groupIndex = findMatchingOption(
subtitlePreference,
subtitleTracks,
);
if (groupIndex > -1) {
currentTrack = subtitleTracks[groupIndex];
} else {
const allIndex = findMatchingOption(subtitlePreference, this.tracks);
currentTrack = this.tracks[allIndex];
}
}

this.tracksInGroup = subtitleTracks;
// Select initial track
let trackId = this.findTrackId(currentTrack);
if (trackId === -1 && currentTrack) {
trackId = this.findTrackId(null);
}

// Dispatch events and load track if needed
const subtitleTracksUpdated: SubtitleTracksUpdatedData = {
subtitleTracks,
};
Expand All @@ -286,10 +295,7 @@ class SubtitleTrackController extends BasePlaylistController {
for (let i = 0; i < tracks.length; i++) {
const track = tracks[i];
if (
(selectDefault &&
!track.default &&
!track.forced &&
!track.autoselect) ||
(selectDefault && !track.default) ||
(!selectDefault && !currentTrack)
) {
continue;
Expand Down Expand Up @@ -492,7 +498,7 @@ class SubtitleTrackController extends BasePlaylistController {
}

// exit if track id as already set or invalid
if (newId < -1 || newId >= tracks.length) {
if (newId < -1 || newId >= tracks.length || !Number.isFinite(newId)) {
this.warn(`Invalid subtitle track id: ${newId}`);
return;
}
Expand All @@ -511,7 +517,7 @@ class SubtitleTrackController extends BasePlaylistController {
this.hls.trigger(Events.SUBTITLE_TRACK_SWITCH, { id: newId });
return;
}
const trackLoaded = track.details && !track.details.live;
const trackLoaded = !!track.details && !track.details.live;
if (newId === this.trackId && track === lastTrack && trackLoaded) {
return;
}
Expand All @@ -533,7 +539,7 @@ class SubtitleTrackController extends BasePlaylistController {
this.loadPlaylist(hlsUrlParameters);
}

private onTextTracksChanged(): void {
private onTextTracksChanged = () => {
if (!this.useTextTrackPolling) {
self.clearInterval(this.subtitlePollingInterval);
}
Expand All @@ -559,7 +565,7 @@ class SubtitleTrackController extends BasePlaylistController {
if (this.subtitleTrack !== trackId) {
this.setSubtitleTrack(trackId);
}
}
};
}

export default SubtitleTrackController;
3 changes: 3 additions & 0 deletions src/types/media-playlist.ts
Expand Up @@ -23,6 +23,7 @@ export type AudioSelectionOption = {
name?: string;
audioCodec?: string;
groupId?: string;
default?: boolean;
};

export type SubtitleSelectionOption = {
Expand All @@ -31,6 +32,8 @@ export type SubtitleSelectionOption = {
characteristics?: string;
name?: string;
groupId?: string;
default?: boolean;
forced?: boolean;
};

// audioTracks, captions and subtitles returned by `M3U8Parser.parseMasterPlaylistMedia`
Expand Down
12 changes: 11 additions & 1 deletion src/utils/rendition-helper.ts
Expand Up @@ -323,12 +323,22 @@ export function matchesOption(
track: MediaPlaylist,
) => boolean,
): boolean {
const { groupId, name, lang, assocLang, characteristics } = option;
const {
groupId,
name,
lang,
assocLang,
characteristics,
default: isDefault,
} = option;
const forced = (option as SubtitleSelectionOption).forced;
return (
(groupId === undefined || track.groupId === groupId) &&
(name === undefined || track.name === name) &&
(lang === undefined || track.lang === lang) &&
(lang === undefined || track.assocLang === assocLang) &&
(isDefault === undefined || track.default === isDefault) &&
(forced === undefined || track.forced === forced) &&
(characteristics === undefined ||
characteristicsMatch(characteristics, track.characteristics)) &&
(matchPredicate === undefined || matchPredicate(option, track))
Expand Down

0 comments on commit cd12094

Please sign in to comment.