From dacd64bab32f9172589855281fa8126d63136a1f Mon Sep 17 00:00:00 2001 From: mangui Date: Mon, 12 Dec 2016 18:47:07 +0100 Subject: [PATCH] playlist-loader: support lazy frag/key URL resolving frag/key absolute URL is resolved on FRAG_LOADING/KEY_LOADING speed up playlist parsing (performance factor 3) related to https://github.com/dailymotion/hls.js/issues/875 --- API.md | 10 ++++++++++ demo/index.html | 7 ++++++- src/hls.js | 1 + src/loader/fragment-loader.js | 4 +++- src/loader/key-loader.js | 3 ++- src/loader/playlist-loader.js | 23 +++++++++++++++++------ 6 files changed, 39 insertions(+), 9 deletions(-) diff --git a/API.md b/API.md index 59a8acbfd30..9803226b93a 100644 --- a/API.md +++ b/API.md @@ -402,6 +402,16 @@ Enable WebWorker (if available on browser) for TS demuxing/MP4 remuxing, to impr Enable to use JavaScript version AES decryption for fallback of WebCrypto API. + +#### `enableLazyURLResolve` + +(default: `false`) + +Enable lazy URL resolving in fragment/key object. +Instead of resolving relative fragment/key URL on playlist parsing, URL are resolved on `FRAG_LOADING` / `KEY_LOADING` +this improves manifest parsing performance. +the drawback is that `frag.url` is not set in frag object, instead it is replaced by `frag.baseurl and `frag.relurl` + #### `startLevel` (default: `undefined`) diff --git a/demo/index.html b/demo/index.html index f25e2d6adea..22abfcebc79 100644 --- a/demo/index.html +++ b/demo/index.html @@ -80,6 +80,7 @@

demo page

+ @@ -226,11 +227,13 @@

Stats Display

$('#enableStreaming').click(function() { enableStreaming = this.checked; loadStream($('#streamURL').val()); }); $('#autoRecoverError').click(function() { autoRecoverError = this.checked; updatePermalink();}); $('#enableWorker').click(function() { enableWorker = this.checked; updatePermalink();}); + $('#enableLazyURLResolve').click(function() { enableLazyURLResolve = this.checked; updatePermalink();}); $('#levelCapping').change(function() { levelCapping = this.value; updatePermalink();}); $('#defaultAudioCodec').change(function() { defaultAudioCodec = this.value; updatePermalink();}); $('#enableStreaming').prop( "checked", enableStreaming ); $('#autoRecoverError').prop( "checked", autoRecoverError ); $('#enableWorker').prop( "checked", enableWorker ); + $('#enableLazyURLResolve').prop( "checked", enableLazyURLResolve ); $('#levelCapping').val(levelCapping); $('#defaultAudioCodec').val(defaultAudioCodec || "undefined"); @@ -244,6 +247,7 @@

Stats Display

enableStreaming = JSON.parse(getURLParam('enableStreaming',true)) autoRecoverError = JSON.parse(getURLParam('autoRecoverError',true)), enableWorker = JSON.parse(getURLParam('enableWorker',true)), + enableLazyURLResolve = JSON.parse(getURLParam('enableLazyURLResolve',true)), levelCapping = JSON.parse(getURLParam('levelCapping',-1)), defaultAudioCodec = getURLParam('defaultAudioCodec',undefined); var video = $('#video')[0]; @@ -274,7 +278,7 @@

Stats Display

$("#HlsStatus").text('loading ' + url); events = { url : url, t0 : performance.now(), load : [], buffer : [], video : [], level : [], bitrate : []}; recoverDecodingErrorDate = recoverSwapAudioCodecDate = null; - hls = new Hls({debug:true, enableWorker : enableWorker, defaultAudioCodec : defaultAudioCodec}); + hls = new Hls({debug:true, enableWorker : enableWorker, enableLazyURLResolve : enableLazyURLResolve, defaultAudioCodec : defaultAudioCodec}); $("#HlsStatus").text('loading manifest and attaching video element...'); hls.loadSource(url); hls.autoLevelCapping = levelCapping; @@ -1057,6 +1061,7 @@

Stats Display

'&enableStreaming=' + enableStreaming + '&autoRecoverError=' + autoRecoverError + '&enableWorker=' + enableWorker + + '&enableLazyURLResolve=' + enableLazyURLResolve + '&levelCapping=' + levelCapping + '&defaultAudioCodec=' + defaultAudioCodec; var description = 'permalink: ' + "" + hlsLink + ""; diff --git a/src/hls.js b/src/hls.js index 95d3c193e98..fbb7829a8b8 100644 --- a/src/hls.js +++ b/src/hls.js @@ -73,6 +73,7 @@ class Hls { maxMaxBufferLength: 600, enableWorker: true, enableSoftwareAES: true, + enableLazyURLResolve : false, manifestLoadingTimeOut: 10000, manifestLoadingMaxRetry: 1, manifestLoadingRetryDelay: 1000, diff --git a/src/loader/fragment-loader.js b/src/loader/fragment-loader.js index 31b4fb04d5f..ef23a38fbc1 100644 --- a/src/loader/fragment-loader.js +++ b/src/loader/fragment-loader.js @@ -6,6 +6,7 @@ import Event from '../events'; import EventHandler from '../event-handler'; import {ErrorTypes, ErrorDetails} from '../errors'; import {logger} from '../utils/logger'; +import URLToolkit from 'url-toolkit'; class FragmentLoader extends EventHandler { @@ -40,7 +41,8 @@ class FragmentLoader extends EventHandler { loader = this.loaders[type] = frag.loader = typeof(config.fLoader) !== 'undefined' ? new config.fLoader(config) : new config.loader(config); let loaderContext, loaderConfig, loaderCallbacks; - loaderContext = { url : frag.url, frag : frag, responseType : 'arraybuffer', progressData : false}; + let url = frag.url ? frag.url : URLToolkit.buildAbsoluteURL(frag.baseurl,frag.relurl); + loaderContext = { url : url , frag : frag, responseType : 'arraybuffer', progressData : false}; let start = frag.byteRangeStartOffset, end = frag.byteRangeEndOffset; if (!isNaN(start) && !isNaN(end)) { loaderContext.rangeStart = start; diff --git a/src/loader/key-loader.js b/src/loader/key-loader.js index 7adad7925c7..2e11ab4178a 100644 --- a/src/loader/key-loader.js +++ b/src/loader/key-loader.js @@ -6,6 +6,7 @@ import Event from '../events'; import EventHandler from '../event-handler'; import {ErrorTypes, ErrorDetails} from '../errors'; import {logger} from '../utils/logger'; +import URLToolkit from 'url-toolkit'; class KeyLoader extends EventHandler { @@ -32,7 +33,7 @@ class KeyLoader extends EventHandler { type = frag.type, loader = this.loaders[type], decryptdata = frag.decryptdata, - uri = decryptdata.uri; + uri = decryptdata.uri ? decryptdata.uri : URLToolkit.buildAbsoluteURL(decryptdata.baseuri,decryptdata.reluri); // if uri is different from previous one or if decrypt key not retrieved yet if (uri !== this.decrypturl || this.decryptkey === null) { let config = this.hls.config; diff --git a/src/loader/playlist-loader.js b/src/loader/playlist-loader.js index 77d1d51a194..5015cdef11b 100644 --- a/src/loader/playlist-loader.js +++ b/src/loader/playlist-loader.js @@ -212,7 +212,9 @@ class PlaylistLoader extends EventHandler { byteRangeEndOffset = null, byteRangeStartOffset = null, tagList = [], - i; + i, + config = this.hls.config, + lazyURLResolve = config ? config.enableLazyURLResolve : false; LEVEL_PLAYLIST_REGEX.lastIndex = 0; @@ -269,9 +271,7 @@ class PlaylistLoader extends EventHandler { if (!isNaN(duration)) { var sn = currentSN++; fragdecryptdata = this.fragmentDecryptdataFromLevelkey(levelkey, sn); - var url = value1 ? this.resolve(value1, baseurl) : null; - frag = {url: url, - type : type, + frag = {type : type, duration: duration, title: title, start: totalduration, @@ -281,6 +281,12 @@ class PlaylistLoader extends EventHandler { decryptdata : fragdecryptdata, programDateTime: programDateTime, tagList: tagList}; + if (lazyURLResolve) { + frag.relurl = value1; + frag.baseurl = baseurl; + } else { + frag.url = value1 ? this.resolve(value1, baseurl) : null; + } // only include byte range options if used/needed if(byteRangeStartOffset !== null) { frag.byteRangeStartOffset = byteRangeStartOffset; @@ -307,7 +313,12 @@ class PlaylistLoader extends EventHandler { if ((decrypturi) && (decryptmethod === 'AES-128')) { levelkey.method = decryptmethod; // URI to get the key - levelkey.uri = this.resolve(decrypturi, baseurl); + if (lazyURLResolve) { + levelkey.baseuri = baseurl; + levelkey.reluri = decrypturi; + } else { + levelkey.uri = this.resolve(decrypturi, baseurl); + } levelkey.key = null; // Initialization Vector (IV) levelkey.iv = decryptiv; @@ -336,7 +347,7 @@ class PlaylistLoader extends EventHandler { } } //logger.log('found ' + level.fragments.length + ' fragments'); - if(frag && !frag.url) { + if(frag && !(frag.url || frag.relurl)) { level.fragments.pop(); totalduration-=frag.duration; }