From cbf8c98c872da6e21a66ecc22b2d6a9420cf247e Mon Sep 17 00:00:00 2001 From: Trek H Date: Thu, 6 Feb 2020 17:37:14 +1030 Subject: [PATCH] remove cmd/mjpeg-player from av to add to vidgrind repo --- cmd/mjpeg-player/eventemitter3/LICENSE | 21 - cmd/mjpeg-player/eventemitter3/index.js | 331 --------------- cmd/mjpeg-player/favicon.ico | Bin 15406 -> 0 bytes cmd/mjpeg-player/hlsjs/LICENSE | 28 -- cmd/mjpeg-player/hlsjs/config.js | 48 --- .../hlsjs/controller/level-controller.js | 280 ------------- .../hlsjs/controller/level-helper.js | 227 ---------- .../hlsjs/controller/stream-controller.js | 113 ----- cmd/mjpeg-player/hlsjs/event-handler.js | 105 ----- cmd/mjpeg-player/hlsjs/events.js | 55 --- cmd/mjpeg-player/hlsjs/hls.js | 53 --- .../hlsjs/loader/fragment-loader.js | 137 ------- cmd/mjpeg-player/hlsjs/loader/fragment.js | 222 ---------- cmd/mjpeg-player/hlsjs/loader/level-key.js | 47 --- cmd/mjpeg-player/hlsjs/loader/level.js | 23 -- cmd/mjpeg-player/hlsjs/loader/m3u8-parser.js | 388 ------------------ .../hlsjs/loader/playlist-loader.js | 343 ---------------- cmd/mjpeg-player/hlsjs/mts-demuxer.js | 369 ----------------- cmd/mjpeg-player/hlsjs/observer.js | 38 -- cmd/mjpeg-player/hlsjs/types/loader.js | 42 -- cmd/mjpeg-player/hlsjs/utils/attr-list.js | 89 ---- cmd/mjpeg-player/hlsjs/utils/codecs.js | 98 ----- cmd/mjpeg-player/hlsjs/utils/xhr-loader.js | 189 --------- cmd/mjpeg-player/index.html | 59 --- cmd/mjpeg-player/lex-mjpeg.js | 63 --- cmd/mjpeg-player/main.js | 132 ------ cmd/mjpeg-player/player.js | 132 ------ cmd/mjpeg-player/url-toolkit/LICENSE | 190 --------- cmd/mjpeg-player/url-toolkit/url-toolkit.js | 149 ------- 29 files changed, 3971 deletions(-) delete mode 100644 cmd/mjpeg-player/eventemitter3/LICENSE delete mode 100644 cmd/mjpeg-player/eventemitter3/index.js delete mode 100644 cmd/mjpeg-player/favicon.ico delete mode 100644 cmd/mjpeg-player/hlsjs/LICENSE delete mode 100644 cmd/mjpeg-player/hlsjs/config.js delete mode 100644 cmd/mjpeg-player/hlsjs/controller/level-controller.js delete mode 100644 cmd/mjpeg-player/hlsjs/controller/level-helper.js delete mode 100644 cmd/mjpeg-player/hlsjs/controller/stream-controller.js delete mode 100644 cmd/mjpeg-player/hlsjs/event-handler.js delete mode 100644 cmd/mjpeg-player/hlsjs/events.js delete mode 100644 cmd/mjpeg-player/hlsjs/hls.js delete mode 100644 cmd/mjpeg-player/hlsjs/loader/fragment-loader.js delete mode 100644 cmd/mjpeg-player/hlsjs/loader/fragment.js delete mode 100644 cmd/mjpeg-player/hlsjs/loader/level-key.js delete mode 100644 cmd/mjpeg-player/hlsjs/loader/level.js delete mode 100644 cmd/mjpeg-player/hlsjs/loader/m3u8-parser.js delete mode 100644 cmd/mjpeg-player/hlsjs/loader/playlist-loader.js delete mode 100644 cmd/mjpeg-player/hlsjs/mts-demuxer.js delete mode 100644 cmd/mjpeg-player/hlsjs/observer.js delete mode 100644 cmd/mjpeg-player/hlsjs/types/loader.js delete mode 100755 cmd/mjpeg-player/hlsjs/utils/attr-list.js delete mode 100644 cmd/mjpeg-player/hlsjs/utils/codecs.js delete mode 100644 cmd/mjpeg-player/hlsjs/utils/xhr-loader.js delete mode 100644 cmd/mjpeg-player/index.html delete mode 100644 cmd/mjpeg-player/lex-mjpeg.js delete mode 100644 cmd/mjpeg-player/main.js delete mode 100644 cmd/mjpeg-player/player.js delete mode 100644 cmd/mjpeg-player/url-toolkit/LICENSE delete mode 100644 cmd/mjpeg-player/url-toolkit/url-toolkit.js diff --git a/cmd/mjpeg-player/eventemitter3/LICENSE b/cmd/mjpeg-player/eventemitter3/LICENSE deleted file mode 100644 index abcbd54e..00000000 --- a/cmd/mjpeg-player/eventemitter3/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Arnout Kazemier - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/cmd/mjpeg-player/eventemitter3/index.js b/cmd/mjpeg-player/eventemitter3/index.js deleted file mode 100644 index 7d65945d..00000000 --- a/cmd/mjpeg-player/eventemitter3/index.js +++ /dev/null @@ -1,331 +0,0 @@ -'use strict'; - -var has = Object.prototype.hasOwnProperty - , prefix = '~'; - -/** - * Constructor to create a storage for our `EE` objects. - * An `Events` instance is a plain object whose properties are event names. - * - * @constructor - * @private - */ -function Events() { } - -// -// We try to not inherit from `Object.prototype`. In some engines creating an -// instance in this way is faster than calling `Object.create(null)` directly. -// If `Object.create(null)` is not supported we prefix the event names with a -// character to make sure that the built-in object properties are not -// overridden or used as an attack vector. -// -if (Object.create) { - Events.prototype = Object.create(null); - - // - // This hack is needed because the `__proto__` property is still inherited in - // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5. - // - if (!new Events().__proto__) prefix = false; -} - -/** - * Representation of a single event listener. - * - * @param {Function} fn The listener function. - * @param {*} context The context to invoke the listener with. - * @param {Boolean} [once=false] Specify if the listener is a one-time listener. - * @constructor - * @private - */ -function EE(fn, context, once) { - this.fn = fn; - this.context = context; - this.once = once || false; -} - -/** - * Add a listener for a given event. - * - * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. - * @param {(String|Symbol)} event The event name. - * @param {Function} fn The listener function. - * @param {*} context The context to invoke the listener with. - * @param {Boolean} once Specify if the listener is a one-time listener. - * @returns {EventEmitter} - * @private - */ -function addListener(emitter, event, fn, context, once) { - if (typeof fn !== 'function') { - throw new TypeError('The listener must be a function'); - } - - var listener = new EE(fn, context || emitter, once) - , evt = prefix ? prefix + event : event; - - if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++; - else if (!emitter._events[evt].fn) emitter._events[evt].push(listener); - else emitter._events[evt] = [emitter._events[evt], listener]; - - return emitter; -} - -/** - * Clear event by name. - * - * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. - * @param {(String|Symbol)} evt The Event name. - * @private - */ -function clearEvent(emitter, evt) { - if (--emitter._eventsCount === 0) emitter._events = new Events(); - else delete emitter._events[evt]; -} - -/** - * Minimal `EventEmitter` interface that is molded against the Node.js - * `EventEmitter` interface. - * - * @constructor - * @public - */ -function EventEmitter() { - this._events = new Events(); - this._eventsCount = 0; -} - -/** - * Return an array listing the events for which the emitter has registered - * listeners. - * - * @returns {Array} - * @public - */ -EventEmitter.prototype.eventNames = function eventNames() { - var names = [] - , events - , name; - - if (this._eventsCount === 0) return names; - - for (name in (events = this._events)) { - if (has.call(events, name)) names.push(prefix ? name.slice(1) : name); - } - - if (Object.getOwnPropertySymbols) { - return names.concat(Object.getOwnPropertySymbols(events)); - } - - return names; -}; - -/** - * Return the listeners registered for a given event. - * - * @param {(String|Symbol)} event The event name. - * @returns {Array} The registered listeners. - * @public - */ -EventEmitter.prototype.listeners = function listeners(event) { - var evt = prefix ? prefix + event : event - , handlers = this._events[evt]; - - if (!handlers) return []; - if (handlers.fn) return [handlers.fn]; - - for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) { - ee[i] = handlers[i].fn; - } - - return ee; -}; - -/** - * Return the number of listeners listening to a given event. - * - * @param {(String|Symbol)} event The event name. - * @returns {Number} The number of listeners. - * @public - */ -EventEmitter.prototype.listenerCount = function listenerCount(event) { - var evt = prefix ? prefix + event : event - , listeners = this._events[evt]; - - if (!listeners) return 0; - if (listeners.fn) return 1; - return listeners.length; -}; - -/** - * Calls each of the listeners registered for a given event. - * - * @param {(String|Symbol)} event The event name. - * @returns {Boolean} `true` if the event had listeners, else `false`. - * @public - */ -EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { - var evt = prefix ? prefix + event : event; - - if (!this._events[evt]) return false; - - var listeners = this._events[evt] - , len = arguments.length - , args - , i; - - if (listeners.fn) { - if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); - - switch (len) { - case 1: return listeners.fn.call(listeners.context), true; - case 2: return listeners.fn.call(listeners.context, a1), true; - case 3: return listeners.fn.call(listeners.context, a1, a2), true; - case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; - case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; - case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; - } - - for (i = 1, args = new Array(len - 1); i < len; i++) { - args[i - 1] = arguments[i]; - } - - listeners.fn.apply(listeners.context, args); - } else { - var length = listeners.length - , j; - - for (i = 0; i < length; i++) { - if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true); - - switch (len) { - case 1: listeners[i].fn.call(listeners[i].context); break; - case 2: listeners[i].fn.call(listeners[i].context, a1); break; - case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; - case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break; - default: - if (!args) for (j = 1, args = new Array(len - 1); j < len; j++) { - args[j - 1] = arguments[j]; - } - - listeners[i].fn.apply(listeners[i].context, args); - } - } - } - - return true; -}; - -/** - * Add a listener for a given event. - * - * @param {(String|Symbol)} event The event name. - * @param {Function} fn The listener function. - * @param {*} [context=this] The context to invoke the listener with. - * @returns {EventEmitter} `this`. - * @public - */ -EventEmitter.prototype.on = function on(event, fn, context) { - return addListener(this, event, fn, context, false); -}; - -/** - * Add a one-time listener for a given event. - * - * @param {(String|Symbol)} event The event name. - * @param {Function} fn The listener function. - * @param {*} [context=this] The context to invoke the listener with. - * @returns {EventEmitter} `this`. - * @public - */ -EventEmitter.prototype.once = function once(event, fn, context) { - return addListener(this, event, fn, context, true); -}; - -/** - * Remove the listeners of a given event. - * - * @param {(String|Symbol)} event The event name. - * @param {Function} fn Only remove the listeners that match this function. - * @param {*} context Only remove the listeners that have this context. - * @param {Boolean} once Only remove one-time listeners. - * @returns {EventEmitter} `this`. - * @public - */ -EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) { - var evt = prefix ? prefix + event : event; - - if (!this._events[evt]) return this; - if (!fn) { - clearEvent(this, evt); - return this; - } - - var listeners = this._events[evt]; - - if (listeners.fn) { - if ( - listeners.fn === fn && - (!once || listeners.once) && - (!context || listeners.context === context) - ) { - clearEvent(this, evt); - } - } else { - for (var i = 0, events = [], length = listeners.length; i < length; i++) { - if ( - listeners[i].fn !== fn || - (once && !listeners[i].once) || - (context && listeners[i].context !== context) - ) { - events.push(listeners[i]); - } - } - - // - // Reset the array, or remove it completely if we have no more listeners. - // - if (events.length) this._events[evt] = events.length === 1 ? events[0] : events; - else clearEvent(this, evt); - } - - return this; -}; - -/** - * Remove all listeners, or those of the specified event. - * - * @param {(String|Symbol)} [event] The event name. - * @returns {EventEmitter} `this`. - * @public - */ -EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) { - var evt; - - if (event) { - evt = prefix ? prefix + event : event; - if (this._events[evt]) clearEvent(this, evt); - } else { - this._events = new Events(); - this._eventsCount = 0; - } - - return this; -}; - -// -// Alias methods names because people roll like that. -// -EventEmitter.prototype.off = EventEmitter.prototype.removeListener; -EventEmitter.prototype.addListener = EventEmitter.prototype.on; - -// -// Expose the prefix. -// -EventEmitter.prefixed = prefix; - -// -// Allow `EventEmitter` to be imported as module namespace. -// -EventEmitter.EventEmitter = EventEmitter; - -export default EventEmitter; diff --git a/cmd/mjpeg-player/favicon.ico b/cmd/mjpeg-player/favicon.ico deleted file mode 100644 index 465b02c257d3a5388ce1a7744355834fbd37fa82..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15406 zcmeHO32>9w5f(`rkPoo2&0#KI_`sKBUAAobKJh78zp*UIwk+SqMihrYN+u3zVIXPK z5E?oyB?U5cI-zMMkf8xmAS6Ii8X%;!C8VSrA($)J@olu-C+n7sEL)b%6wU1Xzx4m_ z?f3TW+ugVOM59fiJxTNMpyAbzR{s=@#--6{ettvWLp^D<9rzoAp?w}qqn+@f(L(VZ zY=SL@e#gtOA4yJVQi)*B-V(mo1xe!U%MT+eZss{j(!3p&`H_SLtuiM(U&L|iDTpZWS99@AZ$On5m%1_JWHS zV&Kkg4ieXbZnv{x*RO)GkExn|gltzRV!42b<9?a&-n=#t)aobz7@n?Q4MS{nc)npC zoIA|`Wv-XL{maiSerQ|L@BQy1;BTKq!r)*u3=Bj;b)J3gjj>Hc`_7I?lra+?JczQu zzj*mP%psSJZC3!{=HLO9f;{h=Oen`nk#6FGWBbR5os%i?;6?x2c zva|)iSLQD{Sd+ix=vb&c)BoLytdMOD(u}6njlwV@7b{q%D5*GZS$e=BA;+yt7(=&P z?lxqsM%yqTj`MC;(K#D^>GgFq@1cGSlxKzQ(_vVp z6fIgNnENI6HSWCicoBm%J!Eroa`IDE1&p>L7JX3v%uatX+IHetuiLE+#Zi(J|Ffg* zV=V1Z9|r465?f0Iz5`?FXDn^yX$#-f`7T{QLKYqEsVgjtcSCZwgWNRjh`-hKW$Rjz z8+OpZe=1OM9LE?oMz04dQ95@0(X?0h2}ZS@SqFyxaTh5_T6qsGs-ycCIk zf@#y|82e1)d*bW+`l8|2n?tP1fcTN(*qQogC2ymdGThEvBwK^$wC z2O^J+tE1rTX)ZjY^s~+%{oL^n6K%#DIzA7(o;w`?C2@41V%^|{mc?-9GZysrFoF2n zwRMZeo&WhWLGY7$e;f}Rx_)5kWlG`zXYlXrWI|^LThCV~D`)Z7@iV_Z=I@;OHNO@U% zkH~xXwg}t@80Z?mA|eze_m?`|~w^#B(t zvwieDjQy+m{gD4N_0wXWD@ar3f-K$NZjz?X0Wr^IM7#+WNy>sFx;%k7HfMZS zNeyUs1}{qo<%MYjiRYnD#0Dr6!C_s;R13;Kg) zf;pF!MXaK>Hrf+9{^UwgOP+3&q~=#-1s^C8%)jqQS>U)Hpsro5E@Ez3(NGYA?4}s= z{(rtFc@j*~(khWgnkT_mH6Jo(t5=u$)|Frx#N%Nb0YHMqM zw6causuprSB6%(??qO|}wtuHS$9(D_=0vVGE=ycvHijpfWm(Z!SNIF&XL~hcc2cg$ zPC2MC_8Kfr@cl-S&#jYZMrm}3_?oIb#%7!&;_D_#S4KLQ8hxVTXPqT}stwn4i!75b zNm+EB=$W1J7%5MOzbC@=pva4UU8Sn@XcWh<7joQ*Uz_Z*H^>2HElUqL)l`umuE=Md zB)R=ZMfO^pYOKZGuA>sNIPYjlPy5ZowbFGCJ|@GkewYTKI1j7Xp6d4 z6&ag`@}Z`Cts$Ny-|Ggll<7z_kUShjGoUO9>uIrA6CH!h&`-A`MSpUr(ruQYYxZc`ad#Tu^*tZR&b zbAL~S&Q2B#V7_KxfZH|kU z;A!&RBRdpv@Xg#hhYUj9e!LMhV^c)!)0!a zU3cH#7Xb&~TL@)|2Hha_it60i@Y?!dICeM@Zr@_V_xBj^+J*?MJ(!Gh82n2RKhHV% z`y=4cz6BcmBrhgQ@q`U)m~i442kza)HK)y$c6LS~{_shO|M2?@p(fuOo^J|-gZntp z{#~3!j(4)+9~g*)bDu}T{=Gc-&sS{d>1Jrodzjmd$%=ny4Ktt*YlGyRiaBoGI+?8a zb@*&uALDnt53?n68UIlH=7O=A8?;D#;SW1_aPgm9 z97hb+c87GzKKxFOpSAHP*}n+cSbI$LfHgIX;hjAk`1-4OO-$6$G4l9RB@0HK&4jZPySFUFvS0wabPShN%DkM6+!Uc+dPctS?~+o$JmVjQ3i?}Up%+W z#u z+w$_v--u&7zZAsm#uC2sLN9Puhz@blUI~yHWu?d?;!q1ss1t`P4)XgRh1jitjJj+&j>k+ z|L>Ww^V)XKy;{WcxT`GStZQqlb!n@ub*T}>HsiVX8;`&okpD~c;@SKIN+CZ2i`bg; zBG1)|<~GVww&Qc3X=}Z37DeG7J0E`@OTd8=_#Y2!$T$E1 diff --git a/cmd/mjpeg-player/hlsjs/LICENSE b/cmd/mjpeg-player/hlsjs/LICENSE deleted file mode 100644 index 8f263a03..00000000 --- a/cmd/mjpeg-player/hlsjs/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -Copyright (c) 2017 Dailymotion (http://www.dailymotion.com) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - -src/remux/mp4-generator.js and src/demux/exp-golomb.js implementation in this project -are derived from the HLS library for video.js (https://github.com/videojs/videojs-contrib-hls) - -That work is also covered by the Apache 2 License, following copyright: -Copyright (c) 2013-2015 Brightcove - - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/cmd/mjpeg-player/hlsjs/config.js b/cmd/mjpeg-player/hlsjs/config.js deleted file mode 100644 index 94797f6b..00000000 --- a/cmd/mjpeg-player/hlsjs/config.js +++ /dev/null @@ -1,48 +0,0 @@ -/* -AUTHOR - Trek Hopton - -LICENSE - This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean) - - It is free software: you can redistribute it and/or modify them - under the terms of the GNU General Public License as published by the - Free Software Foundation, either version 3 of the License, or (at your - option) any later version. - - 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 XhrLoader from './utils/xhr-loader.js'; - -// If possible, keep hlsDefaultConfig shallow -// It is cloned whenever a new Hls instance is created, by keeping the config -// shallow the properties are cloned, and we don't end up manipulating the default -export const hlsDefaultConfig = { - startPosition: -1, // used by stream-controller - manifestLoadingTimeOut: 10000, // used by playlist-loader - manifestLoadingMaxRetry: 1, // used by playlist-loader - manifestLoadingRetryDelay: 1000, // used by playlist-loader - manifestLoadingMaxRetryTimeout: 64000, // used by playlist-loader - startLevel: void 0, // used by level-controller - levelLoadingTimeOut: 10000, // used by playlist-loader - levelLoadingMaxRetry: 4, // used by playlist-loader - levelLoadingRetryDelay: 1000, // used by playlist-loader - levelLoadingMaxRetryTimeout: 64000, // used by playlist-loader - fragLoadingTimeOut: 20000, // used by fragment-loader - fragLoadingMaxRetry: 6, // used by fragment-loader - fragLoadingRetryDelay: 1000, // used by fragment-loader - fragLoadingMaxRetryTimeout: 64000, // used by fragment-loader - loader: XhrLoader, - fLoader: void 0, // used by fragment-loader - pLoader: void 0, // used by playlist-loader - xhrSetup: void 0, // used by xhr-loader -}; \ No newline at end of file diff --git a/cmd/mjpeg-player/hlsjs/controller/level-controller.js b/cmd/mjpeg-player/hlsjs/controller/level-controller.js deleted file mode 100644 index f5fad626..00000000 --- a/cmd/mjpeg-player/hlsjs/controller/level-controller.js +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Level Controller -*/ - -import Event from '../events.js'; -import EventHandler from '../event-handler.js'; -import { addGroupId, computeReloadInterval } from './level-helper.js'; - -const { performance } = window; -let chromeOrFirefox; - -export default class LevelController extends EventHandler { - constructor(hls) { - super(hls, - Event.MANIFEST_LOADED, - Event.LEVEL_LOADED); - - this.canload = false; - this.curLvlIdx = 0; - this.manualLvlIdx = -1; - this.timer = null; - - chromeOrFirefox = /chrome|firefox/.test(navigator.userAgent.toLowerCase()); - } - - onHandlerDestroying() { - this.clearTimer(); - this.manualLvlIdx = -1; - } - - clearTimer() { - if (this.timer !== null) { - clearTimeout(this.timer); - this.timer = null; - } - } - - startLoad() { - let levels = this._levels; - - this.canload = true; - this.levelRetryCount = 0; - - // clean up live level details to force reload them, and reset load errors - if (levels) { - levels.forEach(level => { - level.loadError = 0; - const levelDetails = level.details; - if (levelDetails && levelDetails.live) { - level.details = undefined; - } - }); - } - // speed up live playlist refresh if timer exists - if (this.timer !== null) { - this.loadLevel(); - } - } - - stopLoad() { - this.canload = false; - } - - onManifestLoaded(data) { - let levels = []; - let audioTracks = []; - let bitrateStart; - let levelSet = {}; - let levelFromSet = null; - let videoCodecFound = false; - let audioCodecFound = false; - - // regroup redundant levels together - data.levels.forEach(level => { - const attributes = level.attrs; - level.loadError = 0; - level.fragmentError = false; - - videoCodecFound = videoCodecFound || !!level.videoCodec; - audioCodecFound = audioCodecFound || !!level.audioCodec; - - levelFromSet = levelSet[level.bitrate]; // FIXME: we would also have to match the resolution here - - if (!levelFromSet) { - level.url = [level.url]; - level.urlId = 0; - levelSet[level.bitrate] = level; - levels.push(level); - } else { - levelFromSet.url.push(level.url); - } - - if (attributes) { - if (attributes.AUDIO) { - audioCodecFound = true; - addGroupId(levelFromSet || level, 'audio', attributes.AUDIO); - } - if (attributes.SUBTITLES) { - addGroupId(levelFromSet || level, 'text', attributes.SUBTITLES); - } - } - }); - - if (levels.length > 0) { - // start bitrate is the first bitrate of the manifest - bitrateStart = levels[0].bitrate; - // sort level on bitrate - levels.sort((a, b) => a.bitrate - b.bitrate); - this._levels = levels; - // find index of first level in sorted levels - for (let i = 0; i < levels.length; i++) { - if (levels[i].bitrate === bitrateStart) { - this._firstLevel = i; - break; - } - } - - // Audio is only alternate if manifest include a URI along with the audio group tag - this.hls.trigger(Event.MANIFEST_PARSED, { - levels, - audioTracks, - firstLevel: this._firstLevel, - stats: data.stats, - audio: audioCodecFound, - video: videoCodecFound, - altAudio: audioTracks.some(t => !!t.url) - }); - } else { - this.hls.trigger(Event.ERROR, { - type: ErrorTypes.MEDIA_ERROR, - details: ErrorDetails.MANIFEST_INCOMPATIBLE_CODECS_ERROR, - fatal: true, - url: this.hls.url, - reason: 'no level with compatible codecs found in manifest' - }); - } - } - - get levels() { - return this._levels; - } - - get level() { - return this.curLvlIdx; - } - - set level(newLevel) { - let levels = this._levels; - if (levels) { - newLevel = Math.min(newLevel, levels.length - 1); - if (this.curLvlIdx !== newLevel || !levels[newLevel].details) { - this.setLevelInternal(newLevel); - } - } - } - - setLevelInternal(newLevel) { - const levels = this._levels; - const hls = this.hls; - // check if level idx is valid - if (newLevel >= 0 && newLevel < levels.length) { - // stopping live reloading timer if any - this.clearTimer(); - if (this.curLvlIdx !== newLevel) { - console.log(`switching to level ${newLevel}`); - this.curLvlIdx = newLevel; - const levelProperties = levels[newLevel]; - levelProperties.level = newLevel; - hls.trigger(Event.LEVEL_SWITCHING, levelProperties); - } - const level = levels[newLevel]; - const levelDetails = level.details; - - // check if we need to load playlist for this level - if (!levelDetails || levelDetails.live) { - // level not retrieved yet, or live playlist we need to (re)load it - let urlId = level.urlId; - hls.trigger(Event.LEVEL_LOADING, { url: level.url[urlId], level: newLevel, id: urlId }); - } - } else { - // invalid level id given, trigger error - hls.trigger(Event.ERROR, { - type: ErrorTypes.OTHER_ERROR, - details: ErrorDetails.LEVEL_SWITCH_ERROR, - level: newLevel, - fatal: false, - reason: 'invalid level idx' - }); - } - } - - get manualLevel() { - return this.manualLvlIdx; - } - - set manualLevel(newLevel) { - this.manualLvlIdx = newLevel; - if (this._startLevel === undefined) { - this._startLevel = newLevel; - } - - if (newLevel !== -1) { - this.level = newLevel; - } - } - - get firstLevel() { - return this._firstLevel; - } - - set firstLevel(newLevel) { - this._firstLevel = newLevel; - } - - get startLevel() { - // hls.startLevel takes precedence over config.startLevel - // if none of these values are defined, fallback on this._firstLevel (first quality level appearing in variant manifest) - if (this._startLevel === undefined) { - let configStartLevel = this.hls.config.startLevel; - if (configStartLevel !== undefined) { - return configStartLevel; - } else { - return this._firstLevel; - } - } else { - return this._startLevel; - } - } - - set startLevel(newLevel) { - this._startLevel = newLevel; - } - - onLevelLoaded(data) { - const { level, details } = data; - const curLevel = this._levels[level]; - // if current playlist is a live playlist, arm a timer to reload it - if (details.live) { - const reloadInterval = computeReloadInterval(curLevel.details, details, data.stats.trequest); - console.log(`live playlist, reload in ${Math.round(reloadInterval)} ms`); - this.timer = setTimeout(() => this.loadLevel(), reloadInterval); - } else { - this.clearTimer(); - } - } - - loadLevel() { - console.log('call to loadLevel, index: ' + this.curLvlIdx + "canload: " + this.canload); - - if (this.curLvlIdx !== null && this.canload) { - const levelObject = this._levels[this.curLvlIdx]; - - if (typeof levelObject === 'object' && - levelObject.url.length > 0) { - const level = this.curLvlIdx; - const id = levelObject.urlId; - const url = levelObject.url[id]; - - console.log(`Attempt loading level index ${level} with URL-id ${id}`); - - this.hls.trigger(Event.LEVEL_LOADING, { url, level, id }); - } - } - } - - get nextLoadLevel() { - if (this.manualLvlIdx !== -1) { - return this.manualLvlIdx; - } else { - return this.hls.nextAutoLevel; - } - } - - set nextLoadLevel(nextLevel) { - this.level = nextLevel; - if (this.manualLvlIdx === -1) { - this.hls.nextAutoLevel = nextLevel; - } - } -} diff --git a/cmd/mjpeg-player/hlsjs/controller/level-helper.js b/cmd/mjpeg-player/hlsjs/controller/level-helper.js deleted file mode 100644 index 65d1c053..00000000 --- a/cmd/mjpeg-player/hlsjs/controller/level-helper.js +++ /dev/null @@ -1,227 +0,0 @@ -/** - * @module LevelHelper - * - * Providing methods dealing with playlist sliding and drift - * - * TODO: Create an actual `Level` class/model that deals with all this logic in an object-oriented-manner. - * - * */ - -export function addGroupId(level, type, id) { - switch (type) { - case 'audio': - if (!level.audioGroupIds) { - level.audioGroupIds = []; - } - level.audioGroupIds.push(id); - break; - case 'text': - if (!level.textGroupIds) { - level.textGroupIds = []; - } - level.textGroupIds.push(id); - break; - } -} - -export function updatePTS(fragments, fromIdx, toIdx) { - let fragFrom = fragments[fromIdx], fragTo = fragments[toIdx], fragToPTS = fragTo.startPTS; - // if we know startPTS[toIdx] - if (Number.isFinite(fragToPTS)) { - // update fragment duration. - // it helps to fix drifts between playlist reported duration and fragment real duration - if (toIdx > fromIdx) { - fragFrom.duration = fragToPTS - fragFrom.start; - if (fragFrom.duration < 0) { - console.warn(`negative duration computed for frag ${fragFrom.sn},level ${fragFrom.level}, there should be some duration drift between playlist and fragment!`); - } - } else { - fragTo.duration = fragFrom.start - fragToPTS; - if (fragTo.duration < 0) { - console.warn(`negative duration computed for frag ${fragTo.sn},level ${fragTo.level}, there should be some duration drift between playlist and fragment!`); - } - } - } else { - // we dont know startPTS[toIdx] - if (toIdx > fromIdx) { - fragTo.start = fragFrom.start + fragFrom.duration; - } else { - fragTo.start = Math.max(fragFrom.start - fragTo.duration, 0); - } - } -} - -export function updateFragPTSDTS(details, frag, startPTS, endPTS, startDTS, endDTS) { - // update frag PTS/DTS - let maxStartPTS = startPTS; - if (Number.isFinite(frag.startPTS)) { - // delta PTS between audio and video - let deltaPTS = Math.abs(frag.startPTS - startPTS); - if (!Number.isFinite(frag.deltaPTS)) { - frag.deltaPTS = deltaPTS; - } else { - frag.deltaPTS = Math.max(deltaPTS, frag.deltaPTS); - } - - maxStartPTS = Math.max(startPTS, frag.startPTS); - startPTS = Math.min(startPTS, frag.startPTS); - endPTS = Math.max(endPTS, frag.endPTS); - startDTS = Math.min(startDTS, frag.startDTS); - endDTS = Math.max(endDTS, frag.endDTS); - } - - const drift = startPTS - frag.start; - frag.start = frag.startPTS = startPTS; - frag.maxStartPTS = maxStartPTS; - frag.endPTS = endPTS; - frag.startDTS = startDTS; - frag.endDTS = endDTS; - frag.duration = endPTS - startPTS; - - const sn = frag.sn; - // exit if sn out of range - if (!details || sn < details.startSN || sn > details.endSN) { - return 0; - } - - let fragIdx, fragments, i; - fragIdx = sn - details.startSN; - fragments = details.fragments; - // update frag reference in fragments array - // rationale is that fragments array might not contain this frag object. - // this will happen if playlist has been refreshed between frag loading and call to updateFragPTSDTS() - // if we don't update frag, we won't be able to propagate PTS info on the playlist - // resulting in invalid sliding computation - fragments[fragIdx] = frag; - // adjust fragment PTS/duration from seqnum-1 to frag 0 - for (i = fragIdx; i > 0; i--) { - updatePTS(fragments, i, i - 1); - } - - // adjust fragment PTS/duration from seqnum to last frag - for (i = fragIdx; i < fragments.length - 1; i++) { - updatePTS(fragments, i, i + 1); - } - - details.PTSKnown = true; - return drift; -} - -export function mergeDetails(oldDetails, newDetails) { - // potentially retrieve cached initsegment - if (newDetails.initSegment && oldDetails.initSegment) { - newDetails.initSegment = oldDetails.initSegment; - } - - // check if old/new playlists have fragments in common - // loop through overlapping SN and update startPTS , cc, and duration if any found - let ccOffset = 0; - let PTSFrag; - mapFragmentIntersection(oldDetails, newDetails, (oldFrag, newFrag) => { - ccOffset = oldFrag.cc - newFrag.cc; - if (Number.isFinite(oldFrag.startPTS)) { - newFrag.start = newFrag.startPTS = oldFrag.startPTS; - newFrag.endPTS = oldFrag.endPTS; - newFrag.duration = oldFrag.duration; - newFrag.backtracked = oldFrag.backtracked; - newFrag.dropped = oldFrag.dropped; - PTSFrag = newFrag; - } - // PTS is known when there are overlapping segments - newDetails.PTSKnown = true; - }); - - if (!newDetails.PTSKnown) { - return; - } - - if (ccOffset) { - console.log('discontinuity sliding from playlist, take drift into account'); - const newFragments = newDetails.fragments; - for (let i = 0; i < newFragments.length; i++) { - newFragments[i].cc += ccOffset; - } - } - - // if at least one fragment contains PTS info, recompute PTS information for all fragments - if (PTSFrag) { - updateFragPTSDTS(newDetails, PTSFrag, PTSFrag.startPTS, PTSFrag.endPTS, PTSFrag.startDTS, PTSFrag.endDTS); - } else { - // ensure that delta is within oldFragments range - // also adjust sliding in case delta is 0 (we could have old=[50-60] and new=old=[50-61]) - // in that case we also need to adjust start offset of all fragments - adjustSliding(oldDetails, newDetails); - } - // if we are here, it means we have fragments overlapping between - // old and new level. reliable PTS info is thus relying on old level - newDetails.PTSKnown = oldDetails.PTSKnown; -} - -export function mergeSubtitlePlaylists(oldPlaylist, newPlaylist, referenceStart = 0) { - let lastIndex = -1; - mapFragmentIntersection(oldPlaylist, newPlaylist, (oldFrag, newFrag, index) => { - newFrag.start = oldFrag.start; - lastIndex = index; - }); - - const frags = newPlaylist.fragments; - if (lastIndex < 0) { - frags.forEach(frag => { - frag.start += referenceStart; - }); - return; - } - - for (let i = lastIndex + 1; i < frags.length; i++) { - frags[i].start = (frags[i - 1].start + frags[i - 1].duration); - } -} - -export function mapFragmentIntersection(oldPlaylist, newPlaylist, intersectionFn) { - if (!oldPlaylist || !newPlaylist) { - return; - } - - const start = Math.max(oldPlaylist.startSN, newPlaylist.startSN) - newPlaylist.startSN; - const end = Math.min(oldPlaylist.endSN, newPlaylist.endSN) - newPlaylist.startSN; - const delta = newPlaylist.startSN - oldPlaylist.startSN; - - for (let i = start; i <= end; i++) { - const oldFrag = oldPlaylist.fragments[delta + i]; - const newFrag = newPlaylist.fragments[i]; - if (!oldFrag || !newFrag) { - break; - } - intersectionFn(oldFrag, newFrag, i); - } -} - -export function adjustSliding(oldPlaylist, newPlaylist) { - const delta = newPlaylist.startSN - oldPlaylist.startSN; - const oldFragments = oldPlaylist.fragments; - const newFragments = newPlaylist.fragments; - - if (delta < 0 || delta > oldFragments.length) { - return; - } - for (let i = 0; i < newFragments.length; i++) { - newFragments[i].start += oldFragments[delta].start; - } -} - -export function computeReloadInterval(currentPlaylist, newPlaylist, lastRequestTime) { - let reloadInterval = 1000 * (newPlaylist.averagetargetduration ? newPlaylist.averagetargetduration : newPlaylist.targetduration); - const minReloadInterval = reloadInterval / 2; - if (currentPlaylist && newPlaylist.endSN === currentPlaylist.endSN) { - // follow HLS Spec, If the client reloads a Playlist file and finds that it has not - // changed then it MUST wait for a period of one-half the target - // duration before retrying. - reloadInterval = minReloadInterval; - } - - if (lastRequestTime) { - reloadInterval = Math.max(minReloadInterval, reloadInterval - (window.performance.now() - lastRequestTime)); - } - // in any case, don't reload more than half of target duration - return Math.round(reloadInterval); -} diff --git a/cmd/mjpeg-player/hlsjs/controller/stream-controller.js b/cmd/mjpeg-player/hlsjs/controller/stream-controller.js deleted file mode 100644 index 21687ce3..00000000 --- a/cmd/mjpeg-player/hlsjs/controller/stream-controller.js +++ /dev/null @@ -1,113 +0,0 @@ -/* -AUTHOR - Trek Hopton - -LICENSE - This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean) - - It is free software: you can redistribute it and/or modify them - under the terms of the GNU General Public License as published by the - Free Software Foundation, either version 3 of the License, or (at your - option) any later version. - - 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. -*/ - -/* - * Stream Controller -*/ - -import Event from '../events.js'; -import EventHandler from '../event-handler.js'; - -class StreamController extends EventHandler { - constructor(hls) { - super(hls, - Event.LEVEL_LOADED, - Event.FRAG_LOADED); - this.hls = hls; - this.config = hls.config; - this.audioCodecSwap = false; - this.stallReported = false; - this.gapController = null; - this.currentFragIdx = 0; - this.lastSN = 0; - this.fragments = []; - } - - _fetchPayloadOrEos(levelDetails) { - // Keep track of any new frags and load them. - for (let i = 0; i < levelDetails.fragments.length; i++) { - let frag = levelDetails.fragments[i]; - if (frag.sn > this.lastSN) { - console.log("adding fragment: " + frag.sn); - this.fragments.push(frag); - this.lastSN = frag.sn; - } - } - this._loadFragment(); - } - - _loadFragment() { - let fragLen = this.fragments.length; - if (this.currentFragIdx >= fragLen) { - return; - } - this.hls.trigger(Event.FRAG_LOADING, { frag: this.fragments[this.currentFragIdx++] }); - } - - onLevelLoaded(data) { - const newDetails = data.details; - const newLevelId = data.level; - const levelDetails = data.details; - const duration = newDetails.totalduration; - let sliding = 0; - - console.log(`level ${newLevelId} loaded [${newDetails.startSN},${newDetails.endSN}],duration:${duration}`); - - // override level info - this.levelLastLoaded = newLevelId; - this.hls.trigger(Event.LEVEL_UPDATED, { details: newDetails, level: newLevelId }); - - if (this.startFragRequested === false) { - // compute start position if set to -1. use it straight away if value is defined - if (this.startPosition === -1 || this.lastCurrentTime === -1) { - // first, check if start time offset has been set in playlist, if yes, use this value - let startTimeOffset = newDetails.startTimeOffset; - if (Number.isFinite(startTimeOffset)) { - if (startTimeOffset < 0) { - console.log(`negative start time offset ${startTimeOffset}, count from end of last fragment`); - startTimeOffset = sliding + duration + startTimeOffset; - } - console.log(`start time offset found in playlist, adjust startPosition to ${startTimeOffset}`); - this.startPosition = startTimeOffset; - } else { - // if live playlist, set start position to be fragment N-this.config.liveSyncDurationCount (usually 3) - if (newDetails.live) { - console.log("handling of this case is not implemented"); - } else { - this.startPosition = 0; - } - } - this.lastCurrentTime = this.startPosition; - } - this.nextLoadPosition = this.startPosition; - } - this._fetchPayloadOrEos(levelDetails); - } - - onFragLoaded(data) { - this.hls.loadSuccess(data.payload); - this._loadFragment(); - - } -} -export default StreamController; diff --git a/cmd/mjpeg-player/hlsjs/event-handler.js b/cmd/mjpeg-player/hlsjs/event-handler.js deleted file mode 100644 index 24735225..00000000 --- a/cmd/mjpeg-player/hlsjs/event-handler.js +++ /dev/null @@ -1,105 +0,0 @@ -/* -AUTHOR - Trek Hopton - -LICENSE - This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean) - - It is free software: you can redistribute it and/or modify them - under the terms of the GNU General Public License as published by the - Free Software Foundation, either version 3 of the License, or (at your - option) any later version. - - 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. -*/ - -/* -* -* All objects in the event handling chain should inherit from this class -* -*/ -import Event from './events.js'; - -const FORBIDDEN_EVENT_NAMES = { - 'hlsEventGeneric': true, - 'hlsHandlerDestroying': true, - 'hlsHandlerDestroyed': true -}; - -class EventHandler { - constructor(hls, ...events) { - this.hls = hls; - this.onEvent = this.onEvent.bind(this); - this.handledEvents = events; - this.useGenericHandler = true; - - this.registerListeners(); - } - - destroy() { - this.onHandlerDestroying(); - this.unregisterListeners(); - this.onHandlerDestroyed(); - } - - onHandlerDestroying() { } - onHandlerDestroyed() { } - - isEventHandler() { - return typeof this.handledEvents === 'object' && this.handledEvents.length && typeof this.onEvent === 'function'; - } - - registerListeners() { - if (this.isEventHandler()) { - this.handledEvents.forEach(function (event) { - if (FORBIDDEN_EVENT_NAMES[event]) { - throw new Error('Forbidden event-name: ' + event); - } - - this.hls.on(event, this.onEvent); - }, this); - } - } - - unregisterListeners() { - if (this.isEventHandler()) { - this.handledEvents.forEach(function (event) { - this.hls.off(event, this.onEvent); - }, this); - } - } - - /** - * arguments: event (string), data (any) - */ - onEvent(event, data) { - this.onEventGeneric(event, data); - } - - onEventGeneric(event, data) { - let eventToFunction = function (event, data) { - let funcName = 'on' + event.replace('hls', ''); - if (typeof this[funcName] !== 'function') { - throw new Error(`Event ${event} has no generic handler in this ${this.constructor.name} class (tried ${funcName})`); - } - - return this[funcName].bind(this, data); - }; - try { - eventToFunction.call(this, event, data).call(); - } catch (err) { - console.error(`An internal error happened while handling event ${event}. Error message: "${err.message}". Here is a stacktrace:`, err); - this.hls.trigger(Event.ERROR, { type: ErrorTypes.OTHER_ERROR, details: ErrorDetails.INTERNAL_EXCEPTION, fatal: false, event: event, err: err }); - } - } -} - -export default EventHandler; diff --git a/cmd/mjpeg-player/hlsjs/events.js b/cmd/mjpeg-player/hlsjs/events.js deleted file mode 100644 index e71d71c5..00000000 --- a/cmd/mjpeg-player/hlsjs/events.js +++ /dev/null @@ -1,55 +0,0 @@ -/* -AUTHOR - Trek Hopton - -LICENSE - This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean) - - It is free software: you can redistribute it and/or modify them - under the terms of the GNU General Public License as published by the - Free Software Foundation, either version 3 of the License, or (at your - option) any later version. - - 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. -*/ - -/** - * @readonly - * @enum {string} - */ -const HlsEvents = { - // fired to signal that a manifest loading starts - data: { url : manifestURL} - MANIFEST_LOADING: 'hlsManifestLoading', - // fired after manifest has been loaded - data: { levels : [available quality levels], audioTracks : [ available audio tracks], url : manifestURL, stats : { trequest, tfirst, tload, mtime}} - MANIFEST_LOADED: 'hlsManifestLoaded', - // fired when a level playlist loading starts - data: { url : level URL, level : id of level being loaded} - LEVEL_LOADING: 'hlsLevelLoading', - // fired when a level playlist loading finishes - data: { details : levelDetails object, level : id of loaded level, stats : { trequest, tfirst, tload, mtime} } - LEVEL_LOADED: 'hlsLevelLoaded', - // fired when a level's details have been updated based on previous details, after it has been loaded - data: { details : levelDetails object, level : id of updated level } - LEVEL_UPDATED: 'hlsLevelUpdated', - // fired when an audio track loading starts - data: { url : audio track URL, id : audio track id } - AUDIO_TRACK_LOADING: 'hlsAudioTrackLoading', - // fired when an audio track loading finishes - data: { details : levelDetails object, id : audio track id, stats : { trequest, tfirst, tload, mtime } } - AUDIO_TRACK_LOADED: 'hlsAudioTrackLoaded', - // fired when a subtitle track loading starts - data: { url : subtitle track URL, id : subtitle track id } - SUBTITLE_TRACK_LOADING: 'hlsSubtitleTrackLoading', - // fired when a subtitle track loading finishes - data: { details : levelDetails object, id : subtitle track id, stats : { trequest, tfirst, tload, mtime } } - SUBTITLE_TRACK_LOADED: 'hlsSubtitleTrackLoaded', - // fired when a fragment loading starts - data: { frag : fragment object } - FRAG_LOADING: 'hlsFragLoading', - // fired when a fragment loading is progressing - data: { frag : fragment object, { trequest, tfirst, loaded } } - FRAG_LOAD_PROGRESS: 'hlsFragLoadProgress', - // fired when a fragment loading is completed - data: { frag : fragment object, payload : fragment payload, stats : { trequest, tfirst, tload, length } } - FRAG_LOADED: 'hlsFragLoaded' -}; - -export default HlsEvents; diff --git a/cmd/mjpeg-player/hlsjs/hls.js b/cmd/mjpeg-player/hlsjs/hls.js deleted file mode 100644 index d94c4c03..00000000 --- a/cmd/mjpeg-player/hlsjs/hls.js +++ /dev/null @@ -1,53 +0,0 @@ -/* -AUTHOR - Trek Hopton - -LICENSE - This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean) - - It is free software: you can redistribute it and/or modify them - under the terms of the GNU General Public License as published by the - Free Software Foundation, either version 3 of the License, or (at your - option) any later version. - - 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 URLToolkit from '../url-toolkit/url-toolkit.js'; -import HlsEvents from './events.js'; -import PlaylistLoader from './loader/playlist-loader.js'; -import FragmentLoader from './loader/fragment-loader.js'; -import StreamController from './controller/stream-controller.js'; -import LevelController from './controller/level-controller.js'; -import { hlsDefaultConfig } from './config.js'; -import { Observer } from './observer.js'; - -class Hls extends Observer { - constructor() { - super(); - this.pLoader = new PlaylistLoader(this); - this.streamController = new StreamController(this); - this.levelController = new LevelController(this); - this.fragmentLoader = new FragmentLoader(this); - - this.config = hlsDefaultConfig; - } - - // url is the source URL. Can be relative or absolute. - loadSource(url, callback) { - this.levelController.startLoad(); - this.loadSuccess = callback; - url = URLToolkit.buildAbsoluteURL(window.location.href, url, { alwaysNormalize: true }); - this.trigger(HlsEvents.MANIFEST_LOADING, { url: url }); - } -} - -export default Hls \ No newline at end of file diff --git a/cmd/mjpeg-player/hlsjs/loader/fragment-loader.js b/cmd/mjpeg-player/hlsjs/loader/fragment-loader.js deleted file mode 100644 index b7e1f9e1..00000000 --- a/cmd/mjpeg-player/hlsjs/loader/fragment-loader.js +++ /dev/null @@ -1,137 +0,0 @@ -/* -AUTHOR - Trek Hopton - -LICENSE - This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean) - - It is free software: you can redistribute it and/or modify them - under the terms of the GNU General Public License as published by the - Free Software Foundation, either version 3 of the License, or (at your - option) any later version. - - 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. -*/ - -/* - * Fragment Loader -*/ - -import Event from '../events.js'; -import EventHandler from '../event-handler.js'; - -class FragmentLoader extends EventHandler { - constructor(hls) { - super(hls, Event.FRAG_LOADING); - this.loaders = {}; - } - - destroy() { - let loaders = this.loaders; - for (let loaderName in loaders) { - let loader = loaders[loaderName]; - if (loader) { - loader.destroy(); - } - } - this.loaders = {}; - - super.destroy(); - } - - onFragLoading(data) { - const frag = data.frag, - type = frag.type, - loaders = this.loaders, - config = this.hls.config, - FragmentILoader = config.fLoader, - DefaultILoader = config.loader; - - // reset fragment state - frag.loaded = 0; - - let loader = loaders[type]; - if (loader) { - console.warn(`abort previous fragment loader for type: ${type}`); - loader.abort(); - } - - loader = loaders[type] = frag.loader = - config.fLoader ? new FragmentILoader(config) : new DefaultILoader(config); - - let loaderContext, loaderConfig, loaderCallbacks; - - loaderContext = { url: frag.url, frag: frag, responseType: 'arraybuffer', progressData: false }; - - let start = frag.byteRangeStartOffset, - end = frag.byteRangeEndOffset; - - if (Number.isFinite(start) && Number.isFinite(end)) { - loaderContext.rangeStart = start; - loaderContext.rangeEnd = end; - } - - loaderConfig = { - timeout: config.fragLoadingTimeOut, - maxRetry: 0, - retryDelay: 0, - maxRetryDelay: config.fragLoadingMaxRetryTimeout - }; - - loaderCallbacks = { - onSuccess: this.loadsuccess.bind(this), - onError: this.loaderror.bind(this), - onTimeout: this.loadtimeout.bind(this), - onProgress: this.loadprogress.bind(this) - }; - - loader.load(loaderContext, loaderConfig, loaderCallbacks); - } - - loadsuccess(response, stats, context, networkDetails = null) { - let payload = response.data, frag = context.frag; - // detach fragment loader on load success - frag.loader = undefined; - this.loaders[frag.type] = undefined; - this.hls.trigger(Event.FRAG_LOADED, { payload: payload, frag: frag, stats: stats, networkDetails: networkDetails }); - } - - loaderror(response, context, networkDetails = null) { - const frag = context.frag; - let loader = frag.loader; - if (loader) { - loader.abort(); - } - - this.loaders[frag.type] = undefined; - this.hls.trigger(Event.ERROR, { type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.FRAG_LOAD_ERROR, fatal: false, frag: context.frag, response: response, networkDetails: networkDetails }); - } - - loadtimeout(stats, context, networkDetails = null) { - const frag = context.frag; - let loader = frag.loader; - if (loader) { - loader.abort(); - } - - this.loaders[frag.type] = undefined; - this.hls.trigger(Event.ERROR, { type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.FRAG_LOAD_TIMEOUT, fatal: false, frag: context.frag, networkDetails: networkDetails }); - } - - // data will be used for progressive parsing - loadprogress(stats, context, data, networkDetails = null) { // jshint ignore:line - let frag = context.frag; - frag.loaded = stats.loaded; - this.hls.trigger(Event.FRAG_LOAD_PROGRESS, { frag: frag, stats: stats, networkDetails: networkDetails }); - } -} - -export default FragmentLoader; diff --git a/cmd/mjpeg-player/hlsjs/loader/fragment.js b/cmd/mjpeg-player/hlsjs/loader/fragment.js deleted file mode 100644 index 01fb0509..00000000 --- a/cmd/mjpeg-player/hlsjs/loader/fragment.js +++ /dev/null @@ -1,222 +0,0 @@ -/* -AUTHOR - Trek Hopton - -LICENSE - This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean) - - It is free software: you can redistribute it and/or modify them - under the terms of the GNU General Public License as published by the - Free Software Foundation, either version 3 of the License, or (at your - option) any later version. - - 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 URLToolkit from '../../url-toolkit/url-toolkit.js'; -import LevelKey from './level-key.js'; - -export const ElementaryStreamTypes = { - AUDIO: 'audio', - VIDEO: 'video' -} - -export default class Fragment { - constructor() { - this._url = null; - this._byteRange = null; - this._decryptdata = null; - - // Holds the types of data this fragment supports - this._elementaryStreams = { - [ElementaryStreamTypes.AUDIO]: false, - [ElementaryStreamTypes.VIDEO]: false - }; - - // deltaPTS tracks the change in presentation timestamp between fragments - this.deltaPTS = 0; - - this.rawProgramDateTime = null; - this.programDateTime = null; - this.title = null; - this.tagList = []; - - // TODO: Move at least baseurl to constructor. - // Currently we do a two-pass construction as use the Fragment class almost like a object for holding parsing state. - // It may make more sense to just use a POJO to keep state during the parsing phase. - // Have Fragment be the representation once we have a known state? - // Something to think on. - - // Discontinuity Counter - this.cc; - this.type; - // relurl is the portion of the URL that comes from inside the playlist. - this.relurl; - // baseurl is the URL to the playlist - this.baseurl; - // EXTINF has to be present for a m3u8 to be considered valid - this.duration; - // When this segment starts in the timeline - this.start; - // sn notates the sequence number for a segment, and if set to a string can be 'initSegment' - this.sn = 0; - - this.urlId = 0; - // level matches this fragment to a index playlist - this.level = 0; - // levelkey is the EXT-X-KEY that applies to this segment for decryption - // core difference from the private field _decryptdata is the lack of the initialized IV - // _decryptdata will set the IV for this segment based on the segment number in the fragment - this.levelkey; - - // TODO(typescript-xhrloader) - this.loader; - } - - // setByteRange converts a EXT-X-BYTERANGE attribute into a two element array - setByteRange(value, previousFrag) { - const params = value.split('@', 2); - const byteRange = []; - if (params.length === 1) { - byteRange[0] = previousFrag ? previousFrag.byteRangeEndOffset : 0; - } else { - byteRange[0] = parseInt(params[1]); - } - byteRange[1] = parseInt(params[0]) + byteRange[0]; - this._byteRange = byteRange; - } - - get url() { - if (!this._url && this.relurl) { - this._url = URLToolkit.buildAbsoluteURL(this.baseurl, this.relurl, { alwaysNormalize: true }); - } - - return this._url; - } - - set url(value) { - this._url = value; - } - - get byteRange() { - if (!this._byteRange) { - return []; - } - - return this._byteRange; - } - - /** - * @type {number} - */ - get byteRangeStartOffset() { - return this.byteRange[0]; - } - - get byteRangeEndOffset() { - return this.byteRange[1]; - } - - get decryptdata() { - if (!this.levelkey && !this._decryptdata) { - return null; - } - - if (!this._decryptdata && this.levelkey) { - let sn = this.sn; - if (typeof sn !== 'number') { - // We are fetching decryption data for a initialization segment - // If the segment was encrypted with AES-128 - // It must have an IV defined. We cannot substitute the Segment Number in. - if (this.levelkey && this.levelkey.method === 'AES-128' && !this.levelkey.iv) { - console.warn(`missing IV for initialization segment with method="${this.levelkey.method}" - compliance issue`); - } - - /* - Be converted to a Number. - 'initSegment' will become NaN. - NaN, which when converted through ToInt32() -> +0. - --- - Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation. - */ - sn = 0; - } - this._decryptdata = this.setDecryptDataFromLevelKey(this.levelkey, sn); - } - - return this._decryptdata; - } - - get endProgramDateTime() { - if (this.programDateTime === null) { - return null; - } - - if (!Number.isFinite(this.programDateTime)) { - return null; - } - - let duration = !Number.isFinite(this.duration) ? 0 : this.duration; - - return this.programDateTime + (duration * 1000); - } - - get encrypted() { - return !!((this.decryptdata && this.decryptdata.uri !== null) && (this.decryptdata.key === null)); - } - - /** - * @param {ElementaryStreamTypes} type - */ - addElementaryStream(type) { - this._elementaryStreams[type] = true; - } - - /** - * @param {ElementaryStreamTypes} type - */ - hasElementaryStream(type) { - return this._elementaryStreams[type] === true; - } - - /** - * Utility method for parseLevelPlaylist to create an initialization vector for a given segment - * @param {number} segmentNumber - segment number to generate IV with - * @returns {Uint8Array} - */ - createInitializationVector(segmentNumber) { - let uint8View = new Uint8Array(16); - - for (let i = 12; i < 16; i++) { - uint8View[i] = (segmentNumber >> 8 * (15 - i)) & 0xff; - } - - return uint8View; - } - - /** - * Utility method for parseLevelPlaylist to get a fragment's decryption data from the currently parsed encryption key data - * @param levelkey - a playlist's encryption info - * @param segmentNumber - the fragment's segment number - * @returns {LevelKey} - an object to be applied as a fragment's decryptdata - */ - setDecryptDataFromLevelKey(levelkey, segmentNumber) { - let decryptdata = levelkey; - - if (levelkey && levelkey.method && levelkey.uri && !levelkey.iv) { - decryptdata = new LevelKey(levelkey.baseuri, levelkey.reluri); - decryptdata.method = levelkey.method; - decryptdata.iv = this.createInitializationVector(segmentNumber); - } - - return decryptdata; - } -} diff --git a/cmd/mjpeg-player/hlsjs/loader/level-key.js b/cmd/mjpeg-player/hlsjs/loader/level-key.js deleted file mode 100644 index 8bfe4331..00000000 --- a/cmd/mjpeg-player/hlsjs/loader/level-key.js +++ /dev/null @@ -1,47 +0,0 @@ -/* -AUTHOR - Trek Hopton - -LICENSE - This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean) - - It is free software: you can redistribute it and/or modify them - under the terms of the GNU General Public License as published by the - Free Software Foundation, either version 3 of the License, or (at your - option) any later version. - - 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 URLToolkit from '../../url-toolkit/url-toolkit.js'; - -export default class LevelKey { - constructor(baseURI, relativeURI) { - this._uri = null; - - this.baseuri; - this.reluri; - this.method = null; - this.key = null; - this.iv = null; - - this.baseuri = baseURI; - this.reluri = relativeURI; - } - - get uri() { - if (!this._uri && this.reluri) { - this._uri = URLToolkit.buildAbsoluteURL(this.baseuri, this.reluri, { alwaysNormalize: true }); - } - - return this._uri; - } -} diff --git a/cmd/mjpeg-player/hlsjs/loader/level.js b/cmd/mjpeg-player/hlsjs/loader/level.js deleted file mode 100644 index f0af8a9a..00000000 --- a/cmd/mjpeg-player/hlsjs/loader/level.js +++ /dev/null @@ -1,23 +0,0 @@ -export default class Level { - constructor (baseUrl) { - // Please keep properties in alphabetical order - this.endCC = 0; - this.endSN = 0; - this.fragments = []; - this.initSegment = null; - this.live = true; - this.needSidxRanges = false; - this.startCC = 0; - this.startSN = 0; - this.startTimeOffset = null; - this.targetduration = 0; - this.totalduration = 0; - this.type = null; - this.url = baseUrl; - this.version = null; - } - - get hasProgramDateTime () { - return !!(this.fragments[0] && Number.isFinite(this.fragments[0].programDateTime)); - } -} diff --git a/cmd/mjpeg-player/hlsjs/loader/m3u8-parser.js b/cmd/mjpeg-player/hlsjs/loader/m3u8-parser.js deleted file mode 100644 index 8778dc75..00000000 --- a/cmd/mjpeg-player/hlsjs/loader/m3u8-parser.js +++ /dev/null @@ -1,388 +0,0 @@ -/* -AUTHOR - Trek Hopton - -LICENSE - This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean) - - It is free software: you can redistribute it and/or modify them - under the terms of the GNU General Public License as published by the - Free Software Foundation, either version 3 of the License, or (at your - option) any later version. - - 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 URLToolkit from '../../url-toolkit/url-toolkit.js'; -import Fragment from './fragment.js'; -import Level from './level.js'; -import LevelKey from './level-key.js'; -import AttrList from '../utils/attr-list.js'; -import { isCodecType } from '../utils/codecs.js'; - -/** - * M3U8 parser - * @module - */ - -// https://regex101.com is your friend -const MASTER_PLAYLIST_REGEX = /#EXT-X-STREAM-INF:([^\n\r]*)[\r\n]+([^\r\n]+)/g; -const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g; - -const LEVEL_PLAYLIST_REGEX_FAST = new RegExp([ - /#EXTINF:\s*(\d*(?:\.\d+)?)(?:,(.*)\s+)?/.source, // duration (#EXTINF:,), group 1 => duration, group 2 => title - /|(?!#)([\S+ ?]+)/.source, // segment URI, group 3 => the URI (note newline is not eaten) - /|#EXT-X-BYTERANGE:*(.+)/.source, // next segment's byterange, group 4 => range spec (x@y) - /|#EXT-X-PROGRAM-DATE-TIME:(.+)/.source, // next segment's program date/time group 5 => the datetime spec - /|#.*/.source // All other non-segment oriented tags will match with all groups empty -].join(''), 'g'); - -const LEVEL_PLAYLIST_REGEX_SLOW = /(?:(?:#(EXTM3U))|(?:#EXT-X-(PLAYLIST-TYPE):(.+))|(?:#EXT-X-(MEDIA-SEQUENCE): *(\d+))|(?:#EXT-X-(TARGETDURATION): *(\d+))|(?:#EXT-X-(KEY):(.+))|(?:#EXT-X-(START):(.+))|(?:#EXT-X-(ENDLIST))|(?:#EXT-X-(DISCONTINUITY-SEQ)UENCE:(\d+))|(?:#EXT-X-(DIS)CONTINUITY))|(?:#EXT-X-(VERSION):(\d+))|(?:#EXT-X-(MAP):(.+))|(?:(#)([^:]*):(.*))|(?:(#)(.*))(?:.*)\r?\n?/; - -const MP4_REGEX_SUFFIX = /\.(mp4|m4s|m4v|m4a)$/i; - -export default class M3U8Parser { - static findGroup(groups, mediaGroupId) { - for (let i = 0; i < groups.length; i++) { - const group = groups[i]; - if (group.id === mediaGroupId) { - return group; - } - } - } - - static convertAVC1ToAVCOTI(codec) { - let avcdata = codec.split('.'); - let result; - if (avcdata.length > 2) { - result = avcdata.shift() + '.'; - result += parseInt(avcdata.shift()).toString(16); - result += ('000' + parseInt(avcdata.shift()).toString(16)).substr(-4); - } else { - result = codec; - } - return result; - } - - static resolve(url, baseUrl) { - return URLToolkit.buildAbsoluteURL(baseUrl, url, { alwaysNormalize: true }); - } - - static parseMasterPlaylist(string, baseurl) { - // TODO(typescript-level) - let levels = []; - MASTER_PLAYLIST_REGEX.lastIndex = 0; - - // TODO(typescript-level) - function setCodecs(codecs, level) { - ['video', 'audio'].forEach((type) => { - const filtered = codecs.filter((codec) => isCodecType(codec, type)); - if (filtered.length) { - const preferred = filtered.filter((codec) => { - return codec.lastIndexOf('avc1', 0) === 0 || codec.lastIndexOf('mp4a', 0) === 0; - }); - level[`${type}Codec`] = preferred.length > 0 ? preferred[0] : filtered[0]; - - // remove from list - codecs = codecs.filter((codec) => filtered.indexOf(codec) === -1); - } - }); - - level.unknownCodecs = codecs; - } - - let result; - while ((result = MASTER_PLAYLIST_REGEX.exec(string)) != null) { - // TODO(typescript-level) - const level = {}; - - const attrs = level.attrs = new AttrList(result[1]); - level.url = M3U8Parser.resolve(result[2], baseurl); - - const resolution = attrs.decimalResolution('RESOLUTION'); - if (resolution) { - level.width = resolution.width; - level.height = resolution.height; - } - level.bitrate = attrs.decimalInteger('AVERAGE-BANDWIDTH') || attrs.decimalInteger('BANDWIDTH'); - level.name = attrs.NAME; - - setCodecs([].concat((attrs.CODECS || '').split(/[ ,]+/)), level); - - if (level.videoCodec && level.videoCodec.indexOf('avc1') !== -1) { - level.videoCodec = M3U8Parser.convertAVC1ToAVCOTI(level.videoCodec); - } - - levels.push(level); - } - return levels; - } - - static parseMasterPlaylistMedia(string, baseurl, type, audioGroups = []) { - let result; - let medias = []; - let id = 0; - MASTER_PLAYLIST_MEDIA_REGEX.lastIndex = 0; - while ((result = MASTER_PLAYLIST_MEDIA_REGEX.exec(string)) !== null) { - const attrs = new AttrList(result[1]); - if (attrs.TYPE === type) { - const media = { - id: id++, - groupId: attrs['GROUP-ID'], - name: attrs.NAME || attrs.LANGUAGE, - type, - default: (attrs.DEFAULT === 'YES'), - autoselect: (attrs.AUTOSELECT === 'YES'), - forced: (attrs.FORCED === 'YES'), - lang: attrs.LANGUAGE - }; - - if (attrs.URI) { - media.url = M3U8Parser.resolve(attrs.URI, baseurl); - } - - if (audioGroups.length) { - // If there are audio groups signalled in the manifest, let's look for a matching codec string for this track - const groupCodec = M3U8Parser.findGroup(audioGroups, media.groupId); - - // If we don't find the track signalled, lets use the first audio groups codec we have - // Acting as a best guess - media.audioCodec = groupCodec ? groupCodec.codec : audioGroups[0].codec; - } - - medias.push(media); - } - } - return medias; - } - - static parseLevelPlaylist(string, baseurl, id, type, levelUrlId) { - let currentSN = 0; - let totalduration = 0; - let level = new Level(baseurl); - let discontinuityCounter = 0; - let prevFrag = null; - let frag = new Fragment(); - let result; - let i; - let levelkey; - - let firstPdtIndex = null; - - LEVEL_PLAYLIST_REGEX_FAST.lastIndex = 0; - - while ((result = LEVEL_PLAYLIST_REGEX_FAST.exec(string)) !== null) { - const duration = result[1]; - if (duration) { // INF - frag.duration = parseFloat(duration); - // avoid sliced strings https://github.com/video-dev/hls.js/issues/939 - const title = (' ' + result[2]).slice(1); - frag.title = title || null; - frag.tagList.push(title ? ['INF', duration, title] : ['INF', duration]); - } else if (result[3]) { // url - if (Number.isFinite(frag.duration)) { - const sn = currentSN++; - frag.type = type; - frag.start = totalduration; - if (levelkey) { - frag.levelkey = levelkey; - } - frag.sn = sn; - frag.level = id; - frag.cc = discontinuityCounter; - frag.urlId = levelUrlId; - frag.baseurl = baseurl; - // avoid sliced strings https://github.com/video-dev/hls.js/issues/939 - frag.relurl = (' ' + result[3]).slice(1); - assignProgramDateTime(frag, prevFrag); - - level.fragments.push(frag); - prevFrag = frag; - totalduration += frag.duration; - - frag = new Fragment(); - } - } else if (result[4]) { // X-BYTERANGE - const data = (' ' + result[4]).slice(1); - if (prevFrag) { - frag.setByteRange(data, prevFrag); - } else { - frag.setByteRange(data); - } - } else if (result[5]) { // PROGRAM-DATE-TIME - // avoid sliced strings https://github.com/video-dev/hls.js/issues/939 - frag.rawProgramDateTime = (' ' + result[5]).slice(1); - frag.tagList.push(['PROGRAM-DATE-TIME', frag.rawProgramDateTime]); - if (firstPdtIndex === null) { - firstPdtIndex = level.fragments.length; - } - } else { - result = result[0].match(LEVEL_PLAYLIST_REGEX_SLOW); - if (!result) { - console.warn('No matches on slow regex match for level playlist!'); - continue; - } - for (i = 1; i < result.length; i++) { - if (typeof result[i] !== 'undefined') { - break; - } - } - - // avoid sliced strings https://github.com/video-dev/hls.js/issues/939 - const value1 = (' ' + result[i + 1]).slice(1); - const value2 = (' ' + result[i + 2]).slice(1); - - switch (result[i]) { - case '#': - frag.tagList.push(value2 ? [value1, value2] : [value1]); - break; - case 'PLAYLIST-TYPE': - level.type = value1.toUpperCase(); - break; - case 'MEDIA-SEQUENCE': - currentSN = level.startSN = parseInt(value1); - break; - case 'TARGETDURATION': - level.targetduration = parseFloat(value1); - break; - case 'VERSION': - level.version = parseInt(value1); - break; - case 'EXTM3U': - break; - case 'ENDLIST': - level.live = false; - break; - case 'DIS': - discontinuityCounter++; - frag.tagList.push(['DIS']); - break; - case 'DISCONTINUITY-SEQ': - discontinuityCounter = parseInt(value1); - break; - case 'KEY': { - // https://tools.ietf.org/html/draft-pantos-http-live-streaming-08#section-3.4.4 - const decryptparams = value1; - const keyAttrs = new AttrList(decryptparams); - const decryptmethod = keyAttrs.enumeratedString('METHOD'); - const decrypturi = keyAttrs.URI; - const decryptiv = keyAttrs.hexadecimalInteger('IV'); - - if (decryptmethod) { - levelkey = new LevelKey(baseurl, decrypturi); - if ((decrypturi) && (['AES-128', 'SAMPLE-AES', 'SAMPLE-AES-CENC'].indexOf(decryptmethod) >= 0)) { - levelkey.method = decryptmethod; - levelkey.key = null; - // Initialization Vector (IV) - levelkey.iv = decryptiv; - } - } - break; - } - case 'START': { - const startAttrs = new AttrList(value1); - const startTimeOffset = startAttrs.decimalFloatingPoint('TIME-OFFSET'); - // TIME-OFFSET can be 0 - if (Number.isFinite(startTimeOffset)) { - level.startTimeOffset = startTimeOffset; - } - break; - } - case 'MAP': { - const mapAttrs = new AttrList(value1); - frag.relurl = mapAttrs.URI; - if (mapAttrs.BYTERANGE) { - frag.setByteRange(mapAttrs.BYTERANGE); - } - frag.baseurl = baseurl; - frag.level = id; - frag.type = type; - frag.sn = 'initSegment'; - level.initSegment = frag; - frag = new Fragment(); - frag.rawProgramDateTime = level.initSegment.rawProgramDateTime; - break; - } - default: - console.warn(`line parsed but not handled: ${result}`); - break; - } - } - } - frag = prevFrag; - // console.log('found ' + level.fragments.length + ' fragments'); - if (frag && !frag.relurl) { - level.fragments.pop(); - totalduration -= frag.duration; - } - level.totalduration = totalduration; - level.averagetargetduration = totalduration / level.fragments.length; - level.endSN = currentSN - 1; - level.startCC = level.fragments[0] ? level.fragments[0].cc : 0; - level.endCC = discontinuityCounter; - - if (!level.initSegment && level.fragments.length) { - // this is a bit lurky but HLS really has no other way to tell us - // if the fragments are TS or MP4, except if we download them :/ - // but this is to be able to handle SIDX. - if (level.fragments.every((frag) => MP4_REGEX_SUFFIX.test(frag.relurl))) { - console.warn('MP4 fragments found but no init segment (probably no MAP, incomplete M3U8), trying to fetch SIDX'); - - frag = new Fragment(); - frag.relurl = level.fragments[0].relurl; - frag.baseurl = baseurl; - frag.level = id; - frag.type = type; - frag.sn = 'initSegment'; - - level.initSegment = frag; - level.needSidxRanges = true; - } - } - - /** - * Backfill any missing PDT values - "If the first EXT-X-PROGRAM-DATE-TIME tag in a Playlist appears after - one or more Media Segment URIs, the client SHOULD extrapolate - backward from that tag (using EXTINF durations and/or media - timestamps) to associate dates with those segments." - * We have already extrapolated forward, but all fragments up to the first instance of PDT do not have their PDTs - * computed. - */ - if (firstPdtIndex) { - backfillProgramDateTimes(level.fragments, firstPdtIndex); - } - - return level; - } -} - -function backfillProgramDateTimes(fragments, startIndex) { - let fragPrev = fragments[startIndex]; - for (let i = startIndex - 1; i >= 0; i--) { - const frag = fragments[i]; - frag.programDateTime = fragPrev.programDateTime - (frag.duration * 1000); - fragPrev = frag; - } -} - -function assignProgramDateTime(frag, prevFrag) { - if (frag.rawProgramDateTime) { - frag.programDateTime = Date.parse(frag.rawProgramDateTime); - } else if (prevFrag && prevFrag.programDateTime) { - frag.programDateTime = prevFrag.endProgramDateTime; - } - - if (!Number.isFinite(frag.programDateTime)) { - frag.programDateTime = null; - frag.rawProgramDateTime = null; - } -} diff --git a/cmd/mjpeg-player/hlsjs/loader/playlist-loader.js b/cmd/mjpeg-player/hlsjs/loader/playlist-loader.js deleted file mode 100644 index 5c57f542..00000000 --- a/cmd/mjpeg-player/hlsjs/loader/playlist-loader.js +++ /dev/null @@ -1,343 +0,0 @@ -/* -AUTHOR - Trek Hopton <trek@ausocean.org> - -LICENSE - This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean) - - It is free software: you can redistribute it and/or modify them - under the terms of the GNU General Public License as published by the - Free Software Foundation, either version 3 of the License, or (at your - option) any later version. - - 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; - -class PlaylistLoader extends EventHandler { - constructor(hls) { - super(hls, - Event.MANIFEST_LOADING, - Event.LEVEL_LOADING, - Event.AUDIO_TRACK_LOADING, - Event.SUBTITLE_TRACK_LOADING); - this.hls = hls; - this.loaders = {}; - } - - /** - * @param {PlaylistContextType} type - * @returns {boolean} - */ - static canHaveQualityLevels(type) { - return (type !== PlaylistContextType.AUDIO_TRACK && - type !== PlaylistContextType.SUBTITLE_TRACK); - } - - /** - * Map context.type to LevelType - * @param {PlaylistLoaderContext} context - * @returns {LevelType} - */ - static mapContextToLevelType(context) { - const { type } = context; - - switch (type) { - case PlaylistContextType.AUDIO_TRACK: - return PlaylistLevelType.AUDIO; - case PlaylistContextType.SUBTITLE_TRACK: - return PlaylistLevelType.SUBTITLE; - default: - return PlaylistLevelType.MAIN; - } - } - - 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; - } - - /** - * Returns defaults or configured loader-type overloads (pLoader and loader config params) - * Default loader is XHRLoader (see utils) - * @param {PlaylistLoaderContext} context - * @returns {Loader} or other compatible configured overload - */ - createInternalLoader(context) { - const config = this.hls.config; - const PLoader = config.pLoader; - const Loader = config.loader; - // TODO(typescript-config): Verify once config is typed that InternalLoader always returns a Loader - const InternalLoader = PLoader || Loader; - - const loader = new InternalLoader(config); - - // TODO - Do we really need to assign the instance or if the dep has been lost - context.loader = loader; - this.loaders[context.type] = loader; - - return loader; - } - - getInternalLoader(context) { - return this.loaders[context.type]; - } - - resetInternalLoader(contextType) { - if (this.loaders[contextType]) { - delete this.loaders[contextType]; - } - } - - onManifestLoading(data) { - this.load({ - url: data.url, - type: PlaylistContextType.MANIFEST, - level: 0, - id: null, - responseType: 'text' - }); - } - - 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' - }); - } - - load(context) { - const config = this.hls.config; - - // Check if a loader for this context already exists - let loader = this.getInternalLoader(context); - if (loader) { - const loaderContext = loader.context; - if (loaderContext && loaderContext.url === context.url) { // same URL can't overlap - return false; - } else { - console.warn(`aborting previous loader for type: ${context.type}`); - loader.abort(); - } - } - - let maxRetry; - let timeout; - let retryDelay; - let maxRetryDelay; - - // apply different configs for retries depending on - // context (manifest, level, audio/subs playlist) - switch (context.type) { - case PlaylistContextType.MANIFEST: - maxRetry = config.manifestLoadingMaxRetry; - timeout = config.manifestLoadingTimeOut; - retryDelay = config.manifestLoadingRetryDelay; - maxRetryDelay = config.manifestLoadingMaxRetryTimeout; - break; - case PlaylistContextType.LEVEL: - // Disable internal loader retry logic, since we are managing retries in Level Controller - maxRetry = 0; - maxRetryDelay = 0; - retryDelay = 0; - timeout = config.levelLoadingTimeOut; - // TODO Introduce retry settings for audio-track and subtitle-track, it should not use level retry config - break; - default: - maxRetry = config.levelLoadingMaxRetry; - timeout = config.levelLoadingTimeOut; - retryDelay = config.levelLoadingRetryDelay; - maxRetryDelay = config.levelLoadingMaxRetryTimeout; - break; - } - - loader = this.createInternalLoader(context); - - const loaderConfig = { - timeout, - maxRetry, - retryDelay, - maxRetryDelay - }; - - const loaderCallbacks = { - onSuccess: this.loadsuccess.bind(this), - onError: this.loaderror.bind(this), - onTimeout: this.loadtimeout.bind(this) - }; - - loader.load(context, loaderConfig, loaderCallbacks); - - return true; - } - - loadsuccess(response, stats, context, networkDetails = null) { - if (context.isSidxRequest) { - this._handleSidxRequest(response, context); - this._handlePlaylistLoaded(response, stats, context, networkDetails); - return; - } - - this.resetInternalLoader(context.type); - if (typeof response.data !== 'string') { - throw new Error('expected responseType of "text" for PlaylistLoader'); - } - - const string = response.data; - - stats.tload = performance.now(); - - // Validate if it is an M3U8 at all - if (string.indexOf('#EXTM3U') !== 0) { - console.error("no EXTM3U delimiter"); - return; - } - - // Check if chunk-list or master. handle empty chunk list case (first EXTINF not signaled, but TARGETDURATION present) - if (string.indexOf('#EXTINF:') > 0 || string.indexOf('#EXT-X-TARGETDURATION:') > 0) { - this._handleTrackOrLevelPlaylist(response, stats, context, networkDetails); - } else { - console.log("handling of master playlists is not implemented"); - // this._handleMasterPlaylist(response, stats, context, networkDetails); - } - - } - - loaderror(response, context, networkDetails = null) { - console.error("network error while loading", response); - } - - loadtimeout(stats, context, networkDetails = null) { - console.error("network timeout while loading", stats); - } - - _handleTrackOrLevelPlaylist(response, stats, context, networkDetails) { - const hls = this.hls; - - const { id, level, type } = context; - - const url = PlaylistLoader.getResponseUrl(response, context); - - // if the values are null, they will result in the else conditional - const levelUrlId = Number.isFinite(id) ? id : 0; - const levelId = Number.isFinite(level) ? level : levelUrlId; - - const levelType = PlaylistLoader.mapContextToLevelType(context); - const levelDetails = M3U8Parser.parseLevelPlaylist(response.data, url, levelId, levelType, levelUrlId); - - // set stats on level structure - // TODO(jstackhouse): why? mixing concerns, is it just treated as value bag? - (levelDetails).tload = stats.tload; - - // We have done our first request (Manifest-type) and receive - // not a master playlist but a chunk-list (track/level) - // We fire the manifest-loaded event anyway with the parsed level-details - // by creating a single-level structure for it. - if (type === PlaylistContextType.MANIFEST) { - const singleLevel = { - url, - details: levelDetails - }; - - hls.trigger(Event.MANIFEST_LOADED, { - levels: [singleLevel], - audioTracks: [], - url, - stats, - networkDetails - }); - } - - // save parsing time - stats.tparsed = performance.now(); - - // extend the context with the new levelDetails property - context.levelDetails = levelDetails; - - this._handlePlaylistLoaded(response, stats, context, networkDetails); - } - - _handlePlaylistLoaded(response, stats, context, networkDetails) { - const { type, level, id, levelDetails } = context; - - if (!levelDetails || !levelDetails.targetduration) { - console.error("manifest parsing error"); - return; - } - - const canHaveLevels = PlaylistLoader.canHaveQualityLevels(context.type); - if (canHaveLevels) { - this.hls.trigger(Event.LEVEL_LOADED, { - details: levelDetails, - level: level || 0, - id: id || 0, - stats, - networkDetails - }); - } else { - switch (type) { - case PlaylistContextType.AUDIO_TRACK: - this.hls.trigger(Event.AUDIO_TRACK_LOADED, { - details: levelDetails, - id, - stats, - networkDetails - }); - break; - case PlaylistContextType.SUBTITLE_TRACK: - this.hls.trigger(Event.SUBTITLE_TRACK_LOADED, { - details: levelDetails, - id, - stats, - networkDetails - }); - break; - } - } - } -} - -export default PlaylistLoader; diff --git a/cmd/mjpeg-player/hlsjs/mts-demuxer.js b/cmd/mjpeg-player/hlsjs/mts-demuxer.js deleted file mode 100644 index 5fe7c351..00000000 --- a/cmd/mjpeg-player/hlsjs/mts-demuxer.js +++ /dev/null @@ -1,369 +0,0 @@ -/* -AUTHOR - Trek Hopton <trek@ausocean.org> - -LICENSE - This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean) - - It is free software: you can redistribute it and/or modify them - under the terms of the GNU General Public License as published by the - Free Software Foundation, either version 3 of the License, or (at your - option) any later version. - - 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. -*/ - -// MTSDemuxer demultiplexes an MPEG-TS stream into its individual streams. -// While it is possible that the MPEG-TS stream may contain many streams, -// this demuxer will result in at most one stream of each type ie. video, audio, id3 metadata. -class MTSDemuxer { - constructor() { - this.init(); - } - - // init initialises MTSDemuxer's state. It can be used to reset an MTSDemuxer instance. - init() { - this.pmtParsed = false; - this._pmtId = -1; - } - - // createTrack creates and returns a track model. - /** - * @param {string} type 'audio' | 'video' | 'id3' | 'text' - * @return {object} MTSDemuxer's internal track model. - */ - static createTrack(type) { - return { - type, - pid: -1, - data: [] // This will contain Uint8Arrays representing each PES packet's payload for this track. - }; - } - - // _syncOffset scans the first 'maxScanWindow' bytes and returns an offset to the beginning of the first three MTS packets, - // or -1 if three are not found. - // A TS fragment should contain at least 3 TS packets, a PAT, a PMT, and one PID, each starting with 0x47. - static _syncOffset(data) { - const maxScanWindow = 1000; // 1000 is a reasonable number of bytes to search for the first MTS packets. - const scanWindow = Math.min(maxScanWindow, data.length - 3 * 188); - let i = 0; - while (i < scanWindow) { - if (data[i] === 0x47 && data[i + 188] === 0x47 && data[i + 2 * 188] === 0x47) { - return i; - } else { - i++; - } - } - return -1; - } - - demux(data) { - let start, len = data.length, pusi, pid, afc, offset, pes, - unknownPIDs = false; - let pmtParsed = this.pmtParsed, - videoTrack = MTSDemuxer.createTrack('video'), - audioTrack = MTSDemuxer.createTrack('audio'), - id3Track = MTSDemuxer.createTrack('id3'), - videoId, - audioId, - id3Id, - pmtId = this._pmtId, - videoData = this.videoPesData, - audioData = this.audioPesData, - id3Data = this.id3PesData, - parsePAT = this._parsePAT, - parsePMT = this._parsePMT, - parsePES = this._parsePES; - - const syncOffset = MTSDemuxer._syncOffset(data); - - // Don't parse last TS packet if incomplete. - len -= (len + syncOffset) % 188; - - // Loop through TS packets. - for (start = syncOffset; start < len; start += 188) { - if (data[start] === 0x47) { - pusi = !!(data[start + 1] & 0x40); - // pid is a 13-bit field starting at the last bit of TS[1]. - pid = ((data[start + 1] & 0x1f) << 8) + data[start + 2]; - afc = (data[start + 3] & 0x30) >> 4; - // If an adaption field is present, its length is specified by the fifth byte of the TS packet header. - if (afc > 1) { - offset = start + 5 + data[start + 4]; - // Continue if there is only adaptation field. - if (offset === (start + 188)) { - continue; - } - } else { - offset = start + 4; - } - switch (pid) { - case videoId: - if (pusi) { - if (videoData && (pes = parsePES(videoData)) && pes.pts !== undefined) { - videoTrack.data.push(pes.data); - // TODO: here pes contains data, pts, dts and len. Are all these needed? - } - videoData = { data: [], size: 0 }; - } - if (videoData) { - videoData.data.push(data.subarray(offset, start + 188)); - videoData.size += start + 188 - offset; - } - break; - case audioId: - if (pusi) { - if (audioData && (pes = parsePES(audioData)) && pes.pts !== undefined) { - audioTrack.data.push(pes.data); - } - audioData = { data: [], size: 0 }; - } - if (audioData) { - audioData.data.push(data.subarray(offset, start + 188)); - audioData.size += start + 188 - offset; - } - break; - case id3Id: - if (pusi) { - if (id3Data && (pes = parsePES(id3Data)) && pes.pts !== undefined) { - id3Track.data.push(pes.data); - } - id3Data = { data: [], size: 0 }; - } - if (id3Data) { - id3Data.data.push(data.subarray(offset, start + 188)); - id3Data.size += start + 188 - offset; - } - break; - case 0: - if (pusi) { - offset += data[offset] + 1; - } - - pmtId = this._pmtId = parsePAT(data, offset); - break; - case pmtId: - if (pusi) { - offset += data[offset] + 1; - } - - let parsedPIDs = parsePMT(data, offset); - - // Only update track id if track PID found while parsing PMT. - // This is to avoid resetting the PID to -1 in case track PID transiently disappears from the stream, - // this could happen in case of transient missing audio samples for example. - videoId = parsedPIDs.video; - if (videoId > 0) { - videoTrack.pid = videoId; - } - audioId = parsedPIDs.audio; - if (audioId > 0) { - audioTrack.pid = audioId; - } - id3Id = parsedPIDs.id3; - if (id3Id > 0) { - id3Track.pid = id3Id; - } - - if (unknownPIDs && !pmtParsed) { - // Reparse from beginning. - unknownPIDs = false; - // We set it to -188, the += 188 in the for loop will reset start to 0. - start = syncOffset - 188; - } - pmtParsed = this.pmtParsed = true; - break; - default: - unknownPIDs = true; - break; - } - } else { - console.error('TS packet did not start with 0x47'); - } - } - - // Try to parse last PES packets. - if (videoData && (pes = parsePES(videoData)) && pes.pts !== undefined) { - videoTrack.data.push(pes.data); - this.videoPesData = null; - } else { - // Either pesPkts null or PES truncated, keep it for next frag parsing. - this.videoPesData = videoData; - } - - if (audioData && (pes = parsePES(audioData)) && pes.pts !== undefined) { - audioTrack.data.push(pes.data); - this.audioPesData = null; - } else { - // Either pesPkts null or PES truncated, keep it for next frag parsing. - this.audioPesData = audioData; - } - - if (id3Data && (pes = parsePES(id3Data)) && pes.pts !== undefined) { - id3Track.data.push(pes.data); - this.id3PesData = null; - } else { - // Either pesPkts null or PES truncated, keep it for next frag parsing. - this.id3PesData = id3Data; - } - - return videoTrack; - } - - _parsePAT(data, offset) { - // Skip the PSI header and parse the first PMT entry. - return (data[offset + 10] & 0x1F) << 8 | data[offset + 11]; - // console.log('PMT PID:' + this._pmtId); - } - - _parsePMT(data, offset) { - let programInfoLength, pid, result = { audio: -1, video: -1, id3: -1 }, - sectionLength = (data[offset + 1] & 0x0f) << 8 | data[offset + 2], - tableEnd = offset + 3 + sectionLength - 4; - // To determine where the table is, we have to figure out how - // long the program info descriptors are. - programInfoLength = (data[offset + 10] & 0x0f) << 8 | data[offset + 11]; - // Advance the offset to the first entry in the mapping table. - offset += 12 + programInfoLength; - while (offset < tableEnd) { - pid = (data[offset + 1] & 0x1F) << 8 | data[offset + 2]; - switch (data[offset]) { - case 0x1c: // MJPEG - case 0xdb: // SAMPLE-AES AVC. - case 0x1b: // ITU-T Rec. H.264 and ISO/IEC 14496-10 (lower bit-rate video). - if (result.video === -1) { - result.video = pid; - } - break; - case 0xcf: // SAMPLE-AES AAC. - case 0x0f: // ISO/IEC 13818-7 ADTS AAC (MPEG-2 lower bit-rate audio). - case 0xd2: // ADPCM audio. - case 0x03: // ISO/IEC 11172-3 (MPEG-1 audio). - case 0x24: - // console.warn('HEVC stream type found, not supported for now'); - case 0x04: // or ISO/IEC 13818-3 (MPEG-2 halved sample rate audio). - if (result.audio === -1) { - result.audio = pid; - } - break; - case 0x15: // Packetized metadata (ID3) - // console.log('ID3 PID:' + pid); - if (result.id3 === -1) { - result.id3 = pid; - } - break; - default: - // console.log('unknown stream type:' + data[offset]); - break; - } - // Move to the next table entry, skip past the elementary stream descriptors, if present. - offset += ((data[offset + 3] & 0x0F) << 8 | data[offset + 4]) + 5; - } - return result; - } - - _parsePES(stream) { - let i = 0, frag, pesFlags, pesPrefix, pesLen, pesHdrLen, pesData, pesPts, pesDts, payloadStartOffset, data = stream.data; - // Safety check. - if (!stream || stream.size === 0) { - return null; - } - - // We might need up to 19 bytes to read PES header. - // If first chunk of data is less than 19 bytes, let's merge it with following ones until we get 19 bytes. - // Usually only one merge is needed (and this is rare ...). - while (data[0].length < 19 && data.length > 1) { - let newData = new Uint8Array(data[0].length + data[1].length); - newData.set(data[0]); - newData.set(data[1], data[0].length); - data[0] = newData; - data.splice(1, 1); - } - // Retrieve PTS/DTS from first fragment. - frag = data[0]; - pesPrefix = (frag[0] << 16) + (frag[1] << 8) + frag[2]; - if (pesPrefix === 1) { - pesLen = (frag[4] << 8) + frag[5]; - // If PES parsed length is not zero and greater than total received length, stop parsing. PES might be truncated. - // Minus 6 : PES header size. - if (pesLen && pesLen > stream.size - 6) { - return null; - } - - pesFlags = frag[7]; - if (pesFlags & 0xC0) { - // PES header described here : http://dvd.sourceforge.net/dvdinfo/pes-hdr.html - // As PTS / DTS is 33 bit we cannot use bitwise operator in JS, - // as Bitwise operators treat their operands as a sequence of 32 bits. - pesPts = (frag[9] & 0x0E) * 536870912 +// 1 << 29 - (frag[10] & 0xFF) * 4194304 +// 1 << 22 - (frag[11] & 0xFE) * 16384 +// 1 << 14 - (frag[12] & 0xFF) * 128 +// 1 << 7 - (frag[13] & 0xFE) / 2; - // Check if greater than 2^32 -1. - if (pesPts > 4294967295) { - // Decrement 2^33. - pesPts -= 8589934592; - } - if (pesFlags & 0x40) { - pesDts = (frag[14] & 0x0E) * 536870912 +// 1 << 29 - (frag[15] & 0xFF) * 4194304 +// 1 << 22 - (frag[16] & 0xFE) * 16384 +// 1 << 14 - (frag[17] & 0xFF) * 128 +// 1 << 7 - (frag[18] & 0xFE) / 2; - // Check if greater than 2^32 -1. - if (pesDts > 4294967295) { - // Decrement 2^33. - pesDts -= 8589934592; - } - if (pesPts - pesDts > 60 * 90000) { - // console.warn(`${Math.round((pesPts - pesDts) / 90000)}s delta between PTS and DTS, align them`); - pesPts = pesDts; - } - } else { - pesDts = pesPts; - } - } - pesHdrLen = frag[8]; - // 9 bytes : 6 bytes for PES header + 3 bytes for PES extension. - payloadStartOffset = pesHdrLen + 9; - - stream.size -= payloadStartOffset; - // Reassemble PES packet. - pesData = new Uint8Array(stream.size); - for (let j = 0, dataLen = data.length; j < dataLen; j++) { - frag = data[j]; - let len = frag.byteLength; - if (payloadStartOffset) { - if (payloadStartOffset > len) { - // Trim full frag if PES header bigger than frag. - payloadStartOffset -= len; - continue; - } else { - // Trim partial frag if PES header smaller than frag. - frag = frag.subarray(payloadStartOffset); - len -= payloadStartOffset; - payloadStartOffset = 0; - } - } - pesData.set(frag, i); - i += len; - } - if (pesLen) { - // Payload size : remove PES header + PES extension. - pesLen -= pesHdrLen + 3; - } - return { data: pesData, pts: pesPts, dts: pesDts, len: pesLen }; - } else { - return null; - } - } -} \ No newline at end of file diff --git a/cmd/mjpeg-player/hlsjs/observer.js b/cmd/mjpeg-player/hlsjs/observer.js deleted file mode 100644 index 9f300d23..00000000 --- a/cmd/mjpeg-player/hlsjs/observer.js +++ /dev/null @@ -1,38 +0,0 @@ -/* -AUTHOR - Trek Hopton <trek@ausocean.org> - -LICENSE - This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean) - - It is free software: you can redistribute it and/or modify them - under the terms of the GNU General Public License as published by the - Free Software Foundation, either version 3 of the License, or (at your - option) any later version. - - 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 EventEmitter from '../eventemitter3/index.js'; - -/** - * Simple adapter sub-class of Nodejs-like EventEmitter. - */ -export class Observer extends EventEmitter { - /** - * We simply want to pass along the event-name itself - * in every call to a handler, which is the purpose of our `trigger` method - * extending the standard API. - */ - trigger(event, ...data) { - this.emit(event, event, ...data); - } -} diff --git a/cmd/mjpeg-player/hlsjs/types/loader.js b/cmd/mjpeg-player/hlsjs/types/loader.js deleted file mode 100644 index 2a23cb1b..00000000 --- a/cmd/mjpeg-player/hlsjs/types/loader.js +++ /dev/null @@ -1,42 +0,0 @@ -/* -AUTHOR - Trek Hopton <trek@ausocean.org> - -LICENSE - This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean) - - It is free software: you can redistribute it and/or modify them - under the terms of the GNU General Public License as published by the - Free Software Foundation, either version 3 of the License, or (at your - option) any later version. - - 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. -*/ - -/** - * @readonly - * @enum {string} - */ -export const PlaylistContextType = { - MANIFEST: 'manifest', - LEVEL: 'level', - AUDIO_TRACK: 'audioTrack', - SUBTITLE_TRACK: 'subtitleTrack' -} - -/** - * @enum {string} - */ -export const PlaylistLevelType = { - MAIN: 'main', - AUDIO: 'audio', - SUBTITLE: 'subtitle' -} diff --git a/cmd/mjpeg-player/hlsjs/utils/attr-list.js b/cmd/mjpeg-player/hlsjs/utils/attr-list.js deleted file mode 100755 index b1796d54..00000000 --- a/cmd/mjpeg-player/hlsjs/utils/attr-list.js +++ /dev/null @@ -1,89 +0,0 @@ -const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/; // eslint-disable-line no-useless-escape -const ATTR_LIST_REGEX = /\s*(.+?)\s*=((?:\".*?\")|.*?)(?:,|$)/g; // eslint-disable-line no-useless-escape - -// adapted from https://github.com/kanongil/node-m3u8parse/blob/master/attrlist.js -class AttrList { - constructor (attrs) { - if (typeof attrs === 'string') { - attrs = AttrList.parseAttrList(attrs); - } - - for (let attr in attrs) { - if (attrs.hasOwnProperty(attr)) { - this[attr] = attrs[attr]; - } - } - } - - decimalInteger (attrName) { - const intValue = parseInt(this[attrName], 10); - if (intValue > Number.MAX_SAFE_INTEGER) { - return Infinity; - } - - return intValue; - } - - hexadecimalInteger (attrName) { - if (this[attrName]) { - let stringValue = (this[attrName] || '0x').slice(2); - stringValue = ((stringValue.length & 1) ? '0' : '') + stringValue; - - const value = new Uint8Array(stringValue.length / 2); - for (let i = 0; i < stringValue.length / 2; i++) { - value[i] = parseInt(stringValue.slice(i * 2, i * 2 + 2), 16); - } - - return value; - } else { - return null; - } - } - - hexadecimalIntegerAsNumber (attrName) { - const intValue = parseInt(this[attrName], 16); - if (intValue > Number.MAX_SAFE_INTEGER) { - return Infinity; - } - - return intValue; - } - - decimalFloatingPoint (attrName) { - return parseFloat(this[attrName]); - } - - enumeratedString (attrName) { - return this[attrName]; - } - - decimalResolution (attrName) { - const res = DECIMAL_RESOLUTION_REGEX.exec(this[attrName]); - if (res === null) { - return undefined; - } - - return { - width: parseInt(res[1], 10), - height: parseInt(res[2], 10) - }; - } - - static parseAttrList (input) { - let match, attrs = {}; - ATTR_LIST_REGEX.lastIndex = 0; - while ((match = ATTR_LIST_REGEX.exec(input)) !== null) { - let value = match[2], quote = '"'; - - if (value.indexOf(quote) === 0 && - value.lastIndexOf(quote) === (value.length - 1)) { - value = value.slice(1, -1); - } - - attrs[match[1]] = value; - } - return attrs; - } -} - -export default AttrList; diff --git a/cmd/mjpeg-player/hlsjs/utils/codecs.js b/cmd/mjpeg-player/hlsjs/utils/codecs.js deleted file mode 100644 index a9b345aa..00000000 --- a/cmd/mjpeg-player/hlsjs/utils/codecs.js +++ /dev/null @@ -1,98 +0,0 @@ -/* -AUTHOR - Trek Hopton <trek@ausocean.org> - -LICENSE - This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean) - - It is free software: you can redistribute it and/or modify them - under the terms of the GNU General Public License as published by the - Free Software Foundation, either version 3 of the License, or (at your - option) any later version. - - 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. -*/ - -// from http://mp4ra.org/codecs.html -const sampleEntryCodesISO = { - audio: { - 'a3ds': true, - 'ac-3': true, - 'ac-4': true, - 'alac': true, - 'alaw': true, - 'dra1': true, - 'dts+': true, - 'dts-': true, - 'dtsc': true, - 'dtse': true, - 'dtsh': true, - 'ec-3': true, - 'enca': true, - 'g719': true, - 'g726': true, - 'm4ae': true, - 'mha1': true, - 'mha2': true, - 'mhm1': true, - 'mhm2': true, - 'mlpa': true, - 'mp4a': true, - 'raw ': true, - 'Opus': true, - 'samr': true, - 'sawb': true, - 'sawp': true, - 'sevc': true, - 'sqcp': true, - 'ssmv': true, - 'twos': true, - 'ulaw': true - }, - video: { - 'avc1': true, - 'avc2': true, - 'avc3': true, - 'avc4': true, - 'avcp': true, - 'drac': true, - 'dvav': true, - 'dvhe': true, - 'encv': true, - 'hev1': true, - 'hvc1': true, - 'mjp2': true, - 'mp4v': true, - 'mvc1': true, - 'mvc2': true, - 'mvc3': true, - 'mvc4': true, - 'resv': true, - 'rv60': true, - 's263': true, - 'svc1': true, - 'svc2': true, - 'vc-1': true, - 'vp08': true, - 'vp09': true - } -}; - -function isCodecType(codec, type) { - const typeCodes = sampleEntryCodesISO[type]; - return !!typeCodes && typeCodes[codec.slice(0, 4)] === true; -} - -function isCodecSupportedInMp4(codec, type) { - return MediaSource.isTypeSupported(`${type || 'video'}/mp4;codecs="${codec}"`); -} - -export { isCodecType, isCodecSupportedInMp4 }; diff --git a/cmd/mjpeg-player/hlsjs/utils/xhr-loader.js b/cmd/mjpeg-player/hlsjs/utils/xhr-loader.js deleted file mode 100644 index 67280a87..00000000 --- a/cmd/mjpeg-player/hlsjs/utils/xhr-loader.js +++ /dev/null @@ -1,189 +0,0 @@ -/* -AUTHOR - Trek Hopton <trek@ausocean.org> - -LICENSE - This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean) - - It is free software: you can redistribute it and/or modify them - under the terms of the GNU General Public License as published by the - Free Software Foundation, either version 3 of the License, or (at your - option) any later version. - - 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. -*/ - -/** - * XHR based loader -*/ - -const { performance, XMLHttpRequest } = window; - -class XhrLoader { - constructor(config) { - if (config && config.xhrSetup) { - this.xhrSetup = config.xhrSetup; - } - } - - destroy() { - this.abort(); - this.loader = null; - } - - abort() { - let loader = this.loader; - if (loader && loader.readyState !== 4) { - this.stats.aborted = true; - loader.abort(); - } - - window.clearTimeout(this.requestTimeout); - this.requestTimeout = null; - window.clearTimeout(this.retryTimeout); - this.retryTimeout = null; - } - - load(context, config, callbacks) { - this.context = context; - this.config = config; - this.callbacks = callbacks; - this.stats = { trequest: performance.now(), retry: 0 }; - this.retryDelay = config.retryDelay; - this.loadInternal(); - } - - loadInternal() { - let xhr, context = this.context; - xhr = this.loader = new XMLHttpRequest(); - window.console.log("load internal xhr: " + context.url); - - let stats = this.stats; - stats.tfirst = 0; - stats.loaded = 0; - const xhrSetup = this.xhrSetup; - - try { - if (xhrSetup) { - try { - xhrSetup(xhr, context.url); - } catch (e) { - // fix xhrSetup: (xhr, url) => {xhr.setRequestHeader("Content-Language", "test");} - // not working, as xhr.setRequestHeader expects xhr.readyState === OPEN - xhr.open('GET', context.url, true); - xhrSetup(xhr, context.url); - } - } - if (!xhr.readyState) { - xhr.open('GET', context.url, true); - } - } catch (e) { - // IE11 throws an exception on xhr.open if attempting to access an HTTP resource over HTTPS - this.callbacks.onError({ code: xhr.status, text: e.message }, context, xhr); - return; - } - - if (context.rangeEnd) { - xhr.setRequestHeader('Range', 'bytes=' + context.rangeStart + '-' + (context.rangeEnd - 1)); - } - - xhr.onreadystatechange = this.readystatechange.bind(this); - xhr.onprogress = this.loadprogress.bind(this); - xhr.responseType = context.responseType; - - // setup timeout before we perform request - this.requestTimeout = window.setTimeout(this.loadtimeout.bind(this), this.config.timeout); - xhr.send(); - } - - readystatechange(event) { - let xhr = event.currentTarget, - readyState = xhr.readyState, - stats = this.stats, - context = this.context, - config = this.config; - - // don't proceed if xhr has been aborted - if (stats.aborted) { - return; - } - - // >= HEADERS_RECEIVED - if (readyState >= 2) { - // clear xhr timeout and rearm it if readyState less than 4 - window.clearTimeout(this.requestTimeout); - if (stats.tfirst === 0) { - stats.tfirst = Math.max(performance.now(), stats.trequest); - } - - if (readyState === 4) { - let status = xhr.status; - // http status between 200 to 299 are all successful - if (status >= 200 && status < 300) { - stats.tload = Math.max(stats.tfirst, performance.now()); - let data, len; - if (context.responseType === 'arraybuffer') { - data = xhr.response; - len = data.byteLength; - } else { - data = xhr.responseText; - len = data.length; - } - stats.loaded = stats.total = len; - let response = { url: xhr.responseURL, data: data }; - this.callbacks.onSuccess(response, stats, context, xhr); - } else { - // if max nb of retries reached or if http status between 400 and 499 (such error cannot be recovered, retrying is useless), return error - if (stats.retry >= config.maxRetry || (status >= 400 && status < 499)) { - console.error(`${status} while loading ${context.url}`); - this.callbacks.onError({ code: status, text: xhr.statusText }, context, xhr); - } else { - // retry - console.warn(`${status} while loading ${context.url}, retrying in ${this.retryDelay}...`); - // aborts and resets internal state - this.destroy(); - // schedule retry - this.retryTimeout = window.setTimeout(this.loadInternal.bind(this), this.retryDelay); - // set exponential backoff - this.retryDelay = Math.min(2 * this.retryDelay, config.maxRetryDelay); - stats.retry++; - } - } - } else { - // readyState >= 2 AND readyState !==4 (readyState = HEADERS_RECEIVED || LOADING) rearm timeout as xhr not finished yet - this.requestTimeout = window.setTimeout(this.loadtimeout.bind(this), config.timeout); - } - } - } - - loadtimeout() { - console.warn(`timeout while loading ${this.context.url}`); - this.callbacks.onTimeout(this.stats, this.context, null); - } - - loadprogress(event) { - let xhr = event.currentTarget, - stats = this.stats; - - stats.loaded = event.loaded; - if (event.lengthComputable) { - stats.total = event.total; - } - - let onProgress = this.callbacks.onProgress; - if (onProgress) { - // third arg is to provide on progress data - onProgress(stats, this.context, null, xhr); - } - } -} - -export default XhrLoader; diff --git a/cmd/mjpeg-player/index.html b/cmd/mjpeg-player/index.html deleted file mode 100644 index d42069fb..00000000 --- a/cmd/mjpeg-player/index.html +++ /dev/null @@ -1,59 +0,0 @@ -<!DOCTYPE html> -<!-- -AUTHOR - Trek Hopton <trek@ausocean.org> - -LICENSE - This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean) - - It is free software: you can redistribute it and/or modify them - under the terms of the GNU General Public License as published by the - Free Software Foundation, either version 3 of the License, or (at your - option) any later version. - - 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. ---> - -<html lang="en"> - -<head> - <meta charset="utf-8"> - <title>Mjpeg Player - - - - -
-
-
-
- -
-
-
- - - -
-
- Frame Rate: fps -
-
- -
-
-
-
-
- ©2020 Australian Ocean Laboratory Limited (AusOcean) (License) -
-
- - - \ No newline at end of file diff --git a/cmd/mjpeg-player/lex-mjpeg.js b/cmd/mjpeg-player/lex-mjpeg.js deleted file mode 100644 index 222fa945..00000000 --- a/cmd/mjpeg-player/lex-mjpeg.js +++ /dev/null @@ -1,63 +0,0 @@ -/* -AUTHOR - Trek Hopton - -LICENSE - This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean) - - It is free software: you can redistribute it and/or modify them - under the terms of the GNU General Public License as published by the - Free Software Foundation, either version 3 of the License, or (at your - option) any later version. - - 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. -*/ - -// MJPEGLexer lexes a byte array containing MJPEG into individual JPEGs. -class MJPEGLexer { - constructor() { - this.off = 0; - } - - append(data) { - this.src = new Uint8Array(data); - } - - // read returns the next single frame. - read() { - // Check if the src can contain at least the start and end flags (4B). - if (this.off + 4 > this.src.length) { - return null; - } - // Iterate through bytes until the start flag is found. - while (this.src[this.off] != 0xff || this.src[this.off + 1] != 0xd8) { - this.off++; - if (this.off + 4 > this.src.length) { - return null; - } - } - // Start after the start flag and loop until the end flag is found. - let end = this.off + 2; - while (true) { - if (end + 2 > this.src.length) { - return null; - } - if (this.src[end] == 0xff && this.src[end + 1] == 0xd9) { - break; - } - end++; - } - // Copy the frame's bytes to a new array to return. - // Note: optimally this would return a reference but since we are in a worker thread, - // the main thread doesn't have access to the ArrayBuffer that we are working with. - let frame = this.src.slice(this.off, end + 2); - this.off = end + 2 - return frame; - } -} \ No newline at end of file diff --git a/cmd/mjpeg-player/main.js b/cmd/mjpeg-player/main.js deleted file mode 100644 index c51ee771..00000000 --- a/cmd/mjpeg-player/main.js +++ /dev/null @@ -1,132 +0,0 @@ -/* -AUTHOR - Trek Hopton - -LICENSE - This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean) - - It is free software: you can redistribute it and/or modify them - under the terms of the GNU General Public License as published by the - Free Software Foundation, either version 3 of the License, or (at your - option) any later version. - - 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. -*/ - -import Hls from "./hlsjs/hls.js"; - -let started = false; -let player, viewer; - -// init gets DOM elements once the document has been loaded and adds listeners where necessary. -function init() { - document.addEventListener('DOMContentLoaded', load); - document.addEventListener('DOMContentLoaded', function () { - document.getElementById('urlBtn').addEventListener('click', load); - document.getElementById('fileInput').addEventListener('change', play); - viewer = document.getElementById('viewer'); - } - ); -} - -init(); - -// load gets the URL from the URL input element or the browser's URL bar -// and creates an Hls instance to load the content from the URL. -function load() { - let url = document.getElementById('url').value; - if (url == "") { - // Get everything following the ? from the browser's URL bar. - url = window.location.search.slice(1); - document.getElementById('url').value = url; - } else if (url[0] == '/') { - url = window.location.protocol + '//' + window.location.host + url; - } - if (url == "") { - return; - } - - let hls = new Hls(); - hls.loadSource(url, append); -} - -// append, on the first call, starts a player worker and passes it a frame rate and the video data, -// on subsequent calls it passes the video data to the player worker. -function append(data) { - if (!started) { - player = new Worker("player.js"); - - let rate = document.getElementById('rate'); - if (rate.value && rate.value > 0) { - player.postMessage({ msg: "setFrameRate", data: rate.value }); - } - - player.onmessage = handleMessage; - - player.postMessage({ msg: "loadMtsMjpeg", data: data }, [data]); - started = true; - } else { - player.postMessage({ msg: "appendMtsMjpeg", data: data }, [data]); - } - -} - -// play will process and play the target file chosen with the file input element. -function play() { - const input = event.target.files[0]; - const reader = new FileReader(); - - reader.onload = event => { - const player = new Worker("player.js"); - - let rate = document.getElementById('rate'); - if (rate.value > 0) { - player.postMessage({ msg: "setFrameRate", data: rate.value }); - } - - player.onmessage = handleMessage; - - switch (input.name.split('.')[1]) { - case "mjpeg": - case "mjpg": - player.postMessage({ msg: "loadMjpeg", data: event.target.result }, [event.target.result]); - break; - case "ts": - player.postMessage({ msg: "loadMtsMjpeg", data: event.target.result }, [event.target.result]); - break; - default: - console.error("unknown file format"); - break; - } - }; - reader.onerror = error => reject(error); - reader.readAsArrayBuffer(input); -} - -// handleMessage handles messgaes from the player workers, its main job is to update the display when a frame is received. -function handleMessage(e) { - switch (e.data.msg) { - case "frame": - const blob = new Blob([new Uint8Array(e.data.data)], { - type: 'video/x-motion-jpeg' - }); - const url = URL.createObjectURL(blob); - viewer.src = url; - break; - case "log": - console.log(e.data.data); - break; - case "stop": - console.log("stopped"); - break; - default: - console.error("unknown message from player"); - break; - } -} \ No newline at end of file diff --git a/cmd/mjpeg-player/player.js b/cmd/mjpeg-player/player.js deleted file mode 100644 index 3d872c92..00000000 --- a/cmd/mjpeg-player/player.js +++ /dev/null @@ -1,132 +0,0 @@ -/* -AUTHOR - Trek Hopton - -LICENSE - This file is Copyright (C) 2020 the Australian Ocean Lab (AusOcean) - - It is free software: you can redistribute it and/or modify them - under the terms of the GNU General Public License as published by the - Free Software Foundation, either version 3 of the License, or (at your - option) any later version. - - 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. -*/ - -let frameRate = 25; //Keeps track of the frame rate, default is 25fps. -self.importScripts('./lex-mjpeg.js'); -self.importScripts('./hlsjs/mts-demuxer.js'); - -const codecs = { - MJPEG: 1, - MTS_MJPEG: 2, -} - -// onmessage is called whenever the main thread sends this worker a message. -onmessage = e => { - switch (e.data.msg) { - case "setFrameRate": - frameRate = e.data.data; - break; - case "loadMjpeg": - player = new PlayerWorker(); - player.init(codecs.MJPEG); - player.append(e.data.data); - player.setFrameRate(frameRate); - player.start(); - break; - case "loadMtsMjpeg": - player = new PlayerWorker(); - player.init(codecs.MTS_MJPEG); - player.append(e.data.data); - player.start(); - break; - case "appendMtsMjpeg": - player.append(e.data.data); - break; - default: - console.error("unknown message from main thread"); - break; - } -}; - -// PlayerWorker has a FrameBuffer to hold frames and once started, passes them one at a time to the main thread. -class PlayerWorker { - init(codec) { - this.frameRate = frameRate; - this.codec = codec; - switch (codec) { - case codecs.MJPEG: - this.frameSrc = new MJPEGLexer(); - break; - case codecs.MTS_MJPEG: - this.frameSrc = new FrameBuffer(); - break; - } - } - - setFrameRate(rate) { - this.frameRate = rate; - } - - start() { - let frame = this.frameSrc.read(); - if (frame != null) { - postMessage({ msg: "frame", data: frame.buffer }, [frame.buffer]); - setTimeout(() => { this.start(); }, 1000 / this.frameRate); - } - } - - append(data) { - this.frameSrc.append(data); - } -} - -// FrameBuffer allows an array of subarrays (MJPEG frames) to be read one at a time. -class FrameBuffer { - constructor() { - this.segments = []; - this.off = { segment: 0, frame: 0 }; - this.demuxer = new MTSDemuxer(); - } - - // read returns the next single frame. - read() { - let off = this.off; - let prevOff = off; - if (this.incrementOff()) { - return this.segments[prevOff.segment][prevOff.frame]; - } else { - return null; - } - } - - append(data) { - let demuxed = this.demuxer.demux(new Uint8Array(data)); - this.segments.push(demuxed.data); - } - - incrementOff() { - if (!this.segments || !this.segments[this.off.segment]) { - return false; - } - if (this.off.frame + 1 >= this.segments[this.off.segment].length) { - if (this.off.segment + 1 >= this.segments.length) { - return false; - } else { - this.off.segment++; - this.off.frame = 0; - return true; - } - } else { - this.off.frame++; - return true; - } - } -} \ No newline at end of file diff --git a/cmd/mjpeg-player/url-toolkit/LICENSE b/cmd/mjpeg-player/url-toolkit/LICENSE deleted file mode 100644 index fa122199..00000000 --- a/cmd/mjpeg-player/url-toolkit/LICENSE +++ /dev/null @@ -1,190 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - Copyright 2016 Tom Jenkinson - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/cmd/mjpeg-player/url-toolkit/url-toolkit.js b/cmd/mjpeg-player/url-toolkit/url-toolkit.js deleted file mode 100644 index 75c7f78a..00000000 --- a/cmd/mjpeg-player/url-toolkit/url-toolkit.js +++ /dev/null @@ -1,149 +0,0 @@ -// see https://tools.ietf.org/html/rfc1808 - -var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/\?#]*\/)*.*?)??(;.*?)?(\?.*?)?(#.*?)?$/; -var FIRST_SEGMENT_REGEX = /^([^\/?#]*)(.*)$/; -var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g; -var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g; - -var URLToolkit = { // jshint ignore:line - // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or // - // E.g - // With opts.alwaysNormalize = false (default, spec compliant) - // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g - // With opts.alwaysNormalize = true (not spec compliant) - // http://a.com/b/cd + /e/f/../g => http://a.com/e/g - buildAbsoluteURL: function (baseURL, relativeURL, opts) { - opts = opts || {}; - // remove any remaining space and CRLF - baseURL = baseURL.trim(); - relativeURL = relativeURL.trim(); - if (!relativeURL) { - // 2a) If the embedded URL is entirely empty, it inherits the - // entire base URL (i.e., is set equal to the base URL) - // and we are done. - if (!opts.alwaysNormalize) { - return baseURL; - } - var basePartsForNormalise = URLToolkit.parseURL(baseURL); - if (!basePartsForNormalise) { - throw new Error('Error trying to parse base URL.'); - } - basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path); - return URLToolkit.buildURLFromParts(basePartsForNormalise); - } - var relativeParts = URLToolkit.parseURL(relativeURL); - if (!relativeParts) { - throw new Error('Error trying to parse relative URL.'); - } - if (relativeParts.scheme) { - // 2b) If the embedded URL starts with a scheme name, it is - // interpreted as an absolute URL and we are done. - if (!opts.alwaysNormalize) { - return relativeURL; - } - relativeParts.path = URLToolkit.normalizePath(relativeParts.path); - return URLToolkit.buildURLFromParts(relativeParts); - } - var baseParts = URLToolkit.parseURL(baseURL); - if (!baseParts) { - throw new Error('Error trying to parse base URL.'); - } - if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') { - // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc - // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a' - var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path); - baseParts.netLoc = pathParts[1]; - baseParts.path = pathParts[2]; - } - if (baseParts.netLoc && !baseParts.path) { - baseParts.path = '/'; - } - var builtParts = { - // 2c) Otherwise, the embedded URL inherits the scheme of - // the base URL. - scheme: baseParts.scheme, - netLoc: relativeParts.netLoc, - path: null, - params: relativeParts.params, - query: relativeParts.query, - fragment: relativeParts.fragment - }; - if (!relativeParts.netLoc) { - // 3) If the embedded URL's is non-empty, we skip to - // Step 7. Otherwise, the embedded URL inherits the - // (if any) of the base URL. - builtParts.netLoc = baseParts.netLoc; - // 4) If the embedded URL path is preceded by a slash "/", the - // path is not relative and we skip to Step 7. - if (relativeParts.path[0] !== '/') { - if (!relativeParts.path) { - // 5) If the embedded URL path is empty (and not preceded by a - // slash), then the embedded URL inherits the base URL path - builtParts.path = baseParts.path; - // 5a) if the embedded URL's is non-empty, we skip to - // step 7; otherwise, it inherits the of the base - // URL (if any) and - if (!relativeParts.params) { - builtParts.params = baseParts.params; - // 5b) if the embedded URL's is non-empty, we skip to - // step 7; otherwise, it inherits the of the base - // URL (if any) and we skip to step 7. - if (!relativeParts.query) { - builtParts.query = baseParts.query; - } - } - } else { - // 6) The last segment of the base URL's path (anything - // following the rightmost slash "/", or the entire path if no - // slash is present) is removed and the embedded URL's path is - // appended in its place. - var baseURLPath = baseParts.path; - var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path; - builtParts.path = URLToolkit.normalizePath(newPath); - } - } - } - if (builtParts.path === null) { - builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path; - } - return URLToolkit.buildURLFromParts(builtParts); - }, - parseURL: function (url) { - var parts = URL_REGEX.exec(url); - if (!parts) { - return null; - } - return { - scheme: parts[1] || '', - netLoc: parts[2] || '', - path: parts[3] || '', - params: parts[4] || '', - query: parts[5] || '', - fragment: parts[6] || '' - }; - }, - normalizePath: function (path) { - // The following operations are - // then applied, in order, to the new path: - // 6a) All occurrences of "./", where "." is a complete path - // segment, are removed. - // 6b) If the path ends with "." as a complete path segment, - // that "." is removed. - path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); - // 6c) All occurrences of "/../", where is a - // complete path segment not equal to "..", are removed. - // Removal of these path segments is performed iteratively, - // removing the leftmost matching pattern on each iteration, - // until no matching pattern remains. - // 6d) If the path ends with "/..", where is a - // complete path segment not equal to "..", that - // "/.." is removed. - while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) { } // jshint ignore:line - return path.split('').reverse().join(''); - }, - buildURLFromParts: function (parts) { - return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment; - } -}; - -export default URLToolkit;