diff --git a/src/controller/audio-stream-controller.ts b/src/controller/audio-stream-controller.ts index f3238cec5ae..a546e03efb5 100644 --- a/src/controller/audio-stream-controller.ts +++ b/src/controller/audio-stream-controller.ts @@ -452,6 +452,7 @@ class AudioStreamController this.switchingTrack = data; // main audio track are handled by stream-controller, just do something if switching to alt audio track this.state = State.IDLE; + this.flushAudioIfNeeded(data); } else { this.switchingTrack = null; this.bufferedTrack = data; @@ -746,6 +747,11 @@ class AudioStreamController if (this.state === State.ENDED) { this.state = State.IDLE; } + const mediaBuffer = this.mediaBuffer || this.media; + if (mediaBuffer) { + this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.AUDIO); + this.tick(); + } } } @@ -912,8 +918,8 @@ class AudioStreamController } } - private completeAudioSwitch(switchingTrack: MediaPlaylist) { - const { hls, media, bufferedTrack } = this; + private flushAudioIfNeeded(switchingTrack: MediaPlaylist) { + const { media, bufferedTrack } = this; const bufferedAttributes = bufferedTrack?.attrs; const switchAttributes = switchingTrack.attrs; if ( @@ -925,7 +931,13 @@ class AudioStreamController ) { this.log('Switching audio track : flushing all audio'); super.flushMainBuffer(0, Number.POSITIVE_INFINITY, 'audio'); + this.bufferedTrack = null; } + } + + private completeAudioSwitch(switchingTrack: MediaPlaylist) { + const { hls } = this; + this.flushAudioIfNeeded(switchingTrack); this.bufferedTrack = switchingTrack; this.switchingTrack = null; hls.trigger(Events.AUDIO_TRACK_SWITCHED, { ...switchingTrack }); diff --git a/src/controller/level-controller.ts b/src/controller/level-controller.ts index f0cbea88016..0bb244575f3 100644 --- a/src/controller/level-controller.ts +++ b/src/controller/level-controller.ts @@ -16,7 +16,10 @@ import { import { Level } from '../types/level'; import { Events } from '../events'; import { ErrorTypes, ErrorDetails } from '../errors'; -import { isCodecSupportedInMp4, getCodecCompatibleName } from '../utils/codecs'; +import { + getCodecCompatibleName, + areCodecsMediaSourceSupported, +} from '../utils/codecs'; import BasePlaylistController from './base-playlist-controller'; import { PlaylistContextType, PlaylistLevelType } from '../types/loader'; import type Hls from '../hls'; @@ -175,8 +178,8 @@ export default class LevelController extends BasePlaylistController { audioCodecFound ||= !!audioCodec; return ( !unknownCodecs?.length && - (!audioCodec || isCodecSupportedInMp4(audioCodec, 'audio')) && - (!videoCodec || isCodecSupportedInMp4(videoCodec, 'video')) + (!audioCodec || areCodecsMediaSourceSupported(audioCodec, 'audio')) && + (!videoCodec || areCodecsMediaSourceSupported(videoCodec, 'video')) ); } ); @@ -192,6 +195,11 @@ export default class LevelController extends BasePlaylistController { // Dispatch error after MANIFEST_LOADED is done propagating Promise.resolve().then(() => { if (this.hls) { + if (unfilteredLevels.length) { + this.warn( + `One or more CODECS in variant not supported: "${unfilteredLevels[0].attrs.CODECS}"` + ); + } const error = new Error( 'no level with compatible codecs found in manifest' ); @@ -211,7 +219,8 @@ export default class LevelController extends BasePlaylistController { if (data.audioTracks) { audioTracks = data.audioTracks.filter( (track) => - !track.audioCodec || isCodecSupportedInMp4(track.audioCodec, 'audio') + !track.audioCodec || + areCodecsMediaSourceSupported(track.audioCodec, 'audio') ); // Assign ids after filtering as array indices by group-id assignTrackIdsByGroup(audioTracks); diff --git a/src/controller/stream-controller.ts b/src/controller/stream-controller.ts index 22dc18d67cf..0bf2624a1a6 100644 --- a/src/controller/stream-controller.ts +++ b/src/controller/stream-controller.ts @@ -967,6 +967,7 @@ export default class StreamController ? this.videoBuffer : this.mediaBuffer) || this.media; this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.MAIN); + this.tick(); } } diff --git a/src/loader/m3u8-parser.ts b/src/loader/m3u8-parser.ts index 2cc2a998d33..2ecf9368d66 100644 --- a/src/loader/m3u8-parser.ts +++ b/src/loader/m3u8-parser.ts @@ -809,19 +809,13 @@ function parseStartTimeOffset(startAttributes: string): number | null { return null; } -function setCodecs(codecs: Array, level: LevelParsed) { +function setCodecs(codecs: string[], level: LevelParsed) { ['video', 'audio', 'text'].forEach((type: CodecType) => { const filtered = codecs.filter((codec) => isCodecType(codec, type)); if (filtered.length) { - const preferred = filtered.filter((codec) => { - return ( - codec.lastIndexOf('avc1', 0) === 0 || - codec.lastIndexOf('mp4a', 0) === 0 - ); - }); - level[`${type}Codec`] = preferred.length > 0 ? preferred[0] : filtered[0]; - - // remove from list + // Comma separated list of all codecs for type + level[`${type}Codec`] = filtered.join(','); + // Remove known codecs so that only unknownCodecs are left after iterating through each type codecs = codecs.filter((codec) => filtered.indexOf(codec) === -1); } }); diff --git a/src/utils/codecs.ts b/src/utils/codecs.ts index f49074c029f..3f1e4ea20fd 100644 --- a/src/utils/codecs.ts +++ b/src/utils/codecs.ts @@ -85,7 +85,16 @@ export function isCodecType(codec: string, type: CodecType): boolean { return !!typeCodes && typeCodes[codec.slice(0, 4)] === true; } -export function isCodecSupportedInMp4(codec: string, type: CodecType): boolean { +export function areCodecsMediaSourceSupported( + codecs: string, + type: CodecType +): boolean { + return !codecs + .split(',') + .some((codec) => !isCodecMediaSourceSupported(codec, type)); +} + +function isCodecMediaSourceSupported(codec: string, type: CodecType): boolean { return ( MediaSource?.isTypeSupported(`${type || 'video'}/mp4;codecs="${codec}"`) ?? false @@ -117,7 +126,7 @@ function getCodecCompatibleNameLower( }[lowerCaseCodec]; for (let i = 0; i < codecsToCheck.length; i++) { - if (isCodecSupportedInMp4(codecsToCheck[i], 'audio')) { + if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio')) { CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i]; return codecsToCheck[i]; }