268 lines
7.5 KiB
JavaScript
268 lines
7.5 KiB
JavaScript
|
import window from 'global/window';
|
||
|
var regexs = {
|
||
|
// to determine mime types
|
||
|
mp4: /^(av0?1|avc0?[1234]|vp0?9|flac|opus|mp3|mp4a|mp4v|stpp.ttml.im1t)/,
|
||
|
webm: /^(vp0?[89]|av0?1|opus|vorbis)/,
|
||
|
ogg: /^(vp0?[89]|theora|flac|opus|vorbis)/,
|
||
|
// to determine if a codec is audio or video
|
||
|
video: /^(av0?1|avc0?[1234]|vp0?[89]|hvc1|hev1|theora|mp4v)/,
|
||
|
audio: /^(mp4a|flac|vorbis|opus|ac-[34]|ec-3|alac|mp3|speex|aac)/,
|
||
|
text: /^(stpp.ttml.im1t)/,
|
||
|
// mux.js support regex
|
||
|
muxerVideo: /^(avc0?1)/,
|
||
|
muxerAudio: /^(mp4a)/,
|
||
|
// match nothing as muxer does not support text right now.
|
||
|
// there cannot never be a character before the start of a string
|
||
|
// so this matches nothing.
|
||
|
muxerText: /a^/
|
||
|
};
|
||
|
var mediaTypes = ['video', 'audio', 'text'];
|
||
|
var upperMediaTypes = ['Video', 'Audio', 'Text'];
|
||
|
/**
|
||
|
* Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard
|
||
|
* `avc1.<hhhhhh>`
|
||
|
*
|
||
|
* @param {string} codec
|
||
|
* Codec string to translate
|
||
|
* @return {string}
|
||
|
* The translated codec string
|
||
|
*/
|
||
|
|
||
|
export var translateLegacyCodec = function translateLegacyCodec(codec) {
|
||
|
if (!codec) {
|
||
|
return codec;
|
||
|
}
|
||
|
|
||
|
return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) {
|
||
|
var profileHex = ('00' + Number(profile).toString(16)).slice(-2);
|
||
|
var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2);
|
||
|
return 'avc1.' + profileHex + '00' + avcLevelHex;
|
||
|
});
|
||
|
};
|
||
|
/**
|
||
|
* Replace the old apple-style `avc1.<dd>.<dd>` codec strings with the standard
|
||
|
* `avc1.<hhhhhh>`
|
||
|
*
|
||
|
* @param {string[]} codecs
|
||
|
* An array of codec strings to translate
|
||
|
* @return {string[]}
|
||
|
* The translated array of codec strings
|
||
|
*/
|
||
|
|
||
|
export var translateLegacyCodecs = function translateLegacyCodecs(codecs) {
|
||
|
return codecs.map(translateLegacyCodec);
|
||
|
};
|
||
|
/**
|
||
|
* Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the
|
||
|
* standard `avc1.<hhhhhh>`.
|
||
|
*
|
||
|
* @param {string} codecString
|
||
|
* The codec string
|
||
|
* @return {string}
|
||
|
* The codec string with old apple-style codecs replaced
|
||
|
*
|
||
|
* @private
|
||
|
*/
|
||
|
|
||
|
export var mapLegacyAvcCodecs = function mapLegacyAvcCodecs(codecString) {
|
||
|
return codecString.replace(/avc1\.(\d+)\.(\d+)/i, function (match) {
|
||
|
return translateLegacyCodecs([match])[0];
|
||
|
});
|
||
|
};
|
||
|
/**
|
||
|
* @typedef {Object} ParsedCodecInfo
|
||
|
* @property {number} codecCount
|
||
|
* Number of codecs parsed
|
||
|
* @property {string} [videoCodec]
|
||
|
* Parsed video codec (if found)
|
||
|
* @property {string} [videoObjectTypeIndicator]
|
||
|
* Video object type indicator (if found)
|
||
|
* @property {string|null} audioProfile
|
||
|
* Audio profile
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Parses a codec string to retrieve the number of codecs specified, the video codec and
|
||
|
* object type indicator, and the audio profile.
|
||
|
*
|
||
|
* @param {string} [codecString]
|
||
|
* The codec string to parse
|
||
|
* @return {ParsedCodecInfo}
|
||
|
* Parsed codec info
|
||
|
*/
|
||
|
|
||
|
export var parseCodecs = function parseCodecs(codecString) {
|
||
|
if (codecString === void 0) {
|
||
|
codecString = '';
|
||
|
}
|
||
|
|
||
|
var codecs = codecString.split(',');
|
||
|
var result = [];
|
||
|
codecs.forEach(function (codec) {
|
||
|
codec = codec.trim();
|
||
|
var codecType;
|
||
|
mediaTypes.forEach(function (name) {
|
||
|
var match = regexs[name].exec(codec.toLowerCase());
|
||
|
|
||
|
if (!match || match.length <= 1) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
codecType = name; // maintain codec case
|
||
|
|
||
|
var type = codec.substring(0, match[1].length);
|
||
|
var details = codec.replace(type, '');
|
||
|
result.push({
|
||
|
type: type,
|
||
|
details: details,
|
||
|
mediaType: name
|
||
|
});
|
||
|
});
|
||
|
|
||
|
if (!codecType) {
|
||
|
result.push({
|
||
|
type: codec,
|
||
|
details: '',
|
||
|
mediaType: 'unknown'
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
return result;
|
||
|
};
|
||
|
/**
|
||
|
* Returns a ParsedCodecInfo object for the default alternate audio playlist if there is
|
||
|
* a default alternate audio playlist for the provided audio group.
|
||
|
*
|
||
|
* @param {Object} master
|
||
|
* The master playlist
|
||
|
* @param {string} audioGroupId
|
||
|
* ID of the audio group for which to find the default codec info
|
||
|
* @return {ParsedCodecInfo}
|
||
|
* Parsed codec info
|
||
|
*/
|
||
|
|
||
|
export var codecsFromDefault = function codecsFromDefault(master, audioGroupId) {
|
||
|
if (!master.mediaGroups.AUDIO || !audioGroupId) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
var audioGroup = master.mediaGroups.AUDIO[audioGroupId];
|
||
|
|
||
|
if (!audioGroup) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
for (var name in audioGroup) {
|
||
|
var audioType = audioGroup[name];
|
||
|
|
||
|
if (audioType.default && audioType.playlists) {
|
||
|
// codec should be the same for all playlists within the audio type
|
||
|
return parseCodecs(audioType.playlists[0].attributes.CODECS);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
};
|
||
|
export var isVideoCodec = function isVideoCodec(codec) {
|
||
|
if (codec === void 0) {
|
||
|
codec = '';
|
||
|
}
|
||
|
|
||
|
return regexs.video.test(codec.trim().toLowerCase());
|
||
|
};
|
||
|
export var isAudioCodec = function isAudioCodec(codec) {
|
||
|
if (codec === void 0) {
|
||
|
codec = '';
|
||
|
}
|
||
|
|
||
|
return regexs.audio.test(codec.trim().toLowerCase());
|
||
|
};
|
||
|
export var isTextCodec = function isTextCodec(codec) {
|
||
|
if (codec === void 0) {
|
||
|
codec = '';
|
||
|
}
|
||
|
|
||
|
return regexs.text.test(codec.trim().toLowerCase());
|
||
|
};
|
||
|
export var getMimeForCodec = function getMimeForCodec(codecString) {
|
||
|
if (!codecString || typeof codecString !== 'string') {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var codecs = codecString.toLowerCase().split(',').map(function (c) {
|
||
|
return translateLegacyCodec(c.trim());
|
||
|
}); // default to video type
|
||
|
|
||
|
var type = 'video'; // only change to audio type if the only codec we have is
|
||
|
// audio
|
||
|
|
||
|
if (codecs.length === 1 && isAudioCodec(codecs[0])) {
|
||
|
type = 'audio';
|
||
|
} else if (codecs.length === 1 && isTextCodec(codecs[0])) {
|
||
|
// text uses application/<container> for now
|
||
|
type = 'application';
|
||
|
} // default the container to mp4
|
||
|
|
||
|
|
||
|
var container = 'mp4'; // every codec must be able to go into the container
|
||
|
// for that container to be the correct one
|
||
|
|
||
|
if (codecs.every(function (c) {
|
||
|
return regexs.mp4.test(c);
|
||
|
})) {
|
||
|
container = 'mp4';
|
||
|
} else if (codecs.every(function (c) {
|
||
|
return regexs.webm.test(c);
|
||
|
})) {
|
||
|
container = 'webm';
|
||
|
} else if (codecs.every(function (c) {
|
||
|
return regexs.ogg.test(c);
|
||
|
})) {
|
||
|
container = 'ogg';
|
||
|
}
|
||
|
|
||
|
return type + "/" + container + ";codecs=\"" + codecString + "\"";
|
||
|
};
|
||
|
/**
|
||
|
* Tests whether the codec is supported by MediaSource. Optionally also tests ManagedMediaSource.
|
||
|
*
|
||
|
* @param {string} codecString
|
||
|
* Codec to test
|
||
|
* @param {boolean} [withMMS]
|
||
|
* Whether to check if ManagedMediaSource supports it
|
||
|
* @return {boolean}
|
||
|
* Codec is supported
|
||
|
*/
|
||
|
|
||
|
export var browserSupportsCodec = function browserSupportsCodec(codecString, withMMS) {
|
||
|
if (codecString === void 0) {
|
||
|
codecString = '';
|
||
|
}
|
||
|
|
||
|
if (withMMS === void 0) {
|
||
|
withMMS = false;
|
||
|
}
|
||
|
|
||
|
return window.MediaSource && window.MediaSource.isTypeSupported && window.MediaSource.isTypeSupported(getMimeForCodec(codecString)) || withMMS && window.ManagedMediaSource && window.ManagedMediaSource.isTypeSupported && window.ManagedMediaSource.isTypeSupported(getMimeForCodec(codecString)) || false;
|
||
|
};
|
||
|
export var muxerSupportsCodec = function muxerSupportsCodec(codecString) {
|
||
|
if (codecString === void 0) {
|
||
|
codecString = '';
|
||
|
}
|
||
|
|
||
|
return codecString.toLowerCase().split(',').every(function (codec) {
|
||
|
codec = codec.trim(); // any match is supported.
|
||
|
|
||
|
for (var i = 0; i < upperMediaTypes.length; i++) {
|
||
|
var type = upperMediaTypes[i];
|
||
|
|
||
|
if (regexs["muxer" + type].test(codec)) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
});
|
||
|
};
|
||
|
export var DEFAULT_AUDIO_CODEC = 'mp4a.40.2';
|
||
|
export var DEFAULT_VIDEO_CODEC = 'avc1.4d400d';
|