Skip to content
This repository has been archived by the owner on Jan 12, 2019. It is now read-only.

Commit

Permalink
Fixed codecs to mimetype conversion to take into account all possible…
Browse files Browse the repository at this point in the history
… scenarios
  • Loading branch information
jrivera committed Apr 26, 2017
1 parent f08571c commit 522cf45
Show file tree
Hide file tree
Showing 2 changed files with 209 additions and 160 deletions.
157 changes: 105 additions & 52 deletions src/master-playlist-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ import Decrypter from './decrypter-worker';

let Hls;

// Default codec parameters if non were provided for video and/or audio
const defaultCodecs = {
videoCodec: 'avc1',
videoObjectTypeIndicator: '.4d400d',
audioProfile: '2'
};

// SegmentLoader stats that need to have each loader's
// values summed to calculate the final value
const loaderStats = [
Expand Down Expand Up @@ -64,10 +71,7 @@ const objectChanged = function(a, b) {
*/
const parseCodecs = function(codecs) {
let result = {
codecCount: 0,
videoCodec: null,
videoObjectTypeIndicator: null,
audioProfile: null
codecCount: 0
};
let parsed;

Expand Down Expand Up @@ -104,6 +108,42 @@ export const mapLegacyAvcCodecs_ = function(codecString) {
});
};

/**
* Build a media mime-type string from a set of parameters
* @param {String} type either 'audio' or 'video'
* @param {String} container either 'mp2t' or 'mp4'
* @param {Array} codecs an array of codec strings to add
* @return {String} a valid media mime-type
*/
const makeMimeTypeString = function(type, container, codecs) {
// The codecs array is filtered so that falsey values are
// dropped and don't cause Array#join to create spurious
// commas
return `${type}/${container}; codecs="${codecs.filter(c=>!!c).join(', ')}"`;
};

const getContainerType = function(media) {
// An initialization segment means the media playlists is an iframe
// playlist or is using the mp4 container. We don't currently
// support iframe playlists, so assume this is signalling mp4
// fragments.
if (media.segments && media.segments.length && media.segments[0].map) {
return 'mp4';
}
return 'mp2t';
};

const getCodecs = function(media) {
// if the codecs were explicitly specified, use them instead of the
// defaults
let mediaAttributes = media.attributes || {};

if (mediaAttributes.CODECS) {
return parseCodecs(mediaAttributes.CODECS);
}
return defaultCodecs;
};

/**
* Calculates the MIME type strings for a working configuration of
* SourceBuffers to play variant streams in a master playlist. If
Expand All @@ -119,74 +159,87 @@ export const mapLegacyAvcCodecs_ = function(codecString) {
* @private
*/
export const mimeTypesForPlaylist_ = function(master, media) {
let container = 'mp2t';
let codecs = {
videoCodec: 'avc1',
videoObjectTypeIndicator: '.4d400d',
audioProfile: '2'
};
let audioGroup = [];
let mediaAttributes;
let previousGroup = null;
let containerType = getContainerType(media);
let codecInfo = getCodecs(media);
let mediaAttributes = media.attributes || {};
// Default condition for a traditional HLS (no demuxed audio/video)
let isMuxed = true;
let isMaat = false;

if (!media) {
// not enough information, return an error
// Not enough information, return an error
return [];
}
// An initialization segment means the media playlists is an iframe
// playlist or is using the mp4 container. We don't currently
// support iframe playlists, so assume this is signalling mp4
// fragments.
// the existence check for segments can be removed once
// https://github.com/videojs/m3u8-parser/issues/8 is closed
if (media.segments && media.segments.length && media.segments[0].map) {
container = 'mp4';

if (master.mediaGroups.AUDIO && mediaAttributes.AUDIO) {
let audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO];

// Handle the case where we are in a multiple-audio track scenario
if (audioGroup) {
isMaat = true;
// Start with the everything demuxed then...
isMuxed = false;
// ...check to see if any audio group tracks are muxed (ie. lacking a uri)
for (let groupId in audioGroup) {
if (!audioGroup[groupId].uri) {
isMuxed = true;
break;
}
}
}
}

// if the codecs were explicitly specified, use them instead of the
// defaults
mediaAttributes = media.attributes || {};
if (mediaAttributes.CODECS) {
let parsedCodecs = parseCodecs(mediaAttributes.CODECS);
// HLS with multiple-audio tracks must always get an audio codec.
// Put another way, there is no way to have a video-only multiple-audio HLS!
if (isMaat && !codecInfo.audioProfile) {
codecInfo.audioProfile = defaultCodecs.audioProfile;
}

Object.keys(parsedCodecs).forEach((key) => {
codecs[key] = parsedCodecs[key] || codecs[key];
});
// Generate the final codec strings from the codec object generated above
let codecStrings = {};

if (codecInfo.videoCodec) {
codecStrings.video = `${codecInfo.videoCodec}${codecInfo.videoObjectTypeIndicator}`;
}

if (master.mediaGroups.AUDIO) {
audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO];
if (codecInfo.audioProfile) {
codecStrings.audio = `mp4a.40.${codecInfo.audioProfile}`;
}

// if audio could be muxed or unmuxed, use mime types appropriate
// for both scenarios
for (let groupId in audioGroup) {
if (previousGroup && (!!audioGroup[groupId].uri !== !!previousGroup.uri)) {
// one source buffer with muxed video and audio and another for
// the alternate audio
// Finally, make and return an array with proper mime-types depending on
// the configuration
let justAudio = makeMimeTypeString('audio', containerType, [codecStrings.audio]);
let justVideo = makeMimeTypeString('video', containerType, [codecStrings.video]);
let bothVideoAudio = makeMimeTypeString('video', containerType, [
codecStrings.video,
codecStrings.audio
]);

if (isMaat) {
if (!isMuxed && codecStrings.video) {
return [
'video/' + container + '; codecs="' +
codecs.videoCodec + codecs.videoObjectTypeIndicator + ', mp4a.40.' + codecs.audioProfile + '"',
'audio/' + container + '; codecs="mp4a.40.' + codecs.audioProfile + '"'
justVideo,
justAudio
];
}
previousGroup = audioGroup[groupId];
return [
bothVideoAudio,
justAudio
];
}
// if all video and audio is unmuxed, use two single-codec mime
// types
if (previousGroup && previousGroup.uri) {

// If there is ano video codec at all, always just return a single
// audio/<container> mime-type
if (!codecStrings.video) {
return [
'video/' + container + '; codecs="' +
codecs.videoCodec + codecs.videoObjectTypeIndicator + '"',
'audio/' + container + '; codecs="mp4a.40.' + codecs.audioProfile + '"'
justAudio
];
}

// all video and audio are muxed, use a dual-codec mime type
// When not using separate audio media groups, audio and video is
// *always* muxed
return [
'video/' + container + '; codecs="' +
codecs.videoCodec + codecs.videoObjectTypeIndicator +
', mp4a.40.' + codecs.audioProfile + '"'
bothVideoAudio
];
};

Expand Down
Loading

0 comments on commit 522cf45

Please sign in to comment.