Skip to content

Commit 1f92865

Browse files
committed
feat(codec): changes to handle muxer/browser/video/audio support separately (#10)
BREAKING CHANGE: parseCodecs output has been changed. It now returns an object that can have an audio or video property, depending on the codecs found. Those properties are object that contain type. and details. Type being the codec name and details being codec specific information usually with a leading period. BREAKING CHANGE: `audioProfileFromDefault` has been renamed to `codecsFromDefault` and now returns all output from `parseCodecs` not just audio or audio profile.
1 parent b32e35b commit 1f92865

File tree

2 files changed

+264
-40
lines changed

2 files changed

+264
-40
lines changed

src/codecs.js

Lines changed: 87 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
import window from 'global/window';
2+
3+
const regexs = {
4+
// to determine mime types
5+
mp4: /^(av0?1|avc0?[1234]|vp0?9|flac|opus|mp3|mp4a|mp4v)/,
6+
webm: /^(vp0?[89]|av0?1|opus|vorbis)/,
7+
ogg: /^(vp0?[89]|theora|flac|opus|vorbis)/,
8+
9+
// to determine if a codec is audio or video
10+
video: /^(av0?1|avc0?[1234]|vp0?[89]|hvc1|hev1|theora|mp4v)/,
11+
audio: /^(mp4a|flac|vorbis|opus|ac-[34]|ec-3|alac|mp3)/,
12+
13+
// mux.js support regex
14+
muxerVideo: /^(avc0?1)/,
15+
muxerAudio: /^(mp4a)/
16+
};
17+
118
/**
219
* Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard
320
* `avc1.<hhhhhh>`
@@ -66,31 +83,32 @@ export const mapLegacyAvcCodecs = function(codecString) {
6683
* Parses a codec string to retrieve the number of codecs specified, the video codec and
6784
* object type indicator, and the audio profile.
6885
*
69-
* @param {string} [codecs]
86+
* @param {string} [codecString]
7087
* The codec string to parse
7188
* @return {ParsedCodecInfo}
7289
* Parsed codec info
7390
*/
74-
export const parseCodecs = function(codecs = '') {
75-
const result = {
76-
codecCount: 0
77-
};
91+
export const parseCodecs = function(codecString = '') {
92+
const codecs = codecString.split(',');
93+
const result = {};
7894

79-
result.codecCount = codecs.split(',').length;
80-
result.codecCount = result.codecCount || 2;
95+
codecs.forEach(function(codec) {
96+
codec = codec.trim();
8197

82-
// parse the video codec
83-
const parsed = (/(^|\s|,)+(avc[13]|vp09|av01)([^ ,]*)/i).exec(codecs);
98+
['video', 'audio'].forEach(function(name) {
99+
const match = regexs[name].exec(codec.toLowerCase());
84100

85-
if (parsed) {
86-
result.videoCodec = parsed[2];
87-
result.videoObjectTypeIndicator = parsed[3];
88-
}
101+
if (!match || match.length <= 1) {
102+
return;
103+
}
104+
105+
// maintain codec case
106+
const type = codec.substring(0, match[1].length);
107+
const details = codec.replace(type, '');
89108

90-
// parse the last field of the audio codec
91-
result.audioProfile =
92-
(/(^|\s|,)+mp4a.[0-9A-Fa-f]+\.([0-9A-Fa-f]+)/i).exec(codecs);
93-
result.audioProfile = result.audioProfile && result.audioProfile[2];
109+
result[name] = {type, details};
110+
});
111+
});
94112

95113
return result;
96114
};
@@ -106,7 +124,7 @@ export const parseCodecs = function(codecs = '') {
106124
* @return {ParsedCodecInfo}
107125
* Parsed codec info
108126
*/
109-
export const audioProfileFromDefault = (master, audioGroupId) => {
127+
export const codecsFromDefault = (master, audioGroupId) => {
110128
if (!master.mediaGroups.AUDIO || !audioGroupId) {
111129
return null;
112130
}
@@ -122,9 +140,59 @@ export const audioProfileFromDefault = (master, audioGroupId) => {
122140

123141
if (audioType.default && audioType.playlists) {
124142
// codec should be the same for all playlists within the audio type
125-
return parseCodecs(audioType.playlists[0].attributes.CODECS).audioProfile;
143+
return parseCodecs(audioType.playlists[0].attributes.CODECS);
126144
}
127145
}
128146

129147
return null;
130148
};
149+
150+
export const isVideoCodec = (codec = '') => regexs.video.test(codec.trim().toLowerCase());
151+
export const isAudioCodec = (codec = '') => regexs.audio.test(codec.trim().toLowerCase());
152+
153+
export const getMimeForCodec = (codecString) => {
154+
if (!codecString || typeof codecString !== 'string') {
155+
return;
156+
}
157+
const codecs = codecString
158+
.toLowerCase()
159+
.split(',')
160+
.map((c) => translateLegacyCodec(c.trim()));
161+
162+
// default to video type
163+
let type = 'video';
164+
165+
// only change to audio type if the only codec we have is
166+
// audio
167+
if (codecs.length === 1 && isAudioCodec(codecs[0])) {
168+
type = 'audio';
169+
}
170+
171+
// default the container to mp4
172+
let container = 'mp4';
173+
174+
// every codec must be able to go into the container
175+
// for that container to be the correct one
176+
if (codecs.every((c) => regexs.mp4.test(c))) {
177+
container = 'mp4';
178+
} else if (codecs.every((c) => regexs.webm.test(c))) {
179+
container = 'webm';
180+
} else if (codecs.every((c) => regexs.ogg.test(c))) {
181+
container = 'ogg';
182+
}
183+
184+
return `${type}/${container};codecs="${codecString}"`;
185+
};
186+
187+
export const browserSupportsCodec = (codecString = '') => window.MediaSource &&
188+
window.MediaSource.isTypeSupported &&
189+
window.MediaSource.isTypeSupported(getMimeForCodec(codecString)) || false;
190+
191+
export const muxerSupportsCodec = (codecString = '') => codecString.toLowerCase().split(',').every((codec) => {
192+
codec = codec.trim();
193+
194+
return regexs.muxerVideo.test(codec) || regexs.muxerAudio.test(codec);
195+
});
196+
197+
export const DEFAULT_AUDIO_CODEC = 'mp4a.40.2';
198+
export const DEFAULT_VIDEO_CODEC = 'avc1.4d400d';

0 commit comments

Comments
 (0)