Skip to content

Commit

Permalink
fix: audio only media group playlists, audio group playlists, and aud…
Browse files Browse the repository at this point in the history
…io switches for audio only (#1100)
  • Loading branch information
brandonocasey authored Apr 6, 2021
1 parent 9b116ce commit 6d83de3
Show file tree
Hide file tree
Showing 11 changed files with 1,711 additions and 1,065 deletions.
12 changes: 12 additions & 0 deletions scripts/sources.json
Original file line number Diff line number Diff line change
Expand Up @@ -357,5 +357,17 @@
"uri": "https://d2zihajmogu5jn.cloudfront.net/pdt-test-source/endlist.m3u8",
"mimetype": "application/x-mpegurl",
"features": []
},
{
"name": "audio only dash, two groups",
"uri": "https://d2zihajmogu5jn.cloudfront.net/audio-only-dash/dash.mpd",
"mimetype": "application/dash+xml",
"features": []
},
{
"name": "video only dash, two renditions",
"uri": "https://d2zihajmogu5jn.cloudfront.net/video-only-dash/dash.mpd",
"mimetype": "application/dash+xml",
"features": []
}
]
59 changes: 59 additions & 0 deletions src/master-playlist-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,65 @@ export class MasterPlaylistController extends videojs.EventTarget {
this.abrTimer_ = null;
}

/**
* Get a list of playlists for the currently selected audio playlist
*
* @return {Array} the array of audio playlists
*/
getAudioTrackPlaylists_() {
const master = this.master();

// if we don't have any audio groups then we can only
// assume that the audio tracks are contained in masters
// playlist array, use that or an empty array.
if (!master || !master.mediaGroups || !master.mediaGroups.AUDIO) {
return master && master.playlists || [];
}

const AUDIO = master.mediaGroups.AUDIO;
const groupKeys = Object.keys(AUDIO);
let track;

// get the current active track
if (Object.keys(this.mediaTypes_.AUDIO.groups).length) {
track = this.mediaTypes_.AUDIO.activeTrack();
// or get the default track from master if mediaTypes_ isn't setup yet
} else {
// default group is `main` or just the first group.
const defaultGroup = AUDIO.main || groupKeys.length && AUDIO[groupKeys[0]];

for (const label in defaultGroup) {
if (defaultGroup[label].default) {
track = {label};
break;
}
}
}

// no active track no playlists.
if (!track) {
return [];
}

const playlists = [];

// get all of the playlists that are possible for the
// active track.
for (const group in AUDIO) {
if (AUDIO[group][track.label]) {
const properties = AUDIO[group][track.label];

if (properties.playlists) {
playlists.push.apply(playlists, properties.playlists);
} else {
playlists.push(properties);
}
}
}

return playlists;
}

/**
* Register event handlers on the master playlist loader. A helper
* function for construction time.
Expand Down
128 changes: 93 additions & 35 deletions src/media-groups.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import videojs from 'video.js';
import PlaylistLoader from './playlist-loader';
import DashPlaylistLoader from './dash-playlist-loader';
import noop from './util/noop';
import {isAudioOnly} from './playlist.js';
import logger from './util/logger';

/**
* Convert the properties of an HLS track into an audioTrackKind.
Expand Down Expand Up @@ -78,13 +80,22 @@ export const onGroupChanged = (type, settings) => () => {
mediaTypes: { [type]: mediaType }
} = settings;
const activeTrack = mediaType.activeTrack();
const activeGroup = mediaType.activeGroup(activeTrack);
const activeGroup = mediaType.getActiveGroup();
const previousActiveLoader = mediaType.activePlaylistLoader;
const lastGroup = mediaType.lastGroup_;

// the group did not change do nothing
if (activeGroup && lastGroup && activeGroup.id === lastGroup.id) {
return;
}

mediaType.lastGroup_ = activeGroup;
mediaType.lastTrack_ = activeTrack;

stopLoaders(segmentLoader, mediaType);

if (!activeGroup) {
// there is no group active
if (!activeGroup || activeGroup.isMasterPlaylist) {
// there is no group active or active group is a main playlist and won't change
return;
}

Expand All @@ -109,9 +120,12 @@ export const onGroupChanging = (type, settings) => () => {
const {
segmentLoaders: {
[type]: segmentLoader
}
},
mediaTypes: { [type]: mediaType }
} = settings;

mediaType.lastGroup_ = null;

segmentLoader.abort();
segmentLoader.pause();
};
Expand All @@ -132,15 +146,25 @@ export const onGroupChanging = (type, settings) => () => {
*/
export const onTrackChanged = (type, settings) => () => {
const {
masterPlaylistLoader,
segmentLoaders: {
[type]: segmentLoader,
main: mainSegmentLoader
},
mediaTypes: { [type]: mediaType }
} = settings;
const activeTrack = mediaType.activeTrack();
const activeGroup = mediaType.activeGroup(activeTrack);
const activeGroup = mediaType.getActiveGroup();
const previousActiveLoader = mediaType.activePlaylistLoader;
const lastTrack = mediaType.lastTrack_;

// track did not change, do nothing
if (lastTrack && activeTrack && lastTrack.id === activeTrack.id) {
return;
}

mediaType.lastGroup_ = activeGroup;
mediaType.lastTrack_ = activeTrack;

stopLoaders(segmentLoader, mediaType);

Expand All @@ -149,6 +173,28 @@ export const onTrackChanged = (type, settings) => () => {
return;
}

if (activeGroup.isMasterPlaylist) {
// track did not change, do nothing
if (!activeTrack || !lastTrack || activeTrack.id === lastTrack.id) {
return;
}

const mpc = settings.vhs.masterPlaylistController_;
const newPlaylist = mpc.selectPlaylist();

// media will not change do nothing
if (mpc.media() === newPlaylist) {
return;
}

mediaType.logger_(`track change. Switching master audio from ${lastTrack.id} to ${activeTrack.id}`);
masterPlaylistLoader.pause();
mainSegmentLoader.resetEverything();
mpc.fastQualityChange_(newPlaylist);

return;
}

if (type === 'AUDIO') {
if (!activeGroup.playlistLoader) {
// when switching from demuxed audio/video to muxed audio/video (noted by no
Expand Down Expand Up @@ -375,16 +421,19 @@ export const initialize = {
sourceType,
segmentLoaders: { [type]: segmentLoader },
requestOptions,
master: { mediaGroups, playlists },
master: {mediaGroups},
mediaTypes: {
[type]: {
groups,
tracks
tracks,
logger_
}
},
masterPlaylistLoader
} = settings;

const audioOnlyMaster = isAudioOnly(masterPlaylistLoader.master);

// force a default if we have none
if (!mediaGroups[type] ||
Object.keys(mediaGroups[type]).length === 0) {
Expand All @@ -395,36 +444,19 @@ export const initialize = {
if (!groups[groupId]) {
groups[groupId] = [];
}

// List of playlists that have an AUDIO attribute value matching the current
// group ID
const groupPlaylists = playlists.filter(playlist => {
return playlist.attributes[type] === groupId;
});

for (const variantLabel in mediaGroups[type][groupId]) {
let properties = mediaGroups[type][groupId][variantLabel];

// List of playlists for the current group ID that do not have a matching uri
// with this alternate audio variant
const unmatchingPlaylists = groupPlaylists.filter(playlist => {
return playlist.resolvedUri !== properties.resolvedUri;
});

// If there are no playlists using this audio group other than ones
// that match it's uri, then the playlist is audio only. We delete the resolvedUri
// property here to prevent a playlist loader from being created so that we don't have
// both the main and audio segment loaders loading the same audio segments
// from the same playlist.
if (!unmatchingPlaylists.length && groupPlaylists.length) {
delete properties.resolvedUri;
}

let playlistLoader;

// if vhs-json was provided as the source, and the media playlist was resolved,
// use the resolved media playlist object
if (sourceType === 'vhs-json' && properties.playlists) {
if (audioOnlyMaster) {
logger_(`AUDIO group '${groupId}' label '${variantLabel}' is a master playlist`);
properties.isMasterPlaylist = true;
playlistLoader = null;

// if vhs-json was provided as the source, and the media playlist was resolved,
// use the resolved media playlist object
} else if (sourceType === 'vhs-json' && properties.playlists) {
playlistLoader = new PlaylistLoader(
properties.playlists[0],
vhs,
Expand Down Expand Up @@ -658,17 +690,28 @@ export const activeGroup = (type, settings) => (track) => {

let variants = null;

// set to variants to main media active group
if (media.attributes[type]) {
variants = groups[media.attributes[type]];
}

variants = variants || groups.main;
const groupKeys = Object.keys(groups);

if (!variants) {
// use the main group if it exists
if (groups.main) {
variants = groups.main;
// only one group, use that one
} else if (groupKeys.length === 1) {
variants = groups[groupKeys[0]];
}
}

if (typeof track === 'undefined') {
return variants;
}

if (track === null) {
if (track === null || !variants) {
// An active track was specified so a corresponding group is expected. track === null
// means no track is currently active so there is no corresponding group
return null;
Expand Down Expand Up @@ -726,6 +769,16 @@ export const activeTrack = {
}
};

export const getActiveGroup = (type, {mediaTypes}) => () => {
const activeTrack_ = mediaTypes[type].activeTrack();

if (!activeTrack_) {
return null;
}

return mediaTypes[type].activeGroup(activeTrack_);
};

/**
* Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles,
* Closed-Captions) specified in the master manifest.
Expand Down Expand Up @@ -767,6 +820,7 @@ export const setupMediaGroups = (settings) => {
mediaTypes[type].onGroupChanged = onGroupChanged(type, settings);
mediaTypes[type].onGroupChanging = onGroupChanging(type, settings);
mediaTypes[type].onTrackChanged = onTrackChanged(type, settings);
mediaTypes[type].getActiveGroup = getActiveGroup(type, settings);
});

// DO NOT enable the default subtitle or caption track.
Expand All @@ -777,6 +831,7 @@ export const setupMediaGroups = (settings) => {
const groupId = (audioGroup.filter(group => group.default)[0] || audioGroup[0]).id;

mediaTypes.AUDIO.tracks[groupId].enabled = true;
mediaTypes.AUDIO.onGroupChanged();
mediaTypes.AUDIO.onTrackChanged();
}

Expand Down Expand Up @@ -835,8 +890,11 @@ export const createMediaTypes = () => {
activePlaylistLoader: null,
activeGroup: noop,
activeTrack: noop,
getActiveGroup: noop,
onGroupChanged: noop,
onTrackChanged: noop
onTrackChanged: noop,
lastTrack_: null,
logger_: logger(`MediaGroups[${type}]`)
};
});

Expand Down
29 changes: 22 additions & 7 deletions src/playlist-selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ export const comparePlaylistResolution = function(left, right) {
* Current height of the player element (should account for the device pixel ratio)
* @param {boolean} limitRenditionByPlayerDimensions
* True if the player width and height should be used during the selection, false otherwise
* @param {Object} masterPlaylistController
* the current masterPlaylistController object
* @return {Playlist} the highest bitrate playlist less than the
* currently detected bandwidth, accounting for some amount of
* bandwidth variance
Expand All @@ -152,7 +154,8 @@ export const simpleSelector = function(
playerBandwidth,
playerWidth,
playerHeight,
limitRenditionByPlayerDimensions
limitRenditionByPlayerDimensions,
masterPlaylistController
) {

// If we end up getting called before `master` is available, exit early
Expand All @@ -166,13 +169,23 @@ export const simpleSelector = function(
height: playerHeight,
limitRenditionByPlayerDimensions
};

let playlists = master.playlists;

// if playlist is audio only, select between currently active audio group playlists.
if (Playlist.isAudioOnly(master)) {
playlists = masterPlaylistController.getAudioTrackPlaylists_();
// add audioOnly to options so that we log audioOnly: true
// at the buttom of this function for debugging.
options.audioOnly = true;
}
// convert the playlists to an intermediary representation to make comparisons easier
let sortedPlaylistReps = master.playlists.map((playlist) => {
let sortedPlaylistReps = playlists.map((playlist) => {
let bandwidth;
const width = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width;
const height = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height;
const width = playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width;
const height = playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height;

bandwidth = playlist.attributes.BANDWIDTH;
bandwidth = playlist.attributes && playlist.attributes.BANDWIDTH;

bandwidth = bandwidth || window.Number.MAX_VALUE;

Expand Down Expand Up @@ -320,7 +333,8 @@ export const lastBandwidthSelector = function() {
this.systemBandwidth,
parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio,
parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio,
this.limitRenditionByPlayerDimensions
this.limitRenditionByPlayerDimensions,
this.masterPlaylistController_
);
};

Expand Down Expand Up @@ -358,7 +372,8 @@ export const movingAverageBandwidthSelector = function(decay) {
average,
parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio,
parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio,
this.limitRenditionByPlayerDimensions
this.limitRenditionByPlayerDimensions,
this.masterPlaylistController_
);
};
};
Expand Down
Loading

0 comments on commit 6d83de3

Please sign in to comment.