diff --git a/demo/common/asset.js b/demo/common/asset.js
index 7d39f25be3d..8205db79180 100644
--- a/demo/common/asset.js
+++ b/demo/common/asset.js
@@ -77,6 +77,8 @@ const ShakaDemoAssetInfo = class {
this.imaContentSrcId = null;
/** @type {?string} */
this.mimeType = null;
+ /** @type {?string} */
+ this.mediaPlaylistFullMimeType = null;
// Offline storage values.
@@ -161,6 +163,15 @@ const ShakaDemoAssetInfo = class {
return this.drm.length == 1 && this.drm[0] == shakaAssets.KeySystem.CLEAR;
}
+ /**
+ * @param {string} mediaPlaylistFullMimeType
+ * @return {!ShakaDemoAssetInfo}
+ */
+ setMediaPlaylistFullMimeType(mediaPlaylistFullMimeType) {
+ this.mediaPlaylistFullMimeType = mediaPlaylistFullMimeType;
+ return this;
+ }
+
/**
* @param {!Object} extraConfig
* @return {!ShakaDemoAssetInfo}
@@ -368,7 +379,7 @@ const ShakaDemoAssetInfo = class {
*/
getConfiguration() {
const config = /** @type {shaka.extern.PlayerConfiguration} */(
- {drm: {advanced: {}}, manifest: {dash: {}}});
+ {drm: {advanced: {}}, manifest: {dash: {}, hls: {}}});
if (this.extraConfig) {
for (const key in this.extraConfig) {
@@ -376,6 +387,11 @@ const ShakaDemoAssetInfo = class {
}
}
+ if (this.mediaPlaylistFullMimeType) {
+ config.manifest.hls.mediaPlaylistFullMimeType =
+ this.mediaPlaylistFullMimeType;
+ }
+
if (this.licenseServers.size) {
config.drm.servers = config.drm.servers || {};
this.licenseServers.forEach((value, key) => {
diff --git a/demo/common/assets.js b/demo/common/assets.js
index f735306006b..4efefb8c322 100644
--- a/demo/common/assets.js
+++ b/demo/common/assets.js
@@ -294,6 +294,15 @@ shakaAssets.testAssets = [
.addFeature(shakaAssets.Feature.SUBTITLES)
.addFeature(shakaAssets.Feature.SURROUND)
.addFeature(shakaAssets.Feature.OFFLINE),
+ new ShakaDemoAssetInfo(
+ /* name= */ 'Angel One (HLS, MP4, video media playlist only)',
+ /* iconUri= */ 'https://storage.googleapis.com/shaka-asset-icons/angel_one.png',
+ /* manifestUri= */ 'https://storage.googleapis.com/shaka-demo-assets/angel-one-hls/playlist_v-0480p-1000k-libx264.mp4.m3u8',
+ /* source= */ shakaAssets.Source.SHAKA)
+ .addFeature(shakaAssets.Feature.HLS)
+ .addFeature(shakaAssets.Feature.MP4)
+ .addFeature(shakaAssets.Feature.OFFLINE)
+ .setMediaPlaylistFullMimeType('video/mp4; codecs="avc1.4d401f"'),
new ShakaDemoAssetInfo(
/* name= */ 'Angel One (HLS, MP4, multilingual, Widevine)',
/* iconUri= */ 'https://storage.googleapis.com/shaka-asset-icons/angel_one.png',
diff --git a/demo/common/message_ids.js b/demo/common/message_ids.js
index 159eddcca11..f246ba68db7 100644
--- a/demo/common/message_ids.js
+++ b/demo/common/message_ids.js
@@ -107,6 +107,7 @@ shakaDemo.MessageIds = {
DRM_SYSTEM: 'DEMO_DRM_SYSTEM',
DRM_TAB: 'DEMO_DRM_TAB',
EDIT_CUSTOM: 'DEMO_EDIT_CUSTOM',
+ FULL_MIME_TYPE: 'DEMO_FULL_MIME_TYPE',
HEADERS_TAB: 'DEMO_HEADERS_TAB',
ICON_URL: 'DEMO_ICON_URL',
LICENSE_CERTIFICATE_URL: 'DEMO_LICENSE_CERTIFICATE_URL',
@@ -117,6 +118,7 @@ shakaDemo.MessageIds = {
MAIN_TAB: 'DEMO_MAIN_TAB',
MANIFEST_URL: 'DEMO_MANIFEST_URL',
MANIFEST_URL_ERROR: 'DEMO_MANIFEST_URL_ERROR',
+ MEDIA_PLAYLIST_TAB: 'DEMO_MEDIA_PLAYLIST_TAB',
NAME: 'DEMO_NAME',
NAME_ERROR: 'DEMO_NAME_ERROR',
EXTRA_SHAKA_PLAYER_CONFIG: 'DEMO_EXTRA_SHAKA_PLAYER_CONFIG',
diff --git a/demo/custom.js b/demo/custom.js
index 7ce29cb795e..c87482f96a3 100644
--- a/demo/custom.js
+++ b/demo/custom.js
@@ -245,6 +245,38 @@ shakaDemo.Custom = class {
}
+ /**
+ * @param {!ShakaDemoAssetInfo} assetInProgress
+ * @param {!Array.} inputsToCheck
+ * @return {!Element} div
+ * @private
+ */
+ makeAssetDialogContentsMediaPlaylist_(assetInProgress, inputsToCheck) {
+ const mediaPlaylistDiv = document.createElement('div');
+ const containerStyle = shakaDemo.InputContainer.Style.FLEX;
+ const container = new shakaDemo.InputContainer(
+ mediaPlaylistDiv, /* headerText= */ null, containerStyle,
+ /* docLink= */ null);
+ container.getClassList().add('wide-input');
+ container.setDefaultRowClass('wide-input');
+
+ const fullMimeTypeSetup = (input, container) => {
+ if (assetInProgress.mediaPlaylistFullMimeType) {
+ input.value = assetInProgress.mediaPlaylistFullMimeType;
+ }
+ };
+ const fullMimeTypeOnChange = (input) => {
+ assetInProgress.setMediaPlaylistFullMimeType(input.value);
+ };
+ const fullMimeTypeName = shakaDemoMain.getLocalizedString(
+ shakaDemo.MessageIds.FULL_MIME_TYPE);
+ this.makeField_(
+ container, fullMimeTypeName, fullMimeTypeSetup, fullMimeTypeOnChange);
+
+ return mediaPlaylistDiv;
+ }
+
+
/**
* @param {!ShakaDemoAssetInfo} assetInProgress
* @param {!Array.} inputsToCheck
@@ -674,6 +706,8 @@ shakaDemo.Custom = class {
assetInProgress, inputsToCheck);
const adsDiv = this.makeAssetDialogContentsAds_(
assetInProgress, inputsToCheck);
+ const mediaPlaylistDiv = this.makeAssetDialogContentsMediaPlaylist_(
+ assetInProgress, inputsToCheck);
const extraConfigDiv = this.makeAssetDialogContentsExtra_(
assetInProgress, inputsToCheck);
const finishDiv = this.makeAssetDialogContentsFinish_(
@@ -713,6 +747,9 @@ shakaDemo.Custom = class {
shakaDemo.MessageIds.HEADERS_TAB, headersDiv, /* startOn= */ false);
addTabButton(
shakaDemo.MessageIds.ADS_TAB, adsDiv, /* startOn= */ false);
+ addTabButton(
+ shakaDemo.MessageIds.MEDIA_PLAYLIST_TAB, mediaPlaylistDiv,
+ /* startOn= */ false);
addTabButton(
shakaDemo.MessageIds.EXTRA_TAB, extraConfigDiv, /* startOn= */ false);
@@ -722,6 +759,7 @@ shakaDemo.Custom = class {
this.dialog_.appendChild(drmDiv);
this.dialog_.appendChild(headersDiv);
this.dialog_.appendChild(adsDiv);
+ this.dialog_.appendChild(mediaPlaylistDiv);
this.dialog_.appendChild(extraConfigDiv);
this.dialog_.appendChild(finishDiv);
this.dialog_.appendChild(iconDiv);
diff --git a/demo/locales/en.json b/demo/locales/en.json
index b69622cd85a..f01147f44d3 100644
--- a/demo/locales/en.json
+++ b/demo/locales/en.json
@@ -138,6 +138,7 @@
"DEMO_MAX_PIXELS": "Max Pixels",
"DEMO_MAX_SMALL_GAP_SIZE": "Maximum Small Gap Size",
"DEMO_MAX_WIDTH": "Max Width",
+ "DEMO_MEDIA_PLAYLIST_TAB": "HLS Media Playlist",
"DEMO_METACDN": "MetaCDN",
"DEMO_MICROSOFT": "Microsoft",
"DEMO_MIME_TYPE": "MIME Type",
@@ -166,6 +167,7 @@
"DEMO_OFFLINE_SECTION_HEADER": "Offline",
"DEMO_FAILURE_MISC": "Shaka Player failed to load! If you are using an ad blocker, try switching to compiled mode at the bottom of the page.",
"DEMO_FAILURE_NO_BROWSER_SUPPORT": "Your browser is not supported!",
+ "DEMO_FULL_MIME_TYPE": "Full Mime Type",
"DEMO_PLAY": "Play",
"DEMO_PLAYREADY": "PlayReady DRM",
"DEMO_PREFER_FORCED_SUBS": "Prefer Forced Subs",
diff --git a/demo/locales/source.json b/demo/locales/source.json
index aa2920a5fb0..2d00c9ff986 100644
--- a/demo/locales/source.json
+++ b/demo/locales/source.json
@@ -555,6 +555,10 @@
"description": "The name of a configuration value.",
"message": "Max Width"
},
+ "DEMO_MEDIA_PLAYLIST_TAB": {
+ "description": "The header for a tab within the custom asset creation dialog.",
+ "message": "HLS Media Playlist"
+ },
"DEMO_METACDN": {
"description": "Text that describes an asset that comes from the MetaCDN asset library.",
"message": "[PROPER_NAME:MetaCDN]"
@@ -667,6 +671,10 @@
"description": "An error displayed if Shaka Player is unable to load due to lack of browser support.",
"message": "Your browser is not supported!"
},
+ "DEMO_FULL_MIME_TYPE": {
+ "description": "The label on a field that allows users to provide a full mime type for a custom asset.",
+ "message": "Full Mime Type"
+ },
"DEMO_PLAY": {
"description": "A button to play the attached asset.",
"message": "Play"
diff --git a/externs/shaka/player.js b/externs/shaka/player.js
index 87abd72a266..90870f4ec4d 100644
--- a/externs/shaka/player.js
+++ b/externs/shaka/player.js
@@ -748,7 +748,8 @@ shaka.extern.DashManifestConfiguration;
* ignoreImageStreamFailures: boolean,
* defaultAudioCodec: string,
* defaultVideoCodec: string,
- * ignoreManifestProgramDateTime: boolean
+ * ignoreManifestProgramDateTime: boolean,
+ * mediaPlaylistFullMimeType: string
* }}
*
* @property {boolean} ignoreTextStreamFailures
@@ -768,6 +769,13 @@ shaka.extern.DashManifestConfiguration;
* EXT-X-PROGRAM-DATE-TIME
tags in the manifest.
* Meant for tags that are incorrect or malformed.
* Defaults to false
.
+ * @property {string} mediaPlaylistFullMimeType
+ * A string containing a full mime type, including both the basic mime type
+ * and also the codecs. Used when the HLS parser parses a media playlist
+ * directly.
+ * You can use the shaka.util.MimeUtils.getFullType()
utility to
+ * format this value.
+ * Defaults to 'video/mp4; codecs="avc1.42E01E"'
.
* @exportDoc
*/
shaka.extern.HlsManifestConfiguration;
diff --git a/lib/hls/hls_parser.js b/lib/hls/hls_parser.js
index 5adf6a59762..f6e832f4949 100644
--- a/lib/hls/hls_parser.js
+++ b/lib/hls/hls_parser.js
@@ -216,7 +216,7 @@ shaka.hls.HlsParser = class {
this.masterPlaylistUri_ = response.uri;
goog.asserts.assert(response.data, 'Response data should be non-null!');
- await this.parseManifest_(response.data);
+ await this.parseManifest_(response.data, uri);
// Start the update timer if we want updates.
const delay = this.updatePlaylistDelay_;
@@ -401,10 +401,11 @@ shaka.hls.HlsParser = class {
* Parses the manifest.
*
* @param {BufferSource} data
+ * @param {string} uri
* @return {!Promise}
* @private
*/
- async parseManifest_(data) {
+ async parseManifest_(data, uri) {
const Utils = shaka.hls.Utils;
goog.asserts.assert(this.masterPlaylistUri_,
@@ -413,63 +414,107 @@ shaka.hls.HlsParser = class {
const playlist = this.manifestTextParser_.parsePlaylist(
data, this.masterPlaylistUri_);
- // We don't support directly providing a Media Playlist.
- // See the error code for details.
- if (playlist.type != shaka.hls.PlaylistType.MASTER) {
- throw new shaka.util.Error(
- shaka.util.Error.Severity.CRITICAL,
- shaka.util.Error.Category.MANIFEST,
- shaka.util.Error.Code.HLS_MASTER_PLAYLIST_NOT_PROVIDED);
- }
-
/** @type {!Array.} */
const variablesTags = Utils.filterTagsByName(playlist.tags, 'EXT-X-DEFINE');
this.parseMasterVariables_(variablesTags);
- /** @type {!Array.} */
- const mediaTags = Utils.filterTagsByName(playlist.tags, 'EXT-X-MEDIA');
- /** @type {!Array.} */
- const variantTags = Utils.filterTagsByName(
- playlist.tags, 'EXT-X-STREAM-INF');
- /** @type {!Array.} */
- const imageTags = Utils.filterTagsByName(
- playlist.tags, 'EXT-X-IMAGE-STREAM-INF');
-
- this.parseCodecs_(variantTags);
-
- /** @type {!Array.} */
- const sesionDataTags =
- Utils.filterTagsByName(playlist.tags, 'EXT-X-SESSION-DATA');
- for (const tag of sesionDataTags) {
- const id = tag.getAttributeValue('DATA-ID');
- const uri = tag.getAttributeValue('URI');
- const language = tag.getAttributeValue('LANGUAGE');
- const value = tag.getAttributeValue('VALUE');
- const data = (new Map()).set('id', id);
- if (uri) {
- data.set('uri',
- shaka.hls.Utils.constructAbsoluteUri(this.masterPlaylistUri_, uri));
- }
- if (language) {
- data.set('language', language);
- }
- if (value) {
- data.set('value', value);
- }
- const event = new shaka.util.FakeEvent('sessiondata', data);
- if (this.playerInterface_) {
- this.playerInterface_.onEvent(event);
+ /** @type {!Array.} */
+ let variants = [];
+ /** @type {!Array.} */
+ let textStreams = [];
+ /** @type {!Array.} */
+ let imageStreams = [];
+
+ // Parsing a media playlist results in a single-variant stream.
+ if (playlist.type == shaka.hls.PlaylistType.MEDIA) {
+ // Get necessary info for this stream, from the config. These are things
+ // we would normally be finding from the master playlist (e.g. from values
+ // on EXT-X-MEDIA tags).
+ const fullMimeType = this.config_.hls.mediaPlaylistFullMimeType;
+ const mimeType = shaka.util.MimeUtils.getBasicType(fullMimeType);
+ const type = mimeType.split('/')[0];
+ const codecs = shaka.util.MimeUtils.getCodecs(fullMimeType);
+
+ // Some values we cannot figure out, and aren't important enough to ask
+ // the user to provide through config values. A lot of these are only
+ // relevant to ABR, which isn't necessary if there's only one variant.
+ // So these unknowns should be set to false or null, largely.
+ const language = '';
+ const channelsCount = null;
+ const spatialAudio = false;
+ const characteristics = null;
+ const closedCaptions = new Map();
+ const bandwidth = 0;
+ const forced = false; // Only relevant for text.
+ const primary = true; // This is the only stream!
+ const name = 'Media Playlist';
+
+ // Make the stream info, with those values.
+ const streamInfo = await this.convertParsedPlaylistIntoStreamInfo_(
+ playlist, uri, uri, codecs, type, language, primary, name,
+ channelsCount, closedCaptions, characteristics, forced, spatialAudio,
+ bandwidth, mimeType);
+ goog.asserts.assert(streamInfo != null, 'StreamInfo is null!');
+ this.uriToStreamInfosMap_.set(uri, streamInfo);
+
+ // Wrap the stream from that stream info with a variant.
+ variants.push({
+ id: 0,
+ language: 'und',
+ primary: true,
+ audio: type == 'audio' ? streamInfo.stream : null,
+ video: type == 'video' ? streamInfo.stream : null,
+ bandwidth: 0,
+ allowedByApplication: true,
+ allowedByKeySystem: true,
+ decodingInfos: [],
+ });
+ } else {
+ /** @type {!Array.} */
+ const mediaTags = Utils.filterTagsByName(playlist.tags, 'EXT-X-MEDIA');
+ /** @type {!Array.} */
+ const variantTags = Utils.filterTagsByName(
+ playlist.tags, 'EXT-X-STREAM-INF');
+ /** @type {!Array.} */
+ const imageTags = Utils.filterTagsByName(
+ playlist.tags, 'EXT-X-IMAGE-STREAM-INF');
+
+ this.parseCodecs_(variantTags);
+
+ /** @type {!Array.} */
+ const sesionDataTags =
+ Utils.filterTagsByName(playlist.tags, 'EXT-X-SESSION-DATA');
+ for (const tag of sesionDataTags) {
+ const id = tag.getAttributeValue('DATA-ID');
+ const uri = tag.getAttributeValue('URI');
+ const language = tag.getAttributeValue('LANGUAGE');
+ const value = tag.getAttributeValue('VALUE');
+ const data = (new Map()).set('id', id);
+ if (uri) {
+ data.set('uri', shaka.hls.Utils.constructAbsoluteUri(
+ this.masterPlaylistUri_, uri));
+ }
+ if (language) {
+ data.set('language', language);
+ }
+ if (value) {
+ data.set('value', value);
+ }
+ const event = new shaka.util.FakeEvent('sessiondata', data);
+ if (this.playerInterface_) {
+ this.playerInterface_.onEvent(event);
+ }
}
- }
- // Parse audio and video media tags first, so that we can extract segment
- // start time from audio/video streams and reuse for text streams.
- await this.createStreamInfosFromMediaTags_(mediaTags);
- this.parseClosedCaptions_(mediaTags);
- const variants = await this.createVariantsForTags_(variantTags);
- const textStreams = await this.parseTexts_(mediaTags);
- const imageStreams = await this.parseImages_(imageTags);
+ // Parse audio and video media tags first, so that we can extract segment
+ // start time from audio/video streams and reuse for text streams.
+ await this.createStreamInfosFromMediaTags_(mediaTags);
+ this.parseClosedCaptions_(mediaTags);
+ variants = await this.createVariantsForTags_(variantTags);
+ textStreams = await this.parseTexts_(mediaTags);
+ imageStreams = await this.parseImages_(imageTags);
+ }
// Make sure that the parser has not been destroyed.
if (!this.playerInterface_) {
@@ -1413,6 +1458,35 @@ shaka.hls.HlsParser = class {
const playlist = this.manifestTextParser_.parsePlaylist(
response.data, absoluteMediaPlaylistUri);
+ return this.convertParsedPlaylistIntoStreamInfo_(playlist,
+ verbatimMediaPlaylistUri, absoluteMediaPlaylistUri, codecs, type,
+ language, primary, name, channelsCount, closedCaptions, characteristics,
+ forced, spatialAudio, bandwidth);
+ }
+
+ /**
+ * @param {!shaka.hls.Playlist} playlist
+ * @param {string} verbatimMediaPlaylistUri
+ * @param {string} absoluteMediaPlaylistUri
+ * @param {string} codecs
+ * @param {string} type
+ * @param {string} language
+ * @param {boolean} primary
+ * @param {?string} name
+ * @param {?number} channelsCount
+ * @param {Map.} closedCaptions
+ * @param {?string} characteristics
+ * @param {boolean} forced
+ * @param {boolean} spatialAudio
+ * @param {(number|undefined)} bandwidth
+ * @param {(string|undefined)} mimeType
+ * @return {!Promise.}
+ * @private
+ */
+ async convertParsedPlaylistIntoStreamInfo_(playlist, verbatimMediaPlaylistUri,
+ absoluteMediaPlaylistUri, codecs, type, language, primary, name,
+ channelsCount, closedCaptions, characteristics, forced, spatialAudio,
+ bandwidth = undefined, mimeType = undefined) {
if (playlist.type != shaka.hls.PlaylistType.MEDIA) {
// EXT-X-MEDIA and EXT-X-IMAGE-STREAM-INF tags should point to media
// playlists.
@@ -1433,9 +1507,10 @@ shaka.hls.HlsParser = class {
this.determinePresentationType_(playlist);
- /** @type {string} */
- const mimeType = await this.guessMimeType_(type, codecs, playlist,
- mediaVariables);
+ if (!mimeType) {
+ mimeType = await this.guessMimeType_(type, codecs, playlist,
+ mediaVariables);
+ }
/** @type {!Array.} */
const drmTags = [];
diff --git a/lib/util/error.js b/lib/util/error.js
index 99847cbacc8..4830701d078 100644
--- a/lib/util/error.js
+++ b/lib/util/error.js
@@ -581,13 +581,7 @@ shaka.util.Error.Code = {
// RETIRED: 'HLS_COULD_NOT_GUESS_MIME_TYPE': 4021,
- /**
- * No Master Playlist has been provided. Master playlist provides
- * vital information about the streams (like codecs) that is
- * required for MediaSource. We don't support directly providing
- * a Media Playlist.
- */
- 'HLS_MASTER_PLAYLIST_NOT_PROVIDED': 4022,
+ // RETIRED: 'HLS_MASTER_PLAYLIST_NOT_PROVIDED': 4022,
/**
* One of the required attributes was not provided, so the
diff --git a/lib/util/mime_utils.js b/lib/util/mime_utils.js
index a9d3523ff57..8183e504486 100644
--- a/lib/util/mime_utils.js
+++ b/lib/util/mime_utils.js
@@ -11,6 +11,7 @@ goog.require('shaka.media.Transmuxer');
/**
* @summary A set of utility functions for dealing with MIME types.
+ * @export
*/
shaka.util.MimeUtils = class {
/**
@@ -20,6 +21,7 @@ shaka.util.MimeUtils = class {
* @param {string} mimeType
* @param {string=} codecs
* @return {string}
+ * @export
*/
static getFullType(mimeType, codecs) {
let fullMimeType = mimeType;
diff --git a/lib/util/player_configuration.js b/lib/util/player_configuration.js
index fe4f5d27e9f..93364f460dd 100644
--- a/lib/util/player_configuration.js
+++ b/lib/util/player_configuration.js
@@ -122,6 +122,7 @@ shaka.util.PlayerConfiguration = class {
defaultAudioCodec: 'mp4a.40.2',
defaultVideoCodec: 'avc1.42E01E',
ignoreManifestProgramDateTime: false,
+ mediaPlaylistFullMimeType: 'video/mp4; codecs="avc1.42E01E"',
},
};
diff --git a/test/demo/demo_unit.js b/test/demo/demo_unit.js
index b493f7f151c..bd1afa48062 100644
--- a/test/demo/demo_unit.js
+++ b/test/demo/demo_unit.js
@@ -98,7 +98,8 @@ describe('Demo', () => {
.add('preferredVariantRole')
.add('playRangeStart')
.add('playRangeEnd')
- .add('manifest.dash.keySystemsByURI');
+ .add('manifest.dash.keySystemsByURI')
+ .add('manifest.hls.mediaPlaylistFullMimeType');
/**
* @param {!Object} section
diff --git a/test/hls/hls_parser_unit.js b/test/hls/hls_parser_unit.js
index 0b92a1ec636..691da31db86 100644
--- a/test/hls/hls_parser_unit.js
+++ b/test/hls/hls_parser_unit.js
@@ -3354,4 +3354,54 @@ describe('HlsParser', () => {
jasmine.objectContaining(eventValue3));
});
});
+
+ it('parses media playlists directly', async () => {
+ const media = [
+ '#EXTM3U\n',
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
+ '#EXTINF:5,\n',
+ '#EXT-X-BYTERANGE:121090@616\n',
+ 'main.mp4',
+ ].join('');
+
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
+ manifest.sequenceMode = true;
+ manifest.anyTimeline();
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.mime('video/mp4', 'avc1.42E01E');
+ });
+ });
+ });
+
+ await testHlsParser(media, '', manifest);
+ });
+
+ it('honors hls.mediaPlaylistFullMimeType', async () => {
+ const media = [
+ '#EXTM3U\n',
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
+ '#EXTINF:5,\n',
+ '#EXT-X-BYTERANGE:121090@616\n',
+ 'main.mp4',
+ ].join('');
+
+ const config = shaka.util.PlayerConfiguration.createDefault().manifest;
+ config.hls.mediaPlaylistFullMimeType = 'audio/webm; codecs="vorbis"';
+ parser.configure(config);
+
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
+ manifest.sequenceMode = true;
+ manifest.anyTimeline();
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
+ stream.mime('audio/webm', 'vorbis');
+ });
+ });
+ });
+
+ await testHlsParser(media, '', manifest);
+ });
});