mirror of https://bitbucket.org/ausocean/av.git
Merge branch 'm3u-loader-correction' into m3u-live
This commit is contained in:
commit
3ae4670e49
|
@ -1,58 +1,111 @@
|
||||||
/**
|
/*
|
||||||
* PlaylistLoader - delegate for media manifest/playlist loading tasks. Takes care of parsing media to internal data-models.
|
AUTHOR
|
||||||
*
|
Trek Hopton <trek@ausocean.org>
|
||||||
* Once loaded, dispatches events with parsed data-models of manifest/levels/audio/subtitle tracks.
|
|
||||||
*
|
|
||||||
* Uses loader(s) set in config to do actual internal loading of resource tasks.
|
|
||||||
*
|
|
||||||
* @module
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Event from '../events';
|
LICENSE
|
||||||
import EventHandler from '../event-handler';
|
This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean)
|
||||||
import { ErrorTypes, ErrorDetails } from '../errors';
|
|
||||||
import { logger } from '../utils/logger';
|
It is free software: you can redistribute it and/or modify them
|
||||||
import { Loader, PlaylistContextType, PlaylistLoaderContext, PlaylistLevelType, LoaderCallbacks, LoaderResponse, LoaderStats, LoaderConfiguration } from '../types/loader';
|
under the terms of the GNU General Public License as published by the
|
||||||
import MP4Demuxer from '../demux/mp4demuxer';
|
Free Software Foundation, either version 3 of the License, or (at your
|
||||||
import M3U8Parser from './m3u8-parser';
|
option) any later version.
|
||||||
import { AudioGroup } from '../types/media-playlist';
|
|
||||||
|
It is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License in gpl.txt.
|
||||||
|
If not, see http://www.gnu.org/licenses.
|
||||||
|
|
||||||
|
For hls.js Copyright notice and license, see LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { PlaylistContextType, PlaylistLevelType } from '../types/loader.js';
|
||||||
|
import Event from '../events.js';
|
||||||
|
import EventHandler from '../event-handler.js';
|
||||||
|
import M3U8Parser from './m3u8-parser.js';
|
||||||
|
|
||||||
const { performance } = window;
|
const { performance } = window;
|
||||||
|
|
||||||
/**
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
class PlaylistLoader extends EventHandler {
|
class PlaylistLoader extends EventHandler {
|
||||||
private loaders: Partial<Record<PlaylistContextType, Loader<PlaylistLoaderContext>>> = {};
|
constructor(hls) {
|
||||||
|
|
||||||
/**
|
|
||||||
* @constructs
|
|
||||||
* @param {Hls} hls
|
|
||||||
*/
|
|
||||||
constructor (hls) {
|
|
||||||
super(hls,
|
super(hls,
|
||||||
Event.MANIFEST_LOADING,
|
Event.MANIFEST_LOADING,
|
||||||
Event.LEVEL_LOADING,
|
Event.LEVEL_LOADING,
|
||||||
Event.AUDIO_TRACK_LOADING,
|
Event.AUDIO_TRACK_LOADING,
|
||||||
Event.SUBTITLE_TRACK_LOADING);
|
Event.SUBTITLE_TRACK_LOADING);
|
||||||
|
this.hls = hls;
|
||||||
|
this.loaders = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {PlaylistContextType} type
|
* @param {PlaylistContextType} type
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
static canHaveQualityLevels (type: PlaylistContextType): boolean {
|
static canHaveQualityLevels(type) {
|
||||||
return (type !== PlaylistContextType.AUDIO_TRACK &&
|
return (type !== PlaylistContextType.AUDIO_TRACK &&
|
||||||
type !== PlaylistContextType.SUBTITLE_TRACK);
|
type !== PlaylistContextType.SUBTITLE_TRACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onManifestLoading(data) {
|
||||||
|
this.load({
|
||||||
|
url: data.url,
|
||||||
|
type: PlaylistContextType.MANIFEST,
|
||||||
|
level: 0,
|
||||||
|
id: null,
|
||||||
|
responseType: 'text'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// param 1 -> data: { url: string; level: number | null; id: number | null; }
|
||||||
|
onLevelLoading(data) {
|
||||||
|
this.load({
|
||||||
|
url: data.url,
|
||||||
|
type: PlaylistContextType.LEVEL,
|
||||||
|
level: data.level,
|
||||||
|
id: data.id,
|
||||||
|
responseType: 'text'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onAudioTrackLoading(data) {
|
||||||
|
this.load({
|
||||||
|
url: data.url,
|
||||||
|
type: PlaylistContextType.AUDIO_TRACK,
|
||||||
|
level: null,
|
||||||
|
id: data.id,
|
||||||
|
responseType: 'text'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubtitleTrackLoading(data) {
|
||||||
|
this.load({
|
||||||
|
url: data.url,
|
||||||
|
type: PlaylistContextType.SUBTITLE_TRACK,
|
||||||
|
level: null,
|
||||||
|
id: data.id,
|
||||||
|
responseType: 'text'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static getResponseUrl(response, context) {
|
||||||
|
let url = response.url;
|
||||||
|
// responseURL not supported on some browsers (it is used to detect URL redirection)
|
||||||
|
// data-uri mode also not supported (but no need to detect redirection)
|
||||||
|
if (url === undefined || url.indexOf('data:') === 0) {
|
||||||
|
// fallback to initial URL
|
||||||
|
url = context.url;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map context.type to LevelType
|
* Map context.type to LevelType
|
||||||
* @param {PlaylistLoaderContext} context
|
* @param {PlaylistLoaderContext} context
|
||||||
* @returns {LevelType}
|
* @returns {LevelType}
|
||||||
*/
|
*/
|
||||||
static mapContextToLevelType (context: PlaylistLoaderContext): PlaylistLevelType {
|
static mapContextToLevelType(context) {
|
||||||
const { type } = context;
|
const { type } = context;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
@ -65,15 +118,8 @@ class PlaylistLoader extends EventHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static getResponseUrl (response: LoaderResponse, context: PlaylistLoaderContext): string {
|
getInternalLoader(context) {
|
||||||
let url = response.url;
|
return this.loaders[context.type];
|
||||||
// responseURL not supported on some browsers (it is used to detect URL redirection)
|
|
||||||
// data-uri mode also not supported (but no need to detect redirection)
|
|
||||||
if (url === undefined || url.indexOf('data:') === 0) {
|
|
||||||
// fallback to initial URL
|
|
||||||
url = context.url;
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -82,7 +128,7 @@ class PlaylistLoader extends EventHandler {
|
||||||
* @param {PlaylistLoaderContext} context
|
* @param {PlaylistLoaderContext} context
|
||||||
* @returns {Loader} or other compatible configured overload
|
* @returns {Loader} or other compatible configured overload
|
||||||
*/
|
*/
|
||||||
createInternalLoader (context: PlaylistLoaderContext): Loader<PlaylistLoaderContext> {
|
createInternalLoader(context) {
|
||||||
const config = this.hls.config;
|
const config = this.hls.config;
|
||||||
const PLoader = config.pLoader;
|
const PLoader = config.pLoader;
|
||||||
const Loader = config.loader;
|
const Loader = config.loader;
|
||||||
|
@ -98,98 +144,30 @@ class PlaylistLoader extends EventHandler {
|
||||||
return loader;
|
return loader;
|
||||||
}
|
}
|
||||||
|
|
||||||
getInternalLoader (context: PlaylistLoaderContext): Loader<PlaylistLoaderContext> | undefined {
|
resetInternalLoader(contextType) {
|
||||||
return this.loaders[context.type];
|
|
||||||
}
|
|
||||||
|
|
||||||
resetInternalLoader (contextType: PlaylistContextType) {
|
|
||||||
if (this.loaders[contextType]) {
|
if (this.loaders[contextType]) {
|
||||||
delete this.loaders[contextType];
|
delete this.loaders[contextType];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
load(context) {
|
||||||
* Call `destroy` on all internal loader instances mapped (one per context type)
|
|
||||||
*/
|
|
||||||
destroyInternalLoaders () {
|
|
||||||
for (let contextType in this.loaders) {
|
|
||||||
let loader = this.loaders[contextType];
|
|
||||||
if (loader) {
|
|
||||||
loader.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.resetInternalLoader(contextType as PlaylistContextType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy () {
|
|
||||||
this.destroyInternalLoaders();
|
|
||||||
|
|
||||||
super.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
onManifestLoading (data: { url: string; }) {
|
|
||||||
this.load({
|
|
||||||
url: data.url,
|
|
||||||
type: PlaylistContextType.MANIFEST,
|
|
||||||
level: 0,
|
|
||||||
id: null,
|
|
||||||
responseType: 'text'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onLevelLoading (data: { url: string; level: number | null; id: number | null; }) {
|
|
||||||
this.load({
|
|
||||||
url: data.url,
|
|
||||||
type: PlaylistContextType.LEVEL,
|
|
||||||
level: data.level,
|
|
||||||
id: data.id,
|
|
||||||
responseType: 'text'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onAudioTrackLoading (data: { url: string; id: number | null; }) {
|
|
||||||
this.load({
|
|
||||||
url: data.url,
|
|
||||||
type: PlaylistContextType.AUDIO_TRACK,
|
|
||||||
level: null,
|
|
||||||
id: data.id,
|
|
||||||
responseType: 'text'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onSubtitleTrackLoading (data: { url: string; id: number | null; }) {
|
|
||||||
this.load({
|
|
||||||
url: data.url,
|
|
||||||
type: PlaylistContextType.SUBTITLE_TRACK,
|
|
||||||
level: null,
|
|
||||||
id: data.id,
|
|
||||||
responseType: 'text'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
load (context: PlaylistLoaderContext): boolean {
|
|
||||||
const config = this.hls.config;
|
const config = this.hls.config;
|
||||||
|
|
||||||
logger.debug(`Loading playlist of type ${context.type}, level: ${context.level}, id: ${context.id}`);
|
|
||||||
|
|
||||||
// Check if a loader for this context already exists
|
// Check if a loader for this context already exists
|
||||||
let loader = this.getInternalLoader(context);
|
let loader = this.getInternalLoader(context);
|
||||||
if (loader) {
|
if (loader) {
|
||||||
const loaderContext = loader.context;
|
const loaderContext = loader.context;
|
||||||
if (loaderContext && loaderContext.url === context.url) { // same URL can't overlap
|
if (loaderContext && loaderContext.url === context.url) { // same URL can't overlap
|
||||||
logger.trace('playlist request ongoing');
|
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
logger.warn(`aborting previous loader for type: ${context.type}`);
|
|
||||||
loader.abort();
|
loader.abort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let maxRetry: number;
|
let maxRetry;
|
||||||
let timeout: number;
|
let timeout;
|
||||||
let retryDelay: number;
|
let retryDelay;
|
||||||
let maxRetryDelay: number;
|
let maxRetryDelay;
|
||||||
|
|
||||||
// apply different configs for retries depending on
|
// apply different configs for retries depending on
|
||||||
// context (manifest, level, audio/subs playlist)
|
// context (manifest, level, audio/subs playlist)
|
||||||
|
@ -218,26 +196,25 @@ class PlaylistLoader extends EventHandler {
|
||||||
|
|
||||||
loader = this.createInternalLoader(context);
|
loader = this.createInternalLoader(context);
|
||||||
|
|
||||||
const loaderConfig: LoaderConfiguration = {
|
const loaderConfig = {
|
||||||
timeout,
|
timeout,
|
||||||
maxRetry,
|
maxRetry,
|
||||||
retryDelay,
|
retryDelay,
|
||||||
maxRetryDelay
|
maxRetryDelay
|
||||||
};
|
};
|
||||||
|
|
||||||
const loaderCallbacks: LoaderCallbacks<PlaylistLoaderContext> = {
|
const loaderCallbacks = {
|
||||||
onSuccess: this.loadsuccess.bind(this),
|
onSuccess: this.loadsuccess.bind(this),
|
||||||
onError: this.loaderror.bind(this),
|
onError: this.loaderror.bind(this),
|
||||||
onTimeout: this.loadtimeout.bind(this)
|
onTimeout: this.loadtimeout.bind(this)
|
||||||
};
|
};
|
||||||
|
|
||||||
logger.debug(`Calling internal loader delegate for URL: ${context.url}`);
|
|
||||||
loader.load(context, loaderConfig, loaderCallbacks);
|
loader.load(context, loaderConfig, loaderCallbacks);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadsuccess (response: LoaderResponse, stats: LoaderStats, context: PlaylistLoaderContext, networkDetails: unknown = null) {
|
loadsuccess(response, stats, context, networkDetails = null) {
|
||||||
if (context.isSidxRequest) {
|
if (context.isSidxRequest) {
|
||||||
this._handleSidxRequest(response, context);
|
this._handleSidxRequest(response, context);
|
||||||
this._handlePlaylistLoaded(response, stats, context, networkDetails);
|
this._handlePlaylistLoaded(response, stats, context, networkDetails);
|
||||||
|
@ -252,11 +229,10 @@ class PlaylistLoader extends EventHandler {
|
||||||
const string = response.data;
|
const string = response.data;
|
||||||
|
|
||||||
stats.tload = performance.now();
|
stats.tload = performance.now();
|
||||||
// stats.mtime = new Date(target.getResponseHeader('Last-Modified'));
|
|
||||||
|
|
||||||
// Validate if it is an M3U8 at all
|
// Validate if it is an M3U8 at all
|
||||||
if (string.indexOf('#EXTM3U') !== 0) {
|
if (string.indexOf('#EXTM3U') !== 0) {
|
||||||
this._handleManifestParsingError(response, context, 'no EXTM3U delimiter', networkDetails);
|
console.error("no EXTM3U delimiter");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,77 +240,21 @@ class PlaylistLoader extends EventHandler {
|
||||||
if (string.indexOf('#EXTINF:') > 0 || string.indexOf('#EXT-X-TARGETDURATION:') > 0) {
|
if (string.indexOf('#EXTINF:') > 0 || string.indexOf('#EXT-X-TARGETDURATION:') > 0) {
|
||||||
this._handleTrackOrLevelPlaylist(response, stats, context, networkDetails);
|
this._handleTrackOrLevelPlaylist(response, stats, context, networkDetails);
|
||||||
} else {
|
} else {
|
||||||
this._handleMasterPlaylist(response, stats, context, networkDetails);
|
console.log("handling of master playlists is not implemented");
|
||||||
}
|
// this._handleMasterPlaylist(response, stats, context, networkDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
loaderror (response: LoaderResponse, context: PlaylistLoaderContext, networkDetails = null) {
|
|
||||||
this._handleNetworkError(context, networkDetails, false, response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadtimeout (stats: LoaderStats, context: PlaylistLoaderContext, networkDetails = null) {
|
loaderror(response, context, networkDetails = null) {
|
||||||
this._handleNetworkError(context, networkDetails, true);
|
console.error("network error while loading", response);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(typescript-config): networkDetails can currently be a XHR or Fetch impl,
|
loadtimeout(stats, context, networkDetails = null) {
|
||||||
// but with custom loaders it could be generic investigate this further when config is typed
|
console.error("network timeout while loading", stats);
|
||||||
_handleMasterPlaylist (response: LoaderResponse, stats: LoaderStats, context: PlaylistLoaderContext, networkDetails: unknown) {
|
|
||||||
const hls = this.hls;
|
|
||||||
const string = response.data as string;
|
|
||||||
|
|
||||||
const url = PlaylistLoader.getResponseUrl(response, context);
|
|
||||||
const levels = M3U8Parser.parseMasterPlaylist(string, url);
|
|
||||||
if (!levels.length) {
|
|
||||||
this._handleManifestParsingError(response, context, 'no level found in manifest', networkDetails);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// multi level playlist, parse level info
|
_handleTrackOrLevelPlaylist(response, stats, context, networkDetails) {
|
||||||
const audioGroups: Array<AudioGroup> = levels.map(level => ({
|
|
||||||
id: level.attrs.AUDIO,
|
|
||||||
codec: level.audioCodec
|
|
||||||
}));
|
|
||||||
|
|
||||||
const audioTracks = M3U8Parser.parseMasterPlaylistMedia(string, url, 'AUDIO', audioGroups);
|
|
||||||
const subtitles = M3U8Parser.parseMasterPlaylistMedia(string, url, 'SUBTITLES');
|
|
||||||
|
|
||||||
if (audioTracks.length) {
|
|
||||||
// check if we have found an audio track embedded in main playlist (audio track without URI attribute)
|
|
||||||
let embeddedAudioFound = false;
|
|
||||||
audioTracks.forEach(audioTrack => {
|
|
||||||
if (!audioTrack.url) {
|
|
||||||
embeddedAudioFound = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// if no embedded audio track defined, but audio codec signaled in quality level,
|
|
||||||
// we need to signal this main audio track this could happen with playlists with
|
|
||||||
// alt audio rendition in which quality levels (main)
|
|
||||||
// contains both audio+video. but with mixed audio track not signaled
|
|
||||||
if (embeddedAudioFound === false && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
|
|
||||||
logger.log('audio codec signaled in quality level, but no embedded audio track signaled, create one');
|
|
||||||
audioTracks.unshift({
|
|
||||||
type: 'main',
|
|
||||||
name: 'main',
|
|
||||||
default: false,
|
|
||||||
autoselect: false,
|
|
||||||
forced: false,
|
|
||||||
id: -1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hls.trigger(Event.MANIFEST_LOADED, {
|
|
||||||
levels,
|
|
||||||
audioTracks,
|
|
||||||
subtitles,
|
|
||||||
url,
|
|
||||||
stats,
|
|
||||||
networkDetails
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleTrackOrLevelPlaylist (response: LoaderResponse, stats: LoaderStats, context: PlaylistLoaderContext, networkDetails: unknown) {
|
|
||||||
const hls = this.hls;
|
const hls = this.hls;
|
||||||
|
|
||||||
const { id, level, type } = context;
|
const { id, level, type } = context;
|
||||||
|
@ -342,15 +262,15 @@ class PlaylistLoader extends EventHandler {
|
||||||
const url = PlaylistLoader.getResponseUrl(response, context);
|
const url = PlaylistLoader.getResponseUrl(response, context);
|
||||||
|
|
||||||
// if the values are null, they will result in the else conditional
|
// if the values are null, they will result in the else conditional
|
||||||
const levelUrlId = Number.isFinite(id as number) ? id as number : 0;
|
const levelUrlId = Number.isFinite(id) ? id : 0;
|
||||||
const levelId = Number.isFinite(level as number) ? level as number : levelUrlId;
|
const levelId = Number.isFinite(level) ? level : levelUrlId;
|
||||||
|
|
||||||
const levelType = PlaylistLoader.mapContextToLevelType(context);
|
const levelType = PlaylistLoader.mapContextToLevelType(context);
|
||||||
const levelDetails = M3U8Parser.parseLevelPlaylist(response.data as string, url, levelId, levelType, levelUrlId);
|
const levelDetails = M3U8Parser.parseLevelPlaylist(response.data, url, levelId, levelType, levelUrlId);
|
||||||
|
|
||||||
// set stats on level structure
|
// set stats on level structure
|
||||||
// TODO(jstackhouse): why? mixing concerns, is it just treated as value bag?
|
// TODO(jstackhouse): why? mixing concerns, is it just treated as value bag?
|
||||||
(levelDetails as any).tload = stats.tload;
|
(levelDetails).tload = stats.tload;
|
||||||
|
|
||||||
// We have done our first request (Manifest-type) and receive
|
// We have done our first request (Manifest-type) and receive
|
||||||
// not a master playlist but a chunk-list (track/level)
|
// not a master playlist but a chunk-list (track/level)
|
||||||
|
@ -374,124 +294,17 @@ class PlaylistLoader extends EventHandler {
|
||||||
// save parsing time
|
// save parsing time
|
||||||
stats.tparsed = performance.now();
|
stats.tparsed = performance.now();
|
||||||
|
|
||||||
// in case we need SIDX ranges
|
|
||||||
// return early after calling load for
|
|
||||||
// the SIDX box.
|
|
||||||
if (levelDetails.needSidxRanges) {
|
|
||||||
const sidxUrl = levelDetails.initSegment.url;
|
|
||||||
this.load({
|
|
||||||
url: sidxUrl,
|
|
||||||
isSidxRequest: true,
|
|
||||||
type,
|
|
||||||
level,
|
|
||||||
levelDetails,
|
|
||||||
id,
|
|
||||||
rangeStart: 0,
|
|
||||||
rangeEnd: 2048,
|
|
||||||
responseType: 'arraybuffer'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// extend the context with the new levelDetails property
|
// extend the context with the new levelDetails property
|
||||||
context.levelDetails = levelDetails;
|
context.levelDetails = levelDetails;
|
||||||
|
|
||||||
this._handlePlaylistLoaded(response, stats, context, networkDetails);
|
this._handlePlaylistLoaded(response, stats, context, networkDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleSidxRequest (response: LoaderResponse, context: PlaylistLoaderContext) {
|
_handlePlaylistLoaded(response, stats, context, networkDetails) {
|
||||||
if (typeof response.data === 'string') {
|
|
||||||
throw new Error('sidx request must be made with responseType of array buffer');
|
|
||||||
}
|
|
||||||
|
|
||||||
const sidxInfo = MP4Demuxer.parseSegmentIndex(new Uint8Array(response.data));
|
|
||||||
// if provided fragment does not contain sidx, early return
|
|
||||||
if (!sidxInfo) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const sidxReferences = sidxInfo.references;
|
|
||||||
const levelDetails = context.levelDetails;
|
|
||||||
sidxReferences.forEach((segmentRef, index) => {
|
|
||||||
const segRefInfo = segmentRef.info;
|
|
||||||
if (!levelDetails) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const frag = levelDetails.fragments[index];
|
|
||||||
if (frag.byteRange.length === 0) {
|
|
||||||
frag.setByteRange(String(1 + segRefInfo.end - segRefInfo.start) + '@' + String(segRefInfo.start));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (levelDetails) {
|
|
||||||
levelDetails.initSegment.setByteRange(String(sidxInfo.moovEndOffset) + '@0');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleManifestParsingError (response: LoaderResponse, context: PlaylistLoaderContext, reason: string, networkDetails: unknown) {
|
|
||||||
this.hls.trigger(Event.ERROR, {
|
|
||||||
type: ErrorTypes.NETWORK_ERROR,
|
|
||||||
details: ErrorDetails.MANIFEST_PARSING_ERROR,
|
|
||||||
fatal: true,
|
|
||||||
url: response.url,
|
|
||||||
reason,
|
|
||||||
networkDetails
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleNetworkError (context: PlaylistLoaderContext, networkDetails: unknown, timeout: boolean = false, response: LoaderResponse | null = null) {
|
|
||||||
logger.info(`A network error occured while loading a ${context.type}-type playlist`);
|
|
||||||
|
|
||||||
let details;
|
|
||||||
let fatal;
|
|
||||||
|
|
||||||
const loader = this.getInternalLoader(context);
|
|
||||||
|
|
||||||
switch (context.type) {
|
|
||||||
case PlaylistContextType.MANIFEST:
|
|
||||||
details = (timeout ? ErrorDetails.MANIFEST_LOAD_TIMEOUT : ErrorDetails.MANIFEST_LOAD_ERROR);
|
|
||||||
fatal = true;
|
|
||||||
break;
|
|
||||||
case PlaylistContextType.LEVEL:
|
|
||||||
details = (timeout ? ErrorDetails.LEVEL_LOAD_TIMEOUT : ErrorDetails.LEVEL_LOAD_ERROR);
|
|
||||||
fatal = false;
|
|
||||||
break;
|
|
||||||
case PlaylistContextType.AUDIO_TRACK:
|
|
||||||
details = (timeout ? ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT : ErrorDetails.AUDIO_TRACK_LOAD_ERROR);
|
|
||||||
fatal = false;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// details = ...?
|
|
||||||
fatal = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loader) {
|
|
||||||
loader.abort();
|
|
||||||
this.resetInternalLoader(context.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(typescript-events): when error events are handled, type this
|
|
||||||
let errorData: any = {
|
|
||||||
type: ErrorTypes.NETWORK_ERROR,
|
|
||||||
details,
|
|
||||||
fatal,
|
|
||||||
url: context.url,
|
|
||||||
loader,
|
|
||||||
context,
|
|
||||||
networkDetails
|
|
||||||
};
|
|
||||||
|
|
||||||
if (response) {
|
|
||||||
errorData.response = response;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hls.trigger(Event.ERROR, errorData);
|
|
||||||
}
|
|
||||||
|
|
||||||
_handlePlaylistLoaded (response: LoaderResponse, stats: LoaderStats, context: PlaylistLoaderContext, networkDetails: unknown) {
|
|
||||||
const { type, level, id, levelDetails } = context;
|
const { type, level, id, levelDetails } = context;
|
||||||
|
|
||||||
if (!levelDetails || !levelDetails.targetduration) {
|
if (!levelDetails || !levelDetails.targetduration) {
|
||||||
this._handleManifestParsingError(response, context, 'invalid target duration', networkDetails);
|
console.error("manifest parsing error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue