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

Add FLAC support #4772

Merged
merged 5 commits into from
May 31, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
14 changes: 12 additions & 2 deletions src/controller/buffer-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Events } from '../events';
import { logger } from '../utils/logger';
import { ErrorDetails, ErrorTypes } from '../errors';
import { BufferHelper } from '../utils/buffer-helper';
import { getCodecCompatibleName } from '../utils/codecs';
import { getMediaSource } from '../utils/mediasource-helper';
import { ElementaryStreamTypes } from '../loader/fragment';
import type { TrackSet } from '../types/track';
Expand Down Expand Up @@ -262,7 +263,11 @@ export default class BufferController implements ComponentAPI {
'$1'
);
if (currentCodec !== nextCodec) {
const mimeType = `${container};codecs=${levelCodec || codec}`;
let trackCodec = levelCodec || codec;
if (trackName.slice(0, 5) === 'audio') {
trackCodec = getCodecCompatibleName(trackCodec);
}
const mimeType = `${container};codecs=${trackCodec}`;
this.appendChangeType(trackName, mimeType);
logger.log(
`[buffer-controller]: switching codec ${currentCodec} to ${nextCodec}`
Expand Down Expand Up @@ -747,7 +752,12 @@ export default class BufferController implements ComponentAPI {
);
}
// use levelCodec as first priority
const codec = track.levelCodec || track.codec;
let codec = track.levelCodec || track.codec;
if (codec) {
if (trackName.slice(0, 5) === 'audio') {
codec = getCodecCompatibleName(codec);
}
}
const mimeType = `${track.container};codecs=${codec}`;
logger.log(`[buffer-controller]: creating sourceBuffer(${mimeType})`);
try {
Expand Down
6 changes: 5 additions & 1 deletion src/controller/level-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import { HdcpLevel, HdcpLevels, Level } from '../types/level';
import { Events } from '../events';
import { ErrorTypes, ErrorDetails } from '../errors';
import { isCodecSupportedInMp4 } from '../utils/codecs';
import { isCodecSupportedInMp4, getCodecCompatibleName } from '../utils/codecs';
import BasePlaylistController from './base-playlist-controller';
import { PlaylistContextType, PlaylistLevelType } from '../types/loader';
import type Hls from '../hls';
Expand Down Expand Up @@ -122,6 +122,10 @@ export default class LevelController extends BasePlaylistController {
}
}

if (levelParsed.audioCodec) {
levelParsed.audioCodec = getCodecCompatibleName(levelParsed.audioCodec);
}

const {
AUDIO,
CODECS,
Expand Down
7 changes: 6 additions & 1 deletion src/controller/stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1227,7 +1227,12 @@ export default class StreamController
}
}
// HE-AAC is broken on Android, always signal audio codec as AAC even if variant manifest states otherwise
if (ua.indexOf('android') !== -1 && audio.container !== 'audio/mpeg') {
if (
audioCodec &&
audioCodec.indexOf('mp4a.40.5') !== -1 &&
ua.indexOf('android') !== -1 &&
audio.container !== 'audio/mpeg'
) {
Comment on lines +1230 to +1235
Copy link
Collaborator

Choose a reason for hiding this comment

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

We still need this MSE workaround don't we? Is there a better way to address this? (doesn't need to be in this PR, just curious, and would like to gather feedback and opinions here)

// Exclude mpeg audio
audioCodec = 'mp4a.40.2';
this.log(`Android: force audio codec to ${audioCodec}`);
Expand Down
6 changes: 6 additions & 0 deletions src/remux/passthrough-remuxer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,12 @@ function getParsedTrackCodec(
if (parsedCodec === 'avc1' || type === ElementaryStreamTypes.VIDEO) {
return 'avc1.42e01e';
}
if (parsedCodec === 'fLaC') {
return 'fLaC';
}
if (parsedCodec === 'Opus') {
return 'Opus';
}
return 'mp4a.40.5';
}
export default PassThroughRemuxer;
41 changes: 41 additions & 0 deletions src/utils/codecs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ const sampleEntryCodesISO = {
dtsh: true,
'ec-3': true,
enca: true,
fLaC: true, // MP4-RA listed codec entry for FLAC
flac: true, // legacy browser codec name for FLAC
FLAC: true, // some manifests may list "FLAC" with Apple's tools
g719: true,
g726: true,
m4ae: true,
Expand Down Expand Up @@ -83,3 +86,41 @@ export function isCodecSupportedInMp4(codec: string, type: CodecType): boolean {
`${type || 'video'}/mp4;codecs="${codec}"`
);
}

interface CodecNameCache {
flac?: string;
opus?: string;
}

const CODEC_COMPATIBLE_NAMES: CodecNameCache = {};

type LowerCaseCodecType = 'flac' | 'opus';

function getCodecCompatibleNameLower(
lowerCaseCodec: LowerCaseCodecType
): string {
if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
return CODEC_COMPATIBLE_NAMES[lowerCaseCodec]!;
}

const codecsToCheck = {
flac: ['fLaC', 'flac', 'FLAC'],
opus: ['Opus', 'opus'],
}[lowerCaseCodec];

for (let i = 0; i < codecsToCheck.length; i++) {
if (isCodecSupportedInMp4(codecsToCheck[i], 'audio')) {
CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
return codecsToCheck[i];
}
}

return lowerCaseCodec;
}

const AUDIO_CODEC_REGEXP = /flac|opus/i;
export function getCodecCompatibleName(codec: string): string {
return codec.replace(AUDIO_CODEC_REGEXP, (m) =>
getCodecCompatibleNameLower(m.toLowerCase() as LowerCaseCodecType)
);
}