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

Fix filtering of "mp4a.40.34" variants in Safari #6130

Merged
merged 1 commit into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 5 additions & 16 deletions src/controller/level-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
codecsSetSelectionPreferenceValue,
convertAVC1ToAVCOTI,
getCodecCompatibleName,
getM2TSSupportedAudioTypes,

Check warning on line 19 in src/controller/level-controller.ts

View workflow job for this annotation

GitHub Actions / build

'getM2TSSupportedAudioTypes' is defined but never used
videoCodecPreferenceValue,
} from '../utils/codecs';
import BasePlaylistController from './base-playlist-controller';
Expand All @@ -27,8 +28,6 @@
import type { HlsUrlParameters, LevelParsed } from '../types/level';
import type { MediaPlaylist } from '../types/media-playlist';

let chromeOrFirefox: boolean;

export default class LevelController extends BasePlaylistController {
private _levels: Level[] = [];
private _firstLevel: number = -1;
Expand Down Expand Up @@ -119,22 +118,12 @@

data.levels.forEach((levelParsed: LevelParsed) => {
const attributes = levelParsed.attrs;

// erase audio codec info if browser does not support mp4a.40.34.
// demuxer will autodetect codec and fallback to mpeg/audio
let { audioCodec, videoCodec } = levelParsed;
if (audioCodec?.indexOf('mp4a.40.34') !== -1) {
chromeOrFirefox ||= /chrome|firefox/i.test(navigator.userAgent);
if (chromeOrFirefox) {
levelParsed.audioCodec = audioCodec = undefined;
}
}

if (audioCodec) {
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(
audioCodec,
preferManagedMediaSource,
);
// Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
levelParsed.audioCodec = audioCodec =
getCodecCompatibleName(audioCodec, preferManagedMediaSource) ||
undefined;
}

if (videoCodec?.indexOf('avc1') === 0) {
Expand Down
16 changes: 4 additions & 12 deletions src/demux/transmuxer-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@ import Transmuxer, {
} from '../demux/transmuxer';
import { logger } from '../utils/logger';
import { ErrorTypes, ErrorDetails } from '../errors';
import { getMediaSource } from '../utils/mediasource-helper';
import { EventEmitter } from 'eventemitter3';
import { Fragment, Part } from '../loader/fragment';
import { getM2TSSupportedAudioTypes } from '../utils/codecs';
import type { ChunkMetadata, TransmuxerResult } from '../types/transmuxer';
import type Hls from '../hls';
import type { HlsEventEmitter } from '../events';
import type { PlaylistLevelType } from '../types/loader';
import type { TypeSupported } from './tsdemuxer';
import type { RationalTimestamp } from '../utils/timescale-conversion';

export default class TransmuxerInterface {
Expand Down Expand Up @@ -64,16 +63,9 @@ export default class TransmuxerInterface {
this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
this.observer.on(Events.ERROR, forwardMessage);

const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
isTypeSupported: () => false,
};
const m2tsTypeSupported: TypeSupported = {
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
ac3: __USE_M2TS_ADVANCED_CODECS__
? MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
: false,
};
const m2tsTypeSupported = getM2TSSupportedAudioTypes(
config.preferManagedMediaSource,
);

// navigator.vendor is not always available in Web Worker
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
Expand Down
3 changes: 2 additions & 1 deletion src/demux/transmuxer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ErrorTypes, ErrorDetails } from '../errors';
import Decrypter from '../crypt/decrypter';
import AACDemuxer from './audio/aacdemuxer';
import MP4Demuxer from '../demux/mp4demuxer';
import TSDemuxer, { TypeSupported } from '../demux/tsdemuxer';
import TSDemuxer from '../demux/tsdemuxer';
import MP3Demuxer from './audio/mp3demuxer';
import { AC3Demuxer } from './audio/ac3-demuxer';
import MP4Remuxer from '../remux/mp4-remuxer';
Expand All @@ -16,6 +16,7 @@ import type { TransmuxerResult, ChunkMetadata } from '../types/transmuxer';
import type { HlsConfig } from '../config';
import type { DecryptData } from '../loader/level-key';
import type { PlaylistLevelType } from '../types/loader';
import type { TypeSupported } from '../utils/codecs';
import type { RationalTimestamp } from '../utils/timescale-conversion';
import { optionalSelf } from '../utils/global';

Expand Down
29 changes: 12 additions & 17 deletions src/demux/tsdemuxer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,21 @@ import { logger } from '../utils/logger';
import { ErrorTypes, ErrorDetails } from '../errors';
import type { HlsConfig } from '../config';
import type { HlsEventEmitter } from '../events';
import type { TypeSupported } from '../utils/codecs';
import {
DemuxedVideoTrack,
DemuxedAudioTrack,
DemuxedTrack,
Demuxer,
DemuxerResult,
VideoSample,
DemuxedMetadataTrack,
DemuxedUserdataTrack,
ElementaryStreamData,
KeyData,
MetadataSchema,
type DemuxedVideoTrack,
type DemuxedAudioTrack,
type DemuxedTrack,
type Demuxer,
type DemuxerResult,
type VideoSample,
type DemuxedMetadataTrack,
type DemuxedUserdataTrack,
type ElementaryStreamData,
type KeyData,
} from '../types/demuxer';
import { AudioFrame } from '../types/demuxer';
import type { AudioFrame } from '../types/demuxer';

export type ParsedTimestamp = {
pts?: number;
Expand All @@ -48,12 +49,6 @@ export type PES = ParsedTimestamp & {
export type ParsedVideoSample = ParsedTimestamp &
Omit<VideoSample, 'pts' | 'dts'>;

export interface TypeSupported {
mpeg: boolean;
mp3: boolean;
ac3: boolean;
}

const PACKET_LENGTH = 188;

class TSDemuxer implements Demuxer {
Expand Down
3 changes: 2 additions & 1 deletion src/remux/mp4-remuxer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type { TrackSet } from '../types/track';
import type { SourceBufferName } from '../types/buffer';
import type { Fragment } from '../loader/fragment';
import type { HlsConfig } from '../config';
import type { TypeSupported } from '../utils/codecs';

const MAX_SILENT_FRAME_DURATION = 10 * 1000; // 10 seconds
const AAC_SAMPLES_PER_FRAME = 1024;
Expand All @@ -41,7 +42,7 @@ let safariWebkitVersion: number | null = null;
export default class MP4Remuxer implements Remuxer {
private observer: HlsEventEmitter;
private config: HlsConfig;
private typeSupported: any;
private typeSupported: TypeSupported;
private ISGenerated: boolean = false;
private _initPTS: RationalTimestamp | null = null;
private _initDTS: RationalTimestamp | null = null;
Expand Down
37 changes: 33 additions & 4 deletions src/utils/codecs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,15 @@ function getCodecCompatibleNameLower(
return CODEC_COMPATIBLE_NAMES[lowerCaseCodec]!;
}

// Idealy fLaC and Opus would be first (spec-compliant) but
// some browsers will report that fLaC is supported then fail.
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
const codecsToCheck = {
// Idealy fLaC and Opus would be first (spec-compliant) but
// some browsers will report that fLaC is supported then fail.
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
flac: ['flac', 'fLaC', 'FLAC'],
opus: ['opus', 'Opus'],
// Replace audio codec info if browser does not support mp4a.40.34,
// and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
'mp4a.40.34': ['mp3'],
}[lowerCaseCodec];

for (let i = 0; i < codecsToCheck.length; i++) {
Expand All @@ -165,13 +168,18 @@ function getCodecCompatibleNameLower(
) {
CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
return codecsToCheck[i];
} else if (
codecsToCheck[i] === 'mp3' &&
getMediaSource(preferManagedMediaSource)?.isTypeSupported('audio/mpeg')
) {
return '';
}
}

return lowerCaseCodec;
}

const AUDIO_CODEC_REGEXP = /flac|opus/i;
const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
export function getCodecCompatibleName(
codec: string,
preferManagedMediaSource = true,
Expand Down Expand Up @@ -209,3 +217,24 @@ export function convertAVC1ToAVCOTI(codec: string) {
}
return codec;
}

export interface TypeSupported {
mpeg: boolean;
mp3: boolean;
ac3: boolean;
}

export function getM2TSSupportedAudioTypes(
preferManagedMediaSource: boolean,
): TypeSupported {
const MediaSource = getMediaSource(preferManagedMediaSource) || {
isTypeSupported: () => false,
};
return {
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
ac3: __USE_M2TS_ADVANCED_CODECS__
? MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
: false,
};
}
Loading