/** * Replace the old apple-style `avc1.
.
` codec string with the standard * `avc1.` * * @param {string} codec * Codec string to translate * @return {string} * The translated codec string */ export const translateLegacyCodec = function(codec) { if (!codec) { return codec; } return codec.replace(/avc1\.(\d+)\.(\d+)/i, function(orig, profile, avcLevel) { const profileHex = ('00' + Number(profile).toString(16)).slice(-2); const avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2); return 'avc1.' + profileHex + '00' + avcLevelHex; }); }; /** * Replace the old apple-style `avc1.
.
` codec strings with the standard * `avc1.` * * @param {string[]} codecs * An array of codec strings to translate * @return {string[]} * The translated array of codec strings */ export const translateLegacyCodecs = function(codecs) { return codecs.map(translateLegacyCodec); }; /** * Replace codecs in the codec string with the old apple-style `avc1.
.
` to the * standard `avc1.`. * * @param {string} codecString * The codec string * @return {string} * The codec string with old apple-style codecs replaced * * @private */ export const mapLegacyAvcCodecs = function(codecString) { return codecString.replace(/avc1\.(\d+)\.(\d+)/i, (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} [codecs] * The codec string to parse * @return {ParsedCodecInfo} * Parsed codec info */ export const parseCodecs = function(codecs = '') { const result = { codecCount: 0 }; result.codecCount = codecs.split(',').length; result.codecCount = result.codecCount || 2; // parse the video codec const parsed = (/(^|\s|,)+(avc[13])([^ ,]*)/i).exec(codecs); if (parsed) { result.videoCodec = parsed[2]; result.videoObjectTypeIndicator = parsed[3]; } // parse the last field of the audio codec result.audioProfile = (/(^|\s|,)+mp4a.[0-9A-Fa-f]+\.([0-9A-Fa-f]+)/i).exec(codecs); result.audioProfile = result.audioProfile && result.audioProfile[2]; 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 const audioProfileFromDefault = (master, audioGroupId) => { if (!master.mediaGroups.AUDIO || !audioGroupId) { return null; } const audioGroup = master.mediaGroups.AUDIO[audioGroupId]; if (!audioGroup) { return null; } for (const name in audioGroup) { const 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).audioProfile; } } return null; };