From 849b5c0e69171b98c611b488037a36dadde2b5e6 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Sat, 2 Jun 2018 03:12:26 +0200
Subject: [PATCH 001/197] mp4 track: more fixes to properly support multi trun
+ extract correct absolut sample offsets
---
src/demuxer/mp4/mp4-track.ts | 70 +++++++++++++++++++++---------------
1 file changed, 41 insertions(+), 29 deletions(-)
diff --git a/src/demuxer/mp4/mp4-track.ts b/src/demuxer/mp4/mp4-track.ts
index 86de769..37a3078 100644
--- a/src/demuxer/mp4/mp4-track.ts
+++ b/src/demuxer/mp4/mp4-track.ts
@@ -15,6 +15,7 @@ export type Mp4TrackDefaults = {
export class Mp4Track extends Track {
private sidx: Sidx = null;
private trunInfo: Trun[] = [];
+ private trunInfoReadIndex: number = 0;
private lastPts: number;
private timescale: number;
private defaults: Mp4TrackDefaults;
@@ -74,7 +75,7 @@ export class Mp4Track extends Track {
* Not to be confused with the base offset which maybe be present for each track fragment inside the `tfhd`.
* That value will be shared for each `trun`.
*/
- public updateSampleDataOffset(dataOffset: number) {
+ public updateInitialSampleDataOffset(dataOffset: number) {
this.dataOffset = dataOffset;
}
@@ -85,8 +86,8 @@ export class Mp4Track extends Track {
* Each trun box has it's own offset, which refers to this offset here in order to resolve the absolute position
* of sample runs.
*/
- public getSampleDataOffset(): number {
- return this.baseDataOffset + this.dataOffset;
+ public getFinalSampleDataOffset(): number {
+ return this.dataOffset + this.baseDataOffset;
}
public setSidxAtom(atom: Atom): void {
@@ -99,41 +100,52 @@ export class Mp4Track extends Track {
const trun = atom as Trun;
this.trunInfo.push(trun);
+ }
- const timescale: number = this.sidx ? this.sidx.timescale : 1;
+ public readTrunAtoms() {
+ this.trunInfo.forEach((trun: Trun, index) => {
- const sampleRunDataOffset: number = trun.dataOffset + this.getSampleDataOffset();
+ if (index < this.trunInfoReadIndex) {
+ return;
+ }
- let bytesOffset: number = sampleRunDataOffset;
+ const timescale: number = this.sidx ? this.sidx.timescale : 1;
- for (const sample of trun.samples) {
- const sampleDuration = sample.duration || this.defaults.sampleDuration;
- if (!sampleDuration) {
- throw new Error('Invalid file, samples have no duration');
- }
+ const sampleRunDataOffset: number = trun.dataOffset + this.getFinalSampleDataOffset();
- const duration: number = MICROSECOND_TIMESCALE * sampleDuration / timescale;
+ let bytesOffset: number = sampleRunDataOffset;
- this.lastPts += duration;
- this.duration += duration;
+ for (const sample of trun.samples) {
+ const sampleDuration = sample.duration || this.defaults.sampleDuration;
+ if (!sampleDuration) {
+ throw new Error('Invalid file, samples have no duration');
+ }
- const flags = sample.flags || this.defaults.sampleFlags;
- if (!flags) {
- throw new Error('Invalid file, sample has no flags');
- }
+ const duration: number = MICROSECOND_TIMESCALE * sampleDuration / timescale;
- const cto: number = MICROSECOND_TIMESCALE * (sample.compositionTimeOffset || 0) / timescale;
+ const flags = sample.flags || this.defaults.sampleFlags;
+ if (!flags) {
+ throw new Error('Invalid file, sample has no flags');
+ }
- this.frames.push(new Frame(
- sample.flags.isSyncFrame ? Frame.IDR_FRAME : Frame.P_FRAME,
- this.lastPts,
- sample.size,
- duration,
- bytesOffset,
- cto
- ));
+ const cto: number = MICROSECOND_TIMESCALE * (sample.compositionTimeOffset || 0) / timescale;
- bytesOffset += sample.size;
- }
+ this.frames.push(new Frame(
+ sample.flags.isSyncFrame ? Frame.IDR_FRAME : Frame.P_FRAME,
+ this.lastPts,
+ sample.size,
+ duration,
+ bytesOffset,
+ cto
+ ));
+
+ this.lastPts += duration;
+ this.duration += duration;
+
+ bytesOffset += sample.size;
+ }
+ })
+
+ this.trunInfoReadIndex = this.trunInfo.length;
}
}
From e9b48b8c8b7dd88cfac89100706a9f3abdef3255 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Sat, 2 Jun 2018 03:12:53 +0200
Subject: [PATCH 002/197] mp4 demux: read truns only when we have resolved data
offset fully
---
src/demuxer/mp4/mp4-demuxer.ts | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index d164203..c08e315 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -5,6 +5,7 @@ import { Tfhd } from './atoms/tfhd';
import { Track } from '../track';
import { Mp4Track } from './mp4-track';
import { Tkhd } from './atoms/tkhd';
+import { Trun } from './atoms/trun';
import { IDemuxer } from '../demuxer';
import { Frame } from '../frame';
@@ -15,6 +16,7 @@ export class Mp4Demuxer implements IDemuxer {
private atoms: Atom[];
private lastTrackId: number;
private lastTrackDataOffset: number;
+ private lastTrun: Trun;
constructor() {
this.atoms = [];
@@ -156,9 +158,13 @@ export class Mp4Demuxer implements IDemuxer {
case Atom.trun:
this.ensureTrack();
- this.getCurrentTrack().updateSampleDataOffset(this.lastTrackDataOffset);
this.getCurrentTrack().addTrunAtom(atom);
break;
+
+ case Atom.mdat:
+ this.getCurrentTrack().updateInitialSampleDataOffset(this.lastTrackDataOffset);
+ this.getCurrentTrack().readTrunAtoms();
+ break;
}
}
From 78c93628890bd924483b5adb021d0f9cb432f4b9 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 4 Jun 2018 23:39:30 +0200
Subject: [PATCH 003/197] frame: add unflagged type
---
src/demuxer/frame.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/demuxer/frame.ts b/src/demuxer/frame.ts
index 2031a0e..fb659b2 100644
--- a/src/demuxer/frame.ts
+++ b/src/demuxer/frame.ts
@@ -6,6 +6,7 @@ export class Frame {
public static IDR_FRAME: string = 'I';
public static P_FRAME: string = 'P';
public static B_FRAME: string = 'B';
+ public static UNFLAGGED_FRAME: string = '-';
private presentationTimeUs: number = 0;
From 577181ab3418ae0f456c2197663ef835046cb915 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 4 Jun 2018 23:39:41 +0200
Subject: [PATCH 004/197] frame: init bytesOffset with NaN
---
src/demuxer/frame.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/demuxer/frame.ts b/src/demuxer/frame.ts
index fb659b2..a66a33f 100644
--- a/src/demuxer/frame.ts
+++ b/src/demuxer/frame.ts
@@ -15,7 +15,7 @@ export class Frame {
public timeUs: number,
public size: number,
public duration: number = NaN,
- public bytesOffset: number = -1,
+ public bytesOffset: number = NaN,
presentationTimeOffsetUs: number = 0
) {
this.setPresentationTimeOffsetUs(presentationTimeOffsetUs);
From 97faae8b0548f4a19b22636e8ba2e1a6c81df6c0 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 4 Jun 2018 23:39:56 +0200
Subject: [PATCH 005/197] frame: getDurationInSeconds()
---
src/demuxer/frame.ts | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/demuxer/frame.ts b/src/demuxer/frame.ts
index a66a33f..10180a7 100644
--- a/src/demuxer/frame.ts
+++ b/src/demuxer/frame.ts
@@ -38,6 +38,10 @@ export class Frame {
}
getDecodingTimestampInSeconds() {
- return this.timeUs / MICROSECOND_TIMESCALE;
+ return this.getDecodingTimeUs() / MICROSECOND_TIMESCALE;
+ }
+
+ getDurationInSeconds() {
+ return this.duration / MICROSECOND_TIMESCALE;
}
}
From 5000d6bd7020d7a326a295c65c87d171ce7e90f8 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 4 Jun 2018 23:51:33 +0200
Subject: [PATCH 006/197] mdat: don't continue to try and read what doesn't
seem to be NAL units
we are crashing the Chrome window on AAC mdats
---
src/demuxer/mp4/atoms/mdat.ts | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/demuxer/mp4/atoms/mdat.ts b/src/demuxer/mp4/atoms/mdat.ts
index 64bbaf1..6c80733 100644
--- a/src/demuxer/mp4/atoms/mdat.ts
+++ b/src/demuxer/mp4/atoms/mdat.ts
@@ -16,8 +16,12 @@ export class Mdat extends Atom {
i += 4;
if (length <= 0) {
- console.log('is this an H264 stream?');
- continue;
+ //console.log('is this an H264 stream?');
+ //continue;
+
+ // let's break here since otherwise this crashes on AAC data
+ console.warn('aborted parsing mdat');
+ break;
}
const nalType: number = data[i] & 0x1F;
From 2cbbd615c82684b678b9bcdc2fdaaefc7e0efa66 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 4 Jun 2018 23:51:54 +0200
Subject: [PATCH 007/197] trun: make parseFlags public
---
src/demuxer/mp4/atoms/trun.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/demuxer/mp4/atoms/trun.ts b/src/demuxer/mp4/atoms/trun.ts
index 0536966..8617d78 100644
--- a/src/demuxer/mp4/atoms/trun.ts
+++ b/src/demuxer/mp4/atoms/trun.ts
@@ -90,7 +90,7 @@ export class Trun extends Atom {
return trun;
}
- private static parseFlags(data: Uint8Array): SampleFlags {
+ static parseFlags(data: Uint8Array): SampleFlags {
return new SampleFlags(
(data[0] & 0x0c) >>> 2,
(data[0] & 0x03),
From eefee00c88a1f1f911b9dcea9297c31c3d0d6002 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 4 Jun 2018 23:52:10 +0200
Subject: [PATCH 008/197] trun: more clear members
---
src/demuxer/mp4/atoms/trun.ts | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/src/demuxer/mp4/atoms/trun.ts b/src/demuxer/mp4/atoms/trun.ts
index 8617d78..a6f44d5 100644
--- a/src/demuxer/mp4/atoms/trun.ts
+++ b/src/demuxer/mp4/atoms/trun.ts
@@ -2,9 +2,15 @@ import ByteParserUtils from '../../../utils/byte-parser-utils';
import { Atom } from './atom';
export class SampleFlags {
- constructor(public isLeading: number, public dependsOn: number, public isDependedOn: number,
- public hasRedundancy: number, public paddingValue: number, public isSyncFrame: boolean,
- public degradationPriority: number) {
+ constructor(
+ public isLeading: number,
+ public dependsOn: number,
+ public isDependedOn: number,
+ public hasRedundancy: number,
+ public paddingValue: number,
+ public isSyncFrame: boolean,
+ public degradationPriority: number
+ ) {
}
}
From 2037c7024f81f507c4c089e330269c0ba594d525 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 4 Jun 2018 23:52:51 +0200
Subject: [PATCH 009/197] mp4-track: parse default sample flags correctly and
use as fallback
---
src/demuxer/mp4/mp4-track.ts | 22 +++++++++++++++++-----
1 file changed, 17 insertions(+), 5 deletions(-)
diff --git a/src/demuxer/mp4/mp4-track.ts b/src/demuxer/mp4/mp4-track.ts
index 37a3078..4f5c8e9 100644
--- a/src/demuxer/mp4/mp4-track.ts
+++ b/src/demuxer/mp4/mp4-track.ts
@@ -3,7 +3,7 @@ import { Atom } from './atoms/atom';
import { Frame, MICROSECOND_TIMESCALE } from '../frame';
import { Sidx } from './atoms/sidx';
-import { Trun } from './atoms/trun';
+import { Trun, SampleFlags } from './atoms/trun';
import { Tfhd } from './atoms/tfhd';
export type Mp4TrackDefaults = {
@@ -19,6 +19,7 @@ export class Mp4Track extends Track {
private lastPts: number;
private timescale: number;
private defaults: Mp4TrackDefaults;
+ private defaultSampleFlagsParsed: SampleFlags;
private baseDataOffset: number = 0;
constructor(id: number, type: string, mimeType: string, public referenceAtom: Atom, public dataOffset: number) {
@@ -53,6 +54,14 @@ export class Mp4Track extends Track {
public setDefaults(defaults: Mp4TrackDefaults) {
this.defaults = defaults;
+ if (defaults.sampleFlags) {
+ this.defaultSampleFlagsParsed = Trun.parseFlags(new Uint8Array([
+ defaults.sampleFlags & 0xff000000,
+ defaults.sampleFlags & 0x00ff0000,
+ defaults.sampleFlags & 0x0000ff00,
+ defaults.sampleFlags & 0x000000ff,
+ ]));
+ }
}
public getDefaults() {
@@ -123,16 +132,19 @@ export class Mp4Track extends Track {
const duration: number = MICROSECOND_TIMESCALE * sampleDuration / timescale;
- const flags = sample.flags || this.defaults.sampleFlags;
+ const flags = sample.flags || this.defaultSampleFlagsParsed;
if (!flags) {
- throw new Error('Invalid file, sample has no flags');
+ // in fact the trun box parser should provide a fallback instance of flags in this case
+ //throw new Error('Invalid file, sample has no flags');
}
const cto: number = MICROSECOND_TIMESCALE * (sample.compositionTimeOffset || 0) / timescale;
+ const timeUs = this.lastPts;
+
this.frames.push(new Frame(
- sample.flags.isSyncFrame ? Frame.IDR_FRAME : Frame.P_FRAME,
- this.lastPts,
+ flags ? (flags.isSyncFrame ? Frame.IDR_FRAME : Frame.P_FRAME) : Frame.UNFLAGGED_FRAME,
+ timeUs,
sample.size,
duration,
bytesOffset,
From dcf3502eab62c49e0d11a263720a919f86096493 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 4 Jun 2018 23:53:28 +0200
Subject: [PATCH 010/197] mp4-demuxer: when creating track, accept not knowing
sample data offset
---
src/demuxer/mp4/mp4-demuxer.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index c08e315..afca10e 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -5,7 +5,7 @@ import { Tfhd } from './atoms/tfhd';
import { Track } from '../track';
import { Mp4Track } from './mp4-track';
import { Tkhd } from './atoms/tkhd';
-import { Trun } from './atoms/trun';
+import { Trun, SampleFlags } from './atoms/trun';
import { IDemuxer } from '../demuxer';
import { Frame } from '../frame';
@@ -179,7 +179,7 @@ export class Mp4Demuxer implements IDemuxer {
Track.TYPE_UNKNOWN,
Track.MIME_TYPE_UNKNOWN,
null,
- this.lastTrackDataOffset
+ this.lastTrackDataOffset > 0 ? this.lastTrackDataOffset : 0
);
//this.resetLastTrackInfos();
}
From 37ba1091330e78b24d6f600c49d6346c8fb3a5c8 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 4 Jun 2018 23:55:26 +0200
Subject: [PATCH 011/197] break out main.js + styles from samples index
meanwhile the worker thing should just be optional in the same index
worker can be shipped inline with webpack btw (see worker-loader)
---
samples/main.js | 248 ++++++++++++++++++++++++++++++++++++++++++++++
samples/style.css | 61 ++++++++++++
2 files changed, 309 insertions(+)
create mode 100644 samples/main.js
create mode 100644 samples/style.css
diff --git a/samples/main.js b/samples/main.js
new file mode 100644
index 0000000..1b7ab94
--- /dev/null
+++ b/samples/main.js
@@ -0,0 +1,248 @@
+document.getElementById('fileInput')
+ .addEventListener('change', onFileChosen, false);
+
+var fileChosen = null;
+function onFileChosen(event) {
+ fileChosen = event.target.files[0];
+}
+
+function abortAllLoading() {
+ document.getElementById('loadProgress').value = 0;
+ if (request) {
+ request.onload = null;
+ request.onprogress = null;
+ request.onerror = null;
+ request.abort();
+ request = null;
+ }
+ if (reader) {
+ reader.onload = null;
+ reader.onprogress = null;
+ reader.onerror = null;
+ reader.abort();
+ reader = null;
+ }
+}
+
+var request = null;
+function loadRemoteMedia(mediaUrl, onLoaded) {
+ abortAllLoading();
+
+ var req = request = new XMLHttpRequest();
+ req.open('GET', mediaUrl, true);
+ req.responseType = 'arraybuffer';
+
+ req.onload = function () {
+
+ if (req.status >= 400) {
+ alert('An error (' + req.status + ') happened trying to load the remote resource: ' + mediaUrl);
+ abortAllLoading();
+ return;
+ }
+
+ var arrayBuffer = req.response;
+ onLoaded(arrayBuffer);
+ };
+ req.onprogress = function (event) {
+ document.getElementById('loadProgress').value = (event.loaded / event.total) * 100;
+ }
+ req.onerror = function () {
+ alert('A fatal error happened trying to load the remote resource: ' + mediaUrl);
+ }
+ req.send(null);
+}
+
+var reader = null;
+function loadLocalMedia(file, onLoaded) {
+ abortAllLoading();
+
+ reader = new FileReader();
+ reader.onload = function onFileRead(event) {
+ onLoaded(reader.result);
+ };
+ reader.onprogress = function(event) {
+ document.getElementById('loadProgress').value = (event.loaded / event.total) * 100;
+ }
+ reader.onerror = function () {
+ alert('A fatal error happened trying to load the file resource: ' + file);
+ }
+ reader.readAsArrayBuffer(file);
+}
+
+function inspectMedia(type) {
+ clearTracksInfo();
+ clearAtomsTree();
+ clearAtomDetails();
+
+ switch (type) {
+ case 'local':
+ if (!fileChosen) {
+ window.alert('Please choose a file...');
+ return;
+ }
+ document.getElementById('uri').value = fileChosen.name;
+ loadLocalMedia(fileChosen, onMediaLoaded.bind(null, fileChosen.name));
+ break;
+ case 'remote-sample-media':
+ var mediaUrl = document.getElementById("mediaUrl").value;
+ if (!mediaUrl) {
+ window.alert('Please choose a URL...');
+ return;
+ }
+ document.getElementById('uri').value = mediaUrl;
+ loadRemoteMedia(mediaUrl, onMediaLoaded.bind(null, mediaUrl));
+ break;
+ case 'remote-input-url':
+ var mediaUrl = document.getElementById('urlInput').value;
+ if (!mediaUrl) {
+ window.alert('Please input a URL...');
+ return;
+ }
+ document.getElementById('uri').value = mediaUrl;
+ loadRemoteMedia(mediaUrl, onMediaLoaded.bind(null, mediaUrl));
+ break;
+ }
+}
+
+function onMediaLoaded(uri, arrayBuffer) {
+ document.getElementById('totalBytes')
+ .value = arrayBuffer.byteLength;
+
+ var byteArray = new Uint8Array(arrayBuffer);
+
+ var demuxer = createDemuxer(uri);
+
+ demuxer.append(byteArray);
+ demuxer.end();
+
+ updateAtomsInfo(demuxer);
+ updateTracksInfo(demuxer);
+}
+
+function createDemuxer(uri) {
+ // Find out demuxer looking at file extension. "Enough" good for this sample
+ var ext = uri.split('.').pop();
+ if (ext === 'ts') {
+ return inspectorjs.createMpegTSDemuxer();
+ } else if (ext === 'webm') {
+ return inspectorjs.createWebMDemuxer();
+ } else { // TODO: add regex here
+ return inspectorjs.createMp4Demuxer();
+ }
+}
+
+function updateTracksInfo(demuxer) {
+ var el = document.getElementById('tracks');
+ for (var trackId in demuxer.tracks) {
+ addTrackInfo(el, demuxer.tracks[trackId]);
+ }
+}
+
+function addTrackInfo(el, track) {
+ var trackEl = document.createElement('div');
+
+ // Add track general info
+ trackEl.innerHTML = 'Track # ' + track.id + ' - ' + track.mimeType + '
';
+
+ // Add frame details
+ var framesEl = document.createElement('ul');
+ for (var frame of track.frames) {
+ var frameEl = document.createElement('li');
+ frameEl.innerHTML
+ = 'Frame: type = ' + frame.frameType + ' | '
+ + '[ PTS / DTS = '
+ + frame.getDecodingTimestampInSeconds().toFixed(3)
+ + ' / '
+ + frame.getPresentationTimestampInSeconds().toFixed(3)
+ + ' ( '
+ + (frame.getDurationInSeconds().toFixed(3) || 0) + ' )'
+ + ' @' + frame.bytesOffset
+ + ' -> '
+ + (frame.bytesOffset + frame.size - 1)
+ + ' ( ' + frame.size + ' ) '
+ + ' ]'
+ ;
+
+ framesEl.appendChild(frameEl);
+ }
+ trackEl.appendChild(framesEl);
+
+ // Add track info to dom
+ el.appendChild(trackEl);
+}
+
+function updateAtomsInfo(demuxer) {
+ // Only makes sense for MP4 files
+ if (demuxer.atoms) {
+ var el = document.getElementById('atoms');
+ addAtoms(el, demuxer.atoms);
+ }
+}
+
+// Add information about MP4 atoms
+function addAtoms(parent, atoms) {
+ if (!atoms || atoms.length === 0) return;
+
+ var containerEl = document.createElement('ul');
+ for (var atom of atoms) {
+ var atomEl = document.createElement('li');
+ atomEl.innerHTML = atom.type + ', ' + atom.size + ' bytes';
+ atomEl.onclick = renderAtomDetails.bind(null, atom);
+
+ containerEl.appendChild(atomEl);
+
+ if (atom.atoms && atom.atoms.length > 0) {
+ addAtoms(atomEl, atom.atoms);
+ }
+ }
+
+ parent.appendChild(containerEl);
+}
+
+function renderAtomDetails(atom, event) {
+ event.stopPropagation();
+ var el = document.getElementById('details');
+ clearAtomDetails();
+
+ var containerEl = document.createElement('ul');
+ for (var p in atom) {
+ if (atom.hasOwnProperty(p) && shouldRenderAtom(p, atom)) {
+ var item = document.createElement('li');
+ item.innerHTML = '' + p + ":
" + JSON.stringify(atom[p], null, 4) + '
';
+ containerEl.appendChild(item);
+ //containerEl.appendChild(document.createElement('br'));
+ }
+ }
+
+ el.appendChild(containerEl);
+}
+
+function clearTracksInfo() {
+ var el = document.getElementById('tracks');
+
+ // Remove any child of tracks element
+ while (el.firstChild) {
+ el.removeChild(el.firstChild);
+ }
+}
+
+function clearAtomsTree() {
+ var el = document.getElementById('atoms');
+
+ // Remove any child of tracks element
+ while (el.firstChild) {
+ el.removeChild(el.firstChild);
+ }
+}
+
+function clearAtomDetails() {
+ var el = document.getElementById('details');
+ while (el.firstChild) {
+ el.removeChild(el.firstChild);
+ }
+}
+
+function shouldRenderAtom(p, atom) {
+ return p !== 'atoms' && p !== 'containerDataOffset' && p !== 'constructor';
+ //&& !Array.isArray(atom[p]);
+}
diff --git a/samples/style.css b/samples/style.css
new file mode 100644
index 0000000..217f44e
--- /dev/null
+++ b/samples/style.css
@@ -0,0 +1,61 @@
+body {
+ font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif
+}
+
+input {
+ font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif
+}
+
+input[type=button] {
+ font-size: 1em;
+ background-color: #94bf1e;
+}
+
+input[type=file] {
+ font-size: 1em;
+ border: 1px solid gray;
+}
+
+input[type=input] {
+ font-size: 1em;
+}
+
+#viewer {
+ width: 100%;
+ overflow-x: auto;
+ position: relative;
+ scroll-behavior: auto;
+}
+
+#atoms-info {
+ margin-top: 40px;
+ background-color: #94bf1e;
+ color: #ffffff;
+ border: 1px solid black;
+ display: flex;
+ float:left;
+ /*width: 50%;*/
+}
+
+.atom_column {
+ flex: 50%;
+}
+
+#atoms li {
+ cursor: pointer;
+}
+
+#details {
+ background-color: #000000;
+}
+
+#tracks {
+ float:right;
+ /*width: 50%;*/
+ margin-top: 20px;
+}
+
+#tracks li {
+ font-size: 0.8em;
+}
+
From 0a4e2d130bc1589d3e02fd26ef65d168bd636f28 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 4 Jun 2018 23:56:17 +0200
Subject: [PATCH 012/197] add editorconfig files
---
.editorconfig | 28 ++++++++++++++++++++++++++++
samples/.editorconfig | 2 ++
2 files changed, 30 insertions(+)
create mode 100644 .editorconfig
create mode 100644 samples/.editorconfig
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..1abcdaf
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,28 @@
+root = true
+
+[*]
+end_of_line = lf
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+charset = utf-8
+
+[*.yml]
+indent_size = 2
+
+[*.json]
+indent_size = 2
+
+[*.html]
+indent_size = 2
+
+[*.css]
+indent_size = 2
+
+[.*]
+indent_size = 2
+
+[*.md]
+trim_trailing_whitespace = false
+
diff --git a/samples/.editorconfig b/samples/.editorconfig
new file mode 100644
index 0000000..4ae90dd
--- /dev/null
+++ b/samples/.editorconfig
@@ -0,0 +1,2 @@
+[*.js]
+indent_size = 2
From 1179167e5955feadf82de7006b131aac3ed99013 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 4 Jun 2018 23:56:53 +0200
Subject: [PATCH 013/197] webpack: publicPath should be /
---
webpack.config.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/webpack.config.js b/webpack.config.js
index 91c3d43..7b155f2 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -21,6 +21,7 @@ const config = {
// We target a UMD and name it MyLib. When including the bundle in the browser
// it will be accessible at `window.MyLib`
output: {
+ publicPath: '/',
path: PATHS.bundles,
filename: '[name].js',
libraryTarget: 'umd',
From 82f3369ab3e8fcedf42263bab51794f7038b2f3f Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 4 Jun 2018 23:57:13 +0200
Subject: [PATCH 014/197] samples index: Improve UI
---
samples/index.html | 215 +++++++++------------------------------------
1 file changed, 40 insertions(+), 175 deletions(-)
diff --git a/samples/index.html b/samples/index.html
index c9f296d..b004be2 100644
--- a/samples/index.html
+++ b/samples/index.html
@@ -1,47 +1,20 @@
-
+
- <%= htmlWebpackPlugin.options.title %>
+ Inspector.js - media file viewer and analyzer
-
-
+
- Basic sample
+ Inspector.js - media file viewer and analyzer
Sample media assets:
-
+
-
-
-
-
-
+
+
-
\ No newline at end of file
+
From 8c22289050fb50363b63ef1ddd4ebcf0fbfe22de Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 4 Jun 2018 23:58:47 +0200
Subject: [PATCH 015/197] proper lint script in package
---
package.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/package.json b/package.json
index d86d30f..6f637a2 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,8 @@
"build:prod": "cross-env NODE_ENV=production webpack --config ./webpack.config.js --progress --profile --color --display-error-details --display-cached --bail",
"clean": "npm cache clear && rimraf -- dist",
"test": "npm run build:dev && mocha --compilers js:babel-core/register --colors ./test/*.spec.js",
- "test:watch": "mocha --compilers js:babel-core/register --colors -w ./test/*.spec.js"
+ "test:watch": "mocha --compilers js:babel-core/register --colors -w ./test/*.spec.js",
+ "lint": "tslint --project ."
},
"author": "jesus@epiclabs.io",
"license": "ISC",
@@ -39,7 +40,6 @@
"sass-loader": "^6.0.6",
"style-loader": "^0.18.2",
"tslint": "^5.1.0",
- "tslint-loader": "^3.5.2",
"typescript": "^2.2.2",
"webpack": "^2.4.1",
"webpack-build-notifier": "^0.1.14",
From ed431e81f94e96c3647730dae977a62312261ec8 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Fri, 1 Jun 2018 19:05:04 +0200
Subject: [PATCH 016/197] add package-lock file
---
package-lock.json | 8982 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 8982 insertions(+)
create mode 100644 package-lock.json
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..dcf6cae
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,8982 @@
+{
+ "name": "inspector.js",
+ "version": "1.1.6",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "abbrev": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+ "dev": true
+ },
+ "accepts": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz",
+ "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=",
+ "dev": true,
+ "requires": {
+ "mime-types": "2.1.18",
+ "negotiator": "0.6.1"
+ }
+ },
+ "acorn": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.6.0.tgz",
+ "integrity": "sha512-QatFQ4C0n+PLqemyC6zXEv04tSqRR0hRqe+uGKPEVgKe2G8kl8wJvHzRYWwz6vqqEqt6idPVMFojZ4P1zlyAzQ==",
+ "dev": true
+ },
+ "acorn-dynamic-import": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz",
+ "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=",
+ "dev": true,
+ "requires": {
+ "acorn": "4.0.13"
+ },
+ "dependencies": {
+ "acorn": {
+ "version": "4.0.13",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz",
+ "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=",
+ "dev": true
+ }
+ }
+ },
+ "after": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
+ "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=",
+ "dev": true
+ },
+ "ajv": {
+ "version": "5.5.2",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
+ "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
+ "dev": true,
+ "requires": {
+ "co": "4.6.0",
+ "fast-deep-equal": "1.1.0",
+ "fast-json-stable-stringify": "2.0.0",
+ "json-schema-traverse": "0.3.1"
+ }
+ },
+ "ajv-keywords": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz",
+ "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=",
+ "dev": true
+ },
+ "align-text": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
+ "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
+ "dev": true,
+ "requires": {
+ "kind-of": "3.2.2",
+ "longest": "1.0.1",
+ "repeat-string": "1.6.1"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "1.1.6"
+ }
+ }
+ }
+ },
+ "alphanum-sort": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz",
+ "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=",
+ "dev": true
+ },
+ "amdefine": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
+ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
+ "dev": true
+ },
+ "ansi-html": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz",
+ "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=",
+ "dev": true
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "1.9.1"
+ }
+ },
+ "anymatch": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
+ "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
+ "dev": true,
+ "requires": {
+ "micromatch": "3.1.10",
+ "normalize-path": "2.1.1"
+ }
+ },
+ "aproba": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
+ "dev": true
+ },
+ "are-we-there-yet": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
+ "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
+ "dev": true,
+ "requires": {
+ "delegates": "1.0.0",
+ "readable-stream": "2.3.6"
+ }
+ },
+ "argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "requires": {
+ "sprintf-js": "1.0.3"
+ }
+ },
+ "arr-diff": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+ "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
+ "dev": true
+ },
+ "arr-flatten": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
+ "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
+ "dev": true
+ },
+ "arr-union": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
+ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
+ "dev": true
+ },
+ "array-find-index": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
+ "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
+ "dev": true
+ },
+ "array-flatten": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.1.tgz",
+ "integrity": "sha1-Qmu52oQJDBg42BLIFQryCoMx4pY=",
+ "dev": true
+ },
+ "array-union": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
+ "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
+ "dev": true,
+ "requires": {
+ "array-uniq": "1.0.3"
+ }
+ },
+ "array-uniq": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
+ "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
+ "dev": true
+ },
+ "array-unique": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
+ "dev": true
+ },
+ "arraybuffer.slice": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz",
+ "integrity": "sha1-8zshWfBTKj8xB6JywMz70a0peco=",
+ "dev": true
+ },
+ "asn1": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
+ "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=",
+ "dev": true
+ },
+ "asn1.js": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
+ "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
+ "dev": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "inherits": "2.0.3",
+ "minimalistic-assert": "1.0.1"
+ }
+ },
+ "assert": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz",
+ "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=",
+ "dev": true,
+ "requires": {
+ "util": "0.10.3"
+ }
+ },
+ "assert-plus": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
+ "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=",
+ "dev": true
+ },
+ "assertion-error": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+ "dev": true
+ },
+ "assign-symbols": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
+ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
+ "dev": true
+ },
+ "async": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz",
+ "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==",
+ "dev": true,
+ "requires": {
+ "lodash": "4.17.10"
+ }
+ },
+ "async-each": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz",
+ "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=",
+ "dev": true
+ },
+ "async-foreach": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz",
+ "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=",
+ "dev": true
+ },
+ "asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
+ "dev": true
+ },
+ "atob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz",
+ "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=",
+ "dev": true
+ },
+ "autoprefixer": {
+ "version": "6.7.7",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz",
+ "integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=",
+ "dev": true,
+ "requires": {
+ "browserslist": "1.7.7",
+ "caniuse-db": "1.0.30000847",
+ "normalize-range": "0.1.2",
+ "num2fraction": "1.2.2",
+ "postcss": "5.2.18",
+ "postcss-value-parser": "3.3.0"
+ }
+ },
+ "awesome-typescript-loader": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/awesome-typescript-loader/-/awesome-typescript-loader-3.5.0.tgz",
+ "integrity": "sha512-qzgm9SEvodVkSi9QY7Me1/rujg+YBNMjayNSAyzNghwTEez++gXoPCwMvpbHRG7wrOkDCiF6dquvv9ESmUBAuw==",
+ "dev": true,
+ "requires": {
+ "chalk": "2.4.1",
+ "enhanced-resolve": "3.3.0",
+ "loader-utils": "1.1.0",
+ "lodash": "4.17.10",
+ "micromatch": "3.1.10",
+ "mkdirp": "0.5.1",
+ "source-map-support": "0.5.6"
+ }
+ },
+ "aws-sign2": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
+ "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=",
+ "dev": true
+ },
+ "aws4": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz",
+ "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==",
+ "dev": true
+ },
+ "babel-code-frame": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
+ "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
+ "dev": true,
+ "requires": {
+ "chalk": "1.1.3",
+ "esutils": "2.0.2",
+ "js-tokens": "3.0.2"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+ "dev": true
+ },
+ "chalk": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "2.2.1",
+ "escape-string-regexp": "1.0.5",
+ "has-ansi": "2.0.0",
+ "strip-ansi": "3.0.1",
+ "supports-color": "2.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+ "dev": true
+ }
+ }
+ },
+ "babel-core": {
+ "version": "6.26.3",
+ "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz",
+ "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==",
+ "dev": true,
+ "requires": {
+ "babel-code-frame": "6.26.0",
+ "babel-generator": "6.26.1",
+ "babel-helpers": "6.24.1",
+ "babel-messages": "6.23.0",
+ "babel-register": "6.26.0",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0",
+ "babylon": "6.18.0",
+ "convert-source-map": "1.5.1",
+ "debug": "2.6.9",
+ "json5": "0.5.1",
+ "lodash": "4.17.10",
+ "minimatch": "3.0.4",
+ "path-is-absolute": "1.0.1",
+ "private": "0.1.8",
+ "slash": "1.0.0",
+ "source-map": "0.5.7"
+ }
+ },
+ "babel-generator": {
+ "version": "6.26.1",
+ "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz",
+ "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==",
+ "dev": true,
+ "requires": {
+ "babel-messages": "6.23.0",
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0",
+ "detect-indent": "4.0.0",
+ "jsesc": "1.3.0",
+ "lodash": "4.17.10",
+ "source-map": "0.5.7",
+ "trim-right": "1.0.1"
+ }
+ },
+ "babel-helper-call-delegate": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz",
+ "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=",
+ "dev": true,
+ "requires": {
+ "babel-helper-hoist-variables": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helper-define-map": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz",
+ "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=",
+ "dev": true,
+ "requires": {
+ "babel-helper-function-name": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0",
+ "lodash": "4.17.10"
+ }
+ },
+ "babel-helper-function-name": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz",
+ "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=",
+ "dev": true,
+ "requires": {
+ "babel-helper-get-function-arity": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helper-get-function-arity": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz",
+ "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helper-hoist-variables": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz",
+ "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helper-optimise-call-expression": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz",
+ "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helper-regex": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz",
+ "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0",
+ "lodash": "4.17.10"
+ }
+ },
+ "babel-helper-replace-supers": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz",
+ "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=",
+ "dev": true,
+ "requires": {
+ "babel-helper-optimise-call-expression": "6.24.1",
+ "babel-messages": "6.23.0",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-helpers": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz",
+ "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0"
+ }
+ },
+ "babel-messages": {
+ "version": "6.23.0",
+ "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz",
+ "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-add-module-exports": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-0.2.1.tgz",
+ "integrity": "sha1-mumh9KjcZ/DN7E9K7aHkOl/2XiU=",
+ "dev": true
+ },
+ "babel-plugin-check-es2015-constants": {
+ "version": "6.22.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz",
+ "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-arrow-functions": {
+ "version": "6.22.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz",
+ "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-block-scoped-functions": {
+ "version": "6.22.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz",
+ "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-block-scoping": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz",
+ "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0",
+ "lodash": "4.17.10"
+ }
+ },
+ "babel-plugin-transform-es2015-classes": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz",
+ "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=",
+ "dev": true,
+ "requires": {
+ "babel-helper-define-map": "6.26.0",
+ "babel-helper-function-name": "6.24.1",
+ "babel-helper-optimise-call-expression": "6.24.1",
+ "babel-helper-replace-supers": "6.24.1",
+ "babel-messages": "6.23.0",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-computed-properties": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz",
+ "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-destructuring": {
+ "version": "6.23.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz",
+ "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-duplicate-keys": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz",
+ "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-for-of": {
+ "version": "6.23.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz",
+ "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-function-name": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz",
+ "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=",
+ "dev": true,
+ "requires": {
+ "babel-helper-function-name": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-literals": {
+ "version": "6.22.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz",
+ "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-modules-amd": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz",
+ "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=",
+ "dev": true,
+ "requires": {
+ "babel-plugin-transform-es2015-modules-commonjs": "6.26.2",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-modules-commonjs": {
+ "version": "6.26.2",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz",
+ "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==",
+ "dev": true,
+ "requires": {
+ "babel-plugin-transform-strict-mode": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-modules-systemjs": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz",
+ "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=",
+ "dev": true,
+ "requires": {
+ "babel-helper-hoist-variables": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-modules-umd": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz",
+ "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=",
+ "dev": true,
+ "requires": {
+ "babel-plugin-transform-es2015-modules-amd": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-object-super": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz",
+ "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=",
+ "dev": true,
+ "requires": {
+ "babel-helper-replace-supers": "6.24.1",
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-parameters": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz",
+ "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=",
+ "dev": true,
+ "requires": {
+ "babel-helper-call-delegate": "6.24.1",
+ "babel-helper-get-function-arity": "6.24.1",
+ "babel-runtime": "6.26.0",
+ "babel-template": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-shorthand-properties": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz",
+ "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-spread": {
+ "version": "6.22.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz",
+ "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-sticky-regex": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz",
+ "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=",
+ "dev": true,
+ "requires": {
+ "babel-helper-regex": "6.26.0",
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-template-literals": {
+ "version": "6.22.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz",
+ "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-typeof-symbol": {
+ "version": "6.23.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz",
+ "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-unicode-regex": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz",
+ "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=",
+ "dev": true,
+ "requires": {
+ "babel-helper-regex": "6.26.0",
+ "babel-runtime": "6.26.0",
+ "regexpu-core": "2.0.0"
+ }
+ },
+ "babel-plugin-transform-regenerator": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz",
+ "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=",
+ "dev": true,
+ "requires": {
+ "regenerator-transform": "0.10.1"
+ }
+ },
+ "babel-plugin-transform-strict-mode": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz",
+ "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0"
+ }
+ },
+ "babel-preset-es2015": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz",
+ "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=",
+ "dev": true,
+ "requires": {
+ "babel-plugin-check-es2015-constants": "6.22.0",
+ "babel-plugin-transform-es2015-arrow-functions": "6.22.0",
+ "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0",
+ "babel-plugin-transform-es2015-block-scoping": "6.26.0",
+ "babel-plugin-transform-es2015-classes": "6.24.1",
+ "babel-plugin-transform-es2015-computed-properties": "6.24.1",
+ "babel-plugin-transform-es2015-destructuring": "6.23.0",
+ "babel-plugin-transform-es2015-duplicate-keys": "6.24.1",
+ "babel-plugin-transform-es2015-for-of": "6.23.0",
+ "babel-plugin-transform-es2015-function-name": "6.24.1",
+ "babel-plugin-transform-es2015-literals": "6.22.0",
+ "babel-plugin-transform-es2015-modules-amd": "6.24.1",
+ "babel-plugin-transform-es2015-modules-commonjs": "6.26.2",
+ "babel-plugin-transform-es2015-modules-systemjs": "6.24.1",
+ "babel-plugin-transform-es2015-modules-umd": "6.24.1",
+ "babel-plugin-transform-es2015-object-super": "6.24.1",
+ "babel-plugin-transform-es2015-parameters": "6.24.1",
+ "babel-plugin-transform-es2015-shorthand-properties": "6.24.1",
+ "babel-plugin-transform-es2015-spread": "6.22.0",
+ "babel-plugin-transform-es2015-sticky-regex": "6.24.1",
+ "babel-plugin-transform-es2015-template-literals": "6.22.0",
+ "babel-plugin-transform-es2015-typeof-symbol": "6.23.0",
+ "babel-plugin-transform-es2015-unicode-regex": "6.24.1",
+ "babel-plugin-transform-regenerator": "6.26.0"
+ }
+ },
+ "babel-register": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz",
+ "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=",
+ "dev": true,
+ "requires": {
+ "babel-core": "6.26.3",
+ "babel-runtime": "6.26.0",
+ "core-js": "2.5.7",
+ "home-or-tmp": "2.0.0",
+ "lodash": "4.17.10",
+ "mkdirp": "0.5.1",
+ "source-map-support": "0.4.18"
+ },
+ "dependencies": {
+ "source-map-support": {
+ "version": "0.4.18",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz",
+ "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==",
+ "dev": true,
+ "requires": {
+ "source-map": "0.5.7"
+ }
+ }
+ }
+ },
+ "babel-runtime": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
+ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
+ "dev": true,
+ "requires": {
+ "core-js": "2.5.7",
+ "regenerator-runtime": "0.11.1"
+ }
+ },
+ "babel-template": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz",
+ "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0",
+ "babylon": "6.18.0",
+ "lodash": "4.17.10"
+ }
+ },
+ "babel-traverse": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz",
+ "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=",
+ "dev": true,
+ "requires": {
+ "babel-code-frame": "6.26.0",
+ "babel-messages": "6.23.0",
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0",
+ "babylon": "6.18.0",
+ "debug": "2.6.9",
+ "globals": "9.18.0",
+ "invariant": "2.2.4",
+ "lodash": "4.17.10"
+ }
+ },
+ "babel-types": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz",
+ "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "esutils": "2.0.2",
+ "lodash": "4.17.10",
+ "to-fast-properties": "1.0.3"
+ }
+ },
+ "babylon": {
+ "version": "6.18.0",
+ "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
+ "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==",
+ "dev": true
+ },
+ "backo2": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
+ "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=",
+ "dev": true
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+ "dev": true
+ },
+ "base": {
+ "version": "0.11.2",
+ "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
+ "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
+ "dev": true,
+ "requires": {
+ "cache-base": "1.0.1",
+ "class-utils": "0.3.6",
+ "component-emitter": "1.2.1",
+ "define-property": "1.0.0",
+ "isobject": "3.0.1",
+ "mixin-deep": "1.3.1",
+ "pascalcase": "0.1.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "1.0.2"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "6.0.2"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "6.0.2"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "1.0.0",
+ "is-data-descriptor": "1.0.0",
+ "kind-of": "6.0.2"
+ }
+ }
+ }
+ },
+ "base64-arraybuffer": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
+ "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=",
+ "dev": true
+ },
+ "base64-js": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz",
+ "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==",
+ "dev": true
+ },
+ "base64id": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz",
+ "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=",
+ "dev": true
+ },
+ "batch": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
+ "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=",
+ "dev": true
+ },
+ "bcrypt-pbkdf": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
+ "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "tweetnacl": "0.14.5"
+ }
+ },
+ "better-assert": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz",
+ "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=",
+ "dev": true,
+ "requires": {
+ "callsite": "1.0.0"
+ }
+ },
+ "big.js": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz",
+ "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==",
+ "dev": true
+ },
+ "binary-extensions": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz",
+ "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=",
+ "dev": true
+ },
+ "blessed": {
+ "version": "0.1.81",
+ "resolved": "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz",
+ "integrity": "sha1-+WLWh+wsNpVwrnGvhDJW5tDKESk=",
+ "dev": true
+ },
+ "blob": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz",
+ "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=",
+ "dev": true
+ },
+ "block-stream": {
+ "version": "0.0.9",
+ "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
+ "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.3"
+ }
+ },
+ "bluebird": {
+ "version": "3.5.1",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz",
+ "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==",
+ "dev": true
+ },
+ "bn.js": {
+ "version": "4.11.8",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
+ "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
+ "dev": true
+ },
+ "body-parser": {
+ "version": "1.18.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz",
+ "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=",
+ "dev": true,
+ "requires": {
+ "bytes": "3.0.0",
+ "content-type": "1.0.4",
+ "debug": "2.6.9",
+ "depd": "1.1.2",
+ "http-errors": "1.6.3",
+ "iconv-lite": "0.4.19",
+ "on-finished": "2.3.0",
+ "qs": "6.5.1",
+ "raw-body": "2.3.2",
+ "type-is": "1.6.16"
+ },
+ "dependencies": {
+ "qs": {
+ "version": "6.5.1",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
+ "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==",
+ "dev": true
+ }
+ }
+ },
+ "bonjour": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz",
+ "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=",
+ "dev": true,
+ "requires": {
+ "array-flatten": "2.1.1",
+ "deep-equal": "1.0.1",
+ "dns-equal": "1.0.0",
+ "dns-txt": "2.0.2",
+ "multicast-dns": "6.2.3",
+ "multicast-dns-service-types": "1.1.0"
+ }
+ },
+ "boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
+ "dev": true
+ },
+ "boom": {
+ "version": "2.10.1",
+ "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
+ "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
+ "dev": true,
+ "requires": {
+ "hoek": "2.16.3"
+ }
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "braces": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+ "dev": true,
+ "requires": {
+ "arr-flatten": "1.1.0",
+ "array-unique": "0.3.2",
+ "extend-shallow": "2.0.1",
+ "fill-range": "4.0.0",
+ "isobject": "3.0.1",
+ "repeat-element": "1.1.2",
+ "snapdragon": "0.8.2",
+ "snapdragon-node": "2.1.1",
+ "split-string": "3.1.0",
+ "to-regex": "3.0.2"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "0.1.1"
+ }
+ }
+ }
+ },
+ "brorand": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
+ "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
+ "dev": true
+ },
+ "browser-stdout": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz",
+ "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=",
+ "dev": true
+ },
+ "browserify-aes": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+ "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
+ "dev": true,
+ "requires": {
+ "buffer-xor": "1.0.3",
+ "cipher-base": "1.0.4",
+ "create-hash": "1.2.0",
+ "evp_bytestokey": "1.0.3",
+ "inherits": "2.0.3",
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "browserify-cipher": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
+ "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
+ "dev": true,
+ "requires": {
+ "browserify-aes": "1.2.0",
+ "browserify-des": "1.0.1",
+ "evp_bytestokey": "1.0.3"
+ }
+ },
+ "browserify-des": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.1.tgz",
+ "integrity": "sha512-zy0Cobe3hhgpiOM32Tj7KQ3Vl91m0njwsjzZQK1L+JDf11dzP9qIvjreVinsvXrgfjhStXwUWAEpB9D7Gwmayw==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "1.0.4",
+ "des.js": "1.0.0",
+ "inherits": "2.0.3"
+ }
+ },
+ "browserify-rsa": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+ "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
+ "dev": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "randombytes": "2.0.6"
+ }
+ },
+ "browserify-sign": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz",
+ "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=",
+ "dev": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "browserify-rsa": "4.0.1",
+ "create-hash": "1.2.0",
+ "create-hmac": "1.1.7",
+ "elliptic": "6.4.0",
+ "inherits": "2.0.3",
+ "parse-asn1": "5.1.1"
+ }
+ },
+ "browserify-zlib": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
+ "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
+ "dev": true,
+ "requires": {
+ "pako": "1.0.6"
+ }
+ },
+ "browserslist": {
+ "version": "1.7.7",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz",
+ "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=",
+ "dev": true,
+ "requires": {
+ "caniuse-db": "1.0.30000847",
+ "electron-to-chromium": "1.3.48"
+ }
+ },
+ "buffer": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
+ "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
+ "dev": true,
+ "requires": {
+ "base64-js": "1.3.0",
+ "ieee754": "1.1.11",
+ "isarray": "1.0.0"
+ }
+ },
+ "buffer-from": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz",
+ "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==",
+ "dev": true
+ },
+ "buffer-indexof": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz",
+ "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==",
+ "dev": true
+ },
+ "buffer-xor": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
+ "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
+ "dev": true
+ },
+ "builtin-modules": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
+ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
+ "dev": true
+ },
+ "builtin-status-codes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
+ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
+ "dev": true
+ },
+ "bytes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
+ "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=",
+ "dev": true
+ },
+ "cache-base": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
+ "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
+ "dev": true,
+ "requires": {
+ "collection-visit": "1.0.0",
+ "component-emitter": "1.2.1",
+ "get-value": "2.0.6",
+ "has-value": "1.0.0",
+ "isobject": "3.0.1",
+ "set-value": "2.0.0",
+ "to-object-path": "0.3.0",
+ "union-value": "1.0.0",
+ "unset-value": "1.0.0"
+ }
+ },
+ "callsite": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
+ "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=",
+ "dev": true
+ },
+ "camel-case": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
+ "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=",
+ "dev": true,
+ "requires": {
+ "no-case": "2.3.2",
+ "upper-case": "1.1.3"
+ }
+ },
+ "camelcase": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
+ "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=",
+ "dev": true
+ },
+ "camelcase-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+ "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
+ "dev": true,
+ "requires": {
+ "camelcase": "2.1.1",
+ "map-obj": "1.0.1"
+ }
+ },
+ "caniuse-api": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-1.6.1.tgz",
+ "integrity": "sha1-tTTnxzTE+B7F++isoq0kNUuWLGw=",
+ "dev": true,
+ "requires": {
+ "browserslist": "1.7.7",
+ "caniuse-db": "1.0.30000847",
+ "lodash.memoize": "4.1.2",
+ "lodash.uniq": "4.5.0"
+ }
+ },
+ "caniuse-db": {
+ "version": "1.0.30000847",
+ "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000847.tgz",
+ "integrity": "sha1-/0BypUaICf7ArprDtANe+JHlsUQ=",
+ "dev": true
+ },
+ "caseless": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz",
+ "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=",
+ "dev": true
+ },
+ "center-align": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz",
+ "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=",
+ "dev": true,
+ "requires": {
+ "align-text": "0.1.4",
+ "lazy-cache": "1.0.4"
+ }
+ },
+ "chai": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz",
+ "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=",
+ "dev": true,
+ "requires": {
+ "assertion-error": "1.1.0",
+ "check-error": "1.0.2",
+ "deep-eql": "3.0.1",
+ "get-func-name": "2.0.0",
+ "pathval": "1.1.0",
+ "type-detect": "4.0.8"
+ }
+ },
+ "chalk": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
+ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "3.2.1",
+ "escape-string-regexp": "1.0.5",
+ "supports-color": "5.4.0"
+ }
+ },
+ "check-error": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
+ "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
+ "dev": true
+ },
+ "chokidar": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz",
+ "integrity": "sha512-zW8iXYZtXMx4kux/nuZVXjkLP+CyIK5Al5FHnj1OgTKGZfp4Oy6/ymtMSKFv3GD8DviEmUPmJg9eFdJ/JzudMg==",
+ "dev": true,
+ "requires": {
+ "anymatch": "2.0.0",
+ "async-each": "1.0.1",
+ "braces": "2.3.2",
+ "fsevents": "1.2.4",
+ "glob-parent": "3.1.0",
+ "inherits": "2.0.3",
+ "is-binary-path": "1.0.1",
+ "is-glob": "4.0.0",
+ "normalize-path": "2.1.1",
+ "path-is-absolute": "1.0.1",
+ "readdirp": "2.1.0",
+ "upath": "1.1.0"
+ }
+ },
+ "cipher-base": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
+ "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "clap": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz",
+ "integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==",
+ "dev": true,
+ "requires": {
+ "chalk": "1.1.3"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+ "dev": true
+ },
+ "chalk": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "2.2.1",
+ "escape-string-regexp": "1.0.5",
+ "has-ansi": "2.0.0",
+ "strip-ansi": "3.0.1",
+ "supports-color": "2.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+ "dev": true
+ }
+ }
+ },
+ "class-utils": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
+ "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
+ "dev": true,
+ "requires": {
+ "arr-union": "3.1.0",
+ "define-property": "0.2.5",
+ "isobject": "3.0.1",
+ "static-extend": "0.1.2"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "0.1.6"
+ }
+ }
+ }
+ },
+ "clean-css": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.11.tgz",
+ "integrity": "sha1-Ls3xRaujj1R0DybO/Q/z4D4SXWo=",
+ "dev": true,
+ "requires": {
+ "source-map": "0.5.7"
+ }
+ },
+ "cliui": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
+ "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
+ "dev": true,
+ "requires": {
+ "string-width": "1.0.2",
+ "strip-ansi": "3.0.1",
+ "wrap-ansi": "2.1.0"
+ }
+ },
+ "clone": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+ "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=",
+ "dev": true
+ },
+ "clone-deep": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz",
+ "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==",
+ "dev": true,
+ "requires": {
+ "for-own": "1.0.0",
+ "is-plain-object": "2.0.4",
+ "kind-of": "6.0.2",
+ "shallow-clone": "1.0.0"
+ }
+ },
+ "co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
+ "dev": true
+ },
+ "coa": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.4.tgz",
+ "integrity": "sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0=",
+ "dev": true,
+ "requires": {
+ "q": "1.5.1"
+ }
+ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+ "dev": true
+ },
+ "collection-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
+ "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
+ "dev": true,
+ "requires": {
+ "map-visit": "1.0.0",
+ "object-visit": "1.0.1"
+ }
+ },
+ "color": {
+ "version": "0.11.4",
+ "resolved": "https://registry.npmjs.org/color/-/color-0.11.4.tgz",
+ "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=",
+ "dev": true,
+ "requires": {
+ "clone": "1.0.4",
+ "color-convert": "1.9.1",
+ "color-string": "0.3.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
+ "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "color-string": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz",
+ "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "colormin": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/colormin/-/colormin-1.1.2.tgz",
+ "integrity": "sha1-6i90IKcrlogaOKrlnsEkpvcpgTM=",
+ "dev": true,
+ "requires": {
+ "color": "0.11.4",
+ "css-color-names": "0.0.4",
+ "has": "1.0.1"
+ }
+ },
+ "colors": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
+ "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=",
+ "dev": true
+ },
+ "combined-stream": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz",
+ "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=",
+ "dev": true,
+ "requires": {
+ "delayed-stream": "1.0.0"
+ }
+ },
+ "commander": {
+ "version": "2.15.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
+ "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
+ "dev": true
+ },
+ "component-bind": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
+ "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=",
+ "dev": true
+ },
+ "component-emitter": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
+ "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=",
+ "dev": true
+ },
+ "component-inherit": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
+ "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=",
+ "dev": true
+ },
+ "compressible": {
+ "version": "2.0.13",
+ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.13.tgz",
+ "integrity": "sha1-DRAgq5JLL9tNYnmHXH1tq6a6p6k=",
+ "dev": true,
+ "requires": {
+ "mime-db": "1.33.0"
+ }
+ },
+ "compression": {
+ "version": "1.7.2",
+ "resolved": "http://registry.npmjs.org/compression/-/compression-1.7.2.tgz",
+ "integrity": "sha1-qv+81qr4VLROuygDU9WtFlH1mmk=",
+ "dev": true,
+ "requires": {
+ "accepts": "1.3.5",
+ "bytes": "3.0.0",
+ "compressible": "2.0.13",
+ "debug": "2.6.9",
+ "on-headers": "1.0.1",
+ "safe-buffer": "5.1.1",
+ "vary": "1.1.2"
+ },
+ "dependencies": {
+ "accepts": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
+ "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
+ "dev": true,
+ "requires": {
+ "mime-types": "2.1.18",
+ "negotiator": "0.6.1"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
+ "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
+ "dev": true
+ }
+ }
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "connect-history-api-fallback": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz",
+ "integrity": "sha1-sGhzk0vF40T+9hGhlqb6rgruAVo=",
+ "dev": true
+ },
+ "console-browserify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
+ "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=",
+ "dev": true,
+ "requires": {
+ "date-now": "0.1.4"
+ }
+ },
+ "console-control-strings": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+ "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
+ "dev": true
+ },
+ "constants-browserify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
+ "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=",
+ "dev": true
+ },
+ "content-disposition": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
+ "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=",
+ "dev": true
+ },
+ "content-type": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
+ "dev": true
+ },
+ "convert-source-map": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz",
+ "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=",
+ "dev": true
+ },
+ "cookie": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
+ "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=",
+ "dev": true
+ },
+ "cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
+ "dev": true
+ },
+ "copy-descriptor": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
+ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
+ "dev": true
+ },
+ "core-js": {
+ "version": "2.5.7",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz",
+ "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==",
+ "dev": true
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+ "dev": true
+ },
+ "create-ecdh": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
+ "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
+ "dev": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "elliptic": "6.4.0"
+ }
+ },
+ "create-hash": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+ "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "1.0.4",
+ "inherits": "2.0.3",
+ "md5.js": "1.3.4",
+ "ripemd160": "2.0.2",
+ "sha.js": "2.4.11"
+ }
+ },
+ "create-hmac": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+ "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "1.0.4",
+ "create-hash": "1.2.0",
+ "inherits": "2.0.3",
+ "ripemd160": "2.0.2",
+ "safe-buffer": "5.1.2",
+ "sha.js": "2.4.11"
+ }
+ },
+ "cross-spawn": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
+ "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=",
+ "dev": true,
+ "requires": {
+ "lru-cache": "4.1.3",
+ "which": "1.3.1"
+ }
+ },
+ "cryptiles": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
+ "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
+ "dev": true,
+ "requires": {
+ "boom": "2.10.1"
+ }
+ },
+ "crypto-browserify": {
+ "version": "3.12.0",
+ "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
+ "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
+ "dev": true,
+ "requires": {
+ "browserify-cipher": "1.0.1",
+ "browserify-sign": "4.0.4",
+ "create-ecdh": "4.0.3",
+ "create-hash": "1.2.0",
+ "create-hmac": "1.1.7",
+ "diffie-hellman": "5.0.3",
+ "inherits": "2.0.3",
+ "pbkdf2": "3.0.16",
+ "public-encrypt": "4.0.2",
+ "randombytes": "2.0.6",
+ "randomfill": "1.0.4"
+ }
+ },
+ "css-color-names": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz",
+ "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=",
+ "dev": true
+ },
+ "css-loader": {
+ "version": "0.28.11",
+ "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-0.28.11.tgz",
+ "integrity": "sha512-wovHgjAx8ZIMGSL8pTys7edA1ClmzxHeY6n/d97gg5odgsxEgKjULPR0viqyC+FWMCL9sfqoC/QCUBo62tLvPg==",
+ "dev": true,
+ "requires": {
+ "babel-code-frame": "6.26.0",
+ "css-selector-tokenizer": "0.7.0",
+ "cssnano": "3.10.0",
+ "icss-utils": "2.1.0",
+ "loader-utils": "1.1.0",
+ "lodash.camelcase": "4.3.0",
+ "object-assign": "4.1.1",
+ "postcss": "5.2.18",
+ "postcss-modules-extract-imports": "1.2.0",
+ "postcss-modules-local-by-default": "1.2.0",
+ "postcss-modules-scope": "1.1.0",
+ "postcss-modules-values": "1.3.0",
+ "postcss-value-parser": "3.3.0",
+ "source-list-map": "2.0.0"
+ }
+ },
+ "css-select": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
+ "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
+ "dev": true,
+ "requires": {
+ "boolbase": "1.0.0",
+ "css-what": "2.1.0",
+ "domutils": "1.5.1",
+ "nth-check": "1.0.1"
+ }
+ },
+ "css-selector-tokenizer": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz",
+ "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=",
+ "dev": true,
+ "requires": {
+ "cssesc": "0.1.0",
+ "fastparse": "1.1.1",
+ "regexpu-core": "1.0.0"
+ },
+ "dependencies": {
+ "regexpu-core": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz",
+ "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=",
+ "dev": true,
+ "requires": {
+ "regenerate": "1.4.0",
+ "regjsgen": "0.2.0",
+ "regjsparser": "0.1.5"
+ }
+ }
+ }
+ },
+ "css-what": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz",
+ "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=",
+ "dev": true
+ },
+ "cssesc": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz",
+ "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=",
+ "dev": true
+ },
+ "cssnano": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-3.10.0.tgz",
+ "integrity": "sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg=",
+ "dev": true,
+ "requires": {
+ "autoprefixer": "6.7.7",
+ "decamelize": "1.2.0",
+ "defined": "1.0.0",
+ "has": "1.0.1",
+ "object-assign": "4.1.1",
+ "postcss": "5.2.18",
+ "postcss-calc": "5.3.1",
+ "postcss-colormin": "2.2.2",
+ "postcss-convert-values": "2.6.1",
+ "postcss-discard-comments": "2.0.4",
+ "postcss-discard-duplicates": "2.1.0",
+ "postcss-discard-empty": "2.1.0",
+ "postcss-discard-overridden": "0.1.1",
+ "postcss-discard-unused": "2.2.3",
+ "postcss-filter-plugins": "2.0.3",
+ "postcss-merge-idents": "2.1.7",
+ "postcss-merge-longhand": "2.0.2",
+ "postcss-merge-rules": "2.1.2",
+ "postcss-minify-font-values": "1.0.5",
+ "postcss-minify-gradients": "1.0.5",
+ "postcss-minify-params": "1.2.2",
+ "postcss-minify-selectors": "2.1.1",
+ "postcss-normalize-charset": "1.1.1",
+ "postcss-normalize-url": "3.0.8",
+ "postcss-ordered-values": "2.2.3",
+ "postcss-reduce-idents": "2.4.0",
+ "postcss-reduce-initial": "1.0.1",
+ "postcss-reduce-transforms": "1.0.4",
+ "postcss-svgo": "2.1.6",
+ "postcss-unique-selectors": "2.0.2",
+ "postcss-value-parser": "3.3.0",
+ "postcss-zindex": "2.2.0"
+ }
+ },
+ "csso": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/csso/-/csso-2.3.2.tgz",
+ "integrity": "sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U=",
+ "dev": true,
+ "requires": {
+ "clap": "1.2.3",
+ "source-map": "0.5.7"
+ }
+ },
+ "currently-unhandled": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
+ "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
+ "dev": true,
+ "requires": {
+ "array-find-index": "1.0.2"
+ }
+ },
+ "dashdash": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+ "dev": true,
+ "requires": {
+ "assert-plus": "1.0.0"
+ },
+ "dependencies": {
+ "assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+ "dev": true
+ }
+ }
+ },
+ "date-now": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
+ "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=",
+ "dev": true
+ },
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+ "dev": true
+ },
+ "decode-uri-component": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
+ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
+ "dev": true
+ },
+ "deep-eql": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
+ "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
+ "dev": true,
+ "requires": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "deep-equal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
+ "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=",
+ "dev": true
+ },
+ "define-property": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
+ "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "1.0.2",
+ "isobject": "3.0.1"
+ },
+ "dependencies": {
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "6.0.2"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "6.0.2"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "1.0.0",
+ "is-data-descriptor": "1.0.0",
+ "kind-of": "6.0.2"
+ }
+ }
+ }
+ },
+ "defined": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
+ "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=",
+ "dev": true
+ },
+ "del": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz",
+ "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=",
+ "dev": true,
+ "requires": {
+ "globby": "6.1.0",
+ "is-path-cwd": "1.0.0",
+ "is-path-in-cwd": "1.0.1",
+ "p-map": "1.2.0",
+ "pify": "3.0.0",
+ "rimraf": "2.6.2"
+ },
+ "dependencies": {
+ "pify": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+ "dev": true
+ }
+ }
+ },
+ "delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
+ "dev": true
+ },
+ "delegates": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
+ "dev": true
+ },
+ "depd": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
+ "dev": true
+ },
+ "des.js": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz",
+ "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "minimalistic-assert": "1.0.1"
+ }
+ },
+ "destroy": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=",
+ "dev": true
+ },
+ "detect-indent": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz",
+ "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=",
+ "dev": true,
+ "requires": {
+ "repeating": "2.0.1"
+ }
+ },
+ "detect-node": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.3.tgz",
+ "integrity": "sha1-ogM8CcyOFY03dI+951B4Mr1s4Sc=",
+ "dev": true
+ },
+ "diff": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz",
+ "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=",
+ "dev": true
+ },
+ "diffie-hellman": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+ "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
+ "dev": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "miller-rabin": "4.0.1",
+ "randombytes": "2.0.6"
+ }
+ },
+ "dns-equal": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
+ "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=",
+ "dev": true
+ },
+ "dns-packet": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz",
+ "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==",
+ "dev": true,
+ "requires": {
+ "ip": "1.1.5",
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "dns-txt": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz",
+ "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=",
+ "dev": true,
+ "requires": {
+ "buffer-indexof": "1.1.1"
+ }
+ },
+ "dom-converter": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.1.4.tgz",
+ "integrity": "sha1-pF71cnuJDJv/5tfIduexnLDhfzs=",
+ "dev": true,
+ "requires": {
+ "utila": "0.3.3"
+ },
+ "dependencies": {
+ "utila": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/utila/-/utila-0.3.3.tgz",
+ "integrity": "sha1-1+jn1+MJEHCSsF+NloiCTWM6QiY=",
+ "dev": true
+ }
+ }
+ },
+ "dom-serializer": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
+ "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=",
+ "dev": true,
+ "requires": {
+ "domelementtype": "1.1.3",
+ "entities": "1.1.1"
+ },
+ "dependencies": {
+ "domelementtype": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
+ "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=",
+ "dev": true
+ }
+ }
+ },
+ "domain-browser": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
+ "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
+ "dev": true
+ },
+ "domelementtype": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz",
+ "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=",
+ "dev": true
+ },
+ "domhandler": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.1.0.tgz",
+ "integrity": "sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ=",
+ "dev": true,
+ "requires": {
+ "domelementtype": "1.3.0"
+ }
+ },
+ "domutils": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
+ "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
+ "dev": true,
+ "requires": {
+ "dom-serializer": "0.1.0",
+ "domelementtype": "1.3.0"
+ }
+ },
+ "ecc-jsbn": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
+ "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "jsbn": "0.1.1"
+ }
+ },
+ "ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
+ "dev": true
+ },
+ "electron-to-chromium": {
+ "version": "1.3.48",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.48.tgz",
+ "integrity": "sha1-07DYWTgUBE4JLs4hCPw6ya6kuQA=",
+ "dev": true
+ },
+ "elliptic": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz",
+ "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=",
+ "dev": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "brorand": "1.1.0",
+ "hash.js": "1.1.3",
+ "hmac-drbg": "1.0.1",
+ "inherits": "2.0.3",
+ "minimalistic-assert": "1.0.1",
+ "minimalistic-crypto-utils": "1.0.1"
+ }
+ },
+ "emojis-list": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
+ "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=",
+ "dev": true
+ },
+ "encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
+ "dev": true
+ },
+ "engine.io": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-1.8.5.tgz",
+ "integrity": "sha512-j1DWIcktw4hRwrv6nWx++5nFH2X64x16MAG2P0Lmi5Dvdfi3I+Jhc7JKJIdAmDJa+5aZ/imHV7dWRPy2Cqjh3A==",
+ "dev": true,
+ "requires": {
+ "accepts": "1.3.3",
+ "base64id": "1.0.0",
+ "cookie": "0.3.1",
+ "debug": "2.3.3",
+ "engine.io-parser": "1.3.2",
+ "ws": "1.1.5"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz",
+ "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=",
+ "dev": true,
+ "requires": {
+ "ms": "0.7.2"
+ }
+ },
+ "ms": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz",
+ "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=",
+ "dev": true
+ }
+ }
+ },
+ "engine.io-client": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-1.8.5.tgz",
+ "integrity": "sha512-AYTgHyeVUPitsseqjoedjhYJapNVoSPShbZ+tEUX9/73jgZ/Z3sUlJf9oYgdEBBdVhupUpUqSxH0kBCXlQnmZg==",
+ "dev": true,
+ "requires": {
+ "component-emitter": "1.2.1",
+ "component-inherit": "0.0.3",
+ "debug": "2.3.3",
+ "engine.io-parser": "1.3.2",
+ "has-cors": "1.1.0",
+ "indexof": "0.0.1",
+ "parsejson": "0.0.3",
+ "parseqs": "0.0.5",
+ "parseuri": "0.0.5",
+ "ws": "1.1.5",
+ "xmlhttprequest-ssl": "1.5.3",
+ "yeast": "0.1.2"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz",
+ "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=",
+ "dev": true,
+ "requires": {
+ "ms": "0.7.2"
+ }
+ },
+ "ms": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz",
+ "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=",
+ "dev": true
+ }
+ }
+ },
+ "engine.io-parser": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-1.3.2.tgz",
+ "integrity": "sha1-k3sHnwAH0Ik+xW1GyyILjLQ1Igo=",
+ "dev": true,
+ "requires": {
+ "after": "0.8.2",
+ "arraybuffer.slice": "0.0.6",
+ "base64-arraybuffer": "0.1.5",
+ "blob": "0.0.4",
+ "has-binary": "0.1.7",
+ "wtf-8": "1.0.0"
+ }
+ },
+ "enhanced-resolve": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.3.0.tgz",
+ "integrity": "sha512-2qbxE7ek3YxPJ1ML6V+satHkzHpJQKWkRHmRx6mfAoW59yP8YH8BFplbegSP+u2hBd6B6KCOpvJQ3dZAP+hkpg==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "4.1.11",
+ "memory-fs": "0.4.1",
+ "object-assign": "4.1.1",
+ "tapable": "0.2.8"
+ }
+ },
+ "entities": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz",
+ "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=",
+ "dev": true
+ },
+ "errno": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
+ "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
+ "dev": true,
+ "requires": {
+ "prr": "1.0.1"
+ }
+ },
+ "error-ex": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz",
+ "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=",
+ "dev": true,
+ "requires": {
+ "is-arrayish": "0.2.1"
+ }
+ },
+ "escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=",
+ "dev": true
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "esprima": {
+ "version": "2.7.3",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz",
+ "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=",
+ "dev": true
+ },
+ "esutils": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
+ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
+ "dev": true
+ },
+ "etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
+ "dev": true
+ },
+ "eventemitter3": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz",
+ "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==",
+ "dev": true
+ },
+ "events": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
+ "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=",
+ "dev": true
+ },
+ "eventsource": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-0.1.6.tgz",
+ "integrity": "sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI=",
+ "dev": true,
+ "requires": {
+ "original": "1.0.1"
+ }
+ },
+ "evp_bytestokey": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
+ "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
+ "dev": true,
+ "requires": {
+ "md5.js": "1.3.4",
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "expand-brackets": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
+ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
+ "dev": true,
+ "requires": {
+ "debug": "2.6.9",
+ "define-property": "0.2.5",
+ "extend-shallow": "2.0.1",
+ "posix-character-classes": "0.1.1",
+ "regex-not": "1.0.2",
+ "snapdragon": "0.8.2",
+ "to-regex": "3.0.2"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "0.1.6"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "0.1.1"
+ }
+ }
+ }
+ },
+ "expand-range": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz",
+ "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=",
+ "dev": true,
+ "requires": {
+ "fill-range": "2.2.4"
+ },
+ "dependencies": {
+ "fill-range": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz",
+ "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==",
+ "dev": true,
+ "requires": {
+ "is-number": "2.1.0",
+ "isobject": "2.1.0",
+ "randomatic": "3.0.0",
+ "repeat-element": "1.1.2",
+ "repeat-string": "1.6.1"
+ }
+ },
+ "is-number": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz",
+ "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=",
+ "dev": true,
+ "requires": {
+ "kind-of": "3.2.2"
+ }
+ },
+ "isobject": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+ "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+ "dev": true,
+ "requires": {
+ "isarray": "1.0.0"
+ }
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "1.1.6"
+ }
+ }
+ }
+ },
+ "express": {
+ "version": "4.16.3",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz",
+ "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=",
+ "dev": true,
+ "requires": {
+ "accepts": "1.3.5",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.18.2",
+ "content-disposition": "0.5.2",
+ "content-type": "1.0.4",
+ "cookie": "0.3.1",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "1.1.2",
+ "encodeurl": "1.0.2",
+ "escape-html": "1.0.3",
+ "etag": "1.8.1",
+ "finalhandler": "1.1.1",
+ "fresh": "0.5.2",
+ "merge-descriptors": "1.0.1",
+ "methods": "1.1.2",
+ "on-finished": "2.3.0",
+ "parseurl": "1.3.2",
+ "path-to-regexp": "0.1.7",
+ "proxy-addr": "2.0.3",
+ "qs": "6.5.1",
+ "range-parser": "1.2.0",
+ "safe-buffer": "5.1.1",
+ "send": "0.16.2",
+ "serve-static": "1.13.2",
+ "setprototypeof": "1.1.0",
+ "statuses": "1.4.0",
+ "type-is": "1.6.16",
+ "utils-merge": "1.0.1",
+ "vary": "1.1.2"
+ },
+ "dependencies": {
+ "accepts": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
+ "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
+ "dev": true,
+ "requires": {
+ "mime-types": "2.1.18",
+ "negotiator": "0.6.1"
+ }
+ },
+ "array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
+ "dev": true
+ },
+ "qs": {
+ "version": "6.5.1",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
+ "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==",
+ "dev": true
+ },
+ "safe-buffer": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
+ "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
+ "dev": true
+ }
+ }
+ },
+ "extend": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
+ "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=",
+ "dev": true
+ },
+ "extend-shallow": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+ "dev": true,
+ "requires": {
+ "assign-symbols": "1.0.0",
+ "is-extendable": "1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "dev": true,
+ "requires": {
+ "is-plain-object": "2.0.4"
+ }
+ }
+ }
+ },
+ "extglob": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
+ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
+ "dev": true,
+ "requires": {
+ "array-unique": "0.3.2",
+ "define-property": "1.0.0",
+ "expand-brackets": "2.1.4",
+ "extend-shallow": "2.0.1",
+ "fragment-cache": "0.2.1",
+ "regex-not": "1.0.2",
+ "snapdragon": "0.8.2",
+ "to-regex": "3.0.2"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "1.0.2"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "0.1.1"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "6.0.2"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "6.0.2"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "1.0.0",
+ "is-data-descriptor": "1.0.0",
+ "kind-of": "6.0.2"
+ }
+ }
+ }
+ },
+ "extract-text-webpack-plugin": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/extract-text-webpack-plugin/-/extract-text-webpack-plugin-2.1.2.tgz",
+ "integrity": "sha1-dW7076gVXDaBgz+8NNpTuUF0bWw=",
+ "dev": true,
+ "requires": {
+ "async": "2.6.1",
+ "loader-utils": "1.1.0",
+ "schema-utils": "0.3.0",
+ "webpack-sources": "1.1.0"
+ }
+ },
+ "extsprintf": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
+ "dev": true
+ },
+ "fast-deep-equal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
+ "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=",
+ "dev": true
+ },
+ "fast-json-stable-stringify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
+ "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
+ "dev": true
+ },
+ "fastparse": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz",
+ "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=",
+ "dev": true
+ },
+ "faye-websocket": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz",
+ "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=",
+ "dev": true,
+ "requires": {
+ "websocket-driver": "0.7.0"
+ }
+ },
+ "filename-regex": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz",
+ "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=",
+ "dev": true
+ },
+ "filesize": {
+ "version": "3.6.1",
+ "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz",
+ "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==",
+ "dev": true
+ },
+ "fill-range": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "2.0.1",
+ "is-number": "3.0.0",
+ "repeat-string": "1.6.1",
+ "to-regex-range": "2.1.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "0.1.1"
+ }
+ }
+ }
+ },
+ "finalhandler": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
+ "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
+ "dev": true,
+ "requires": {
+ "debug": "2.6.9",
+ "encodeurl": "1.0.2",
+ "escape-html": "1.0.3",
+ "on-finished": "2.3.0",
+ "parseurl": "1.3.2",
+ "statuses": "1.4.0",
+ "unpipe": "1.0.0"
+ }
+ },
+ "find-up": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
+ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+ "dev": true,
+ "requires": {
+ "path-exists": "2.1.0",
+ "pinkie-promise": "2.0.1"
+ }
+ },
+ "flatten": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz",
+ "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=",
+ "dev": true
+ },
+ "follow-redirects": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.0.tgz",
+ "integrity": "sha512-fdrt472/9qQ6Kgjvb935ig6vJCuofpBUD14f9Vb+SLlm7xIe4Qva5gey8EKtv8lp7ahE1wilg3xL1znpVGtZIA==",
+ "dev": true,
+ "requires": {
+ "debug": "3.1.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
+ "for-in": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
+ "dev": true
+ },
+ "for-own": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz",
+ "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=",
+ "dev": true,
+ "requires": {
+ "for-in": "1.0.2"
+ }
+ },
+ "forever-agent": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
+ "dev": true
+ },
+ "form-data": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz",
+ "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=",
+ "dev": true,
+ "requires": {
+ "asynckit": "0.4.0",
+ "combined-stream": "1.0.6",
+ "mime-types": "2.1.18"
+ }
+ },
+ "forwarded": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
+ "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=",
+ "dev": true
+ },
+ "fragment-cache": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
+ "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
+ "dev": true,
+ "requires": {
+ "map-cache": "0.2.2"
+ }
+ },
+ "fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
+ "dev": true
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "fsevents": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz",
+ "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "nan": "2.10.0",
+ "node-pre-gyp": "0.10.0"
+ },
+ "dependencies": {
+ "abbrev": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+ "dev": true,
+ "optional": true
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+ "dev": true
+ },
+ "aproba": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
+ "dev": true,
+ "optional": true
+ },
+ "are-we-there-yet": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz",
+ "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "delegates": "1.0.0",
+ "readable-stream": "2.3.6"
+ }
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+ "dev": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "chownr": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz",
+ "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=",
+ "dev": true,
+ "optional": true
+ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+ "dev": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "console-control-strings": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+ "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
+ "dev": true
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+ "dev": true,
+ "optional": true
+ },
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "deep-extend": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz",
+ "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==",
+ "dev": true,
+ "optional": true
+ },
+ "delegates": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
+ "dev": true,
+ "optional": true
+ },
+ "detect-libc": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+ "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
+ "dev": true,
+ "optional": true
+ },
+ "fs-minipass": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz",
+ "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minipass": "2.2.4"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true,
+ "optional": true
+ },
+ "gauge": {
+ "version": "2.7.4",
+ "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
+ "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "aproba": "1.2.0",
+ "console-control-strings": "1.1.0",
+ "has-unicode": "2.0.1",
+ "object-assign": "4.1.1",
+ "signal-exit": "3.0.2",
+ "string-width": "1.0.2",
+ "strip-ansi": "3.0.1",
+ "wide-align": "1.1.2"
+ }
+ },
+ "glob": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
+ "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "fs.realpath": "1.0.0",
+ "inflight": "1.0.6",
+ "inherits": "2.0.3",
+ "minimatch": "3.0.4",
+ "once": "1.4.0",
+ "path-is-absolute": "1.0.1"
+ }
+ },
+ "has-unicode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+ "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
+ "dev": true,
+ "optional": true
+ },
+ "iconv-lite": {
+ "version": "0.4.21",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.21.tgz",
+ "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "safer-buffer": "2.1.2"
+ }
+ },
+ "ignore-walk": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz",
+ "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minimatch": "3.0.4"
+ }
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "once": "1.4.0",
+ "wrappy": "1.0.2"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+ "dev": true
+ },
+ "ini": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
+ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
+ "dev": true,
+ "optional": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+ "dev": true,
+ "requires": {
+ "number-is-nan": "1.0.1"
+ }
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true,
+ "optional": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "1.1.11"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+ "dev": true
+ },
+ "minipass": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.2.4.tgz",
+ "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "5.1.1",
+ "yallist": "3.0.2"
+ }
+ },
+ "minizlib": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz",
+ "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minipass": "2.2.4"
+ }
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "dev": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true,
+ "optional": true
+ },
+ "needle": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.0.tgz",
+ "integrity": "sha512-eFagy6c+TYayorXw/qtAdSvaUpEbBsDwDyxYFgLZ0lTojfH7K+OdBqAF7TAFwDokJaGpubpSGG0wO3iC0XPi8w==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "debug": "2.6.9",
+ "iconv-lite": "0.4.21",
+ "sax": "1.2.4"
+ }
+ },
+ "node-pre-gyp": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.0.tgz",
+ "integrity": "sha512-G7kEonQLRbcA/mOoFoxvlMrw6Q6dPf92+t/l0DFSMuSlDoWaI9JWIyPwK0jyE1bph//CUEL65/Fz1m2vJbmjQQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "detect-libc": "1.0.3",
+ "mkdirp": "0.5.1",
+ "needle": "2.2.0",
+ "nopt": "4.0.1",
+ "npm-packlist": "1.1.10",
+ "npmlog": "4.1.2",
+ "rc": "1.2.7",
+ "rimraf": "2.6.2",
+ "semver": "5.5.0",
+ "tar": "4.4.1"
+ }
+ },
+ "nopt": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
+ "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "abbrev": "1.1.1",
+ "osenv": "0.1.5"
+ }
+ },
+ "npm-bundled": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.3.tgz",
+ "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==",
+ "dev": true,
+ "optional": true
+ },
+ "npm-packlist": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.10.tgz",
+ "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ignore-walk": "3.0.1",
+ "npm-bundled": "1.0.3"
+ }
+ },
+ "npmlog": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
+ "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "are-we-there-yet": "1.1.4",
+ "console-control-strings": "1.1.0",
+ "gauge": "2.7.4",
+ "set-blocking": "2.0.0"
+ }
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+ "dev": true
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+ "dev": true,
+ "optional": true
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "requires": {
+ "wrappy": "1.0.2"
+ }
+ },
+ "os-homedir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+ "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
+ "dev": true,
+ "optional": true
+ },
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+ "dev": true,
+ "optional": true
+ },
+ "osenv": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
+ "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "os-homedir": "1.0.2",
+ "os-tmpdir": "1.0.2"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true,
+ "optional": true
+ },
+ "process-nextick-args": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
+ "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
+ "dev": true,
+ "optional": true
+ },
+ "rc": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.7.tgz",
+ "integrity": "sha512-LdLD8xD4zzLsAT5xyushXDNscEjB7+2ulnl8+r1pnESlYtlJtVSoCMBGr30eDRJ3+2Gq89jK9P9e4tCEH1+ywA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "deep-extend": "0.5.1",
+ "ini": "1.3.5",
+ "minimist": "1.2.0",
+ "strip-json-comments": "2.0.1"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "core-util-is": "1.0.2",
+ "inherits": "2.0.3",
+ "isarray": "1.0.0",
+ "process-nextick-args": "2.0.0",
+ "safe-buffer": "5.1.1",
+ "string_decoder": "1.1.1",
+ "util-deprecate": "1.0.2"
+ }
+ },
+ "rimraf": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
+ "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "glob": "7.1.2"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
+ "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
+ "dev": true
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "optional": true
+ },
+ "sax": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
+ "dev": true,
+ "optional": true
+ },
+ "semver": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
+ "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==",
+ "dev": true,
+ "optional": true
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+ "dev": true,
+ "optional": true
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+ "dev": true,
+ "optional": true
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "dev": true,
+ "requires": {
+ "code-point-at": "1.1.0",
+ "is-fullwidth-code-point": "1.0.0",
+ "strip-ansi": "3.0.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "safe-buffer": "5.1.1"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "2.1.1"
+ }
+ },
+ "strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+ "dev": true,
+ "optional": true
+ },
+ "tar": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.1.tgz",
+ "integrity": "sha512-O+v1r9yN4tOsvl90p5HAP4AEqbYhx4036AGMm075fH9F8Qwi3oJ+v4u50FkT/KkvywNGtwkk0zRI+8eYm1X/xg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "chownr": "1.0.1",
+ "fs-minipass": "1.2.5",
+ "minipass": "2.2.4",
+ "minizlib": "1.1.0",
+ "mkdirp": "0.5.1",
+ "safe-buffer": "5.1.1",
+ "yallist": "3.0.2"
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+ "dev": true,
+ "optional": true
+ },
+ "wide-align": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz",
+ "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "string-width": "1.0.2"
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "yallist": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz",
+ "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=",
+ "dev": true
+ }
+ }
+ },
+ "fstream": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
+ "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "4.1.11",
+ "inherits": "2.0.3",
+ "mkdirp": "0.5.1",
+ "rimraf": "2.6.2"
+ }
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "gauge": {
+ "version": "2.7.4",
+ "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
+ "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
+ "dev": true,
+ "requires": {
+ "aproba": "1.2.0",
+ "console-control-strings": "1.1.0",
+ "has-unicode": "2.0.1",
+ "object-assign": "4.1.1",
+ "signal-exit": "3.0.2",
+ "string-width": "1.0.2",
+ "strip-ansi": "3.0.1",
+ "wide-align": "1.1.3"
+ }
+ },
+ "gaze": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz",
+ "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==",
+ "dev": true,
+ "requires": {
+ "globule": "1.2.1"
+ }
+ },
+ "generate-function": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz",
+ "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=",
+ "dev": true
+ },
+ "generate-object-property": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
+ "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=",
+ "dev": true,
+ "requires": {
+ "is-property": "1.0.2"
+ }
+ },
+ "get-caller-file": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz",
+ "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=",
+ "dev": true
+ },
+ "get-func-name": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
+ "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
+ "dev": true
+ },
+ "get-stdin": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
+ "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
+ "dev": true
+ },
+ "get-value": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
+ "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
+ "dev": true
+ },
+ "getpass": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+ "dev": true,
+ "requires": {
+ "assert-plus": "1.0.0"
+ },
+ "dependencies": {
+ "assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+ "dev": true
+ }
+ }
+ },
+ "glob": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz",
+ "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "1.0.0",
+ "inflight": "1.0.6",
+ "inherits": "2.0.3",
+ "minimatch": "3.0.4",
+ "once": "1.4.0",
+ "path-is-absolute": "1.0.1"
+ }
+ },
+ "glob-base": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz",
+ "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=",
+ "dev": true,
+ "requires": {
+ "glob-parent": "2.0.0",
+ "is-glob": "2.0.1"
+ },
+ "dependencies": {
+ "glob-parent": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
+ "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=",
+ "dev": true,
+ "requires": {
+ "is-glob": "2.0.1"
+ }
+ },
+ "is-extglob": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
+ "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
+ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "1.0.0"
+ }
+ }
+ }
+ },
+ "glob-parent": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
+ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+ "dev": true,
+ "requires": {
+ "is-glob": "3.1.0",
+ "path-dirname": "1.0.2"
+ },
+ "dependencies": {
+ "is-glob": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
+ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "2.1.1"
+ }
+ }
+ }
+ },
+ "globals": {
+ "version": "9.18.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
+ "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==",
+ "dev": true
+ },
+ "globby": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
+ "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
+ "dev": true,
+ "requires": {
+ "array-union": "1.0.2",
+ "glob": "7.1.1",
+ "object-assign": "4.1.1",
+ "pify": "2.3.0",
+ "pinkie-promise": "2.0.1"
+ }
+ },
+ "globule": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz",
+ "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==",
+ "dev": true,
+ "requires": {
+ "glob": "7.1.1",
+ "lodash": "4.17.10",
+ "minimatch": "3.0.4"
+ }
+ },
+ "graceful-fs": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
+ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
+ "dev": true
+ },
+ "graceful-readlink": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
+ "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=",
+ "dev": true
+ },
+ "growl": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz",
+ "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=",
+ "dev": true
+ },
+ "growly": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
+ "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=",
+ "dev": true
+ },
+ "handle-thing": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz",
+ "integrity": "sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ=",
+ "dev": true
+ },
+ "har-validator": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz",
+ "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=",
+ "dev": true,
+ "requires": {
+ "chalk": "1.1.3",
+ "commander": "2.15.1",
+ "is-my-json-valid": "2.17.2",
+ "pinkie-promise": "2.0.1"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+ "dev": true
+ },
+ "chalk": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "2.2.1",
+ "escape-string-regexp": "1.0.5",
+ "has-ansi": "2.0.0",
+ "strip-ansi": "3.0.1",
+ "supports-color": "2.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+ "dev": true
+ }
+ }
+ },
+ "has": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz",
+ "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=",
+ "dev": true,
+ "requires": {
+ "function-bind": "1.1.1"
+ }
+ },
+ "has-ansi": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "2.1.1"
+ }
+ },
+ "has-binary": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.7.tgz",
+ "integrity": "sha1-aOYesWIQyVRaClzOBqhzkS/h5ow=",
+ "dev": true,
+ "requires": {
+ "isarray": "0.0.1"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+ "dev": true
+ }
+ }
+ },
+ "has-cors": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
+ "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true
+ },
+ "has-unicode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+ "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
+ "dev": true
+ },
+ "has-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
+ "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
+ "dev": true,
+ "requires": {
+ "get-value": "2.0.6",
+ "has-values": "1.0.0",
+ "isobject": "3.0.1"
+ }
+ },
+ "has-values": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
+ "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
+ "dev": true,
+ "requires": {
+ "is-number": "3.0.0",
+ "kind-of": "4.0.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
+ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "1.1.6"
+ }
+ }
+ }
+ },
+ "hash-base": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
+ "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "hash.js": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz",
+ "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "minimalistic-assert": "1.0.1"
+ }
+ },
+ "hawk": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
+ "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
+ "dev": true,
+ "requires": {
+ "boom": "2.10.1",
+ "cryptiles": "2.0.5",
+ "hoek": "2.16.3",
+ "sntp": "1.0.9"
+ }
+ },
+ "he": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
+ "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
+ "dev": true
+ },
+ "hmac-drbg": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
+ "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
+ "dev": true,
+ "requires": {
+ "hash.js": "1.1.3",
+ "minimalistic-assert": "1.0.1",
+ "minimalistic-crypto-utils": "1.0.1"
+ }
+ },
+ "hoek": {
+ "version": "2.16.3",
+ "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
+ "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=",
+ "dev": true
+ },
+ "home-or-tmp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz",
+ "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=",
+ "dev": true,
+ "requires": {
+ "os-homedir": "1.0.2",
+ "os-tmpdir": "1.0.2"
+ }
+ },
+ "hosted-git-info": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz",
+ "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==",
+ "dev": true
+ },
+ "hpack.js": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
+ "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "obuf": "1.1.2",
+ "readable-stream": "2.3.6",
+ "wbuf": "1.7.3"
+ }
+ },
+ "html-comment-regex": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.1.tgz",
+ "integrity": "sha1-ZouTd26q5V696POtRkswekljYl4=",
+ "dev": true
+ },
+ "html-entities": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz",
+ "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=",
+ "dev": true
+ },
+ "html-minifier": {
+ "version": "3.5.16",
+ "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.16.tgz",
+ "integrity": "sha512-zP5EfLSpiLRp0aAgud4CQXPQZm9kXwWjR/cF0PfdOj+jjWnOaCgeZcll4kYXSvIBPeUMmyaSc7mM4IDtA+kboA==",
+ "dev": true,
+ "requires": {
+ "camel-case": "3.0.0",
+ "clean-css": "4.1.11",
+ "commander": "2.15.1",
+ "he": "1.1.1",
+ "param-case": "2.1.1",
+ "relateurl": "0.2.7",
+ "uglify-js": "3.3.28"
+ }
+ },
+ "html-webpack-plugin": {
+ "version": "2.30.1",
+ "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-2.30.1.tgz",
+ "integrity": "sha1-f5xCG36pHsRg9WUn1430hO51N9U=",
+ "dev": true,
+ "requires": {
+ "bluebird": "3.5.1",
+ "html-minifier": "3.5.16",
+ "loader-utils": "0.2.17",
+ "lodash": "4.17.10",
+ "pretty-error": "2.1.1",
+ "toposort": "1.0.7"
+ },
+ "dependencies": {
+ "loader-utils": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz",
+ "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=",
+ "dev": true,
+ "requires": {
+ "big.js": "3.2.0",
+ "emojis-list": "2.1.0",
+ "json5": "0.5.1",
+ "object-assign": "4.1.1"
+ }
+ }
+ }
+ },
+ "htmlparser2": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz",
+ "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=",
+ "dev": true,
+ "requires": {
+ "domelementtype": "1.3.0",
+ "domhandler": "2.1.0",
+ "domutils": "1.1.6",
+ "readable-stream": "1.0.34"
+ },
+ "dependencies": {
+ "domutils": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.1.6.tgz",
+ "integrity": "sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU=",
+ "dev": true,
+ "requires": {
+ "domelementtype": "1.3.0"
+ }
+ },
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "1.0.34",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
+ "dev": true,
+ "requires": {
+ "core-util-is": "1.0.2",
+ "inherits": "2.0.3",
+ "isarray": "0.0.1",
+ "string_decoder": "0.10.31"
+ }
+ },
+ "string_decoder": {
+ "version": "0.10.31",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
+ "dev": true
+ }
+ }
+ },
+ "http-deceiver": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
+ "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=",
+ "dev": true
+ },
+ "http-errors": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
+ "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
+ "dev": true,
+ "requires": {
+ "depd": "1.1.2",
+ "inherits": "2.0.3",
+ "setprototypeof": "1.1.0",
+ "statuses": "1.4.0"
+ }
+ },
+ "http-parser-js": {
+ "version": "0.4.13",
+ "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.13.tgz",
+ "integrity": "sha1-O9bW/ebjFyyTNMOzO2wZPYD+ETc=",
+ "dev": true
+ },
+ "http-proxy": {
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz",
+ "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==",
+ "dev": true,
+ "requires": {
+ "eventemitter3": "3.1.0",
+ "follow-redirects": "1.5.0",
+ "requires-port": "1.0.0"
+ }
+ },
+ "http-proxy-middleware": {
+ "version": "0.17.4",
+ "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz",
+ "integrity": "sha1-ZC6ISIUdZvCdTxJJEoRtuutBuDM=",
+ "dev": true,
+ "requires": {
+ "http-proxy": "1.17.0",
+ "is-glob": "3.1.0",
+ "lodash": "4.17.10",
+ "micromatch": "2.3.11"
+ },
+ "dependencies": {
+ "arr-diff": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz",
+ "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=",
+ "dev": true,
+ "requires": {
+ "arr-flatten": "1.1.0"
+ }
+ },
+ "array-unique": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz",
+ "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=",
+ "dev": true
+ },
+ "braces": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz",
+ "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=",
+ "dev": true,
+ "requires": {
+ "expand-range": "1.8.2",
+ "preserve": "0.2.0",
+ "repeat-element": "1.1.2"
+ }
+ },
+ "expand-brackets": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz",
+ "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=",
+ "dev": true,
+ "requires": {
+ "is-posix-bracket": "0.1.1"
+ }
+ },
+ "extglob": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz",
+ "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "1.0.0"
+ },
+ "dependencies": {
+ "is-extglob": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
+ "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
+ "dev": true
+ }
+ }
+ },
+ "is-glob": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
+ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "2.1.1"
+ }
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "1.1.6"
+ }
+ },
+ "micromatch": {
+ "version": "2.3.11",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz",
+ "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=",
+ "dev": true,
+ "requires": {
+ "arr-diff": "2.0.0",
+ "array-unique": "0.2.1",
+ "braces": "1.8.5",
+ "expand-brackets": "0.1.5",
+ "extglob": "0.3.2",
+ "filename-regex": "2.0.1",
+ "is-extglob": "1.0.0",
+ "is-glob": "2.0.1",
+ "kind-of": "3.2.2",
+ "normalize-path": "2.1.1",
+ "object.omit": "2.0.1",
+ "parse-glob": "3.0.4",
+ "regex-cache": "0.4.4"
+ },
+ "dependencies": {
+ "is-extglob": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
+ "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
+ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "1.0.0"
+ }
+ }
+ }
+ }
+ }
+ },
+ "http-signature": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
+ "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
+ "dev": true,
+ "requires": {
+ "assert-plus": "0.2.0",
+ "jsprim": "1.4.1",
+ "sshpk": "1.14.1"
+ }
+ },
+ "https-browserify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
+ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
+ "dev": true
+ },
+ "iconv-lite": {
+ "version": "0.4.19",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
+ "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==",
+ "dev": true
+ },
+ "icss-replace-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz",
+ "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=",
+ "dev": true
+ },
+ "icss-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-2.1.0.tgz",
+ "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=",
+ "dev": true,
+ "requires": {
+ "postcss": "6.0.22"
+ },
+ "dependencies": {
+ "postcss": {
+ "version": "6.0.22",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.22.tgz",
+ "integrity": "sha512-Toc9lLoUASwGqxBSJGTVcOQiDqjK+Z2XlWBg+IgYwQMY9vA2f7iMpXVc1GpPcfTSyM5lkxNo0oDwDRO+wm7XHA==",
+ "dev": true,
+ "requires": {
+ "chalk": "2.4.1",
+ "source-map": "0.6.1",
+ "supports-color": "5.4.0"
+ }
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "ieee754": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.11.tgz",
+ "integrity": "sha512-VhDzCKN7K8ufStx/CLj5/PDTMgph+qwN5Pkd5i0sGnVwk56zJ0lkT8Qzi1xqWLS0Wp29DgDtNeS7v8/wMoZeHg==",
+ "dev": true
+ },
+ "in-publish": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz",
+ "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=",
+ "dev": true
+ },
+ "indent-string": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
+ "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
+ "dev": true,
+ "requires": {
+ "repeating": "2.0.1"
+ }
+ },
+ "indexes-of": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
+ "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=",
+ "dev": true
+ },
+ "indexof": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
+ "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=",
+ "dev": true
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "requires": {
+ "once": "1.4.0",
+ "wrappy": "1.0.2"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+ "dev": true
+ },
+ "internal-ip": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-1.2.0.tgz",
+ "integrity": "sha1-rp+/k7mEh4eF1QqN4bNWlWBYz1w=",
+ "dev": true,
+ "requires": {
+ "meow": "3.7.0"
+ }
+ },
+ "interpret": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz",
+ "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=",
+ "dev": true
+ },
+ "invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "dev": true,
+ "requires": {
+ "loose-envify": "1.3.1"
+ }
+ },
+ "invert-kv": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
+ "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=",
+ "dev": true
+ },
+ "ip": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
+ "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
+ "dev": true
+ },
+ "ipaddr.js": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz",
+ "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=",
+ "dev": true
+ },
+ "is-absolute-url": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz",
+ "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=",
+ "dev": true
+ },
+ "is-accessor-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+ "dev": true,
+ "requires": {
+ "kind-of": "3.2.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "1.1.6"
+ }
+ }
+ }
+ },
+ "is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+ "dev": true
+ },
+ "is-binary-path": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
+ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
+ "dev": true,
+ "requires": {
+ "binary-extensions": "1.11.0"
+ }
+ },
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
+ "dev": true
+ },
+ "is-builtin-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
+ "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
+ "dev": true,
+ "requires": {
+ "builtin-modules": "1.1.1"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+ "dev": true,
+ "requires": {
+ "kind-of": "3.2.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "1.1.6"
+ }
+ }
+ }
+ },
+ "is-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "0.1.6",
+ "is-data-descriptor": "0.1.4",
+ "kind-of": "5.1.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+ "dev": true
+ }
+ }
+ },
+ "is-dotfile": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz",
+ "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=",
+ "dev": true
+ },
+ "is-equal-shallow": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz",
+ "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=",
+ "dev": true,
+ "requires": {
+ "is-primitive": "2.0.0"
+ }
+ },
+ "is-extendable": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+ "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
+ "dev": true
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true
+ },
+ "is-finite": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz",
+ "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=",
+ "dev": true,
+ "requires": {
+ "number-is-nan": "1.0.1"
+ }
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+ "dev": true,
+ "requires": {
+ "number-is-nan": "1.0.1"
+ }
+ },
+ "is-glob": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz",
+ "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "2.1.1"
+ }
+ },
+ "is-my-ip-valid": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz",
+ "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==",
+ "dev": true
+ },
+ "is-my-json-valid": {
+ "version": "2.17.2",
+ "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz",
+ "integrity": "sha512-IBhBslgngMQN8DDSppmgDv7RNrlFotuuDsKcrCP3+HbFaVivIBU7u9oiiErw8sH4ynx3+gOGQ3q2otkgiSi6kg==",
+ "dev": true,
+ "requires": {
+ "generate-function": "2.0.0",
+ "generate-object-property": "1.2.0",
+ "is-my-ip-valid": "1.0.0",
+ "jsonpointer": "4.0.1",
+ "xtend": "4.0.1"
+ }
+ },
+ "is-number": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+ "dev": true,
+ "requires": {
+ "kind-of": "3.2.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "1.1.6"
+ }
+ }
+ }
+ },
+ "is-odd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz",
+ "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==",
+ "dev": true,
+ "requires": {
+ "is-number": "4.0.0"
+ },
+ "dependencies": {
+ "is-number": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz",
+ "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==",
+ "dev": true
+ }
+ }
+ },
+ "is-path-cwd": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz",
+ "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=",
+ "dev": true
+ },
+ "is-path-in-cwd": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz",
+ "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==",
+ "dev": true,
+ "requires": {
+ "is-path-inside": "1.0.1"
+ }
+ },
+ "is-path-inside": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz",
+ "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=",
+ "dev": true,
+ "requires": {
+ "path-is-inside": "1.0.2"
+ }
+ },
+ "is-plain-obj": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+ "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
+ "dev": true
+ },
+ "is-plain-object": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+ "dev": true,
+ "requires": {
+ "isobject": "3.0.1"
+ }
+ },
+ "is-posix-bracket": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz",
+ "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=",
+ "dev": true
+ },
+ "is-primitive": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz",
+ "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=",
+ "dev": true
+ },
+ "is-property": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+ "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=",
+ "dev": true
+ },
+ "is-svg": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-2.1.0.tgz",
+ "integrity": "sha1-z2EJDaDZ77yrhyLeum8DIgjbsOk=",
+ "dev": true,
+ "requires": {
+ "html-comment-regex": "1.1.1"
+ }
+ },
+ "is-typedarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
+ "dev": true
+ },
+ "is-utf8": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
+ "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
+ "dev": true
+ },
+ "is-windows": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
+ "dev": true
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+ "dev": true
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ },
+ "isstream": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
+ "dev": true
+ },
+ "js-base64": {
+ "version": "2.4.5",
+ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.5.tgz",
+ "integrity": "sha512-aUnNwqMOXw3yvErjMPSQu6qIIzUmT1e5KcU1OZxRDU1g/am6mzBvcrmLAYwzmB59BHPrh5/tKaiF4OPhqRWESQ==",
+ "dev": true
+ },
+ "js-tokens": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
+ "dev": true
+ },
+ "js-yaml": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz",
+ "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=",
+ "dev": true,
+ "requires": {
+ "argparse": "1.0.10",
+ "esprima": "2.7.3"
+ }
+ },
+ "jsbn": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+ "dev": true,
+ "optional": true
+ },
+ "jsesc": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz",
+ "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=",
+ "dev": true
+ },
+ "json-loader": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz",
+ "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==",
+ "dev": true
+ },
+ "json-schema": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
+ "dev": true
+ },
+ "json-schema-traverse": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
+ "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=",
+ "dev": true
+ },
+ "json-stable-stringify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
+ "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=",
+ "dev": true,
+ "requires": {
+ "jsonify": "0.0.0"
+ }
+ },
+ "json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
+ "dev": true
+ },
+ "json3": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz",
+ "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=",
+ "dev": true
+ },
+ "json5": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
+ "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
+ "dev": true
+ },
+ "jsonify": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
+ "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
+ "dev": true
+ },
+ "jsonpointer": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz",
+ "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=",
+ "dev": true
+ },
+ "jsprim": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+ "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+ "dev": true,
+ "requires": {
+ "assert-plus": "1.0.0",
+ "extsprintf": "1.3.0",
+ "json-schema": "0.2.3",
+ "verror": "1.10.0"
+ },
+ "dependencies": {
+ "assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+ "dev": true
+ }
+ }
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
+ "dev": true
+ },
+ "lazy-cache": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz",
+ "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=",
+ "dev": true
+ },
+ "lcid": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
+ "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
+ "dev": true,
+ "requires": {
+ "invert-kv": "1.0.0"
+ }
+ },
+ "load-json-file": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+ "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "4.1.11",
+ "parse-json": "2.2.0",
+ "pify": "2.3.0",
+ "pinkie-promise": "2.0.1",
+ "strip-bom": "2.0.0"
+ }
+ },
+ "loader-runner": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz",
+ "integrity": "sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=",
+ "dev": true
+ },
+ "loader-utils": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz",
+ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=",
+ "dev": true,
+ "requires": {
+ "big.js": "3.2.0",
+ "emojis-list": "2.1.0",
+ "json5": "0.5.1"
+ }
+ },
+ "lodash": {
+ "version": "4.17.10",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
+ "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==",
+ "dev": true
+ },
+ "lodash._baseassign": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz",
+ "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=",
+ "dev": true,
+ "requires": {
+ "lodash._basecopy": "3.0.1",
+ "lodash.keys": "3.1.2"
+ }
+ },
+ "lodash._basecopy": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz",
+ "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=",
+ "dev": true
+ },
+ "lodash._basecreate": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz",
+ "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=",
+ "dev": true
+ },
+ "lodash._getnative": {
+ "version": "3.9.1",
+ "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz",
+ "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=",
+ "dev": true
+ },
+ "lodash._isiterateecall": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz",
+ "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=",
+ "dev": true
+ },
+ "lodash.assign": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
+ "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=",
+ "dev": true
+ },
+ "lodash.camelcase": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+ "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=",
+ "dev": true
+ },
+ "lodash.clonedeep": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+ "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
+ "dev": true
+ },
+ "lodash.create": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz",
+ "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=",
+ "dev": true,
+ "requires": {
+ "lodash._baseassign": "3.2.0",
+ "lodash._basecreate": "3.0.3",
+ "lodash._isiterateecall": "3.0.9"
+ }
+ },
+ "lodash.isarguments": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
+ "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=",
+ "dev": true
+ },
+ "lodash.isarray": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz",
+ "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=",
+ "dev": true
+ },
+ "lodash.keys": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz",
+ "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=",
+ "dev": true,
+ "requires": {
+ "lodash._getnative": "3.9.1",
+ "lodash.isarguments": "3.1.0",
+ "lodash.isarray": "3.0.4"
+ }
+ },
+ "lodash.memoize": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
+ "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
+ "dev": true
+ },
+ "lodash.mergewith": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz",
+ "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==",
+ "dev": true
+ },
+ "lodash.tail": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz",
+ "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=",
+ "dev": true
+ },
+ "lodash.uniq": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
+ "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=",
+ "dev": true
+ },
+ "loglevel": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz",
+ "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=",
+ "dev": true
+ },
+ "longest": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
+ "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=",
+ "dev": true
+ },
+ "loose-envify": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
+ "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
+ "dev": true,
+ "requires": {
+ "js-tokens": "3.0.2"
+ }
+ },
+ "loud-rejection": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
+ "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
+ "dev": true,
+ "requires": {
+ "currently-unhandled": "0.4.1",
+ "signal-exit": "3.0.2"
+ }
+ },
+ "lower-case": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
+ "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=",
+ "dev": true
+ },
+ "lru-cache": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz",
+ "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==",
+ "dev": true,
+ "requires": {
+ "pseudomap": "1.0.2",
+ "yallist": "2.1.2"
+ }
+ },
+ "map-cache": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
+ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
+ "dev": true
+ },
+ "map-obj": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+ "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
+ "dev": true
+ },
+ "map-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
+ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
+ "dev": true,
+ "requires": {
+ "object-visit": "1.0.1"
+ }
+ },
+ "math-expression-evaluator": {
+ "version": "1.2.17",
+ "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz",
+ "integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw=",
+ "dev": true
+ },
+ "math-random": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz",
+ "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=",
+ "dev": true
+ },
+ "md5.js": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz",
+ "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=",
+ "dev": true,
+ "requires": {
+ "hash-base": "3.0.4",
+ "inherits": "2.0.3"
+ }
+ },
+ "media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
+ "dev": true
+ },
+ "memory-fs": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
+ "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=",
+ "dev": true,
+ "requires": {
+ "errno": "0.1.7",
+ "readable-stream": "2.3.6"
+ }
+ },
+ "meow": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+ "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
+ "dev": true,
+ "requires": {
+ "camelcase-keys": "2.1.0",
+ "decamelize": "1.2.0",
+ "loud-rejection": "1.6.0",
+ "map-obj": "1.0.1",
+ "minimist": "1.2.0",
+ "normalize-package-data": "2.4.0",
+ "object-assign": "4.1.1",
+ "read-pkg-up": "1.0.1",
+ "redent": "1.0.0",
+ "trim-newlines": "1.0.0"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "dev": true
+ }
+ }
+ },
+ "merge-descriptors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=",
+ "dev": true
+ },
+ "methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
+ "dev": true
+ },
+ "micromatch": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+ "dev": true,
+ "requires": {
+ "arr-diff": "4.0.0",
+ "array-unique": "0.3.2",
+ "braces": "2.3.2",
+ "define-property": "2.0.2",
+ "extend-shallow": "3.0.2",
+ "extglob": "2.0.4",
+ "fragment-cache": "0.2.1",
+ "kind-of": "6.0.2",
+ "nanomatch": "1.2.9",
+ "object.pick": "1.3.0",
+ "regex-not": "1.0.2",
+ "snapdragon": "0.8.2",
+ "to-regex": "3.0.2"
+ }
+ },
+ "miller-rabin": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
+ "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
+ "dev": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "brorand": "1.1.0"
+ }
+ },
+ "mime": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
+ "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==",
+ "dev": true
+ },
+ "mime-db": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
+ "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==",
+ "dev": true
+ },
+ "mime-types": {
+ "version": "2.1.18",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
+ "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
+ "dev": true,
+ "requires": {
+ "mime-db": "1.33.0"
+ }
+ },
+ "minimalistic-assert": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+ "dev": true
+ },
+ "minimalistic-crypto-utils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
+ "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
+ "dev": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "1.1.11"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+ "dev": true
+ },
+ "mixin-deep": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
+ "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==",
+ "dev": true,
+ "requires": {
+ "for-in": "1.0.2",
+ "is-extendable": "1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "dev": true,
+ "requires": {
+ "is-plain-object": "2.0.4"
+ }
+ }
+ }
+ },
+ "mixin-object": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz",
+ "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=",
+ "dev": true,
+ "requires": {
+ "for-in": "0.1.8",
+ "is-extendable": "0.1.1"
+ },
+ "dependencies": {
+ "for-in": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz",
+ "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=",
+ "dev": true
+ }
+ }
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "dev": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "mocha": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz",
+ "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==",
+ "dev": true,
+ "requires": {
+ "browser-stdout": "1.3.0",
+ "commander": "2.9.0",
+ "debug": "2.6.8",
+ "diff": "3.2.0",
+ "escape-string-regexp": "1.0.5",
+ "glob": "7.1.1",
+ "growl": "1.9.2",
+ "he": "1.1.1",
+ "json3": "3.3.2",
+ "lodash.create": "3.1.1",
+ "mkdirp": "0.5.1",
+ "supports-color": "3.1.2"
+ },
+ "dependencies": {
+ "commander": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
+ "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=",
+ "dev": true,
+ "requires": {
+ "graceful-readlink": "1.0.1"
+ }
+ },
+ "debug": {
+ "version": "2.6.8",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
+ "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "has-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
+ "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz",
+ "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=",
+ "dev": true,
+ "requires": {
+ "has-flag": "1.0.0"
+ }
+ }
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ },
+ "multicast-dns": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz",
+ "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==",
+ "dev": true,
+ "requires": {
+ "dns-packet": "1.3.1",
+ "thunky": "1.0.2"
+ }
+ },
+ "multicast-dns-service-types": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
+ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=",
+ "dev": true
+ },
+ "nan": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
+ "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==",
+ "dev": true
+ },
+ "nanomatch": {
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz",
+ "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==",
+ "dev": true,
+ "requires": {
+ "arr-diff": "4.0.0",
+ "array-unique": "0.3.2",
+ "define-property": "2.0.2",
+ "extend-shallow": "3.0.2",
+ "fragment-cache": "0.2.1",
+ "is-odd": "2.0.0",
+ "is-windows": "1.0.2",
+ "kind-of": "6.0.2",
+ "object.pick": "1.3.0",
+ "regex-not": "1.0.2",
+ "snapdragon": "0.8.2",
+ "to-regex": "3.0.2"
+ }
+ },
+ "negotiator": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
+ "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=",
+ "dev": true
+ },
+ "neo-async": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.1.tgz",
+ "integrity": "sha512-3KL3fvuRkZ7s4IFOMfztb7zJp3QaVWnBeGoJlgB38XnCRPj/0tLzzLG5IB8NYOHbJ8g8UGrgZv44GLDk6CxTxA==",
+ "dev": true
+ },
+ "no-case": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
+ "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==",
+ "dev": true,
+ "requires": {
+ "lower-case": "1.1.4"
+ }
+ },
+ "node-forge": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz",
+ "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==",
+ "dev": true
+ },
+ "node-gyp": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.6.2.tgz",
+ "integrity": "sha1-m/vlRWIoYoSDjnUOrAUpWFP6HGA=",
+ "dev": true,
+ "requires": {
+ "fstream": "1.0.11",
+ "glob": "7.1.1",
+ "graceful-fs": "4.1.11",
+ "minimatch": "3.0.4",
+ "mkdirp": "0.5.1",
+ "nopt": "3.0.6",
+ "npmlog": "4.1.2",
+ "osenv": "0.1.5",
+ "request": "2.79.0",
+ "rimraf": "2.6.2",
+ "semver": "5.3.0",
+ "tar": "2.2.1",
+ "which": "1.3.1"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+ "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
+ "dev": true
+ }
+ }
+ },
+ "node-libs-browser": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz",
+ "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==",
+ "dev": true,
+ "requires": {
+ "assert": "1.4.1",
+ "browserify-zlib": "0.2.0",
+ "buffer": "4.9.1",
+ "console-browserify": "1.1.0",
+ "constants-browserify": "1.0.0",
+ "crypto-browserify": "3.12.0",
+ "domain-browser": "1.2.0",
+ "events": "1.1.1",
+ "https-browserify": "1.0.0",
+ "os-browserify": "0.3.0",
+ "path-browserify": "0.0.0",
+ "process": "0.11.10",
+ "punycode": "1.4.1",
+ "querystring-es3": "0.2.1",
+ "readable-stream": "2.3.6",
+ "stream-browserify": "2.0.1",
+ "stream-http": "2.8.2",
+ "string_decoder": "1.1.1",
+ "timers-browserify": "2.0.10",
+ "tty-browserify": "0.0.0",
+ "url": "0.11.0",
+ "util": "0.10.3",
+ "vm-browserify": "0.0.4"
+ }
+ },
+ "node-notifier": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.1.2.tgz",
+ "integrity": "sha1-L6nhJgX6EACdRFSdb82KY93g5P8=",
+ "dev": true,
+ "requires": {
+ "growly": "1.3.0",
+ "semver": "5.5.0",
+ "shellwords": "0.1.1",
+ "which": "1.3.1"
+ }
+ },
+ "node-sass": {
+ "version": "4.9.0",
+ "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.0.tgz",
+ "integrity": "sha512-QFHfrZl6lqRU3csypwviz2XLgGNOoWQbo2GOvtsfQqOfL4cy1BtWnhx/XUeAO9LT3ahBzSRXcEO6DdvAH9DzSg==",
+ "dev": true,
+ "requires": {
+ "async-foreach": "0.1.3",
+ "chalk": "1.1.3",
+ "cross-spawn": "3.0.1",
+ "gaze": "1.1.3",
+ "get-stdin": "4.0.1",
+ "glob": "7.1.1",
+ "in-publish": "2.0.0",
+ "lodash.assign": "4.2.0",
+ "lodash.clonedeep": "4.5.0",
+ "lodash.mergewith": "4.6.1",
+ "meow": "3.7.0",
+ "mkdirp": "0.5.1",
+ "nan": "2.10.0",
+ "node-gyp": "3.6.2",
+ "npmlog": "4.1.2",
+ "request": "2.79.0",
+ "sass-graph": "2.2.4",
+ "stdout-stream": "1.4.0",
+ "true-case-path": "1.0.2"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+ "dev": true
+ },
+ "chalk": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "2.2.1",
+ "escape-string-regexp": "1.0.5",
+ "has-ansi": "2.0.0",
+ "strip-ansi": "3.0.1",
+ "supports-color": "2.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+ "dev": true
+ }
+ }
+ },
+ "nopt": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
+ "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
+ "dev": true,
+ "requires": {
+ "abbrev": "1.1.1"
+ }
+ },
+ "normalize-package-data": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
+ "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
+ "dev": true,
+ "requires": {
+ "hosted-git-info": "2.6.0",
+ "is-builtin-module": "1.0.0",
+ "semver": "5.5.0",
+ "validate-npm-package-license": "3.0.3"
+ }
+ },
+ "normalize-path": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
+ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
+ "dev": true,
+ "requires": {
+ "remove-trailing-separator": "1.1.0"
+ }
+ },
+ "normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=",
+ "dev": true
+ },
+ "normalize-url": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz",
+ "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=",
+ "dev": true,
+ "requires": {
+ "object-assign": "4.1.1",
+ "prepend-http": "1.0.4",
+ "query-string": "4.3.4",
+ "sort-keys": "1.1.2"
+ }
+ },
+ "npmlog": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
+ "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
+ "dev": true,
+ "requires": {
+ "are-we-there-yet": "1.1.5",
+ "console-control-strings": "1.1.0",
+ "gauge": "2.7.4",
+ "set-blocking": "2.0.0"
+ }
+ },
+ "nth-check": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz",
+ "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=",
+ "dev": true,
+ "requires": {
+ "boolbase": "1.0.0"
+ }
+ },
+ "num2fraction": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz",
+ "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=",
+ "dev": true
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+ "dev": true
+ },
+ "oauth-sign": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
+ "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=",
+ "dev": true
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+ "dev": true
+ },
+ "object-component": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz",
+ "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=",
+ "dev": true
+ },
+ "object-copy": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
+ "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
+ "dev": true,
+ "requires": {
+ "copy-descriptor": "0.1.1",
+ "define-property": "0.2.5",
+ "kind-of": "3.2.2"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "0.1.6"
+ }
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "1.1.6"
+ }
+ }
+ }
+ },
+ "object-visit": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
+ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
+ "dev": true,
+ "requires": {
+ "isobject": "3.0.1"
+ }
+ },
+ "object.omit": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz",
+ "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=",
+ "dev": true,
+ "requires": {
+ "for-own": "0.1.5",
+ "is-extendable": "0.1.1"
+ },
+ "dependencies": {
+ "for-own": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz",
+ "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=",
+ "dev": true,
+ "requires": {
+ "for-in": "1.0.2"
+ }
+ }
+ }
+ },
+ "object.pick": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
+ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
+ "dev": true,
+ "requires": {
+ "isobject": "3.0.1"
+ }
+ },
+ "obuf": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
+ "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==",
+ "dev": true
+ },
+ "on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+ "dev": true,
+ "requires": {
+ "ee-first": "1.1.1"
+ }
+ },
+ "on-headers": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz",
+ "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=",
+ "dev": true
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "requires": {
+ "wrappy": "1.0.2"
+ }
+ },
+ "opn": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/opn/-/opn-4.0.2.tgz",
+ "integrity": "sha1-erwi5kTf9jsKltWrfyeQwPAavJU=",
+ "dev": true,
+ "requires": {
+ "object-assign": "4.1.1",
+ "pinkie-promise": "2.0.1"
+ }
+ },
+ "options": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz",
+ "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=",
+ "dev": true
+ },
+ "original": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/original/-/original-1.0.1.tgz",
+ "integrity": "sha512-IEvtB5vM5ULvwnqMxWBLxkS13JIEXbakizMSo3yoPNPCIWzg8TG3Usn/UhXoZFM/m+FuEA20KdzPSFq/0rS+UA==",
+ "dev": true,
+ "requires": {
+ "url-parse": "1.4.0"
+ }
+ },
+ "os-browserify": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
+ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=",
+ "dev": true
+ },
+ "os-homedir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+ "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
+ "dev": true
+ },
+ "os-locale": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
+ "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
+ "dev": true,
+ "requires": {
+ "lcid": "1.0.0"
+ }
+ },
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+ "dev": true
+ },
+ "osenv": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
+ "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
+ "dev": true,
+ "requires": {
+ "os-homedir": "1.0.2",
+ "os-tmpdir": "1.0.2"
+ }
+ },
+ "p-map": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz",
+ "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==",
+ "dev": true
+ },
+ "pako": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz",
+ "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==",
+ "dev": true
+ },
+ "param-case": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
+ "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=",
+ "dev": true,
+ "requires": {
+ "no-case": "2.3.2"
+ }
+ },
+ "parse-asn1": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz",
+ "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==",
+ "dev": true,
+ "requires": {
+ "asn1.js": "4.10.1",
+ "browserify-aes": "1.2.0",
+ "create-hash": "1.2.0",
+ "evp_bytestokey": "1.0.3",
+ "pbkdf2": "3.0.16"
+ }
+ },
+ "parse-glob": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz",
+ "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=",
+ "dev": true,
+ "requires": {
+ "glob-base": "0.3.0",
+ "is-dotfile": "1.0.3",
+ "is-extglob": "1.0.0",
+ "is-glob": "2.0.1"
+ },
+ "dependencies": {
+ "is-extglob": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
+ "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
+ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "1.0.0"
+ }
+ }
+ }
+ },
+ "parse-json": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+ "dev": true,
+ "requires": {
+ "error-ex": "1.3.1"
+ }
+ },
+ "parsejson": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.3.tgz",
+ "integrity": "sha1-q343WfIJ7OmUN5c/fQ8fZK4OZKs=",
+ "dev": true,
+ "requires": {
+ "better-assert": "1.0.2"
+ }
+ },
+ "parseqs": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
+ "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=",
+ "dev": true,
+ "requires": {
+ "better-assert": "1.0.2"
+ }
+ },
+ "parseuri": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz",
+ "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=",
+ "dev": true,
+ "requires": {
+ "better-assert": "1.0.2"
+ }
+ },
+ "parseurl": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
+ "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=",
+ "dev": true
+ },
+ "pascalcase": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
+ "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
+ "dev": true
+ },
+ "path-browserify": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz",
+ "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=",
+ "dev": true
+ },
+ "path-dirname": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
+ "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
+ "dev": true
+ },
+ "path-exists": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
+ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
+ "dev": true,
+ "requires": {
+ "pinkie-promise": "2.0.1"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true
+ },
+ "path-is-inside": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+ "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
+ "dev": true
+ },
+ "path-parse": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
+ "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=",
+ "dev": true
+ },
+ "path-to-regexp": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
+ "dev": true
+ },
+ "path-type": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
+ "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "4.1.11",
+ "pify": "2.3.0",
+ "pinkie-promise": "2.0.1"
+ }
+ },
+ "pathval": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
+ "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
+ "dev": true
+ },
+ "pbkdf2": {
+ "version": "3.0.16",
+ "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.16.tgz",
+ "integrity": "sha512-y4CXP3thSxqf7c0qmOF+9UeOTrifiVTIM+u7NWlq+PRsHbr7r7dpCmvzrZxa96JJUNi0Y5w9VqG5ZNeCVMoDcA==",
+ "dev": true,
+ "requires": {
+ "create-hash": "1.2.0",
+ "create-hmac": "1.1.7",
+ "ripemd160": "2.0.2",
+ "safe-buffer": "5.1.2",
+ "sha.js": "2.4.11"
+ }
+ },
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "dev": true
+ },
+ "pinkie": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+ "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+ "dev": true
+ },
+ "pinkie-promise": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+ "dev": true,
+ "requires": {
+ "pinkie": "2.0.4"
+ }
+ },
+ "portfinder": {
+ "version": "1.0.13",
+ "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.13.tgz",
+ "integrity": "sha1-uzLs2HwnEErm7kS1o8y/Drsa7ek=",
+ "dev": true,
+ "requires": {
+ "async": "1.5.2",
+ "debug": "2.6.9",
+ "mkdirp": "0.5.1"
+ },
+ "dependencies": {
+ "async": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
+ "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
+ "dev": true
+ }
+ }
+ },
+ "posix-character-classes": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
+ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
+ "dev": true
+ },
+ "postcss": {
+ "version": "5.2.18",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz",
+ "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==",
+ "dev": true,
+ "requires": {
+ "chalk": "1.1.3",
+ "js-base64": "2.4.5",
+ "source-map": "0.5.7",
+ "supports-color": "3.2.3"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+ "dev": true
+ },
+ "chalk": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "2.2.1",
+ "escape-string-regexp": "1.0.5",
+ "has-ansi": "2.0.0",
+ "strip-ansi": "3.0.1",
+ "supports-color": "2.0.0"
+ },
+ "dependencies": {
+ "supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+ "dev": true
+ }
+ }
+ },
+ "has-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
+ "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz",
+ "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=",
+ "dev": true,
+ "requires": {
+ "has-flag": "1.0.0"
+ }
+ }
+ }
+ },
+ "postcss-calc": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz",
+ "integrity": "sha1-d7rnypKK2FcW4v2kLyYb98HWW14=",
+ "dev": true,
+ "requires": {
+ "postcss": "5.2.18",
+ "postcss-message-helpers": "2.0.0",
+ "reduce-css-calc": "1.3.0"
+ }
+ },
+ "postcss-colormin": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-2.2.2.tgz",
+ "integrity": "sha1-ZjFBfV8OkJo9fsJrJMio0eT5bks=",
+ "dev": true,
+ "requires": {
+ "colormin": "1.1.2",
+ "postcss": "5.2.18",
+ "postcss-value-parser": "3.3.0"
+ }
+ },
+ "postcss-convert-values": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz",
+ "integrity": "sha1-u9hZPFwf0uPRwyK7kl3K6Nrk1i0=",
+ "dev": true,
+ "requires": {
+ "postcss": "5.2.18",
+ "postcss-value-parser": "3.3.0"
+ }
+ },
+ "postcss-discard-comments": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz",
+ "integrity": "sha1-vv6J+v1bPazlzM5Rt2uBUUvgDj0=",
+ "dev": true,
+ "requires": {
+ "postcss": "5.2.18"
+ }
+ },
+ "postcss-discard-duplicates": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz",
+ "integrity": "sha1-uavye4isGIFYpesSq8riAmO5GTI=",
+ "dev": true,
+ "requires": {
+ "postcss": "5.2.18"
+ }
+ },
+ "postcss-discard-empty": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz",
+ "integrity": "sha1-0rS9nVztXr2Nyt52QMfXzX9PkrU=",
+ "dev": true,
+ "requires": {
+ "postcss": "5.2.18"
+ }
+ },
+ "postcss-discard-overridden": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz",
+ "integrity": "sha1-ix6vVU9ob7KIzYdMVWZ7CqNmjVg=",
+ "dev": true,
+ "requires": {
+ "postcss": "5.2.18"
+ }
+ },
+ "postcss-discard-unused": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz",
+ "integrity": "sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM=",
+ "dev": true,
+ "requires": {
+ "postcss": "5.2.18",
+ "uniqs": "2.0.0"
+ }
+ },
+ "postcss-filter-plugins": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/postcss-filter-plugins/-/postcss-filter-plugins-2.0.3.tgz",
+ "integrity": "sha512-T53GVFsdinJhgwm7rg1BzbeBRomOg9y5MBVhGcsV0CxurUdVj1UlPdKtn7aqYA/c/QVkzKMjq2bSV5dKG5+AwQ==",
+ "dev": true,
+ "requires": {
+ "postcss": "5.2.18"
+ }
+ },
+ "postcss-merge-idents": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz",
+ "integrity": "sha1-TFUwMTwI4dWzu/PSu8dH4njuonA=",
+ "dev": true,
+ "requires": {
+ "has": "1.0.1",
+ "postcss": "5.2.18",
+ "postcss-value-parser": "3.3.0"
+ }
+ },
+ "postcss-merge-longhand": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz",
+ "integrity": "sha1-I9kM0Sewp3mUkVMyc5A0oaTz1lg=",
+ "dev": true,
+ "requires": {
+ "postcss": "5.2.18"
+ }
+ },
+ "postcss-merge-rules": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz",
+ "integrity": "sha1-0d9d+qexrMO+VT8OnhDofGG19yE=",
+ "dev": true,
+ "requires": {
+ "browserslist": "1.7.7",
+ "caniuse-api": "1.6.1",
+ "postcss": "5.2.18",
+ "postcss-selector-parser": "2.2.3",
+ "vendors": "1.0.2"
+ }
+ },
+ "postcss-message-helpers": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz",
+ "integrity": "sha1-pPL0+rbk/gAvCu0ABHjN9S+bpg4=",
+ "dev": true
+ },
+ "postcss-minify-font-values": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz",
+ "integrity": "sha1-S1jttWZB66fIR0qzUmyv17vey2k=",
+ "dev": true,
+ "requires": {
+ "object-assign": "4.1.1",
+ "postcss": "5.2.18",
+ "postcss-value-parser": "3.3.0"
+ }
+ },
+ "postcss-minify-gradients": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz",
+ "integrity": "sha1-Xb2hE3NwP4PPtKPqOIHY11/15uE=",
+ "dev": true,
+ "requires": {
+ "postcss": "5.2.18",
+ "postcss-value-parser": "3.3.0"
+ }
+ },
+ "postcss-minify-params": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz",
+ "integrity": "sha1-rSzgcTc7lDs9kwo/pZo1jCjW8fM=",
+ "dev": true,
+ "requires": {
+ "alphanum-sort": "1.0.2",
+ "postcss": "5.2.18",
+ "postcss-value-parser": "3.3.0",
+ "uniqs": "2.0.0"
+ }
+ },
+ "postcss-minify-selectors": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz",
+ "integrity": "sha1-ssapjAByz5G5MtGkllCBFDEXNb8=",
+ "dev": true,
+ "requires": {
+ "alphanum-sort": "1.0.2",
+ "has": "1.0.1",
+ "postcss": "5.2.18",
+ "postcss-selector-parser": "2.2.3"
+ }
+ },
+ "postcss-modules-extract-imports": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz",
+ "integrity": "sha1-ZhQOzs447wa/DT41XWm/WdFB6oU=",
+ "dev": true,
+ "requires": {
+ "postcss": "6.0.22"
+ },
+ "dependencies": {
+ "postcss": {
+ "version": "6.0.22",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.22.tgz",
+ "integrity": "sha512-Toc9lLoUASwGqxBSJGTVcOQiDqjK+Z2XlWBg+IgYwQMY9vA2f7iMpXVc1GpPcfTSyM5lkxNo0oDwDRO+wm7XHA==",
+ "dev": true,
+ "requires": {
+ "chalk": "2.4.1",
+ "source-map": "0.6.1",
+ "supports-color": "5.4.0"
+ }
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "postcss-modules-local-by-default": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz",
+ "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=",
+ "dev": true,
+ "requires": {
+ "css-selector-tokenizer": "0.7.0",
+ "postcss": "6.0.22"
+ },
+ "dependencies": {
+ "postcss": {
+ "version": "6.0.22",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.22.tgz",
+ "integrity": "sha512-Toc9lLoUASwGqxBSJGTVcOQiDqjK+Z2XlWBg+IgYwQMY9vA2f7iMpXVc1GpPcfTSyM5lkxNo0oDwDRO+wm7XHA==",
+ "dev": true,
+ "requires": {
+ "chalk": "2.4.1",
+ "source-map": "0.6.1",
+ "supports-color": "5.4.0"
+ }
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "postcss-modules-scope": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz",
+ "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=",
+ "dev": true,
+ "requires": {
+ "css-selector-tokenizer": "0.7.0",
+ "postcss": "6.0.22"
+ },
+ "dependencies": {
+ "postcss": {
+ "version": "6.0.22",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.22.tgz",
+ "integrity": "sha512-Toc9lLoUASwGqxBSJGTVcOQiDqjK+Z2XlWBg+IgYwQMY9vA2f7iMpXVc1GpPcfTSyM5lkxNo0oDwDRO+wm7XHA==",
+ "dev": true,
+ "requires": {
+ "chalk": "2.4.1",
+ "source-map": "0.6.1",
+ "supports-color": "5.4.0"
+ }
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "postcss-modules-values": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz",
+ "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=",
+ "dev": true,
+ "requires": {
+ "icss-replace-symbols": "1.1.0",
+ "postcss": "6.0.22"
+ },
+ "dependencies": {
+ "postcss": {
+ "version": "6.0.22",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.22.tgz",
+ "integrity": "sha512-Toc9lLoUASwGqxBSJGTVcOQiDqjK+Z2XlWBg+IgYwQMY9vA2f7iMpXVc1GpPcfTSyM5lkxNo0oDwDRO+wm7XHA==",
+ "dev": true,
+ "requires": {
+ "chalk": "2.4.1",
+ "source-map": "0.6.1",
+ "supports-color": "5.4.0"
+ }
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "postcss-normalize-charset": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz",
+ "integrity": "sha1-757nEhLX/nWceO0WL2HtYrXLk/E=",
+ "dev": true,
+ "requires": {
+ "postcss": "5.2.18"
+ }
+ },
+ "postcss-normalize-url": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz",
+ "integrity": "sha1-EI90s/L82viRov+j6kWSJ5/HgiI=",
+ "dev": true,
+ "requires": {
+ "is-absolute-url": "2.1.0",
+ "normalize-url": "1.9.1",
+ "postcss": "5.2.18",
+ "postcss-value-parser": "3.3.0"
+ }
+ },
+ "postcss-ordered-values": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz",
+ "integrity": "sha1-7sbCpntsQSqNsgQud/6NpD+VwR0=",
+ "dev": true,
+ "requires": {
+ "postcss": "5.2.18",
+ "postcss-value-parser": "3.3.0"
+ }
+ },
+ "postcss-reduce-idents": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz",
+ "integrity": "sha1-wsbSDMlYKE9qv75j92Cb9AkFmtM=",
+ "dev": true,
+ "requires": {
+ "postcss": "5.2.18",
+ "postcss-value-parser": "3.3.0"
+ }
+ },
+ "postcss-reduce-initial": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz",
+ "integrity": "sha1-aPgGlfBF0IJjqHmtJA343WT2ROo=",
+ "dev": true,
+ "requires": {
+ "postcss": "5.2.18"
+ }
+ },
+ "postcss-reduce-transforms": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz",
+ "integrity": "sha1-/3b02CEkN7McKYpC0uFEQCV3GuE=",
+ "dev": true,
+ "requires": {
+ "has": "1.0.1",
+ "postcss": "5.2.18",
+ "postcss-value-parser": "3.3.0"
+ }
+ },
+ "postcss-selector-parser": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz",
+ "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=",
+ "dev": true,
+ "requires": {
+ "flatten": "1.0.2",
+ "indexes-of": "1.0.1",
+ "uniq": "1.0.1"
+ }
+ },
+ "postcss-svgo": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.6.tgz",
+ "integrity": "sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0=",
+ "dev": true,
+ "requires": {
+ "is-svg": "2.1.0",
+ "postcss": "5.2.18",
+ "postcss-value-parser": "3.3.0",
+ "svgo": "0.7.2"
+ }
+ },
+ "postcss-unique-selectors": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz",
+ "integrity": "sha1-mB1X0p3csz57Hf4f1DuGSfkzyh0=",
+ "dev": true,
+ "requires": {
+ "alphanum-sort": "1.0.2",
+ "postcss": "5.2.18",
+ "uniqs": "2.0.0"
+ }
+ },
+ "postcss-value-parser": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz",
+ "integrity": "sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU=",
+ "dev": true
+ },
+ "postcss-zindex": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.2.0.tgz",
+ "integrity": "sha1-0hCd3AVbka9n/EyzsCWUZjnSryI=",
+ "dev": true,
+ "requires": {
+ "has": "1.0.1",
+ "postcss": "5.2.18",
+ "uniqs": "2.0.0"
+ }
+ },
+ "prepend-http": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
+ "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
+ "dev": true
+ },
+ "preserve": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz",
+ "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=",
+ "dev": true
+ },
+ "pretty-error": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz",
+ "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=",
+ "dev": true,
+ "requires": {
+ "renderkid": "2.0.1",
+ "utila": "0.4.0"
+ }
+ },
+ "private": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
+ "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==",
+ "dev": true
+ },
+ "process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
+ "dev": true
+ },
+ "process-nextick-args": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
+ "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
+ "dev": true
+ },
+ "proxy-addr": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz",
+ "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==",
+ "dev": true,
+ "requires": {
+ "forwarded": "0.1.2",
+ "ipaddr.js": "1.6.0"
+ }
+ },
+ "prr": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
+ "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
+ "dev": true
+ },
+ "pseudomap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
+ "dev": true
+ },
+ "public-encrypt": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.2.tgz",
+ "integrity": "sha512-4kJ5Esocg8X3h8YgJsKAuoesBgB7mqH3eowiDzMUPKiRDDE7E/BqqZD1hnTByIaAFiwAw246YEltSq7tdrOH0Q==",
+ "dev": true,
+ "requires": {
+ "bn.js": "4.11.8",
+ "browserify-rsa": "4.0.1",
+ "create-hash": "1.2.0",
+ "parse-asn1": "5.1.1",
+ "randombytes": "2.0.6"
+ }
+ },
+ "punycode": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+ "dev": true
+ },
+ "q": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
+ "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=",
+ "dev": true
+ },
+ "qs": {
+ "version": "6.3.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz",
+ "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=",
+ "dev": true
+ },
+ "query-string": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
+ "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
+ "dev": true,
+ "requires": {
+ "object-assign": "4.1.1",
+ "strict-uri-encode": "1.1.0"
+ }
+ },
+ "querystring": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
+ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
+ "dev": true
+ },
+ "querystring-es3": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
+ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
+ "dev": true
+ },
+ "querystringify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.0.0.tgz",
+ "integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw==",
+ "dev": true
+ },
+ "randomatic": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz",
+ "integrity": "sha512-VdxFOIEY3mNO5PtSRkkle/hPJDHvQhK21oa73K4yAc9qmp6N429gAyF1gZMOTMeS0/AYzaV/2Trcef+NaIonSA==",
+ "dev": true,
+ "requires": {
+ "is-number": "4.0.0",
+ "kind-of": "6.0.2",
+ "math-random": "1.0.1"
+ },
+ "dependencies": {
+ "is-number": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz",
+ "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==",
+ "dev": true
+ }
+ }
+ },
+ "randombytes": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz",
+ "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "randomfill": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
+ "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
+ "dev": true,
+ "requires": {
+ "randombytes": "2.0.6",
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "range-parser": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
+ "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=",
+ "dev": true
+ },
+ "raw-body": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz",
+ "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=",
+ "dev": true,
+ "requires": {
+ "bytes": "3.0.0",
+ "http-errors": "1.6.2",
+ "iconv-lite": "0.4.19",
+ "unpipe": "1.0.0"
+ },
+ "dependencies": {
+ "depd": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz",
+ "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=",
+ "dev": true
+ },
+ "http-errors": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz",
+ "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=",
+ "dev": true,
+ "requires": {
+ "depd": "1.1.1",
+ "inherits": "2.0.3",
+ "setprototypeof": "1.0.3",
+ "statuses": "1.4.0"
+ }
+ },
+ "setprototypeof": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz",
+ "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=",
+ "dev": true
+ }
+ }
+ },
+ "read-pkg": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
+ "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
+ "dev": true,
+ "requires": {
+ "load-json-file": "1.1.0",
+ "normalize-package-data": "2.4.0",
+ "path-type": "1.1.0"
+ }
+ },
+ "read-pkg-up": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
+ "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
+ "dev": true,
+ "requires": {
+ "find-up": "1.1.2",
+ "read-pkg": "1.1.0"
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "1.0.2",
+ "inherits": "2.0.3",
+ "isarray": "1.0.0",
+ "process-nextick-args": "2.0.0",
+ "safe-buffer": "5.1.2",
+ "string_decoder": "1.1.1",
+ "util-deprecate": "1.0.2"
+ }
+ },
+ "readdirp": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz",
+ "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "4.1.11",
+ "minimatch": "3.0.4",
+ "readable-stream": "2.3.6",
+ "set-immediate-shim": "1.0.1"
+ }
+ },
+ "redent": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
+ "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
+ "dev": true,
+ "requires": {
+ "indent-string": "2.1.0",
+ "strip-indent": "1.0.1"
+ }
+ },
+ "reduce-css-calc": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz",
+ "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=",
+ "dev": true,
+ "requires": {
+ "balanced-match": "0.4.2",
+ "math-expression-evaluator": "1.2.17",
+ "reduce-function-call": "1.0.2"
+ },
+ "dependencies": {
+ "balanced-match": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
+ "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=",
+ "dev": true
+ }
+ }
+ },
+ "reduce-function-call": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.2.tgz",
+ "integrity": "sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk=",
+ "dev": true,
+ "requires": {
+ "balanced-match": "0.4.2"
+ },
+ "dependencies": {
+ "balanced-match": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
+ "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=",
+ "dev": true
+ }
+ }
+ },
+ "regenerate": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
+ "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==",
+ "dev": true
+ },
+ "regenerator-runtime": {
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
+ "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==",
+ "dev": true
+ },
+ "regenerator-transform": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz",
+ "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "babel-types": "6.26.0",
+ "private": "0.1.8"
+ }
+ },
+ "regex-cache": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz",
+ "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==",
+ "dev": true,
+ "requires": {
+ "is-equal-shallow": "0.1.3"
+ }
+ },
+ "regex-not": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
+ "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "3.0.2",
+ "safe-regex": "1.1.0"
+ }
+ },
+ "regexpu-core": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz",
+ "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=",
+ "dev": true,
+ "requires": {
+ "regenerate": "1.4.0",
+ "regjsgen": "0.2.0",
+ "regjsparser": "0.1.5"
+ }
+ },
+ "regjsgen": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
+ "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=",
+ "dev": true
+ },
+ "regjsparser": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
+ "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=",
+ "dev": true,
+ "requires": {
+ "jsesc": "0.5.0"
+ },
+ "dependencies": {
+ "jsesc": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+ "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
+ "dev": true
+ }
+ }
+ },
+ "relateurl": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
+ "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=",
+ "dev": true
+ },
+ "remove-trailing-separator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
+ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
+ "dev": true
+ },
+ "renderkid": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.1.tgz",
+ "integrity": "sha1-iYyr/Ivt5Le5ETWj/9Mj5YwNsxk=",
+ "dev": true,
+ "requires": {
+ "css-select": "1.2.0",
+ "dom-converter": "0.1.4",
+ "htmlparser2": "3.3.0",
+ "strip-ansi": "3.0.1",
+ "utila": "0.3.3"
+ },
+ "dependencies": {
+ "utila": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/utila/-/utila-0.3.3.tgz",
+ "integrity": "sha1-1+jn1+MJEHCSsF+NloiCTWM6QiY=",
+ "dev": true
+ }
+ }
+ },
+ "repeat-element": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz",
+ "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=",
+ "dev": true
+ },
+ "repeat-string": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
+ "dev": true
+ },
+ "repeating": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
+ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
+ "dev": true,
+ "requires": {
+ "is-finite": "1.0.2"
+ }
+ },
+ "request": {
+ "version": "2.79.0",
+ "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz",
+ "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=",
+ "dev": true,
+ "requires": {
+ "aws-sign2": "0.6.0",
+ "aws4": "1.7.0",
+ "caseless": "0.11.0",
+ "combined-stream": "1.0.6",
+ "extend": "3.0.1",
+ "forever-agent": "0.6.1",
+ "form-data": "2.1.4",
+ "har-validator": "2.0.6",
+ "hawk": "3.1.3",
+ "http-signature": "1.1.1",
+ "is-typedarray": "1.0.0",
+ "isstream": "0.1.2",
+ "json-stringify-safe": "5.0.1",
+ "mime-types": "2.1.18",
+ "oauth-sign": "0.8.2",
+ "qs": "6.3.2",
+ "stringstream": "0.0.6",
+ "tough-cookie": "2.3.4",
+ "tunnel-agent": "0.4.3",
+ "uuid": "3.2.1"
+ }
+ },
+ "require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+ "dev": true
+ },
+ "require-main-filename": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
+ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
+ "dev": true
+ },
+ "requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
+ "dev": true
+ },
+ "resolve": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz",
+ "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==",
+ "dev": true,
+ "requires": {
+ "path-parse": "1.0.5"
+ }
+ },
+ "resolve-url": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
+ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
+ "dev": true
+ },
+ "ret": {
+ "version": "0.1.15",
+ "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
+ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
+ "dev": true
+ },
+ "right-align": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
+ "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=",
+ "dev": true,
+ "requires": {
+ "align-text": "0.1.4"
+ }
+ },
+ "rimraf": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
+ "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
+ "dev": true,
+ "requires": {
+ "glob": "7.1.1"
+ }
+ },
+ "ripemd160": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
+ "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
+ "dev": true,
+ "requires": {
+ "hash-base": "3.0.4",
+ "inherits": "2.0.3"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ },
+ "safe-regex": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+ "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
+ "dev": true,
+ "requires": {
+ "ret": "0.1.15"
+ }
+ },
+ "sass-graph": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz",
+ "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=",
+ "dev": true,
+ "requires": {
+ "glob": "7.1.1",
+ "lodash": "4.17.10",
+ "scss-tokenizer": "0.2.3",
+ "yargs": "7.1.0"
+ }
+ },
+ "sass-loader": {
+ "version": "6.0.7",
+ "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-6.0.7.tgz",
+ "integrity": "sha512-JoiyD00Yo1o61OJsoP2s2kb19L1/Y2p3QFcCdWdF6oomBGKVYuZyqHWemRBfQ2uGYsk+CH3eCguXNfpjzlcpaA==",
+ "dev": true,
+ "requires": {
+ "clone-deep": "2.0.2",
+ "loader-utils": "1.1.0",
+ "lodash.tail": "4.1.1",
+ "neo-async": "2.5.1",
+ "pify": "3.0.0"
+ },
+ "dependencies": {
+ "pify": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+ "dev": true
+ }
+ }
+ },
+ "sax": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
+ "dev": true
+ },
+ "schema-utils": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz",
+ "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=",
+ "dev": true,
+ "requires": {
+ "ajv": "5.5.2"
+ }
+ },
+ "scss-tokenizer": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
+ "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=",
+ "dev": true,
+ "requires": {
+ "js-base64": "2.4.5",
+ "source-map": "0.4.4"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
+ "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
+ "dev": true,
+ "requires": {
+ "amdefine": "1.0.1"
+ }
+ }
+ }
+ },
+ "select-hose": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
+ "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=",
+ "dev": true
+ },
+ "selfsigned": {
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.3.tgz",
+ "integrity": "sha512-vmZenZ+8Al3NLHkWnhBQ0x6BkML1eCP2xEi3JE+f3D9wW9fipD9NNJHYtE9XJM4TsPaHGZJIamrSI6MTg1dU2Q==",
+ "dev": true,
+ "requires": {
+ "node-forge": "0.7.5"
+ }
+ },
+ "semver": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
+ "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==",
+ "dev": true
+ },
+ "send": {
+ "version": "0.16.2",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
+ "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
+ "dev": true,
+ "requires": {
+ "debug": "2.6.9",
+ "depd": "1.1.2",
+ "destroy": "1.0.4",
+ "encodeurl": "1.0.2",
+ "escape-html": "1.0.3",
+ "etag": "1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "1.6.3",
+ "mime": "1.4.1",
+ "ms": "2.0.0",
+ "on-finished": "2.3.0",
+ "range-parser": "1.2.0",
+ "statuses": "1.4.0"
+ }
+ },
+ "serve-index": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
+ "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=",
+ "dev": true,
+ "requires": {
+ "accepts": "1.3.5",
+ "batch": "0.6.1",
+ "debug": "2.6.9",
+ "escape-html": "1.0.3",
+ "http-errors": "1.6.3",
+ "mime-types": "2.1.18",
+ "parseurl": "1.3.2"
+ },
+ "dependencies": {
+ "accepts": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
+ "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
+ "dev": true,
+ "requires": {
+ "mime-types": "2.1.18",
+ "negotiator": "0.6.1"
+ }
+ }
+ }
+ },
+ "serve-static": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
+ "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
+ "dev": true,
+ "requires": {
+ "encodeurl": "1.0.2",
+ "escape-html": "1.0.3",
+ "parseurl": "1.3.2",
+ "send": "0.16.2"
+ }
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+ "dev": true
+ },
+ "set-immediate-shim": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
+ "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=",
+ "dev": true
+ },
+ "set-value": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
+ "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "2.0.1",
+ "is-extendable": "0.1.1",
+ "is-plain-object": "2.0.4",
+ "split-string": "3.1.0"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "0.1.1"
+ }
+ }
+ }
+ },
+ "setimmediate": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
+ "dev": true
+ },
+ "setprototypeof": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
+ "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==",
+ "dev": true
+ },
+ "sha.js": {
+ "version": "2.4.11",
+ "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+ "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "shallow-clone": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz",
+ "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==",
+ "dev": true,
+ "requires": {
+ "is-extendable": "0.1.1",
+ "kind-of": "5.1.0",
+ "mixin-object": "2.0.1"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+ "dev": true
+ }
+ }
+ },
+ "shellwords": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz",
+ "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==",
+ "dev": true
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+ "dev": true
+ },
+ "slash": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
+ "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=",
+ "dev": true
+ },
+ "snapdragon": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
+ "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
+ "dev": true,
+ "requires": {
+ "base": "0.11.2",
+ "debug": "2.6.9",
+ "define-property": "0.2.5",
+ "extend-shallow": "2.0.1",
+ "map-cache": "0.2.2",
+ "source-map": "0.5.7",
+ "source-map-resolve": "0.5.2",
+ "use": "3.1.0"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "0.1.6"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "0.1.1"
+ }
+ }
+ }
+ },
+ "snapdragon-node": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
+ "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
+ "dev": true,
+ "requires": {
+ "define-property": "1.0.0",
+ "isobject": "3.0.1",
+ "snapdragon-util": "3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "1.0.2"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "6.0.2"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "6.0.2"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "1.0.0",
+ "is-data-descriptor": "1.0.0",
+ "kind-of": "6.0.2"
+ }
+ }
+ }
+ },
+ "snapdragon-util": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
+ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "3.2.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "1.1.6"
+ }
+ }
+ }
+ },
+ "sntp": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
+ "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
+ "dev": true,
+ "requires": {
+ "hoek": "2.16.3"
+ }
+ },
+ "socket.io": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-1.7.4.tgz",
+ "integrity": "sha1-L37O3DORvy1cc+KR/iM+bjTU3QA=",
+ "dev": true,
+ "requires": {
+ "debug": "2.3.3",
+ "engine.io": "1.8.5",
+ "has-binary": "0.1.7",
+ "object-assign": "4.1.0",
+ "socket.io-adapter": "0.5.0",
+ "socket.io-client": "1.7.4",
+ "socket.io-parser": "2.3.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz",
+ "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=",
+ "dev": true,
+ "requires": {
+ "ms": "0.7.2"
+ }
+ },
+ "ms": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz",
+ "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=",
+ "dev": true
+ },
+ "object-assign": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz",
+ "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=",
+ "dev": true
+ }
+ }
+ },
+ "socket.io-adapter": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-0.5.0.tgz",
+ "integrity": "sha1-y21LuL7IHhB4uZZ3+c7QBGBmu4s=",
+ "dev": true,
+ "requires": {
+ "debug": "2.3.3",
+ "socket.io-parser": "2.3.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz",
+ "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=",
+ "dev": true,
+ "requires": {
+ "ms": "0.7.2"
+ }
+ },
+ "ms": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz",
+ "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=",
+ "dev": true
+ }
+ }
+ },
+ "socket.io-client": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-1.7.4.tgz",
+ "integrity": "sha1-7J+CA1btme9tNX8HVtZIcXvdQoE=",
+ "dev": true,
+ "requires": {
+ "backo2": "1.0.2",
+ "component-bind": "1.0.0",
+ "component-emitter": "1.2.1",
+ "debug": "2.3.3",
+ "engine.io-client": "1.8.5",
+ "has-binary": "0.1.7",
+ "indexof": "0.0.1",
+ "object-component": "0.0.3",
+ "parseuri": "0.0.5",
+ "socket.io-parser": "2.3.1",
+ "to-array": "0.1.4"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz",
+ "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=",
+ "dev": true,
+ "requires": {
+ "ms": "0.7.2"
+ }
+ },
+ "ms": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz",
+ "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=",
+ "dev": true
+ }
+ }
+ },
+ "socket.io-parser": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-2.3.1.tgz",
+ "integrity": "sha1-3VMgJRA85Clpcya+/WQAX8/ltKA=",
+ "dev": true,
+ "requires": {
+ "component-emitter": "1.1.2",
+ "debug": "2.2.0",
+ "isarray": "0.0.1",
+ "json3": "3.3.2"
+ },
+ "dependencies": {
+ "component-emitter": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz",
+ "integrity": "sha1-KWWU8nU9qmOZbSrwjRWpURbJrsM=",
+ "dev": true
+ },
+ "debug": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
+ "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
+ "dev": true,
+ "requires": {
+ "ms": "0.7.1"
+ }
+ },
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+ "dev": true
+ },
+ "ms": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
+ "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=",
+ "dev": true
+ }
+ }
+ },
+ "sockjs": {
+ "version": "0.3.18",
+ "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.18.tgz",
+ "integrity": "sha1-2bKJMWyn33dZXvKZ4HXw+TfrQgc=",
+ "dev": true,
+ "requires": {
+ "faye-websocket": "0.10.0",
+ "uuid": "2.0.3"
+ },
+ "dependencies": {
+ "uuid": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz",
+ "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=",
+ "dev": true
+ }
+ }
+ },
+ "sockjs-client": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.1.4.tgz",
+ "integrity": "sha1-W6vjhrd15M8U51IJEUUmVAFsixI=",
+ "dev": true,
+ "requires": {
+ "debug": "2.6.9",
+ "eventsource": "0.1.6",
+ "faye-websocket": "0.11.1",
+ "inherits": "2.0.3",
+ "json3": "3.3.2",
+ "url-parse": "1.4.0"
+ },
+ "dependencies": {
+ "faye-websocket": {
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz",
+ "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=",
+ "dev": true,
+ "requires": {
+ "websocket-driver": "0.7.0"
+ }
+ }
+ }
+ },
+ "sort-keys": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
+ "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=",
+ "dev": true,
+ "requires": {
+ "is-plain-obj": "1.1.0"
+ }
+ },
+ "source-list-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz",
+ "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==",
+ "dev": true
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+ "dev": true
+ },
+ "source-map-resolve": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz",
+ "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==",
+ "dev": true,
+ "requires": {
+ "atob": "2.1.1",
+ "decode-uri-component": "0.2.0",
+ "resolve-url": "0.2.1",
+ "source-map-url": "0.4.0",
+ "urix": "0.1.0"
+ }
+ },
+ "source-map-support": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.6.tgz",
+ "integrity": "sha512-N4KXEz7jcKqPf2b2vZF11lQIz9W5ZMuUcIOGj243lduidkf2fjkVKJS9vNxVWn3u/uxX38AcE8U9nnH9FPcq+g==",
+ "dev": true,
+ "requires": {
+ "buffer-from": "1.1.0",
+ "source-map": "0.6.1"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "source-map-url": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
+ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
+ "dev": true
+ },
+ "spdx-correct": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz",
+ "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==",
+ "dev": true,
+ "requires": {
+ "spdx-expression-parse": "3.0.0",
+ "spdx-license-ids": "3.0.0"
+ }
+ },
+ "spdx-exceptions": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz",
+ "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==",
+ "dev": true
+ },
+ "spdx-expression-parse": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
+ "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
+ "dev": true,
+ "requires": {
+ "spdx-exceptions": "2.1.0",
+ "spdx-license-ids": "3.0.0"
+ }
+ },
+ "spdx-license-ids": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz",
+ "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==",
+ "dev": true
+ },
+ "spdy": {
+ "version": "3.4.7",
+ "resolved": "https://registry.npmjs.org/spdy/-/spdy-3.4.7.tgz",
+ "integrity": "sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw=",
+ "dev": true,
+ "requires": {
+ "debug": "2.6.9",
+ "handle-thing": "1.2.5",
+ "http-deceiver": "1.2.7",
+ "safe-buffer": "5.1.2",
+ "select-hose": "2.0.0",
+ "spdy-transport": "2.1.0"
+ }
+ },
+ "spdy-transport": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.1.0.tgz",
+ "integrity": "sha512-bpUeGpZcmZ692rrTiqf9/2EUakI6/kXX1Rpe0ib/DyOzbiexVfXkw6GnvI9hVGvIwVaUhkaBojjCZwLNRGQg1g==",
+ "dev": true,
+ "requires": {
+ "debug": "2.6.9",
+ "detect-node": "2.0.3",
+ "hpack.js": "2.1.6",
+ "obuf": "1.1.2",
+ "readable-stream": "2.3.6",
+ "safe-buffer": "5.1.2",
+ "wbuf": "1.7.3"
+ }
+ },
+ "split-string": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
+ "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "3.0.2"
+ }
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+ "dev": true
+ },
+ "sshpk": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz",
+ "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=",
+ "dev": true,
+ "requires": {
+ "asn1": "0.2.3",
+ "assert-plus": "1.0.0",
+ "bcrypt-pbkdf": "1.0.1",
+ "dashdash": "1.14.1",
+ "ecc-jsbn": "0.1.1",
+ "getpass": "0.1.7",
+ "jsbn": "0.1.1",
+ "tweetnacl": "0.14.5"
+ },
+ "dependencies": {
+ "assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+ "dev": true
+ }
+ }
+ },
+ "static-extend": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
+ "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
+ "dev": true,
+ "requires": {
+ "define-property": "0.2.5",
+ "object-copy": "0.1.0"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "0.1.6"
+ }
+ }
+ }
+ },
+ "statuses": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
+ "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==",
+ "dev": true
+ },
+ "stdout-stream": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.0.tgz",
+ "integrity": "sha1-osfIWH5U2UJ+qe2zrD8s1SLfN4s=",
+ "dev": true,
+ "requires": {
+ "readable-stream": "2.3.6"
+ }
+ },
+ "stream-browserify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
+ "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "readable-stream": "2.3.6"
+ }
+ },
+ "stream-http": {
+ "version": "2.8.2",
+ "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.2.tgz",
+ "integrity": "sha512-QllfrBhqF1DPcz46WxKTs6Mz1Bpc+8Qm6vbqOpVav5odAXwbyzwnEczoWqtxrsmlO+cJqtPrp/8gWKWjaKLLlA==",
+ "dev": true,
+ "requires": {
+ "builtin-status-codes": "3.0.0",
+ "inherits": "2.0.3",
+ "readable-stream": "2.3.6",
+ "to-arraybuffer": "1.0.1",
+ "xtend": "4.0.1"
+ }
+ },
+ "strict-uri-encode": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
+ "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
+ "dev": true
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "dev": true,
+ "requires": {
+ "code-point-at": "1.1.0",
+ "is-fullwidth-code-point": "1.0.0",
+ "strip-ansi": "3.0.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "stringstream": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz",
+ "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "2.1.1"
+ }
+ },
+ "strip-bom": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
+ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
+ "dev": true,
+ "requires": {
+ "is-utf8": "0.2.1"
+ }
+ },
+ "strip-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
+ "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
+ "dev": true,
+ "requires": {
+ "get-stdin": "4.0.1"
+ }
+ },
+ "style-loader": {
+ "version": "0.18.2",
+ "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.18.2.tgz",
+ "integrity": "sha512-WPpJPZGUxWYHWIUMNNOYqql7zh85zGmr84FdTVWq52WTIkqlW9xSxD3QYWi/T31cqn9UNSsietVEgGn2aaSCzw==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "1.1.0",
+ "schema-utils": "0.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
+ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
+ "dev": true,
+ "requires": {
+ "has-flag": "3.0.0"
+ }
+ },
+ "svgo": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz",
+ "integrity": "sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U=",
+ "dev": true,
+ "requires": {
+ "coa": "1.0.4",
+ "colors": "1.1.2",
+ "csso": "2.3.2",
+ "js-yaml": "3.7.0",
+ "mkdirp": "0.5.1",
+ "sax": "1.2.4",
+ "whet.extend": "0.9.9"
+ }
+ },
+ "tapable": {
+ "version": "0.2.8",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.8.tgz",
+ "integrity": "sha1-mTcqXJmb8t8WCvwNdL7U9HlIzSI=",
+ "dev": true
+ },
+ "tar": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
+ "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
+ "dev": true,
+ "requires": {
+ "block-stream": "0.0.9",
+ "fstream": "1.0.11",
+ "inherits": "2.0.3"
+ }
+ },
+ "thunky": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.2.tgz",
+ "integrity": "sha1-qGLgGOP7HqLsP85dVWBc9X8kc3E=",
+ "dev": true
+ },
+ "time-stamp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-2.0.0.tgz",
+ "integrity": "sha1-lcakRTDhW6jW9KPsuMOj+sRto1c=",
+ "dev": true
+ },
+ "timers-browserify": {
+ "version": "2.0.10",
+ "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz",
+ "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==",
+ "dev": true,
+ "requires": {
+ "setimmediate": "1.0.5"
+ }
+ },
+ "to-array": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
+ "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=",
+ "dev": true
+ },
+ "to-arraybuffer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
+ "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=",
+ "dev": true
+ },
+ "to-fast-properties": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
+ "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=",
+ "dev": true
+ },
+ "to-object-path": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
+ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
+ "dev": true,
+ "requires": {
+ "kind-of": "3.2.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "1.1.6"
+ }
+ }
+ }
+ },
+ "to-regex": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
+ "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
+ "dev": true,
+ "requires": {
+ "define-property": "2.0.2",
+ "extend-shallow": "3.0.2",
+ "regex-not": "1.0.2",
+ "safe-regex": "1.1.0"
+ }
+ },
+ "to-regex-range": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
+ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
+ "dev": true,
+ "requires": {
+ "is-number": "3.0.0",
+ "repeat-string": "1.6.1"
+ }
+ },
+ "toposort": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz",
+ "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=",
+ "dev": true
+ },
+ "tough-cookie": {
+ "version": "2.3.4",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz",
+ "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==",
+ "dev": true,
+ "requires": {
+ "punycode": "1.4.1"
+ }
+ },
+ "trim-newlines": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
+ "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
+ "dev": true
+ },
+ "trim-right": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
+ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
+ "dev": true
+ },
+ "true-case-path": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.2.tgz",
+ "integrity": "sha1-fskRMJJHZsf1c74wIMNPj9/QDWI=",
+ "dev": true,
+ "requires": {
+ "glob": "6.0.4"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
+ "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
+ "dev": true,
+ "requires": {
+ "inflight": "1.0.6",
+ "inherits": "2.0.3",
+ "minimatch": "3.0.4",
+ "once": "1.4.0",
+ "path-is-absolute": "1.0.1"
+ }
+ }
+ }
+ },
+ "tslib": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.2.tgz",
+ "integrity": "sha512-AVP5Xol3WivEr7hnssHDsaM+lVrVXWUvd1cfXTRkTj80b//6g2wIFEH6hZG0muGZRnHGrfttpdzRk3YlBkWjKw==",
+ "dev": true
+ },
+ "tslint": {
+ "version": "5.10.0",
+ "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.10.0.tgz",
+ "integrity": "sha1-EeJrzLiK+gLdDZlWyuPUVAtfVMM=",
+ "dev": true,
+ "requires": {
+ "babel-code-frame": "6.26.0",
+ "builtin-modules": "1.1.1",
+ "chalk": "2.4.1",
+ "commander": "2.15.1",
+ "diff": "3.2.0",
+ "glob": "7.1.1",
+ "js-yaml": "3.7.0",
+ "minimatch": "3.0.4",
+ "resolve": "1.7.1",
+ "semver": "5.5.0",
+ "tslib": "1.9.2",
+ "tsutils": "2.27.1"
+ }
+ },
+ "tslint-loader": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/tslint-loader/-/tslint-loader-3.6.0.tgz",
+ "integrity": "sha512-Me9Qf/87BOfCY8uJJw+J7VMF4U8WiMXKLhKKKugMydF0xMhMOt9wo2mjYTNhwbF9H7SHh8PAIwRG8roisTNekQ==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "1.1.0",
+ "mkdirp": "0.5.1",
+ "object-assign": "4.1.1",
+ "rimraf": "2.6.2",
+ "semver": "5.5.0"
+ }
+ },
+ "tsutils": {
+ "version": "2.27.1",
+ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.27.1.tgz",
+ "integrity": "sha512-AE/7uzp32MmaHvNNFES85hhUDHFdFZp6OAiZcd6y4ZKKIg6orJTm8keYWBhIhrJQH3a4LzNKat7ZPXZt5aTf6w==",
+ "dev": true,
+ "requires": {
+ "tslib": "1.9.2"
+ }
+ },
+ "tty-browserify": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
+ "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
+ "dev": true
+ },
+ "tunnel-agent": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
+ "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=",
+ "dev": true
+ },
+ "tweetnacl": {
+ "version": "0.14.5",
+ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+ "dev": true,
+ "optional": true
+ },
+ "type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true
+ },
+ "type-is": {
+ "version": "1.6.16",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
+ "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
+ "dev": true,
+ "requires": {
+ "media-typer": "0.3.0",
+ "mime-types": "2.1.18"
+ }
+ },
+ "typescript": {
+ "version": "2.8.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.8.4.tgz",
+ "integrity": "sha512-IIU5cN1mR5J3z9jjdESJbnxikTrEz3lzAw/D0Tf45jHpBp55nY31UkUvmVHoffCfKHTqJs3fCLPDxknQTTFegQ==",
+ "dev": true
+ },
+ "uglify-js": {
+ "version": "3.3.28",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.28.tgz",
+ "integrity": "sha512-68Rc/aA6cswiaQ5SrE979UJcXX+ADA1z33/ZsPd+fbAiVdjZ16OXdbtGO+rJUUBgK6qdf3SOPhQf3K/ybF5Miw==",
+ "dev": true,
+ "requires": {
+ "commander": "2.15.1",
+ "source-map": "0.6.1"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "uglify-to-browserify": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz",
+ "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=",
+ "dev": true,
+ "optional": true
+ },
+ "ultron": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz",
+ "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=",
+ "dev": true
+ },
+ "union-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
+ "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=",
+ "dev": true,
+ "requires": {
+ "arr-union": "3.1.0",
+ "get-value": "2.0.6",
+ "is-extendable": "0.1.1",
+ "set-value": "0.4.3"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "0.1.1"
+ }
+ },
+ "set-value": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz",
+ "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "2.0.1",
+ "is-extendable": "0.1.1",
+ "is-plain-object": "2.0.4",
+ "to-object-path": "0.3.0"
+ }
+ }
+ }
+ },
+ "uniq": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
+ "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=",
+ "dev": true
+ },
+ "uniqs": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz",
+ "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=",
+ "dev": true
+ },
+ "unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
+ "dev": true
+ },
+ "unset-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
+ "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
+ "dev": true,
+ "requires": {
+ "has-value": "0.3.1",
+ "isobject": "3.0.1"
+ },
+ "dependencies": {
+ "has-value": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
+ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
+ "dev": true,
+ "requires": {
+ "get-value": "2.0.6",
+ "has-values": "0.1.4",
+ "isobject": "2.1.0"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+ "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+ "dev": true,
+ "requires": {
+ "isarray": "1.0.0"
+ }
+ }
+ }
+ },
+ "has-values": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
+ "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
+ "dev": true
+ }
+ }
+ },
+ "upath": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz",
+ "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==",
+ "dev": true
+ },
+ "upper-case": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
+ "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=",
+ "dev": true
+ },
+ "urix": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
+ "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
+ "dev": true
+ },
+ "url": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
+ "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
+ "dev": true,
+ "requires": {
+ "punycode": "1.3.2",
+ "querystring": "0.2.0"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
+ "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
+ "dev": true
+ }
+ }
+ },
+ "url-parse": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.0.tgz",
+ "integrity": "sha512-ERuGxDiQ6Xw/agN4tuoCRbmwRuZP0cJ1lJxJubXr5Q/5cDa78+Dc4wfvtxzhzhkm5VvmW6Mf8EVj9SPGN4l8Lg==",
+ "dev": true,
+ "requires": {
+ "querystringify": "2.0.0",
+ "requires-port": "1.0.0"
+ }
+ },
+ "use": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz",
+ "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==",
+ "dev": true,
+ "requires": {
+ "kind-of": "6.0.2"
+ }
+ },
+ "util": {
+ "version": "0.10.3",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
+ "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.1"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
+ "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=",
+ "dev": true
+ }
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+ "dev": true
+ },
+ "utila": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz",
+ "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=",
+ "dev": true
+ },
+ "utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
+ "dev": true
+ },
+ "uuid": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz",
+ "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==",
+ "dev": true
+ },
+ "validate-npm-package-license": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz",
+ "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==",
+ "dev": true,
+ "requires": {
+ "spdx-correct": "3.0.0",
+ "spdx-expression-parse": "3.0.0"
+ }
+ },
+ "vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
+ "dev": true
+ },
+ "vendors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.2.tgz",
+ "integrity": "sha512-w/hry/368nO21AN9QljsaIhb9ZiZtZARoVH5f3CsFbawdLdayCgKRPup7CggujvySMxx0I91NOyxdVENohprLQ==",
+ "dev": true
+ },
+ "verror": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+ "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+ "dev": true,
+ "requires": {
+ "assert-plus": "1.0.0",
+ "core-util-is": "1.0.2",
+ "extsprintf": "1.3.0"
+ },
+ "dependencies": {
+ "assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+ "dev": true
+ }
+ }
+ },
+ "vm-browserify": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz",
+ "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=",
+ "dev": true,
+ "requires": {
+ "indexof": "0.0.1"
+ }
+ },
+ "watchpack": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz",
+ "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==",
+ "dev": true,
+ "requires": {
+ "chokidar": "2.0.3",
+ "graceful-fs": "4.1.11",
+ "neo-async": "2.5.1"
+ }
+ },
+ "wbuf": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
+ "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==",
+ "dev": true,
+ "requires": {
+ "minimalistic-assert": "1.0.1"
+ }
+ },
+ "webpack": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-2.7.0.tgz",
+ "integrity": "sha512-MjAA0ZqO1ba7ZQJRnoCdbM56mmFpipOPUv/vQpwwfSI42p5PVDdoiuK2AL2FwFUVgT859Jr43bFZXRg/LNsqvg==",
+ "dev": true,
+ "requires": {
+ "acorn": "5.6.0",
+ "acorn-dynamic-import": "2.0.2",
+ "ajv": "4.11.8",
+ "ajv-keywords": "1.5.1",
+ "async": "2.6.1",
+ "enhanced-resolve": "3.3.0",
+ "interpret": "1.1.0",
+ "json-loader": "0.5.7",
+ "json5": "0.5.1",
+ "loader-runner": "2.3.0",
+ "loader-utils": "0.2.17",
+ "memory-fs": "0.4.1",
+ "mkdirp": "0.5.1",
+ "node-libs-browser": "2.1.0",
+ "source-map": "0.5.7",
+ "supports-color": "3.2.3",
+ "tapable": "0.2.8",
+ "uglify-js": "2.8.29",
+ "watchpack": "1.6.0",
+ "webpack-sources": "1.1.0",
+ "yargs": "6.6.0"
+ },
+ "dependencies": {
+ "ajv": {
+ "version": "4.11.8",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
+ "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=",
+ "dev": true,
+ "requires": {
+ "co": "4.6.0",
+ "json-stable-stringify": "1.0.1"
+ }
+ },
+ "camelcase": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz",
+ "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=",
+ "dev": true
+ },
+ "cliui": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
+ "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=",
+ "dev": true,
+ "requires": {
+ "center-align": "0.1.3",
+ "right-align": "0.1.3",
+ "wordwrap": "0.0.2"
+ }
+ },
+ "has-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
+ "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=",
+ "dev": true
+ },
+ "loader-utils": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz",
+ "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=",
+ "dev": true,
+ "requires": {
+ "big.js": "3.2.0",
+ "emojis-list": "2.1.0",
+ "json5": "0.5.1",
+ "object-assign": "4.1.1"
+ }
+ },
+ "supports-color": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz",
+ "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=",
+ "dev": true,
+ "requires": {
+ "has-flag": "1.0.0"
+ }
+ },
+ "uglify-js": {
+ "version": "2.8.29",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz",
+ "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=",
+ "dev": true,
+ "requires": {
+ "source-map": "0.5.7",
+ "uglify-to-browserify": "1.0.2",
+ "yargs": "3.10.0"
+ },
+ "dependencies": {
+ "yargs": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz",
+ "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=",
+ "dev": true,
+ "requires": {
+ "camelcase": "1.2.1",
+ "cliui": "2.1.0",
+ "decamelize": "1.2.0",
+ "window-size": "0.1.0"
+ }
+ }
+ }
+ },
+ "yargs": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz",
+ "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=",
+ "dev": true,
+ "requires": {
+ "camelcase": "3.0.0",
+ "cliui": "3.2.0",
+ "decamelize": "1.2.0",
+ "get-caller-file": "1.0.2",
+ "os-locale": "1.4.0",
+ "read-pkg-up": "1.0.1",
+ "require-directory": "2.1.1",
+ "require-main-filename": "1.0.1",
+ "set-blocking": "2.0.0",
+ "string-width": "1.0.2",
+ "which-module": "1.0.0",
+ "y18n": "3.2.1",
+ "yargs-parser": "4.2.1"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
+ "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
+ "dev": true
+ },
+ "cliui": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
+ "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
+ "dev": true,
+ "requires": {
+ "string-width": "1.0.2",
+ "strip-ansi": "3.0.1",
+ "wrap-ansi": "2.1.0"
+ }
+ }
+ }
+ },
+ "yargs-parser": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz",
+ "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=",
+ "dev": true,
+ "requires": {
+ "camelcase": "3.0.0"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
+ "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
+ "dev": true
+ }
+ }
+ }
+ }
+ },
+ "webpack-build-notifier": {
+ "version": "0.1.25",
+ "resolved": "https://registry.npmjs.org/webpack-build-notifier/-/webpack-build-notifier-0.1.25.tgz",
+ "integrity": "sha512-iiWibDuCY+hy2D9WgOKEYmQRgZ/B5wJ7GbVpwZ2z/JGgTEIqm2OunAdmFWj1h02Hvtw3syQbpYKSZawZGSsiUw==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "2.1.1",
+ "node-notifier": "5.1.2",
+ "strip-ansi": "3.0.1"
+ }
+ },
+ "webpack-dashboard": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/webpack-dashboard/-/webpack-dashboard-0.3.0.tgz",
+ "integrity": "sha1-0P4YKgl2qP8OoBsFB/KaA0zWChw=",
+ "dev": true,
+ "requires": {
+ "blessed": "0.1.81",
+ "commander": "2.15.1",
+ "cross-spawn": "4.0.2",
+ "filesize": "3.6.1",
+ "socket.io": "1.7.4",
+ "socket.io-client": "1.7.4"
+ },
+ "dependencies": {
+ "cross-spawn": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz",
+ "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=",
+ "dev": true,
+ "requires": {
+ "lru-cache": "4.1.3",
+ "which": "1.3.1"
+ }
+ }
+ }
+ },
+ "webpack-dev-middleware": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz",
+ "integrity": "sha512-FCrqPy1yy/sN6U/SaEZcHKRXGlqU0DUaEBL45jkUYoB8foVb6wCnbIJ1HKIx+qUFTW+3JpVcCJCxZ8VATL4e+A==",
+ "dev": true,
+ "requires": {
+ "memory-fs": "0.4.1",
+ "mime": "1.6.0",
+ "path-is-absolute": "1.0.1",
+ "range-parser": "1.2.0",
+ "time-stamp": "2.0.0"
+ },
+ "dependencies": {
+ "mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "dev": true
+ }
+ }
+ },
+ "webpack-dev-server": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-2.7.1.tgz",
+ "integrity": "sha1-IVgPWgjNBlxxFEz29hw0W8pZqLg=",
+ "dev": true,
+ "requires": {
+ "ansi-html": "0.0.7",
+ "bonjour": "3.5.0",
+ "chokidar": "1.7.0",
+ "compression": "1.7.2",
+ "connect-history-api-fallback": "1.5.0",
+ "del": "3.0.0",
+ "express": "4.16.3",
+ "html-entities": "1.2.1",
+ "http-proxy-middleware": "0.17.4",
+ "internal-ip": "1.2.0",
+ "ip": "1.1.5",
+ "loglevel": "1.6.1",
+ "opn": "4.0.2",
+ "portfinder": "1.0.13",
+ "selfsigned": "1.10.3",
+ "serve-index": "1.9.1",
+ "sockjs": "0.3.18",
+ "sockjs-client": "1.1.4",
+ "spdy": "3.4.7",
+ "strip-ansi": "3.0.1",
+ "supports-color": "3.2.3",
+ "webpack-dev-middleware": "1.12.2",
+ "yargs": "6.6.0"
+ },
+ "dependencies": {
+ "anymatch": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz",
+ "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==",
+ "dev": true,
+ "requires": {
+ "micromatch": "2.3.11",
+ "normalize-path": "2.1.1"
+ }
+ },
+ "arr-diff": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz",
+ "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=",
+ "dev": true,
+ "requires": {
+ "arr-flatten": "1.1.0"
+ }
+ },
+ "array-unique": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz",
+ "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=",
+ "dev": true
+ },
+ "braces": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz",
+ "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=",
+ "dev": true,
+ "requires": {
+ "expand-range": "1.8.2",
+ "preserve": "0.2.0",
+ "repeat-element": "1.1.2"
+ }
+ },
+ "camelcase": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
+ "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
+ "dev": true
+ },
+ "chokidar": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz",
+ "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=",
+ "dev": true,
+ "requires": {
+ "anymatch": "1.3.2",
+ "async-each": "1.0.1",
+ "fsevents": "1.2.4",
+ "glob-parent": "2.0.0",
+ "inherits": "2.0.3",
+ "is-binary-path": "1.0.1",
+ "is-glob": "2.0.1",
+ "path-is-absolute": "1.0.1",
+ "readdirp": "2.1.0"
+ }
+ },
+ "expand-brackets": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz",
+ "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=",
+ "dev": true,
+ "requires": {
+ "is-posix-bracket": "0.1.1"
+ }
+ },
+ "extglob": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz",
+ "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "1.0.0"
+ }
+ },
+ "glob-parent": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
+ "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=",
+ "dev": true,
+ "requires": {
+ "is-glob": "2.0.1"
+ }
+ },
+ "has-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
+ "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=",
+ "dev": true
+ },
+ "is-extglob": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
+ "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
+ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "1.0.0"
+ }
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "1.1.6"
+ }
+ },
+ "micromatch": {
+ "version": "2.3.11",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz",
+ "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=",
+ "dev": true,
+ "requires": {
+ "arr-diff": "2.0.0",
+ "array-unique": "0.2.1",
+ "braces": "1.8.5",
+ "expand-brackets": "0.1.5",
+ "extglob": "0.3.2",
+ "filename-regex": "2.0.1",
+ "is-extglob": "1.0.0",
+ "is-glob": "2.0.1",
+ "kind-of": "3.2.2",
+ "normalize-path": "2.1.1",
+ "object.omit": "2.0.1",
+ "parse-glob": "3.0.4",
+ "regex-cache": "0.4.4"
+ }
+ },
+ "supports-color": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz",
+ "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=",
+ "dev": true,
+ "requires": {
+ "has-flag": "1.0.0"
+ }
+ },
+ "yargs": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz",
+ "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=",
+ "dev": true,
+ "requires": {
+ "camelcase": "3.0.0",
+ "cliui": "3.2.0",
+ "decamelize": "1.2.0",
+ "get-caller-file": "1.0.2",
+ "os-locale": "1.4.0",
+ "read-pkg-up": "1.0.1",
+ "require-directory": "2.1.1",
+ "require-main-filename": "1.0.1",
+ "set-blocking": "2.0.0",
+ "string-width": "1.0.2",
+ "which-module": "1.0.0",
+ "y18n": "3.2.1",
+ "yargs-parser": "4.2.1"
+ }
+ },
+ "yargs-parser": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz",
+ "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=",
+ "dev": true,
+ "requires": {
+ "camelcase": "3.0.0"
+ }
+ }
+ }
+ },
+ "webpack-sources": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.1.0.tgz",
+ "integrity": "sha512-aqYp18kPphgoO5c/+NaUvEeACtZjMESmDChuD3NBciVpah3XpMEU9VAAtIaB1BsfJWWTSdv8Vv1m3T0aRk2dUw==",
+ "dev": true,
+ "requires": {
+ "source-list-map": "2.0.0",
+ "source-map": "0.6.1"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "websocket-driver": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz",
+ "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=",
+ "dev": true,
+ "requires": {
+ "http-parser-js": "0.4.13",
+ "websocket-extensions": "0.1.3"
+ }
+ },
+ "websocket-extensions": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz",
+ "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==",
+ "dev": true
+ },
+ "whet.extend": {
+ "version": "0.9.9",
+ "resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz",
+ "integrity": "sha1-+HfVv2SMl+WqVC+twW1qJZucEaE=",
+ "dev": true
+ },
+ "which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "dev": true,
+ "requires": {
+ "isexe": "2.0.0"
+ }
+ },
+ "which-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz",
+ "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=",
+ "dev": true
+ },
+ "wide-align": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+ "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+ "dev": true,
+ "requires": {
+ "string-width": "1.0.2"
+ }
+ },
+ "window-size": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz",
+ "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=",
+ "dev": true
+ },
+ "wordwrap": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz",
+ "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=",
+ "dev": true
+ },
+ "wrap-ansi": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+ "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
+ "dev": true,
+ "requires": {
+ "string-width": "1.0.2",
+ "strip-ansi": "3.0.1"
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "ws": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz",
+ "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==",
+ "dev": true,
+ "requires": {
+ "options": "0.0.6",
+ "ultron": "1.0.2"
+ }
+ },
+ "wtf-8": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wtf-8/-/wtf-8-1.0.0.tgz",
+ "integrity": "sha1-OS2LotDxw00e4tYw8V0O+2jhBIo=",
+ "dev": true
+ },
+ "xmlhttprequest-ssl": {
+ "version": "1.5.3",
+ "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz",
+ "integrity": "sha1-GFqIjATspGw+QHDZn3tJ3jUomS0=",
+ "dev": true
+ },
+ "xtend": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
+ "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
+ "dev": true
+ },
+ "y18n": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
+ "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
+ "dev": true
+ },
+ "yallist": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
+ "dev": true
+ },
+ "yargs": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz",
+ "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=",
+ "dev": true,
+ "requires": {
+ "camelcase": "3.0.0",
+ "cliui": "3.2.0",
+ "decamelize": "1.2.0",
+ "get-caller-file": "1.0.2",
+ "os-locale": "1.4.0",
+ "read-pkg-up": "1.0.1",
+ "require-directory": "2.1.1",
+ "require-main-filename": "1.0.1",
+ "set-blocking": "2.0.0",
+ "string-width": "1.0.2",
+ "which-module": "1.0.0",
+ "y18n": "3.2.1",
+ "yargs-parser": "5.0.0"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
+ "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
+ "dev": true
+ }
+ }
+ },
+ "yargs-parser": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz",
+ "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=",
+ "dev": true,
+ "requires": {
+ "camelcase": "3.0.0"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
+ "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
+ "dev": true
+ }
+ }
+ },
+ "yeast": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
+ "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=",
+ "dev": true
+ }
+ }
+}
From 31d9098d66ba4ead704faf58bb506a6a90778ff4 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Fri, 15 Jun 2018 18:21:32 +0200
Subject: [PATCH 017/197] track: add type-checking methods
---
src/demuxer/track.ts | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/src/demuxer/track.ts b/src/demuxer/track.ts
index 00bc85f..c82e656 100644
--- a/src/demuxer/track.ts
+++ b/src/demuxer/track.ts
@@ -28,6 +28,24 @@ export class Track {
this.frames = [];
}
+ public isVideo() {
+ return this.type === Track.TYPE_VIDEO;
+ }
+
+ public isAudio() {
+ return this.type === Track.TYPE_AUDIO;
+ }
+
+ public isText() {
+ return this.type === Track.TYPE_TEXT;
+ }
+
+ public isOther() {
+ return this.type !== Track.TYPE_TEXT
+ && this.type !== Track.TYPE_AUDIO
+ && this.type !== Track.TYPE_VIDEO;
+ }
+
public getFrames(): Frame[] {
return this.frames;
}
From 71d5d1f819df67be1afc16c43040b73a8ab93c96 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Fri, 15 Jun 2018 18:22:03 +0200
Subject: [PATCH 018/197] mp4-track: add method to get relavant
audio/video-atom and resolution
---
src/demuxer/mp4/mp4-track.ts | 24 +++++++++++++++++++++++-
1 file changed, 23 insertions(+), 1 deletion(-)
diff --git a/src/demuxer/mp4/mp4-track.ts b/src/demuxer/mp4/mp4-track.ts
index 4f5c8e9..e5c95a4 100644
--- a/src/demuxer/mp4/mp4-track.ts
+++ b/src/demuxer/mp4/mp4-track.ts
@@ -1,10 +1,14 @@
import { Track } from '../track';
import { Atom } from './atoms/atom';
+import { AvcC } from './atoms/avcC'
import { Frame, MICROSECOND_TIMESCALE } from '../frame';
import { Sidx } from './atoms/sidx';
import { Trun, SampleFlags } from './atoms/trun';
import { Tfhd } from './atoms/tfhd';
+import { AudioAtom } from './atoms/helpers/audio-atom';
+import { VideoAtom } from './atoms/helpers/video-atom';
+import { Avc1 } from './atoms/avc1';
export type Mp4TrackDefaults = {
sampleDuration: number;
@@ -22,7 +26,12 @@ export class Mp4Track extends Track {
private defaultSampleFlagsParsed: SampleFlags;
private baseDataOffset: number = 0;
- constructor(id: number, type: string, mimeType: string, public referenceAtom: Atom, public dataOffset: number) {
+ constructor(id: number,
+ type: string, mimeType: string,
+ public referenceAtom: Atom,
+ public metadataAtom: AudioAtom | VideoAtom,
+ public dataOffset: number) {
+
super(id, type, mimeType);
this.lastPts = 0;
this.duration = 0;
@@ -32,6 +41,15 @@ export class Mp4Track extends Track {
}
}
+ // TODO: make this abstract on Track class
+ public getResolution(): [number, number] {
+ if (!this.isVideo()) {
+ throw new Error('Can not get resolution of non-video track');
+ }
+ const avc1 = this.metadataAtom as Avc1;
+ return [avc1.width, avc1.height];
+ }
+
public getSegmentIndex(): Sidx {
return this.sidx;
}
@@ -44,6 +62,10 @@ export class Mp4Track extends Track {
return this.referenceAtom;
}
+ public getMetadataAtom(): VideoAtom | AudioAtom {
+ return this.metadataAtom;
+ }
+
public getLastPts(): number {
return this.lastPts;
}
From 6f3cd3c06e6cbdf97712f770bafeddae079ebf1c Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Fri, 15 Jun 2018 18:22:49 +0200
Subject: [PATCH 019/197] mp4-demuxer: streamline parsing flow (create tracks
once all metadata available)
---
src/demuxer/mp4/mp4-demuxer.ts | 45 ++++++++++++++++++++++------------
1 file changed, 29 insertions(+), 16 deletions(-)
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index afca10e..8182c10 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -6,17 +6,23 @@ import { Track } from '../track';
import { Mp4Track } from './mp4-track';
import { Tkhd } from './atoms/tkhd';
import { Trun, SampleFlags } from './atoms/trun';
-import { IDemuxer } from '../demuxer';
+import { IDemuxer, TracksHash } from '../demuxer';
import { Frame } from '../frame';
+import { AudioAtom } from './atoms/helpers/audio-atom';
+import { VideoAtom } from './atoms/helpers/video-atom';
+import { isNullOrUndefined } from 'util';
export class Mp4Demuxer implements IDemuxer {
- public tracks: { [id: number] : Track; };
+ public tracks: TracksHash = {};
+ private data: Uint8Array = null;
+ private atoms: Atom[] = [];
- private data: Uint8Array;
- private atoms: Atom[];
+ // parsing stack
private lastTrackId: number;
private lastTrackDataOffset: number;
- private lastTrun: Trun;
+ private lastTrun: Trun = null;
+ private lastAudioVideoAtom: AudioAtom | VideoAtom = null;
+ private lastCodecDataAtom: Atom = null;
constructor() {
this.atoms = [];
@@ -101,42 +107,49 @@ export class Mp4Demuxer implements IDemuxer {
this.lastTrackId = (atom as Tkhd).trackId;
break;
+ case Atom.hvcC:
case Atom.avcC:
+ this.lastCodecDataAtom = atom;
+ break;
+
+ case Atom.avc1:
+ this.lastAudioVideoAtom = atom as AudioAtom | VideoAtom;
if (this.lastTrackId > 0) {
+ console.log('new video track')
this.tracks[this.lastTrackId] = new Mp4Track(
this.lastTrackId,
Track.TYPE_VIDEO,
Track.MIME_TYPE_AVC,
- atom,
+ this.lastCodecDataAtom,
+ this.lastAudioVideoAtom,
this.lastTrackDataOffset
- );
- //this.resetLastTrackInfos();
+ );
}
break;
-
- case Atom.hvcC:
+ case Atom.hev1:
+ this.lastAudioVideoAtom = atom as AudioAtom | VideoAtom;
if (this.lastTrackId > 0) {
this.tracks[this.lastTrackId] = new Mp4Track(
this.lastTrackId,
Track.TYPE_VIDEO,
Track.MIME_TYPE_HEVC,
- atom,
+ this.lastCodecDataAtom,
+ this.lastAudioVideoAtom,
this.lastTrackDataOffset
- );
- //this.resetLastTrackInfos();
+ );
}
break;
-
case Atom.mp4a:
+ this.lastAudioVideoAtom = atom as AudioAtom | VideoAtom;
if (this.lastTrackId > 0) {
this.tracks[this.lastTrackId] = new Mp4Track(
this.lastTrackId,
Track.TYPE_AUDIO,
Track.MIME_TYPE_AAC,
atom,
+ this.lastAudioVideoAtom,
this.lastTrackDataOffset
);
- //this.resetLastTrackInfos();
}
break;
@@ -179,9 +192,9 @@ export class Mp4Demuxer implements IDemuxer {
Track.TYPE_UNKNOWN,
Track.MIME_TYPE_UNKNOWN,
null,
+ null,
this.lastTrackDataOffset > 0 ? this.lastTrackDataOffset : 0
);
- //this.resetLastTrackInfos();
}
}
From d8605b6ccd686ee7dfa9a77764cf798545ee964b Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Sat, 16 Jun 2018 03:41:54 +0200
Subject: [PATCH 020/197] add data property to avcC
---
src/demuxer/mp4/atoms/avcC.ts | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/demuxer/mp4/atoms/avcC.ts b/src/demuxer/mp4/atoms/avcC.ts
index 9af1265..c289563 100644
--- a/src/demuxer/mp4/atoms/avcC.ts
+++ b/src/demuxer/mp4/atoms/avcC.ts
@@ -14,10 +14,12 @@ export class AvcC extends Atom {
public sps: Uint8Array[];
public spsParsed: Sps[];
public pps: Uint8Array[];
+ public data: Uint8Array;
public static parse(data: Uint8Array): Atom {
const avcC: AvcC = new AvcC(Atom.avcC, data.byteLength);
+ avcC.data = data;
avcC.version = data[0];
avcC.profile = data[1];
avcC.profileCompatibility = data[2];
From ae89deb4d85be79cd5d61978a7ec2251c6e9e81d Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 26 Jul 2018 03:38:22 +0200
Subject: [PATCH 021/197] export TSTrack
---
src/index.ts | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/index.ts b/src/index.ts
index 8f2a2e7..f022ba0 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -2,6 +2,8 @@ import { MpegTSDemuxer } from './demuxer/ts/mpegts-demuxer';
import { Mp4Demuxer } from './demuxer/mp4/mp4-demuxer';
import { WebMDemuxer } from './demuxer/webm/webm-demuxer';
+import { TSTrack } from './demuxer/ts/ts-track';
+
import { IDemuxer, TracksHash } from './demuxer/demuxer';
import { Track } from './demuxer/track';
import { Frame } from './demuxer/frame';
@@ -13,6 +15,7 @@ import { FrameRate, Size } from './codecs/video-types';
import { WebWorker } from './utils/web-worker';
export type MpegTSDemuxer = MpegTSDemuxer;
+export type TSTrack = TSTrack;
export type Mp4Demuxer = Mp4Demuxer;
export type WebMDemuxer = WebMDemuxer;
export type IDemuxer = IDemuxer;
From af654b2169138e7c280f025e15510a91d5c6e7e5 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 26 Jul 2018 03:38:33 +0200
Subject: [PATCH 022/197] avcC: unused import
---
src/demuxer/mp4/atoms/avcC.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/demuxer/mp4/atoms/avcC.ts b/src/demuxer/mp4/atoms/avcC.ts
index c289563..3f02c5c 100644
--- a/src/demuxer/mp4/atoms/avcC.ts
+++ b/src/demuxer/mp4/atoms/avcC.ts
@@ -1,5 +1,5 @@
import ByteParserUtils from '../../../utils/byte-parser-utils';
-import { Atom, ContainerAtom } from './atom';
+import { Atom } from './atom';
import { SPSParser } from '../../../codecs/h264/sps-parser';
import { Sps } from '../../../codecs/h264/nal-units';
From 95186df03d873f8c9b438dfe6ba3cda9889ccfff Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 26 Jul 2018 03:39:17 +0200
Subject: [PATCH 023/197] ts demuxer: use TSTrack class
---
src/demuxer/ts/mpegts-demuxer.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index a0fe156..b98d0a9 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -14,7 +14,7 @@ export class MpegTSDemuxer implements IDemuxer {
private static MPEGTS_SYNC: number = 0x47;
private static MPEGTS_PACKET_SIZE: number = 187;
- public tracks: { [id: number] : Track; };
+ public tracks: { [id: number] : TSTrack; };
private data: Uint8Array;
private dataOffset: number;
From 8eaa9743468e91c1346270b977483eb3344fd0a2 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 26 Jul 2018 03:39:32 +0200
Subject: [PATCH 024/197] ts demuxer: add RAW_MPEG_AUDIO
---
src/demuxer/ts/mpegts-demuxer.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index b98d0a9..a584b61 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -8,6 +8,7 @@ enum CONTAINER_TYPE {
UNKNOWN = 1,
MPEG_TS,
RAW_AAC,
+ RAW_MPEG_AUDIO
}
export class MpegTSDemuxer implements IDemuxer {
From 1af02d0c9c37065fe340d009cd6c2e12a73d0c84 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 26 Jul 2018 03:39:47 +0200
Subject: [PATCH 025/197] ts demuxer: add comment
---
src/demuxer/ts/mpegts-demuxer.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index a584b61..3d5a56c 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -70,7 +70,7 @@ export class MpegTSDemuxer implements IDemuxer {
if (this.containerType === CONTAINER_TYPE.MPEG_TS) {
this.readHeader();
this.readSamples();
- } else {
+ } else { // FIXME: support raw mpeg audio
const dataParser: BitReader = new BitReader(this.data);
this.tracks[0] = new TSTrack(0, Track.TYPE_AUDIO, Track.MIME_TYPE_AAC,
new PESReader(0, PESReader.TS_STREAM_TYPE_AAC));
From 9b21e3c341ce6a837a95a04232795f15736bba7f Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 26 Jul 2018 03:40:07 +0200
Subject: [PATCH 026/197] ts demuxer: use const insted of let
---
src/demuxer/ts/mpegts-demuxer.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index 3d5a56c..d08131f 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -154,7 +154,7 @@ export class MpegTSDemuxer implements IDemuxer {
private processTSPacket(packet: Uint8Array): void {
this.packetsCount++;
- let packetParser: BitReader = new BitReader(packet);
+ const packetParser: BitReader = new BitReader(packet);
packetParser.skipBits(1);
const payloadUnitStartIndicator: boolean = (packetParser.readBits(1) !== 0);
From ea1053e3b50738612e764af717c40030d720c92c Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 26 Jul 2018 03:40:27 +0200
Subject: [PATCH 027/197] bit-reader: fix syntax
---
src/utils/bit-reader.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/utils/bit-reader.ts b/src/utils/bit-reader.ts
index edac63f..1b97aa3 100644
--- a/src/utils/bit-reader.ts
+++ b/src/utils/bit-reader.ts
@@ -75,7 +75,7 @@ export class BitReader {
return this.readBits(1) === 1;
}
- public readByte = function(): number {
+ public readByte(): number {
return this.readBits(8);
};
From 8f5ddfd4bea0beae1ca8c0cc31dd48a35a6e2174 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 18 Oct 2018 15:13:51 +0200
Subject: [PATCH 028/197] mp4-track: progress on plain MOV support
---
src/demuxer/mp4/mp4-demuxer.ts | 23 +++++++++++++++--------
src/demuxer/mp4/mp4-track.ts | 19 ++++++++++---------
2 files changed, 25 insertions(+), 17 deletions(-)
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index 8182c10..f92f274 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -5,12 +5,12 @@ import { Tfhd } from './atoms/tfhd';
import { Track } from '../track';
import { Mp4Track } from './mp4-track';
import { Tkhd } from './atoms/tkhd';
-import { Trun, SampleFlags } from './atoms/trun';
+import { Trun } from './atoms/trun';
import { IDemuxer, TracksHash } from '../demuxer';
-import { Frame } from '../frame';
import { AudioAtom } from './atoms/helpers/audio-atom';
import { VideoAtom } from './atoms/helpers/video-atom';
-import { isNullOrUndefined } from 'util';
+import { AvcC } from './atoms/avcC';
+import { Hev1 } from './atoms/hev1';
export class Mp4Demuxer implements IDemuxer {
public tracks: TracksHash = {};
@@ -20,9 +20,8 @@ export class Mp4Demuxer implements IDemuxer {
// parsing stack
private lastTrackId: number;
private lastTrackDataOffset: number;
- private lastTrun: Trun = null;
private lastAudioVideoAtom: AudioAtom | VideoAtom = null;
- private lastCodecDataAtom: Atom = null;
+ private lastCodecDataAtom: AvcC | Hev1 = null;
constructor() {
this.atoms = [];
@@ -100,6 +99,7 @@ export class Mp4Demuxer implements IDemuxer {
case Atom.ftyp:
case Atom.moov:
case Atom.moof:
+ case Atom.trak:
this.lastTrackDataOffset = dataOffset;
break;
@@ -108,14 +108,17 @@ export class Mp4Demuxer implements IDemuxer {
break;
case Atom.hvcC:
+ this.lastCodecDataAtom = atom as Hev1;
+ break;
case Atom.avcC:
- this.lastCodecDataAtom = atom;
+ this.lastCodecDataAtom = atom as AvcC;
break;
+ // H264
case Atom.avc1:
- this.lastAudioVideoAtom = atom as AudioAtom | VideoAtom;
+ this.lastAudioVideoAtom = atom as (AudioAtom | VideoAtom);
if (this.lastTrackId > 0) {
- console.log('new video track')
+ console.log('new video track', this.lastCodecDataAtom)
this.tracks[this.lastTrackId] = new Mp4Track(
this.lastTrackId,
Track.TYPE_VIDEO,
@@ -126,6 +129,7 @@ export class Mp4Demuxer implements IDemuxer {
);
}
break;
+ // H265
case Atom.hev1:
this.lastAudioVideoAtom = atom as AudioAtom | VideoAtom;
if (this.lastTrackId > 0) {
@@ -139,9 +143,11 @@ export class Mp4Demuxer implements IDemuxer {
);
}
break;
+ // AAC
case Atom.mp4a:
this.lastAudioVideoAtom = atom as AudioAtom | VideoAtom;
if (this.lastTrackId > 0) {
+ console.log('new audio track')
this.tracks[this.lastTrackId] = new Mp4Track(
this.lastTrackId,
Track.TYPE_AUDIO,
@@ -175,6 +181,7 @@ export class Mp4Demuxer implements IDemuxer {
break;
case Atom.mdat:
+ this.ensureTrack();
this.getCurrentTrack().updateInitialSampleDataOffset(this.lastTrackDataOffset);
this.getCurrentTrack().readTrunAtoms();
break;
diff --git a/src/demuxer/mp4/mp4-track.ts b/src/demuxer/mp4/mp4-track.ts
index e5c95a4..27f0a39 100644
--- a/src/demuxer/mp4/mp4-track.ts
+++ b/src/demuxer/mp4/mp4-track.ts
@@ -1,11 +1,9 @@
import { Track } from '../track';
import { Atom } from './atoms/atom';
-import { AvcC } from './atoms/avcC'
import { Frame, MICROSECOND_TIMESCALE } from '../frame';
import { Sidx } from './atoms/sidx';
import { Trun, SampleFlags } from './atoms/trun';
-import { Tfhd } from './atoms/tfhd';
import { AudioAtom } from './atoms/helpers/audio-atom';
import { VideoAtom } from './atoms/helpers/video-atom';
import { Avc1 } from './atoms/avc1';
@@ -20,17 +18,20 @@ export class Mp4Track extends Track {
private sidx: Sidx = null;
private trunInfo: Trun[] = [];
private trunInfoReadIndex: number = 0;
- private lastPts: number;
- private timescale: number;
- private defaults: Mp4TrackDefaults;
- private defaultSampleFlagsParsed: SampleFlags;
+ private lastPts: number = null;
+ private timescale: number = null;
+ private defaults: Mp4TrackDefaults = null;
+ private defaultSampleFlagsParsed: SampleFlags = null;
private baseDataOffset: number = 0;
- constructor(id: number,
- type: string, mimeType: string,
+ constructor(
+ id: number,
+ type: string,
+ mimeType: string,
public referenceAtom: Atom,
public metadataAtom: AudioAtom | VideoAtom,
- public dataOffset: number) {
+ public dataOffset: number
+ ) {
super(id, type, mimeType);
this.lastPts = 0;
From 3e7dd80b9bfe366ec2bae6f88398e52e9eb49381 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 18 Oct 2018 17:05:52 +0200
Subject: [PATCH 029/197] add ctts and stss boxes
---
src/demuxer/mp4/atoms/atom.ts | 3 +--
src/demuxer/mp4/atoms/ctts.ts | 29 +++++++++++++++++++++++++++++
src/demuxer/mp4/atoms/index.ts | 6 ++++++
src/demuxer/mp4/atoms/stss.ts | 23 +++++++++++++++++++++++
4 files changed, 59 insertions(+), 2 deletions(-)
create mode 100644 src/demuxer/mp4/atoms/ctts.ts
create mode 100644 src/demuxer/mp4/atoms/stss.ts
diff --git a/src/demuxer/mp4/atoms/atom.ts b/src/demuxer/mp4/atoms/atom.ts
index ea193b1..97147e9 100644
--- a/src/demuxer/mp4/atoms/atom.ts
+++ b/src/demuxer/mp4/atoms/atom.ts
@@ -80,8 +80,7 @@ export class Atom {
public static mfhd: string = 'mfhd';
public static emsg: string = 'emsg';
- constructor (public type: string, public size: number) {
- }
+ constructor (public type: string, public size: number) {}
public static isContainerBox(type: string): boolean {
return type === Atom.moov || type === Atom.trak || type === Atom.mdia
diff --git a/src/demuxer/mp4/atoms/ctts.ts b/src/demuxer/mp4/atoms/ctts.ts
new file mode 100644
index 0000000..c428e0b
--- /dev/null
+++ b/src/demuxer/mp4/atoms/ctts.ts
@@ -0,0 +1,29 @@
+import ByteParserUtils from '../../../utils/byte-parser-utils';
+import { Atom } from './atom';
+
+export class CTimeOffsetToSampleEntry {
+ constructor(public sampleCount: number, public sampleCTimeOffset: number) {
+ }
+}
+
+export class Ctts extends Atom {
+ public version: number;
+ public flags: Uint8Array;
+ public cTimeOffsetToSamples: CTimeOffsetToSampleEntry[] = [];
+
+ public static parse(data: Uint8Array): Atom {
+ const ctts: Ctts = new Ctts(Atom.ctts, data.byteLength);
+ ctts.version = data[0];
+ ctts.flags = data.subarray(1, 4);
+ ctts.cTimeOffsetToSamples = [];
+ const entryCount: number = ByteParserUtils.parseUint32(data, 4);
+ let offset: number = 8;
+ for (let i: number = 0; i < entryCount; i++) {
+ ctts.cTimeOffsetToSamples.push(new CTimeOffsetToSampleEntry(
+ ByteParserUtils.parseUint32(data, offset),
+ ByteParserUtils.parseUint32(data, offset + 4)));
+ offset += 8;
+ }
+ return ctts;
+ }
+}
diff --git a/src/demuxer/mp4/atoms/index.ts b/src/demuxer/mp4/atoms/index.ts
index e6a075a..41226b9 100644
--- a/src/demuxer/mp4/atoms/index.ts
+++ b/src/demuxer/mp4/atoms/index.ts
@@ -29,9 +29,13 @@ import { Pssh } from './pssh';
import { HvcC } from './hvcC';
import { Hvc1 } from './hvc1';
import { Hev1 } from './hev1';
+import { Stss } from './stss';
+import { Ctts } from './ctts';
export const boxesParsers: {[type: string] : (data: Uint8Array) => Atom } = { };
+// Q: can this generalized or abstracted as opposed to explicit registration?
+
boxesParsers[Atom.ftyp] = Ftyp.parse;
boxesParsers[Atom.stsd] = Stsd.parse;
boxesParsers[Atom.avc1] = Avc1.parse;
@@ -44,8 +48,10 @@ boxesParsers[Atom.hdlr] = Hdlr.parse;
boxesParsers[Atom.vmhd] = Vmhd.parse;
boxesParsers[Atom.dref] = Dref.parse;
boxesParsers[Atom.stts] = Stts.parse;
+boxesParsers[Atom.stss] = Stss.parse;
boxesParsers[Atom.stsc] = Stsc.parse;
boxesParsers[Atom.stsz] = Stsz.parse;
+boxesParsers[Atom.ctts] = Ctts.parse;
boxesParsers[Atom.stco] = Stco.parse;
boxesParsers[Atom.smhd] = Smhd.parse;
boxesParsers[Atom.mp4a] = Mp4a.parse;
diff --git a/src/demuxer/mp4/atoms/stss.ts b/src/demuxer/mp4/atoms/stss.ts
new file mode 100644
index 0000000..9446500
--- /dev/null
+++ b/src/demuxer/mp4/atoms/stss.ts
@@ -0,0 +1,23 @@
+import { Atom } from "./atom";
+import ByteParserUtils from "../../../utils/byte-parser-utils";
+
+export class Stss extends Atom {
+
+ public version: number;
+ public flags: Uint8Array;
+ public syncSampleNumbers: number[] = [];
+
+ public static parse(data: Uint8Array): Atom {
+ const stss: Stss = new Stss(Atom.stts, data.byteLength);
+ stss.version = data[0];
+ stss.flags = data.subarray(1, 4);
+ const entryCount: number = ByteParserUtils.parseUint32(data, 4);
+
+ let offset: number = 8;
+ for (let i: number = 0; i < entryCount; i++) {
+ stss.syncSampleNumbers.push(ByteParserUtils.parseUint32(data, offset));
+ offset += 8;
+ }
+ return stss;
+ }
+}
From dd7fc1707431c0319d1928a3f6f217849861b532 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 18 Oct 2018 17:06:24 +0200
Subject: [PATCH 030/197] add logger module
---
src/utils/logger.ts | 60 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
create mode 100644 src/utils/logger.ts
diff --git a/src/utils/logger.ts b/src/utils/logger.ts
new file mode 100644
index 0000000..fe07ad8
--- /dev/null
+++ b/src/utils/logger.ts
@@ -0,0 +1,60 @@
+// TODO: Move to Objec-TS long-term
+
+const PREFIX_ROOT = '1nspect0r.js'
+
+const noop = () => {};
+
+const getPrefix = function(type: string, category: string): string {
+ const prefix = `[${PREFIX_ROOT}]:[${type}]:[${category}] >`
+ return prefix
+}
+
+export function checkLogLevel(level: number, catLevel: number) {
+ switch(catLevel) {
+ case LoggerLevels.INFO: return (level >= LoggerLevels.INFO) && console.info
+ case LoggerLevels.LOG: return (level >= LoggerLevels.LOG) && console.log
+ case LoggerLevels.DEBUG: return (level >= LoggerLevels.DEBUG) && console.debug
+ case LoggerLevels.WARN: return (level >= LoggerLevels.WARN) && console.warn
+ case LoggerLevels.ERROR: return (level >= LoggerLevels.ERROR) && console.error
+ }
+}
+
+export type LoggerFunc = (...args: any[]) => void
+
+export type Logger = {
+ info: LoggerFunc,
+ log: LoggerFunc
+ debug: LoggerFunc
+ warn: LoggerFunc
+ error: LoggerFunc
+}
+
+export enum LoggerLevels {
+ ON = Infinity,
+ LOG = 5,
+ INFO = 4,
+ DEBUG = 3,
+ WARN = 2,
+ ERROR = 1,
+ OFF = 0
+}
+
+export const getLogger = function(category: string, level: number = LoggerLevels.ON): Logger {
+ var window = self; // Needed for WebWorker compat
+
+ return {
+ info: checkLogLevel(level, LoggerLevels.INFO) ? console.info.bind(window['console'], getPrefix('i', category)) : noop,
+ log: checkLogLevel(level, LoggerLevels.LOG) ? console.log.bind(window['console'], getPrefix('l', category)) : noop,
+ debug: checkLogLevel(level, LoggerLevels.DEBUG) ? console.debug.bind(window['console'], getPrefix('d', category)) : noop,
+ warn: checkLogLevel(level, LoggerLevels.WARN) ? console.warn.bind(window['console'], getPrefix('w', category)) : noop,
+ error: checkLogLevel(level, LoggerLevels.ERROR) ? console.error.bind(window['console'], getPrefix('e', category)) : noop
+ }
+}
+
+export function makeLogTimestamped(...args): string {
+ let message = `[${(new Date()).toISOString()}]`
+ args.forEach((arg) => {
+ message += ' ' + arg
+ })
+ return message
+}
From 48dd5a11354c98211be88e3e7ba7db7381e62571 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 18 Oct 2018 17:07:25 +0200
Subject: [PATCH 031/197] add support for sample-table (mov) parsing (as
opposed to moof/trun) + ensure to process boxes in hiearchical order
---
src/demuxer/mp4/mp4-demuxer.ts | 130 +++++++++++++++++++++------------
src/demuxer/mp4/mp4-track.ts | 13 +++-
2 files changed, 94 insertions(+), 49 deletions(-)
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index f92f274..f3cd7a3 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -1,20 +1,43 @@
import ByteParserUtils from '../../utils/byte-parser-utils';
-import { boxesParsers } from './atoms';
-import { Atom, ContainerAtom } from './atoms/atom';
-import { Tfhd } from './atoms/tfhd';
+
import { Track } from '../track';
import { Mp4Track } from './mp4-track';
-import { Tkhd } from './atoms/tkhd';
-import { Trun } from './atoms/trun';
+
import { IDemuxer, TracksHash } from '../demuxer';
+
import { AudioAtom } from './atoms/helpers/audio-atom';
import { VideoAtom } from './atoms/helpers/video-atom';
+
+import { boxesParsers } from './atoms';
+import { Atom, ContainerAtom } from './atoms/atom';
+
+import { Tfhd } from './atoms/tfhd';
+import { Tkhd } from './atoms/tkhd';
import { AvcC } from './atoms/avcC';
import { Hev1 } from './atoms/hev1';
+import { Stts } from './atoms/stts';
+import { Stsc } from './atoms/stsc';
+import { Stsz } from './atoms/stsz';
+import { Ctts } from './atoms/ctts';
+
+import {getLogger} from '../../utils/logger';
+import { Stss } from './atoms/stss';
+import { Stco } from './atoms/stco';
+
+const {log, warn} = getLogger('Mp4Demuxer');
+
+export class Mp4DemuxerSampleTable {
+ decodingTimestamps: Stts;
+ syncSamples: Stss;
+ compositionTimestampOffsets: Ctts;
+ chunks: Stsc;
+ sampleSizes: Stsz;
+ chunkOffsets: Stco
+};
export class Mp4Demuxer implements IDemuxer {
public tracks: TracksHash = {};
- private data: Uint8Array = null;
+
private atoms: Atom[] = [];
// parsing stack
@@ -22,6 +45,7 @@ export class Mp4Demuxer implements IDemuxer {
private lastTrackDataOffset: number;
private lastAudioVideoAtom: AudioAtom | VideoAtom = null;
private lastCodecDataAtom: AvcC | Hev1 = null;
+ private lastSampleTable: Mp4DemuxerSampleTable = null;
constructor() {
this.atoms = [];
@@ -53,9 +77,8 @@ export class Mp4Demuxer implements IDemuxer {
private parseAtoms(data: Uint8Array, offset: number = 0): Atom[] {
const atoms: Atom[] = [];
- this.data = data;
- let dataOffset: number = offset;
+ let dataOffset: number = offset;
while (dataOffset < data.byteLength) {
const size: number = ByteParserUtils.parseUint32(data, dataOffset);
const type: string = ByteParserUtils.parseIsoBoxType(data, dataOffset + 4);
@@ -78,11 +101,14 @@ export class Mp4Demuxer implements IDemuxer {
}
}
+ atoms.push(atom);
+
+ this.processAtom(atom, dataOffset);
+
if (atom instanceof ContainerAtom) {
(atom as ContainerAtom).atoms = this.parseAtoms(boxData, (atom as ContainerAtom).containerDataOffset);
}
- atoms.push(atom);
- this.processAtom(atom, dataOffset);
+
dataOffset = end;
}
return atoms;
@@ -96,10 +122,11 @@ export class Mp4Demuxer implements IDemuxer {
// FIXME: much of this isn't going to work for plain old unfrag'd MP4 and MOV :)
+ case Atom.trak:
+ this.lastSampleTable = null;
case Atom.ftyp:
case Atom.moov:
case Atom.moof:
- case Atom.trak:
this.lastTrackDataOffset = dataOffset;
break;
@@ -109,54 +136,27 @@ export class Mp4Demuxer implements IDemuxer {
case Atom.hvcC:
this.lastCodecDataAtom = atom as Hev1;
+ this._attemptCreateTrack(Track.TYPE_VIDEO, Track.MIME_TYPE_HEVC, atom);
break;
case Atom.avcC:
this.lastCodecDataAtom = atom as AvcC;
+ this._attemptCreateTrack(Track.TYPE_VIDEO, Track.MIME_TYPE_AVC, atom);
break;
// H264
case Atom.avc1:
this.lastAudioVideoAtom = atom as (AudioAtom | VideoAtom);
- if (this.lastTrackId > 0) {
- console.log('new video track', this.lastCodecDataAtom)
- this.tracks[this.lastTrackId] = new Mp4Track(
- this.lastTrackId,
- Track.TYPE_VIDEO,
- Track.MIME_TYPE_AVC,
- this.lastCodecDataAtom,
- this.lastAudioVideoAtom,
- this.lastTrackDataOffset
- );
- }
break;
+
// H265
case Atom.hev1:
this.lastAudioVideoAtom = atom as AudioAtom | VideoAtom;
- if (this.lastTrackId > 0) {
- this.tracks[this.lastTrackId] = new Mp4Track(
- this.lastTrackId,
- Track.TYPE_VIDEO,
- Track.MIME_TYPE_HEVC,
- this.lastCodecDataAtom,
- this.lastAudioVideoAtom,
- this.lastTrackDataOffset
- );
- }
break;
+
// AAC
case Atom.mp4a:
this.lastAudioVideoAtom = atom as AudioAtom | VideoAtom;
- if (this.lastTrackId > 0) {
- console.log('new audio track')
- this.tracks[this.lastTrackId] = new Mp4Track(
- this.lastTrackId,
- Track.TYPE_AUDIO,
- Track.MIME_TYPE_AAC,
- atom,
- this.lastAudioVideoAtom,
- this.lastTrackDataOffset
- );
- }
+ this._attemptCreateTrack(Track.TYPE_AUDIO, Track.MIME_TYPE_AAC, atom);
break;
case Atom.sidx:
@@ -181,10 +181,47 @@ export class Mp4Demuxer implements IDemuxer {
break;
case Atom.mdat:
- this.ensureTrack();
- this.getCurrentTrack().updateInitialSampleDataOffset(this.lastTrackDataOffset);
- this.getCurrentTrack().readTrunAtoms();
+ // in plain old MOV the moov may be at the end of the file (and mdat before)
+ if (this.getCurrentTrack()) {
+ this.getCurrentTrack().updateInitialSampleDataOffset(this.lastTrackDataOffset);
+ this.getCurrentTrack().readTrunAtoms();
+ }
+ break;
+
+ case Atom.stbl:
+ if (this.lastSampleTable !== null) {
+ throw new Error('Sample-table already existing, but should be null');
+ }
+ this.lastSampleTable = new Mp4DemuxerSampleTable();
+ break;
+ case Atom.stts:
+ this.lastSampleTable.decodingTimestamps = atom as Stts;
break;
+ case Atom.stss:
+ break;
+ case Atom.ctts:
+ break;
+ case Atom.stsc:
+ break;
+ case Atom.stsz:
+ break;
+ case Atom.stco:
+ break;
+ }
+
+ }
+
+ private _attemptCreateTrack(type: string, mime: string, ref: Atom) {
+ if (this.lastTrackId > 0) {
+ log('creating new track:', type, mime)
+ this.tracks[this.lastTrackId] = new Mp4Track(
+ this.lastTrackId,
+ type,
+ mime,
+ ref,
+ this.lastAudioVideoAtom,
+ this.lastTrackDataOffset
+ );
}
}
@@ -193,6 +230,7 @@ export class Mp4Demuxer implements IDemuxer {
*/
private ensureTrack(): void {
if (!this.lastTrackId || !this.tracks[this.lastTrackId]) {
+ warn('creating unknown-typed track');
this.lastTrackId = 1;
this.tracks[this.lastTrackId] = new Mp4Track(
this.lastTrackId,
diff --git a/src/demuxer/mp4/mp4-track.ts b/src/demuxer/mp4/mp4-track.ts
index 27f0a39..d344c4d 100644
--- a/src/demuxer/mp4/mp4-track.ts
+++ b/src/demuxer/mp4/mp4-track.ts
@@ -1,13 +1,20 @@
import { Track } from '../track';
-import { Atom } from './atoms/atom';
+
import { Frame, MICROSECOND_TIMESCALE } from '../frame';
-import { Sidx } from './atoms/sidx';
-import { Trun, SampleFlags } from './atoms/trun';
+import { Atom } from './atoms/atom';
+
import { AudioAtom } from './atoms/helpers/audio-atom';
import { VideoAtom } from './atoms/helpers/video-atom';
+
+import { Sidx } from './atoms/sidx';
+import { Trun, SampleFlags } from './atoms/trun';
import { Avc1 } from './atoms/avc1';
+import {getLogger} from '../../utils/logger';
+
+const {log, warn} = getLogger('Mp4Track');
+
export type Mp4TrackDefaults = {
sampleDuration: number;
sampleSize: number;
From 18025bfd6870a485b103f278cd604517a1fc678a Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 18 Oct 2018 17:07:59 +0200
Subject: [PATCH 032/197] mp4 demuxer logs and cosmetics
---
src/demuxer/mp4/atoms/mdat.ts | 5 ++++-
src/demuxer/mp4/atoms/stts.ts | 2 +-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/demuxer/mp4/atoms/mdat.ts b/src/demuxer/mp4/atoms/mdat.ts
index 6c80733..4bf3e3a 100644
--- a/src/demuxer/mp4/atoms/mdat.ts
+++ b/src/demuxer/mp4/atoms/mdat.ts
@@ -1,5 +1,8 @@
import ByteParserUtils from '../../../utils/byte-parser-utils';
import { Atom } from './atom';
+import {getLogger} from '../../../utils/logger';
+
+const {log, warn} = getLogger('Mdat');
export class Mdat extends Atom {
@@ -20,7 +23,7 @@ export class Mdat extends Atom {
//continue;
// let's break here since otherwise this crashes on AAC data
- console.warn('aborted parsing mdat');
+ warn('aborted parsing mdat');
break;
}
diff --git a/src/demuxer/mp4/atoms/stts.ts b/src/demuxer/mp4/atoms/stts.ts
index 155bfa1..e6ad7bf 100644
--- a/src/demuxer/mp4/atoms/stts.ts
+++ b/src/demuxer/mp4/atoms/stts.ts
@@ -9,7 +9,7 @@ export class TimeToSampleEntry {
export class Stts extends Atom {
public version: number;
public flags: Uint8Array;
- public timeToSamples: TimeToSampleEntry[];
+ public timeToSamples: TimeToSampleEntry[] = [];
public static parse(data: Uint8Array): Atom {
const stts: Stts = new Stts(Atom.stts, data.byteLength);
From 7f0d0ec82389f5d00275329696a37870f423de14 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 18 Oct 2018 17:55:36 +0200
Subject: [PATCH 033/197] support processing sample table
---
src/demuxer/mp4/mp4-demuxer.ts | 77 +++++++++++++++++++++++-----------
src/demuxer/mp4/mp4-track.ts | 14 ++++---
2 files changed, 61 insertions(+), 30 deletions(-)
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index f3cd7a3..19a7a56 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -33,6 +33,16 @@ export class Mp4DemuxerSampleTable {
chunks: Stsc;
sampleSizes: Stsz;
chunkOffsets: Stco
+
+ constructor(private _track: Mp4Track) {
+ if (!_track) {
+ throw new Error('Sample-table can not be created without a Track');
+ }
+ }
+
+ digest() {
+
+ }
};
export class Mp4Demuxer implements IDemuxer {
@@ -51,7 +61,7 @@ export class Mp4Demuxer implements IDemuxer {
this.atoms = [];
this.tracks = {};
- this.resetLastTrackInfos();
+ this._resetLastTrackInfos();
}
public getAtoms(): Atom[] {
@@ -59,15 +69,15 @@ export class Mp4Demuxer implements IDemuxer {
}
public append(data: Uint8Array): void {
- this.atoms = this.parseAtoms(data);
- this.updateTracks();
+ this.atoms = this._parseAtoms(data);
+ this._updateTracks();
}
public end(): void {
- this.updateTracks();
+ this._updateTracks();
}
- private updateTracks(): void {
+ private _updateTracks(): void {
for (const trackId in this.tracks) {
if (this.tracks.hasOwnProperty(trackId)) {
this.tracks[trackId].update();
@@ -75,7 +85,7 @@ export class Mp4Demuxer implements IDemuxer {
}
}
- private parseAtoms(data: Uint8Array, offset: number = 0): Atom[] {
+ private _parseAtoms(data: Uint8Array, offset: number = 0): Atom[] {
const atoms: Atom[] = [];
let dataOffset: number = offset;
@@ -103,10 +113,10 @@ export class Mp4Demuxer implements IDemuxer {
atoms.push(atom);
- this.processAtom(atom, dataOffset);
+ this._processAtom(atom, dataOffset);
if (atom instanceof ContainerAtom) {
- (atom as ContainerAtom).atoms = this.parseAtoms(boxData, (atom as ContainerAtom).containerDataOffset);
+ (atom as ContainerAtom).atoms = this._parseAtoms(boxData, (atom as ContainerAtom).containerDataOffset);
}
dataOffset = end;
@@ -114,7 +124,7 @@ export class Mp4Demuxer implements IDemuxer {
return atoms;
}
- private processAtom(atom: Atom, dataOffset: number): void {
+ private _processAtom(atom: Atom, dataOffset: number): void {
switch (atom.type) {
// FIXME !!! `trex` box can contain super based set of default sample-duration/flags/size ...
@@ -138,6 +148,7 @@ export class Mp4Demuxer implements IDemuxer {
this.lastCodecDataAtom = atom as Hev1;
this._attemptCreateTrack(Track.TYPE_VIDEO, Track.MIME_TYPE_HEVC, atom);
break;
+
case Atom.avcC:
this.lastCodecDataAtom = atom as AvcC;
this._attemptCreateTrack(Track.TYPE_VIDEO, Track.MIME_TYPE_AVC, atom);
@@ -160,15 +171,15 @@ export class Mp4Demuxer implements IDemuxer {
break;
case Atom.sidx:
- this.ensureTrack();
- this.getCurrentTrack().setSidxAtom(atom);
+ this._attemptCreateUnknownTrack();
+ this._getLastTrackCreated().setSidxAtom(atom);
break;
case Atom.tfhd:
- this.ensureTrack();
+ this._attemptCreateUnknownTrack();
const tfhd: Tfhd = ( atom);
- this.getCurrentTrack().setBaseDataOffset(tfhd.baseDataOffset);
- this.getCurrentTrack().setDefaults({
+ this._getLastTrackCreated().setBaseDataOffset(tfhd.baseDataOffset);
+ this._getLastTrackCreated().setDefaults({
sampleDuration: tfhd.defaultSampleDuration,
sampleFlags: tfhd.defaultSampleFlags,
sampleSize: tfhd.defaultSampleSize
@@ -176,39 +187,55 @@ export class Mp4Demuxer implements IDemuxer {
break;
case Atom.trun:
- this.ensureTrack();
- this.getCurrentTrack().addTrunAtom(atom);
+ this._attemptCreateUnknownTrack();
+ this._getLastTrackCreated().addTrunAtom(atom);
break;
case Atom.mdat:
// in plain old MOV the moov may be at the end of the file (and mdat before)
- if (this.getCurrentTrack()) {
- this.getCurrentTrack().updateInitialSampleDataOffset(this.lastTrackDataOffset);
- this.getCurrentTrack().readTrunAtoms();
+ if (this._getLastTrackCreated()) {
+ this._getLastTrackCreated().updateInitialSampleDataOffset(this.lastTrackDataOffset);
+ this._getLastTrackCreated().processTrunAtoms();
}
break;
case Atom.stbl:
if (this.lastSampleTable !== null) {
- throw new Error('Sample-table already existing, but should be null');
+ throw new Error('Sample-table should not exist yet');
}
- this.lastSampleTable = new Mp4DemuxerSampleTable();
break;
case Atom.stts:
+ this._haveSampleTable();
this.lastSampleTable.decodingTimestamps = atom as Stts;
break;
case Atom.stss:
+ this._haveSampleTable();
+ this.lastSampleTable.syncSamples = atom as Stss;
break;
case Atom.ctts:
+ this._haveSampleTable();
+ this.lastSampleTable.compositionTimestampOffsets = atom as Ctts;
break;
case Atom.stsc:
+ this._haveSampleTable();
+ this.lastSampleTable.chunks = atom as Stsc;
break;
case Atom.stsz:
+ this._haveSampleTable();
+ this.lastSampleTable.sampleSizes = atom as Stsz;
break;
case Atom.stco:
+ this._haveSampleTable();
+ this.lastSampleTable.chunkOffsets = atom as Stco;
break;
}
+ }
+ private _haveSampleTable() {
+ if (this.lastSampleTable) {
+ return;
+ }
+ this.lastSampleTable = new Mp4DemuxerSampleTable(this._getLastTrackCreated());
}
private _attemptCreateTrack(type: string, mime: string, ref: Atom) {
@@ -228,7 +255,7 @@ export class Mp4Demuxer implements IDemuxer {
/**
* Creates a track in case we haven't found a codec box
*/
- private ensureTrack(): void {
+ private _attemptCreateUnknownTrack(): void {
if (!this.lastTrackId || !this.tracks[this.lastTrackId]) {
warn('creating unknown-typed track');
this.lastTrackId = 1;
@@ -246,12 +273,12 @@ export class Mp4Demuxer implements IDemuxer {
/**
* should be called everytime we create a track
*/
- private resetLastTrackInfos() {
+ private _resetLastTrackInfos() {
this.lastTrackId = 0;
this.lastTrackDataOffset = -1;
}
- private getCurrentTrack(): Mp4Track {
- return (this.tracks[this.lastTrackId] as Mp4Track);
+ private _getLastTrackCreated(): Mp4Track {
+ return (this.tracks[this.lastTrackId] as Mp4Track) || null;
}
}
diff --git a/src/demuxer/mp4/mp4-track.ts b/src/demuxer/mp4/mp4-track.ts
index d344c4d..a24c450 100644
--- a/src/demuxer/mp4/mp4-track.ts
+++ b/src/demuxer/mp4/mp4-track.ts
@@ -135,13 +135,20 @@ export class Mp4Track extends Track {
this.timescale = this.sidx.timescale;
}
+ public appendFrame(frame: Frame) {
+ this.lastPts += frame.duration;
+ this.duration += frame.duration;
+ this.frames.push(frame);
+ }
+
+ // TODO: move the truns array and processTrunAtoms to a own container class (like sample-table)
public addTrunAtom(atom: Atom): void {
const trun = atom as Trun;
this.trunInfo.push(trun);
}
- public readTrunAtoms() {
+ public processTrunAtoms() {
this.trunInfo.forEach((trun: Trun, index) => {
if (index < this.trunInfoReadIndex) {
@@ -172,7 +179,7 @@ export class Mp4Track extends Track {
const timeUs = this.lastPts;
- this.frames.push(new Frame(
+ this.appendFrame(new Frame(
flags ? (flags.isSyncFrame ? Frame.IDR_FRAME : Frame.P_FRAME) : Frame.UNFLAGGED_FRAME,
timeUs,
sample.size,
@@ -181,9 +188,6 @@ export class Mp4Track extends Track {
cto
));
- this.lastPts += duration;
- this.duration += duration;
-
bytesOffset += sample.size;
}
})
From f40d51724b39392552fd87cbf08e8374044614ec Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 18 Oct 2018 19:35:00 +0200
Subject: [PATCH 034/197] digest sample tables
---
src/demuxer/frame.ts | 8 +-
src/demuxer/mp4/atoms/stsc.ts | 4 +-
src/demuxer/mp4/atoms/stss.ts | 2 +-
src/demuxer/mp4/mp4-demuxer.ts | 129 +++++++++++++++++++++++++++------
src/demuxer/mp4/mp4-track.ts | 11 ++-
src/demuxer/track.ts | 5 +-
6 files changed, 127 insertions(+), 32 deletions(-)
diff --git a/src/demuxer/frame.ts b/src/demuxer/frame.ts
index 10180a7..0fc39a2 100644
--- a/src/demuxer/frame.ts
+++ b/src/demuxer/frame.ts
@@ -1,4 +1,4 @@
-export const MICROSECOND_TIMESCALE = 1000000;
+import { toSecondsFromMicros } from "../utils/timescale";
export class Frame {
@@ -34,14 +34,14 @@ export class Frame {
}
getPresentationTimestampInSeconds(): number {
- return this.getPresentationTimeUs() / MICROSECOND_TIMESCALE;
+ return toSecondsFromMicros(this.getPresentationTimeUs())
}
getDecodingTimestampInSeconds() {
- return this.getDecodingTimeUs() / MICROSECOND_TIMESCALE;
+ return toSecondsFromMicros(this.getDecodingTimeUs());
}
getDurationInSeconds() {
- return this.duration / MICROSECOND_TIMESCALE;
+ return toSecondsFromMicros(this.duration);
}
}
diff --git a/src/demuxer/mp4/atoms/stsc.ts b/src/demuxer/mp4/atoms/stsc.ts
index 379a28e..3dfc672 100644
--- a/src/demuxer/mp4/atoms/stsc.ts
+++ b/src/demuxer/mp4/atoms/stsc.ts
@@ -2,7 +2,9 @@ import ByteParserUtils from '../../../utils/byte-parser-utils';
import { Atom } from './atom';
export class SampleToChunkEntry {
- constructor(public firstChunk: number, public samplesPerChunk: number,
+ constructor(
+ public firstChunk: number,
+ public samplesPerChunk: number,
public sampleDescriptionIndex: number) {
}
}
diff --git a/src/demuxer/mp4/atoms/stss.ts b/src/demuxer/mp4/atoms/stss.ts
index 9446500..1106ca7 100644
--- a/src/demuxer/mp4/atoms/stss.ts
+++ b/src/demuxer/mp4/atoms/stss.ts
@@ -8,7 +8,7 @@ export class Stss extends Atom {
public syncSampleNumbers: number[] = [];
public static parse(data: Uint8Array): Atom {
- const stss: Stss = new Stss(Atom.stts, data.byteLength);
+ const stss: Stss = new Stss(Atom.stss, data.byteLength);
stss.version = data[0];
stss.flags = data.subarray(1, 4);
const entryCount: number = ByteParserUtils.parseUint32(data, 4);
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index 19a7a56..e4e5842 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -15,14 +15,17 @@ import { Tfhd } from './atoms/tfhd';
import { Tkhd } from './atoms/tkhd';
import { AvcC } from './atoms/avcC';
import { Hev1 } from './atoms/hev1';
-import { Stts } from './atoms/stts';
-import { Stsc } from './atoms/stsc';
+import { Stts, TimeToSampleEntry } from './atoms/stts';
+import { Stsc, SampleToChunkEntry } from './atoms/stsc';
import { Stsz } from './atoms/stsz';
-import { Ctts } from './atoms/ctts';
+import { Ctts, CTimeOffsetToSampleEntry } from './atoms/ctts';
import {getLogger} from '../../utils/logger';
import { Stss } from './atoms/stss';
import { Stco } from './atoms/stco';
+import { Frame } from '../frame';
+import { Mdhd } from './atoms/mdhd';
+import { toMicroseconds } from '../../utils/timescale';
const {log, warn} = getLogger('Mp4Demuxer');
@@ -30,8 +33,8 @@ export class Mp4DemuxerSampleTable {
decodingTimestamps: Stts;
syncSamples: Stss;
compositionTimestampOffsets: Ctts;
- chunks: Stsc;
sampleSizes: Stsz;
+ chunks: Stsc;
chunkOffsets: Stco
constructor(private _track: Mp4Track) {
@@ -42,6 +45,50 @@ export class Mp4DemuxerSampleTable {
digest() {
+ let dts = 0;
+ let frameCount = 0;
+
+ const frames: Frame[] = [];
+
+ this.decodingTimestamps.timeToSamples.forEach((entry: TimeToSampleEntry) => {
+
+ for (let i = 0; i < entry.sampleCount; i++) {
+
+ const isSyncFrame = this.syncSamples ? (this.syncSamples.syncSampleNumbers.indexOf(frameCount + 1) >= 0) : false;
+
+ frames.push(
+ new Frame(
+ isSyncFrame ? Frame.IDR_FRAME : Frame.P_FRAME,
+ toMicroseconds(dts, this._track.getTimescale()),
+ this.sampleSizes.sampleSize || this.sampleSizes.entries[frameCount],
+ toMicroseconds(entry.sampleDelta, this._track.getTimescale())
+ )
+ )
+
+ frameCount++;
+
+ dts += entry.sampleDelta;
+ }
+ });
+
+ frameCount = 0;
+
+ this.compositionTimestampOffsets && this.compositionTimestampOffsets.cTimeOffsetToSamples.forEach((entry: CTimeOffsetToSampleEntry) => {
+ for (let i = 0; i < entry.sampleCount; i++) {
+
+ frames[frameCount]
+ .setPresentationTimeOffsetUs(toMicroseconds(entry.sampleCTimeOffset, this._track.getTimescale()));
+
+ frameCount++; // note: here we incr the count after using it as an ordinal index
+ }
+ });
+
+ this.chunks.sampleToChunks.forEach((entry: SampleToChunkEntry) => {
+ // TODO
+ })
+
+ // Finally, append all frames to our track
+ frames.forEach((frame) => this._track.appendFrame(frame));
}
};
@@ -50,12 +97,13 @@ export class Mp4Demuxer implements IDemuxer {
private atoms: Atom[] = [];
- // parsing stack
+ // track specific parsing stack
private lastTrackId: number;
private lastTrackDataOffset: number;
private lastAudioVideoAtom: AudioAtom | VideoAtom = null;
private lastCodecDataAtom: AvcC | Hev1 = null;
private lastSampleTable: Mp4DemuxerSampleTable = null;
+ private lastTimescale: number = null;
constructor() {
this.atoms = [];
@@ -70,6 +118,10 @@ export class Mp4Demuxer implements IDemuxer {
public append(data: Uint8Array): void {
this.atoms = this._parseAtoms(data);
+
+ // "HACK" digest any last sample-table
+ this._digestSampleTable();
+
this._updateTracks();
}
@@ -121,6 +173,7 @@ export class Mp4Demuxer implements IDemuxer {
dataOffset = end;
}
+
return atoms;
}
@@ -130,14 +183,27 @@ export class Mp4Demuxer implements IDemuxer {
// FIXME !!! `trex` box can contain super based set of default sample-duration/flags/size ...
// (those are often repeated inside the tfhd when it is a fragmented file however, but still ... :)
- // FIXME: much of this isn't going to work for plain old unfrag'd MP4 and MOV :)
-
case Atom.trak:
- this.lastSampleTable = null;
+
+ this._digestSampleTable();
+
+ this.lastTrackId = -1;
+ this.lastTimescale = null;
+ this.lastCodecDataAtom = null;
+ this.lastAudioVideoAtom = null;
+ this.lastTrackDataOffset = dataOffset;
+
case Atom.ftyp:
case Atom.moov:
case Atom.moof:
- this.lastTrackDataOffset = dataOffset;
+ // FIXME
+ break;
+
+ // Moov box / "initialization"-data and SIDX
+
+ case Atom.sidx:
+ this._attemptCreateUnknownTrack();
+ this._getLastTrackCreated().setSidxAtom(atom);
break;
case Atom.tkhd:
@@ -154,6 +220,8 @@ export class Mp4Demuxer implements IDemuxer {
this._attemptCreateTrack(Track.TYPE_VIDEO, Track.MIME_TYPE_AVC, atom);
break;
+ // Inside moov: Codec data -> create "known" tracks
+
// H264
case Atom.avc1:
this.lastAudioVideoAtom = atom as (AudioAtom | VideoAtom);
@@ -170,14 +238,12 @@ export class Mp4Demuxer implements IDemuxer {
this._attemptCreateTrack(Track.TYPE_AUDIO, Track.MIME_TYPE_AAC, atom);
break;
- case Atom.sidx:
- this._attemptCreateUnknownTrack();
- this._getLastTrackCreated().setSidxAtom(atom);
- break;
+ // Fragmented-mode ...
case Atom.tfhd:
+ // FIXME: should be handled differently by looking at other things inside fragments and mapping eventually to previously parsed moov
this._attemptCreateUnknownTrack();
- const tfhd: Tfhd = ( atom);
+ const tfhd: Tfhd = atom as Tfhd;
this._getLastTrackCreated().setBaseDataOffset(tfhd.baseDataOffset);
this._getLastTrackCreated().setDefaults({
sampleDuration: tfhd.defaultSampleDuration,
@@ -187,16 +253,15 @@ export class Mp4Demuxer implements IDemuxer {
break;
case Atom.trun:
+ // FIXME: should be handled differently by looking at other things inside fragments and mapping eventually to previously parsed moov
this._attemptCreateUnknownTrack();
this._getLastTrackCreated().addTrunAtom(atom);
break;
- case Atom.mdat:
- // in plain old MOV the moov may be at the end of the file (and mdat before)
- if (this._getLastTrackCreated()) {
- this._getLastTrackCreated().updateInitialSampleDataOffset(this.lastTrackDataOffset);
- this._getLastTrackCreated().processTrunAtoms();
- }
+ // Plain-old MOV ie unfragmented mode ...
+
+ case Atom.mdhd:
+ this.lastTimescale = (atom as Mdhd).timescale;
break;
case Atom.stbl:
@@ -228,6 +293,16 @@ export class Mp4Demuxer implements IDemuxer {
this._haveSampleTable();
this.lastSampleTable.chunkOffsets = atom as Stco;
break;
+
+ // Sample data ...
+
+ case Atom.mdat:
+ // in plain old MOV the moov may be at the end of the file (and mdat before)
+ if (this._getLastTrackCreated()) {
+ this._getLastTrackCreated().updateInitialSampleDataOffset(this.lastTrackDataOffset);
+ this._getLastTrackCreated().processTrunAtoms();
+ }
+ break;
}
}
@@ -238,10 +313,18 @@ export class Mp4Demuxer implements IDemuxer {
this.lastSampleTable = new Mp4DemuxerSampleTable(this._getLastTrackCreated());
}
+
+ private _digestSampleTable() {
+ if (this.lastSampleTable) {
+ this.lastSampleTable.digest();
+ this.lastSampleTable = null;
+ }
+ }
+
private _attemptCreateTrack(type: string, mime: string, ref: Atom) {
if (this.lastTrackId > 0) {
log('creating new track:', type, mime)
- this.tracks[this.lastTrackId] = new Mp4Track(
+ const track = new Mp4Track(
this.lastTrackId,
type,
mime,
@@ -249,6 +332,10 @@ export class Mp4Demuxer implements IDemuxer {
this.lastAudioVideoAtom,
this.lastTrackDataOffset
);
+ if (this.lastTimescale !== null) {
+ track.setTimescale(this.lastTimescale);
+ }
+ this.tracks[this.lastTrackId] = track;
}
}
diff --git a/src/demuxer/mp4/mp4-track.ts b/src/demuxer/mp4/mp4-track.ts
index a24c450..7540d28 100644
--- a/src/demuxer/mp4/mp4-track.ts
+++ b/src/demuxer/mp4/mp4-track.ts
@@ -1,6 +1,6 @@
import { Track } from '../track';
-import { Frame, MICROSECOND_TIMESCALE } from '../frame';
+import { Frame } from '../frame';
import { Atom } from './atoms/atom';
@@ -12,6 +12,7 @@ import { Trun, SampleFlags } from './atoms/trun';
import { Avc1 } from './atoms/avc1';
import {getLogger} from '../../utils/logger';
+import { toMicroseconds } from '../../utils/timescale';
const {log, warn} = getLogger('Mp4Track');
@@ -82,6 +83,10 @@ export class Mp4Track extends Track {
return this.timescale;
}
+ public setTimescale(timescale: number) {
+ this.timescale = timescale;
+ }
+
public setDefaults(defaults: Mp4TrackDefaults) {
this.defaults = defaults;
if (defaults.sampleFlags) {
@@ -167,7 +172,7 @@ export class Mp4Track extends Track {
throw new Error('Invalid file, samples have no duration');
}
- const duration: number = MICROSECOND_TIMESCALE * sampleDuration / timescale;
+ const duration: number = toMicroseconds(sampleDuration, timescale);
const flags = sample.flags || this.defaultSampleFlagsParsed;
if (!flags) {
@@ -175,7 +180,7 @@ export class Mp4Track extends Track {
//throw new Error('Invalid file, sample has no flags');
}
- const cto: number = MICROSECOND_TIMESCALE * (sample.compositionTimeOffset || 0) / timescale;
+ const cto: number = toMicroseconds((sample.compositionTimeOffset || 0), timescale);
const timeUs = this.lastPts;
diff --git a/src/demuxer/track.ts b/src/demuxer/track.ts
index c82e656..2f08093 100644
--- a/src/demuxer/track.ts
+++ b/src/demuxer/track.ts
@@ -1,4 +1,5 @@
-import { Frame, MICROSECOND_TIMESCALE } from './frame';
+import { Frame } from './frame';
+import { toSecondsFromMicros } from '../utils/timescale';
export class Track {
// FIXME: should be an enum type
@@ -55,7 +56,7 @@ export class Track {
}
public getDurationInSeconds(): number {
- return this.getDuration() / MICROSECOND_TIMESCALE;
+ return toSecondsFromMicros(this.getDuration());
}
public getMetadata(): {} { // FIXME: Make this a string-to-any hash
From 89080d6b0c1f39d8177fa37f7df22632fc9b7b9b Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 18 Oct 2018 19:35:10 +0200
Subject: [PATCH 035/197] add timescale util functions
---
src/utils/timescale.ts | 10 ++++++++++
1 file changed, 10 insertions(+)
create mode 100644 src/utils/timescale.ts
diff --git a/src/utils/timescale.ts b/src/utils/timescale.ts
new file mode 100644
index 0000000..97da9bb
--- /dev/null
+++ b/src/utils/timescale.ts
@@ -0,0 +1,10 @@
+export const MICROSECOND_TIMESCALE = 1000000;
+
+export function toMicroseconds(value, timescale) {
+ return MICROSECOND_TIMESCALE * value / timescale;
+}
+
+export function toSecondsFromMicros(us) {
+ return us / MICROSECOND_TIMESCALE;
+}
+
From 806e64b2eb04c43138287f03c79bd9e058b1f65a Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 30 Oct 2018 20:28:12 +0100
Subject: [PATCH 036/197] support for mov demuxing (samples tables)
---
src/demuxer/mp4/mp4-demuxer.ts | 82 +++---------------
src/demuxer/mp4/mp4-sample-flags.ts | 9 ++
src/demuxer/mp4/mp4-sample-table.ts | 125 ++++++++++++++++++++++++++++
src/utils/byte-parser-utils.ts | 11 +--
4 files changed, 146 insertions(+), 81 deletions(-)
create mode 100644 src/demuxer/mp4/mp4-sample-flags.ts
create mode 100644 src/demuxer/mp4/mp4-sample-table.ts
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index e4e5842..fd2c3b4 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -15,82 +15,20 @@ import { Tfhd } from './atoms/tfhd';
import { Tkhd } from './atoms/tkhd';
import { AvcC } from './atoms/avcC';
import { Hev1 } from './atoms/hev1';
-import { Stts, TimeToSampleEntry } from './atoms/stts';
-import { Stsc, SampleToChunkEntry } from './atoms/stsc';
+import { Stts } from './atoms/stts';
+import { Stsc } from './atoms/stsc';
import { Stsz } from './atoms/stsz';
-import { Ctts, CTimeOffsetToSampleEntry } from './atoms/ctts';
+import { Ctts } from './atoms/ctts';
-import {getLogger} from '../../utils/logger';
import { Stss } from './atoms/stss';
import { Stco } from './atoms/stco';
-import { Frame } from '../frame';
import { Mdhd } from './atoms/mdhd';
-import { toMicroseconds } from '../../utils/timescale';
-
-const {log, warn} = getLogger('Mp4Demuxer');
-
-export class Mp4DemuxerSampleTable {
- decodingTimestamps: Stts;
- syncSamples: Stss;
- compositionTimestampOffsets: Ctts;
- sampleSizes: Stsz;
- chunks: Stsc;
- chunkOffsets: Stco
-
- constructor(private _track: Mp4Track) {
- if (!_track) {
- throw new Error('Sample-table can not be created without a Track');
- }
- }
-
- digest() {
-
- let dts = 0;
- let frameCount = 0;
-
- const frames: Frame[] = [];
-
- this.decodingTimestamps.timeToSamples.forEach((entry: TimeToSampleEntry) => {
-
- for (let i = 0; i < entry.sampleCount; i++) {
- const isSyncFrame = this.syncSamples ? (this.syncSamples.syncSampleNumbers.indexOf(frameCount + 1) >= 0) : false;
-
- frames.push(
- new Frame(
- isSyncFrame ? Frame.IDR_FRAME : Frame.P_FRAME,
- toMicroseconds(dts, this._track.getTimescale()),
- this.sampleSizes.sampleSize || this.sampleSizes.entries[frameCount],
- toMicroseconds(entry.sampleDelta, this._track.getTimescale())
- )
- )
-
- frameCount++;
-
- dts += entry.sampleDelta;
- }
- });
-
- frameCount = 0;
-
- this.compositionTimestampOffsets && this.compositionTimestampOffsets.cTimeOffsetToSamples.forEach((entry: CTimeOffsetToSampleEntry) => {
- for (let i = 0; i < entry.sampleCount; i++) {
-
- frames[frameCount]
- .setPresentationTimeOffsetUs(toMicroseconds(entry.sampleCTimeOffset, this._track.getTimescale()));
-
- frameCount++; // note: here we incr the count after using it as an ordinal index
- }
- });
+import {getLogger} from '../../utils/logger';
- this.chunks.sampleToChunks.forEach((entry: SampleToChunkEntry) => {
- // TODO
- })
+import { Mp4SampleTable } from './mp4-sample-table';
- // Finally, append all frames to our track
- frames.forEach((frame) => this._track.appendFrame(frame));
- }
-};
+const {log, debug, warn} = getLogger('Mp4Demuxer');
export class Mp4Demuxer implements IDemuxer {
public tracks: TracksHash = {};
@@ -102,7 +40,7 @@ export class Mp4Demuxer implements IDemuxer {
private lastTrackDataOffset: number;
private lastAudioVideoAtom: AudioAtom | VideoAtom = null;
private lastCodecDataAtom: AvcC | Hev1 = null;
- private lastSampleTable: Mp4DemuxerSampleTable = null;
+ private lastSampleTable: Mp4SampleTable = null;
private lastTimescale: number = null;
constructor() {
@@ -283,7 +221,7 @@ export class Mp4Demuxer implements IDemuxer {
break;
case Atom.stsc:
this._haveSampleTable();
- this.lastSampleTable.chunks = atom as Stsc;
+ this.lastSampleTable.samplesToChunkBox = atom as Stsc;
break;
case Atom.stsz:
this._haveSampleTable();
@@ -291,7 +229,7 @@ export class Mp4Demuxer implements IDemuxer {
break;
case Atom.stco:
this._haveSampleTable();
- this.lastSampleTable.chunkOffsets = atom as Stco;
+ this.lastSampleTable.chunkOffsetBox = atom as Stco;
break;
// Sample data ...
@@ -310,7 +248,7 @@ export class Mp4Demuxer implements IDemuxer {
if (this.lastSampleTable) {
return;
}
- this.lastSampleTable = new Mp4DemuxerSampleTable(this._getLastTrackCreated());
+ this.lastSampleTable = new Mp4SampleTable(this._getLastTrackCreated());
}
diff --git a/src/demuxer/mp4/mp4-sample-flags.ts b/src/demuxer/mp4/mp4-sample-flags.ts
new file mode 100644
index 0000000..92131d0
--- /dev/null
+++ b/src/demuxer/mp4/mp4-sample-flags.ts
@@ -0,0 +1,9 @@
+export class Mp4SampleFlags {
+ public isLeading: number;
+ public dependsOn: number;
+ public isDependedOn: number;
+ public hasRedundancy: number;
+ public paddingValue: number;
+ public isNonSyncSample: number;
+ public degradationPriority: number;
+}
diff --git a/src/demuxer/mp4/mp4-sample-table.ts b/src/demuxer/mp4/mp4-sample-table.ts
new file mode 100644
index 0000000..51284cd
--- /dev/null
+++ b/src/demuxer/mp4/mp4-sample-table.ts
@@ -0,0 +1,125 @@
+import { Stts, TimeToSampleEntry } from './atoms/stts';
+import { Stsc, SampleToChunkEntry } from './atoms/stsc';
+import { Stsz } from './atoms/stsz';
+import { Ctts, CTimeOffsetToSampleEntry } from './atoms/ctts';
+import { Mp4Track } from './mp4-track';
+import { Stss } from './atoms/stss';
+import { Stco } from './atoms/stco';
+
+import { Frame } from '../frame';
+
+import { toMicroseconds } from '../../utils/timescale';
+
+import {getLogger} from '../../utils/logger';
+
+const {log, debug, warn} = getLogger('Mp4SampleTable');
+
+export class Mp4SampleTable {
+ decodingTimestamps: Stts;
+ compositionTimestampOffsets: Ctts;
+ syncSamples: Stss;
+ sampleSizes: Stsz;
+
+ chunkOffsetBox: Stco
+ samplesToChunkBox: Stsc;
+
+ constructor(private _track: Mp4Track) {
+ if (!_track) {
+ throw new Error('Sample-table can not be created without a Track');
+ }
+ }
+
+ digest() {
+
+ debug('digesting sample table');
+
+ let dts = 0;
+ let frameCount = 0;
+
+ const frames: Frame[] = [];
+ const chunksDecompressed: {samplesPerChunk: number, sampleDescriptionIndex: number}[] = []
+ const chunkOffsetsDecompressed: number[] = [];
+
+ this.decodingTimestamps.timeToSamples.forEach((entry: TimeToSampleEntry) => {
+
+ for (let i = 0; i < entry.sampleCount; i++) {
+
+ const isSyncFrame = this.syncSamples ? (this.syncSamples.syncSampleNumbers.indexOf(frameCount + 1) >= 0) : false;
+
+ frames.push(
+ new Frame(
+ isSyncFrame ? Frame.IDR_FRAME : Frame.P_FRAME,
+ toMicroseconds(dts, this._track.getTimescale()),
+ this.sampleSizes.sampleSize || this.sampleSizes.entries[frameCount],
+ toMicroseconds(entry.sampleDelta, this._track.getTimescale())
+ )
+ )
+
+ frameCount++; // note: here we incr the count after using it as an ordinal index
+
+ dts += entry.sampleDelta;
+ }
+ });
+
+ frameCount = 0;
+
+ this.compositionTimestampOffsets && this.compositionTimestampOffsets.cTimeOffsetToSamples.forEach((entry: CTimeOffsetToSampleEntry) => {
+ for (let i = 0; i < entry.sampleCount; i++) {
+
+ frames[frameCount]
+ .setPresentationTimeOffsetUs(toMicroseconds(entry.sampleCTimeOffset, this._track.getTimescale()));
+
+ frameCount++; // note: here we incr the count after using it as an ordinal index
+ }
+ });
+
+ frameCount = 0;
+
+ this.samplesToChunkBox.sampleToChunks.forEach((sampleToChunkEntry: SampleToChunkEntry, index) => {
+ // the sample-to-chunk box contains a compressed list
+ // of possibly repeating properties (samplesPerChunk + sampleDescriptionIndex)
+ // we need to decompress this information by looking at firstChunkIndex
+ let chunksInThisEntry = 1;
+ if (index < this.samplesToChunkBox.sampleToChunks.length - 1) {
+ chunksInThisEntry = this.samplesToChunkBox.sampleToChunks[index + 1].firstChunk
+ - sampleToChunkEntry.firstChunk;
+ }
+
+ for (let i=0; i < chunksInThisEntry; i++) {
+ frameCount += this.samplesToChunkBox.sampleToChunks[index].samplesPerChunk
+
+ chunksDecompressed.push(sampleToChunkEntry);
+ }
+
+ });
+
+ if (frameCount !== frames.length) {
+ throw new Error('Sample-to-chunk-list decompression yields inconsistent sample count. Input data may be corrupt.');
+ }
+
+ frameCount = 0;
+
+ chunksDecompressed.forEach((chunkSampleInfo, index) => {
+
+ let sampleOffsetInChunk = 0;
+
+ for (let i = 0; i < chunkSampleInfo.samplesPerChunk; i++) {
+
+ const frame = frames[frameCount];
+
+ frame.bytesOffset = this.chunkOffsetBox.chunkOffsets[index];
+ frame.bytesOffset += sampleOffsetInChunk;
+
+ sampleOffsetInChunk += frame.size;
+
+ frameCount++;
+ }
+
+ });
+
+ // Finally, append all frames to our track
+ frames.forEach((frame) => this._track.appendFrame(frame));
+
+ log(frames)
+ }
+};
diff --git a/src/utils/byte-parser-utils.ts b/src/utils/byte-parser-utils.ts
index 14584f9..c196993 100644
--- a/src/utils/byte-parser-utils.ts
+++ b/src/utils/byte-parser-utils.ts
@@ -1,12 +1,5 @@
-export class Mp4SampleFlags {
- public isLeading: number;
- public dependsOn: number;
- public isDependedOn: number;
- public hasRedundancy: number;
- public paddingValue: number;
- public isNonSyncSample: number;
- public degradationPriority: number;
-}
+import { Mp4SampleFlags } from "../demuxer/mp4/mp4-sample-flags";
+
declare function escape(s: string): string;
export default class ByteParserUtils {
From 82b40d4163a735dcee2dd42b7e03a17a5b50b20e Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 30 Oct 2018 21:14:46 +0100
Subject: [PATCH 037/197] fix demuxing fmp4 (now both mov + fmp4 work)
---
src/demuxer/mp4/mp4-demuxer.ts | 13 +++++++------
src/demuxer/mp4/mp4-track.ts | 22 +++++++++++++---------
2 files changed, 20 insertions(+), 15 deletions(-)
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index fd2c3b4..aba896f 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -19,7 +19,6 @@ import { Stts } from './atoms/stts';
import { Stsc } from './atoms/stsc';
import { Stsz } from './atoms/stsz';
import { Ctts } from './atoms/ctts';
-
import { Stss } from './atoms/stss';
import { Stco } from './atoms/stco';
import { Mdhd } from './atoms/mdhd';
@@ -28,7 +27,7 @@ import {getLogger} from '../../utils/logger';
import { Mp4SampleTable } from './mp4-sample-table';
-const {log, debug, warn} = getLogger('Mp4Demuxer');
+const {log, warn} = getLogger('Mp4Demuxer');
export class Mp4Demuxer implements IDemuxer {
public tracks: TracksHash = {};
@@ -124,17 +123,17 @@ export class Mp4Demuxer implements IDemuxer {
case Atom.trak:
this._digestSampleTable();
-
this.lastTrackId = -1;
this.lastTimescale = null;
this.lastCodecDataAtom = null;
this.lastAudioVideoAtom = null;
- this.lastTrackDataOffset = dataOffset;
case Atom.ftyp:
case Atom.moov:
case Atom.moof:
- // FIXME
+ // (only) needed for fragmented mode
+ this.lastTrackDataOffset = dataOffset;
+ // FIXME
break;
// Moov box / "initialization"-data and SIDX
@@ -237,7 +236,9 @@ export class Mp4Demuxer implements IDemuxer {
case Atom.mdat:
// in plain old MOV the moov may be at the end of the file (and mdat before)
if (this._getLastTrackCreated()) {
+ log('updating sample-data offset:', this.lastTrackDataOffset)
this._getLastTrackCreated().updateInitialSampleDataOffset(this.lastTrackDataOffset);
+ log('processing current track-run');
this._getLastTrackCreated().processTrunAtoms();
}
break;
@@ -296,7 +297,7 @@ export class Mp4Demuxer implements IDemuxer {
}
/**
- * should be called everytime we create a track
+ * Should be called everytime we create a track
*/
private _resetLastTrackInfos() {
this.lastTrackId = 0;
diff --git a/src/demuxer/mp4/mp4-track.ts b/src/demuxer/mp4/mp4-track.ts
index 7540d28..1e6b575 100644
--- a/src/demuxer/mp4/mp4-track.ts
+++ b/src/demuxer/mp4/mp4-track.ts
@@ -14,7 +14,7 @@ import { Avc1 } from './atoms/avc1';
import {getLogger} from '../../utils/logger';
import { toMicroseconds } from '../../utils/timescale';
-const {log, warn} = getLogger('Mp4Track');
+const {log, debug, warn} = getLogger('Mp4Track');
export type Mp4TrackDefaults = {
sampleDuration: number;
@@ -184,14 +184,18 @@ export class Mp4Track extends Track {
const timeUs = this.lastPts;
- this.appendFrame(new Frame(
- flags ? (flags.isSyncFrame ? Frame.IDR_FRAME : Frame.P_FRAME) : Frame.UNFLAGGED_FRAME,
- timeUs,
- sample.size,
- duration,
- bytesOffset,
- cto
- ));
+ const newFrame = new Frame(
+ flags ? (flags.isSyncFrame ? Frame.IDR_FRAME : Frame.P_FRAME) : Frame.UNFLAGGED_FRAME,
+ timeUs,
+ sample.size,
+ duration,
+ bytesOffset,
+ cto
+ );
+
+ this.appendFrame(newFrame);
+
+ debug(`frame: ${newFrame.timeUs} -> ${newFrame.bytesOffset} / ${newFrame.size}`, newFrame)
bytesOffset += sample.size;
}
From 931f7275017cf49c6aa11c49a2cbde584711ec0f Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 30 Oct 2018 21:18:16 +0100
Subject: [PATCH 038/197] remove unused comment
---
src/demuxer/mp4/mp4-demuxer.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index aba896f..7204cbb 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -133,7 +133,7 @@ export class Mp4Demuxer implements IDemuxer {
case Atom.moof:
// (only) needed for fragmented mode
this.lastTrackDataOffset = dataOffset;
- // FIXME
+
break;
// Moov box / "initialization"-data and SIDX
From 4ef206227091db3b3b316ec3aadebcc93f663799 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 14 Nov 2018 19:06:12 +0100
Subject: [PATCH 039/197] use esds as track reference atom and extract ES
descriptor data container raw
---
src/demuxer/frame.ts | 2 +-
src/demuxer/mp4/atoms/esds.ts | 4 ++++
src/demuxer/mp4/mp4-demuxer.ts | 36 +++++++++++++++++++++-------------
3 files changed, 27 insertions(+), 15 deletions(-)
diff --git a/src/demuxer/frame.ts b/src/demuxer/frame.ts
index 0fc39a2..ab461f5 100644
--- a/src/demuxer/frame.ts
+++ b/src/demuxer/frame.ts
@@ -6,7 +6,7 @@ export class Frame {
public static IDR_FRAME: string = 'I';
public static P_FRAME: string = 'P';
public static B_FRAME: string = 'B';
- public static UNFLAGGED_FRAME: string = '-';
+ public static UNFLAGGED_FRAME: string = '/';
private presentationTimeUs: number = 0;
diff --git a/src/demuxer/mp4/atoms/esds.ts b/src/demuxer/mp4/atoms/esds.ts
index 0946b3d..374d22b 100644
--- a/src/demuxer/mp4/atoms/esds.ts
+++ b/src/demuxer/mp4/atoms/esds.ts
@@ -21,12 +21,16 @@ export class Esds extends Atom {
public esId: number;
public streamPriority: number;
public decoderConfig: DecoderConfig;
+ public data: Uint8Array;
public static parse(data: Uint8Array): Atom {
const esds: Esds = new Esds(Atom.esds, data.byteLength);
+ esds.data = data;
+
esds.version = data[0];
esds.flags = data.subarray(1, 4);
+
esds.esId = ByteParserUtils.parseUint16(data, 6);
esds.streamPriority = data[8] & 0x1f;
esds.decoderConfig = new DecoderConfig(
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index 7204cbb..3714c7f 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -26,6 +26,7 @@ import { Mdhd } from './atoms/mdhd';
import {getLogger} from '../../utils/logger';
import { Mp4SampleTable } from './mp4-sample-table';
+import { Esds } from './atoms/esds';
const {log, warn} = getLogger('Mp4Demuxer');
@@ -38,7 +39,7 @@ export class Mp4Demuxer implements IDemuxer {
private lastTrackId: number;
private lastTrackDataOffset: number;
private lastAudioVideoAtom: AudioAtom | VideoAtom = null;
- private lastCodecDataAtom: AvcC | Hev1 = null;
+ private lastCodecDataAtom: AvcC | Hev1 | Esds = null;
private lastSampleTable: Mp4SampleTable = null;
private lastTimescale: number = null;
@@ -147,17 +148,14 @@ export class Mp4Demuxer implements IDemuxer {
this.lastTrackId = (atom as Tkhd).trackId;
break;
- case Atom.hvcC:
- this.lastCodecDataAtom = atom as Hev1;
- this._attemptCreateTrack(Track.TYPE_VIDEO, Track.MIME_TYPE_HEVC, atom);
- break;
+ // Inside moov: Codec data -> create "known" tracks
- case Atom.avcC:
- this.lastCodecDataAtom = atom as AvcC;
- this._attemptCreateTrack(Track.TYPE_VIDEO, Track.MIME_TYPE_AVC, atom);
- break;
+ // stsd-boxed codec identifying atoms
- // Inside moov: Codec data -> create "known" tracks
+ // AAC
+ case Atom.mp4a:
+ this.lastAudioVideoAtom = atom as AudioAtom | VideoAtom;
+ break;
// H264
case Atom.avc1:
@@ -169,12 +167,21 @@ export class Mp4Demuxer implements IDemuxer {
this.lastAudioVideoAtom = atom as AudioAtom | VideoAtom;
break;
- // AAC
- case Atom.mp4a:
- this.lastAudioVideoAtom = atom as AudioAtom | VideoAtom;
- this._attemptCreateTrack(Track.TYPE_AUDIO, Track.MIME_TYPE_AAC, atom);
+ // AVC/HEVC -> H264/5
+ case Atom.hvcC:
+ this.lastCodecDataAtom = atom as Hev1;
+ this._attemptCreateTrack(Track.TYPE_VIDEO, Track.MIME_TYPE_HEVC, atom);
break;
+ case Atom.avcC:
+ this.lastCodecDataAtom = atom as AvcC;
+ this._attemptCreateTrack(Track.TYPE_VIDEO, Track.MIME_TYPE_AVC, atom);
+ break;
+
+ case Atom.esds:
+ this._attemptCreateTrack(Track.TYPE_AUDIO, Track.MIME_TYPE_AAC, atom);
+ this.lastCodecDataAtom = atom as Esds;
+
// Fragmented-mode ...
case Atom.tfhd:
@@ -272,6 +279,7 @@ export class Mp4Demuxer implements IDemuxer {
this.lastTrackDataOffset
);
if (this.lastTimescale !== null) {
+ log('got timescale:', this.lastTimescale);
track.setTimescale(this.lastTimescale);
}
this.tracks[this.lastTrackId] = track;
From 4b347ebf3bea225a17b33db14ba66a5897b5e911 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 14 Nov 2018 23:56:18 +0100
Subject: [PATCH 040/197] enable unscaled integer sample timing values for mp4
---
src/demuxer/frame.ts | 13 +++++++++++++
src/demuxer/mp4/mp4-sample-table.ts | 27 ++++++++++++++++++---------
src/demuxer/mp4/mp4-track.ts | 19 ++++++++++++++++---
3 files changed, 47 insertions(+), 12 deletions(-)
diff --git a/src/demuxer/frame.ts b/src/demuxer/frame.ts
index ab461f5..913d7c0 100644
--- a/src/demuxer/frame.ts
+++ b/src/demuxer/frame.ts
@@ -10,6 +10,12 @@ export class Frame {
private presentationTimeUs: number = 0;
+ // unscaled integer values
+ public timescale: number = NaN;
+ public timeUnscaled: number = NaN;
+ public ptOffsetUnscaled: number = NaN;
+ public durationUnscaled: number = NaN;
+
constructor (
public frameType: string,
public timeUs: number,
@@ -21,6 +27,13 @@ export class Frame {
this.setPresentationTimeOffsetUs(presentationTimeOffsetUs);
}
+ hasUnscaledIntegerTiming() {
+ return Number.isFinite(this.timescale)
+ && Number.isFinite(this.timeUnscaled)
+ && Number.isFinite(this.ptOffsetUnscaled)
+ && Number.isFinite(this.durationUnscaled);
+ }
+
getDecodingTimeUs() {
return this.timeUs;
}
diff --git a/src/demuxer/mp4/mp4-sample-table.ts b/src/demuxer/mp4/mp4-sample-table.ts
index 51284cd..de6e30d 100644
--- a/src/demuxer/mp4/mp4-sample-table.ts
+++ b/src/demuxer/mp4/mp4-sample-table.ts
@@ -46,14 +46,19 @@ export class Mp4SampleTable {
const isSyncFrame = this.syncSamples ? (this.syncSamples.syncSampleNumbers.indexOf(frameCount + 1) >= 0) : false;
- frames.push(
- new Frame(
- isSyncFrame ? Frame.IDR_FRAME : Frame.P_FRAME,
- toMicroseconds(dts, this._track.getTimescale()),
- this.sampleSizes.sampleSize || this.sampleSizes.entries[frameCount],
- toMicroseconds(entry.sampleDelta, this._track.getTimescale())
- )
- )
+ const newFrame = new Frame(
+ isSyncFrame ? Frame.IDR_FRAME : Frame.P_FRAME,
+ toMicroseconds(dts, this._track.getTimescale()),
+ this.sampleSizes.sampleSize || this.sampleSizes.entries[frameCount],
+ toMicroseconds(entry.sampleDelta, this._track.getTimescale())
+ );
+
+ newFrame.durationUnscaled = entry.sampleDelta;
+ newFrame.timeUnscaled = dts;
+ newFrame.ptOffsetUnscaled = 0;
+ newFrame.timescale = this._track.getTimescale();
+
+ frames.push(newFrame);
frameCount++; // note: here we incr the count after using it as an ordinal index
@@ -69,6 +74,8 @@ export class Mp4SampleTable {
frames[frameCount]
.setPresentationTimeOffsetUs(toMicroseconds(entry.sampleCTimeOffset, this._track.getTimescale()));
+ frames[frameCount].ptOffsetUnscaled = entry.sampleCTimeOffset;
+
frameCount++; // note: here we incr the count after using it as an ordinal index
}
});
@@ -118,7 +125,9 @@ export class Mp4SampleTable {
});
// Finally, append all frames to our track
- frames.forEach((frame) => this._track.appendFrame(frame));
+ frames.forEach((frame) => {
+ this._track.appendFrame(frame)
+ });
log(frames)
}
diff --git a/src/demuxer/mp4/mp4-track.ts b/src/demuxer/mp4/mp4-track.ts
index 1e6b575..9753931 100644
--- a/src/demuxer/mp4/mp4-track.ts
+++ b/src/demuxer/mp4/mp4-track.ts
@@ -27,6 +27,7 @@ export class Mp4Track extends Track {
private trunInfo: Trun[] = [];
private trunInfoReadIndex: number = 0;
private lastPts: number = null;
+ private lastPtsUnscaledUint: number = null;
private timescale: number = null;
private defaults: Mp4TrackDefaults = null;
private defaultSampleFlagsParsed: SampleFlags = null;
@@ -43,6 +44,7 @@ export class Mp4Track extends Track {
super(id, type, mimeType);
this.lastPts = 0;
+ this.lastPtsUnscaledUint = 0;
this.duration = 0;
if (this.dataOffset < 0) {
@@ -136,11 +138,16 @@ export class Mp4Track extends Track {
public setSidxAtom(atom: Atom): void {
this.sidx = atom as Sidx;
+ this.lastPtsUnscaledUint = this.sidx.earliestPresentationTime;
this.lastPts = 1000000 * this.sidx.earliestPresentationTime / this.sidx.timescale;
this.timescale = this.sidx.timescale;
}
public appendFrame(frame: Frame) {
+ if (!frame.hasUnscaledIntegerTiming()) {
+ throw new Error('Frame must have unscaled-int sample timing');
+ }
+ this.lastPtsUnscaledUint += frame.durationUnscaled;
this.lastPts += frame.duration;
this.duration += frame.duration;
this.frames.push(frame);
@@ -176,8 +183,9 @@ export class Mp4Track extends Track {
const flags = sample.flags || this.defaultSampleFlagsParsed;
if (!flags) {
- // in fact the trun box parser should provide a fallback instance of flags in this case
- //throw new Error('Invalid file, sample has no flags');
+ warn('no default sample flags in track sample-run');
+ // in fact the trun box parser should provide a fallback instance of flags in this case
+ //throw new Error('Invalid file, sample has no flags');
}
const cto: number = toMicroseconds((sample.compositionTimeOffset || 0), timescale);
@@ -193,9 +201,14 @@ export class Mp4Track extends Track {
cto
);
+ newFrame.durationUnscaled = sampleDuration;
+ newFrame.timeUnscaled = this.lastPtsUnscaledUint;
+ newFrame.ptOffsetUnscaled = sample.compositionTimeOffset || 0;
+ newFrame.timescale = timescale;
+
this.appendFrame(newFrame);
- debug(`frame: ${newFrame.timeUs} -> ${newFrame.bytesOffset} / ${newFrame.size}`, newFrame)
+ debug(`frame: @ ${newFrame.timeUs} [us] -> ${newFrame.bytesOffset} / ${newFrame.size}`)
bytesOffset += sample.size;
}
From 1feec291efc68fc17a57ca4f90e8c047d03e55c6 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 29 Nov 2018 07:08:01 +0100
Subject: [PATCH 041/197] rename frame properties to unscaled..
---
src/demuxer/frame.ts | 17 ++++++------
src/demuxer/mp4/mp4-demuxer.ts | 3 ++-
src/demuxer/mp4/mp4-sample-table.ts | 8 +++---
src/demuxer/mp4/mp4-track.ts | 12 ++++-----
src/demuxer/ts/mpegts-demuxer.ts | 3 +++
src/demuxer/ts/payload/adts-reader.ts | 3 ++-
src/demuxer/ts/payload/h264-reader.ts | 34 +++++++++++++++++-------
src/demuxer/ts/payload/payload-reader.ts | 23 +++++++++++++---
src/index.ts | 32 +++++++---------------
9 files changed, 78 insertions(+), 57 deletions(-)
diff --git a/src/demuxer/frame.ts b/src/demuxer/frame.ts
index 913d7c0..9090f1d 100644
--- a/src/demuxer/frame.ts
+++ b/src/demuxer/frame.ts
@@ -8,13 +8,14 @@ export class Frame {
public static B_FRAME: string = 'B';
public static UNFLAGGED_FRAME: string = '/';
+ // normalized micros value
private presentationTimeUs: number = 0;
- // unscaled integer values
+ // ideally have unnormalized integer values
public timescale: number = NaN;
- public timeUnscaled: number = NaN;
- public ptOffsetUnscaled: number = NaN;
- public durationUnscaled: number = NaN;
+ public scaledDecodingTime: number = NaN;
+ public scaledPresentationTimeOffset: number = NaN;
+ public scaledDuration: number = NaN;
constructor (
public frameType: string,
@@ -27,11 +28,11 @@ export class Frame {
this.setPresentationTimeOffsetUs(presentationTimeOffsetUs);
}
- hasUnscaledIntegerTiming() {
+ hasUnnormalizedIntegerTiming() {
return Number.isFinite(this.timescale)
- && Number.isFinite(this.timeUnscaled)
- && Number.isFinite(this.ptOffsetUnscaled)
- && Number.isFinite(this.durationUnscaled);
+ && Number.isFinite(this.scaledDecodingTime)
+ && Number.isFinite(this.scaledPresentationTimeOffset)
+ && Number.isFinite(this.scaledDuration);
}
getDecodingTimeUs() {
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index 3714c7f..12d3a36 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -27,8 +27,9 @@ import {getLogger} from '../../utils/logger';
import { Mp4SampleTable } from './mp4-sample-table';
import { Esds } from './atoms/esds';
+import { LoggerLevels } from '../../../../../logger';
-const {log, warn} = getLogger('Mp4Demuxer');
+const {log, warn} = getLogger('Mp4Demuxer', LoggerLevels.OFF);
export class Mp4Demuxer implements IDemuxer {
public tracks: TracksHash = {};
diff --git a/src/demuxer/mp4/mp4-sample-table.ts b/src/demuxer/mp4/mp4-sample-table.ts
index de6e30d..5aa8923 100644
--- a/src/demuxer/mp4/mp4-sample-table.ts
+++ b/src/demuxer/mp4/mp4-sample-table.ts
@@ -53,9 +53,9 @@ export class Mp4SampleTable {
toMicroseconds(entry.sampleDelta, this._track.getTimescale())
);
- newFrame.durationUnscaled = entry.sampleDelta;
- newFrame.timeUnscaled = dts;
- newFrame.ptOffsetUnscaled = 0;
+ newFrame.scaledDuration = entry.sampleDelta;
+ newFrame.scaledDecodingTime = dts;
+ newFrame.scaledPresentationTimeOffset = 0;
newFrame.timescale = this._track.getTimescale();
frames.push(newFrame);
@@ -74,7 +74,7 @@ export class Mp4SampleTable {
frames[frameCount]
.setPresentationTimeOffsetUs(toMicroseconds(entry.sampleCTimeOffset, this._track.getTimescale()));
- frames[frameCount].ptOffsetUnscaled = entry.sampleCTimeOffset;
+ frames[frameCount].scaledPresentationTimeOffset = entry.sampleCTimeOffset;
frameCount++; // note: here we incr the count after using it as an ordinal index
}
diff --git a/src/demuxer/mp4/mp4-track.ts b/src/demuxer/mp4/mp4-track.ts
index 9753931..b9d24b4 100644
--- a/src/demuxer/mp4/mp4-track.ts
+++ b/src/demuxer/mp4/mp4-track.ts
@@ -14,7 +14,7 @@ import { Avc1 } from './atoms/avc1';
import {getLogger} from '../../utils/logger';
import { toMicroseconds } from '../../utils/timescale';
-const {log, debug, warn} = getLogger('Mp4Track');
+const {log, debug, warn} = getLogger('Mp4Track', 0);
export type Mp4TrackDefaults = {
sampleDuration: number;
@@ -144,10 +144,10 @@ export class Mp4Track extends Track {
}
public appendFrame(frame: Frame) {
- if (!frame.hasUnscaledIntegerTiming()) {
+ if (!frame.hasUnnormalizedIntegerTiming()) {
throw new Error('Frame must have unscaled-int sample timing');
}
- this.lastPtsUnscaledUint += frame.durationUnscaled;
+ this.lastPtsUnscaledUint += frame.scaledDuration;
this.lastPts += frame.duration;
this.duration += frame.duration;
this.frames.push(frame);
@@ -201,9 +201,9 @@ export class Mp4Track extends Track {
cto
);
- newFrame.durationUnscaled = sampleDuration;
- newFrame.timeUnscaled = this.lastPtsUnscaledUint;
- newFrame.ptOffsetUnscaled = sample.compositionTimeOffset || 0;
+ newFrame.scaledDuration = sampleDuration;
+ newFrame.scaledDecodingTime = this.lastPtsUnscaledUint;
+ newFrame.scaledPresentationTimeOffset = sample.compositionTimeOffset || 0;
newFrame.timescale = timescale;
this.appendFrame(newFrame);
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index d08131f..3cbf63d 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -152,6 +152,9 @@ export class MpegTSDemuxer implements IDemuxer {
}
private processTSPacket(packet: Uint8Array): void {
+
+ console.log('packet found at offset:', packet.byteOffset)
+
this.packetsCount++;
const packetParser: BitReader = new BitReader(packet);
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index ccfd4f5..ecf05df 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -64,7 +64,8 @@ export class AdtsReader extends PayloadReader {
AdtsReader.ADTS_HEADER_SIZE + this.currentFrameSize)) {
break;
}
- this.frames.push(new Frame(Frame.IDR_FRAME, this.timeUs, this.currentFrameSize));
+ this.frames.push(new Frame(Frame.IDR_FRAME, this.timeUs, this.currentFrameSize,
+ this.frameDuration, this.dataOffset));
this.timeUs = this.timeUs + this.frameDuration;
this.dataOffset += (AdtsReader.ADTS_SYNC_SIZE + AdtsReader.ADTS_HEADER_SIZE +
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index a43a019..09596d6 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -38,6 +38,8 @@ export class H264Reader extends PayloadReader {
public pps: boolean;
public pendingBytes: number;
+ private frameBytesOffset: number;
+
constructor() {
super();
this.pendingBytes = 0;
@@ -54,6 +56,7 @@ export class H264Reader extends PayloadReader {
if (this.dataBuffer.byteLength > 0) {
const offset: number = this.findNextNALUnit(0);
if (offset < this.dataBuffer.byteLength) {
+ this.frameBytesOffset = this.getFirstPacketDataOffset() + this.dataOffset + offset;
this.processNALUnit(offset, this.dataBuffer.byteLength, this.dataBuffer[offset + 3] & 0x1F);
}
}
@@ -68,6 +71,9 @@ export class H264Reader extends PayloadReader {
}
public consumeData(pts: number): void {
+
+ console.log('consumeData');
+
if (!this.dataBuffer) {
return;
}
@@ -75,12 +81,13 @@ export class H264Reader extends PayloadReader {
this.timeUs = this.firstTimestamp = pts;
}
- // process any possible reminding data
+ // process any possible remaining data
let nextNalUnit: number = 0;
let offset: number = 0;
- if (this.pendingBytes) {
+ if (this.pendingBytes > 0) {
nextNalUnit = this.findNextNALUnit(this.pendingBytes);
if (nextNalUnit < this.dataBuffer.byteLength) {
+ this.frameBytesOffset = this.getFirstPacketDataOffset() + this.dataOffset + 0;
this.processNALUnit(0, nextNalUnit, this.dataBuffer[offset + 3] & 0x1F);
offset = nextNalUnit;
}
@@ -97,11 +104,14 @@ export class H264Reader extends PayloadReader {
if (this.dataBuffer.byteLength > 0) {
while (nextNalUnit < this.dataBuffer.byteLength) {
nextNalUnit = this.findNextNALUnit(offset + 3);
- if (nextNalUnit < this.dataBuffer.byteLength) {
- this.processNALUnit(offset, nextNalUnit, this.dataBuffer[offset + 3] & 0x1F);
- offset = nextNalUnit;
- }
+ this.frameBytesOffset = this.getFirstPacketDataOffset() + this.dataOffset + offset;
+ this.processNALUnit(offset, nextNalUnit, this.dataBuffer[offset + 3] & 0x1F);
+ offset = nextNalUnit;
}
+
+ console.log('shifting buffer:', offset)
+
+ this.dataOffset += offset;
this.dataBuffer = this.dataBuffer.subarray(offset);
this.pendingBytes = this.dataBuffer.byteLength;
}
@@ -126,7 +136,7 @@ export class H264Reader extends PayloadReader {
} else if (nalType === NAL_UNIT_TYPE.AUD) {
this.parseAUDNALUnit(start, limit);
} else if (nalType === NAL_UNIT_TYPE.IDR) {
- this.addNewFrame(Frame.IDR_FRAME, limit - start);
+ this.addNewFrame(Frame.IDR_FRAME, limit - start, NaN);
} else if (nalType === NAL_UNIT_TYPE.SEI) {
this.parseSEINALUnit(start, limit);
} else if (nalType === NAL_UNIT_TYPE.SLICE) {
@@ -182,7 +192,7 @@ export class H264Reader extends PayloadReader {
const sliceType: number = sliceParser.readUEG();
const type: string = this.getSliceTypeName(sliceType);
if (this.sps && this.pps) {
- this.addNewFrame(type, limit - start);
+ this.addNewFrame(type, limit - start, NaN);
} else {
// console.warn('Slice ' + type + ' received without sps/pps been set');
}
@@ -238,8 +248,12 @@ export class H264Reader extends PayloadReader {
}
}
- private addNewFrame(frameType: string, frameSize: number): void {
- this.frames.push(new Frame(frameType, this.timeUs, frameSize));
+ private addNewFrame(frameType: string, frameSize: number, duration: number): void {
+
+ console.log('frame bytes offset:', this.frameBytesOffset)
+
+ const frame = new Frame(frameType, this.timeUs, frameSize, duration, this.frameBytesOffset);
+ this.frames.push(frame);
}
}
diff --git a/src/demuxer/ts/payload/payload-reader.ts b/src/demuxer/ts/payload/payload-reader.ts
index 9fe266d..28a82a3 100644
--- a/src/demuxer/ts/payload/payload-reader.ts
+++ b/src/demuxer/ts/payload/payload-reader.ts
@@ -1,7 +1,7 @@
import { BitReader } from '../../../utils/bit-reader';
import { Frame } from '../../frame';
-export class PayloadReader {
+export abstract class PayloadReader {
public firstTimestamp: number = -1;
public timeUs: number = -1;
public frames: Frame[] = [];
@@ -9,8 +9,21 @@ export class PayloadReader {
protected dataOffset: number;
+ private firstPacketDataOffset: number;
+
+ constructor() {
+ this.reset();
+ }
+
public append(packet: BitReader): void {
+
+ if (isNaN(this.firstPacketDataOffset)) {
+ console.log('first packet data offset:', packet.buffer.byteOffset + packet.bytesOffset())
+ this.firstPacketDataOffset = packet.buffer.byteOffset + packet.bytesOffset();
+ }
+
const dataToAppend: Uint8Array = packet.buffer.subarray(packet.bytesOffset());
+
if (!this.dataBuffer) {
this.dataBuffer = dataToAppend;
} else {
@@ -37,9 +50,7 @@ export class PayloadReader {
this.dataOffset = 0;
}
- public consumeData(pts: number): void {
- throw new Error('Should have implemented this');
- }
+ public abstract consumeData(pts: number): void;
public getMimeType(): string {
return 'Unknown';
@@ -56,4 +67,8 @@ export class PayloadReader {
public getLastPTS(): number {
return this.timeUs;
}
+
+ public getFirstPacketDataOffset(): number {
+ return this.firstPacketDataOffset;
+ }
}
diff --git a/src/index.ts b/src/index.ts
index f022ba0..8bff4b8 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,31 +1,17 @@
import { MpegTSDemuxer } from './demuxer/ts/mpegts-demuxer';
import { Mp4Demuxer } from './demuxer/mp4/mp4-demuxer';
import { WebMDemuxer } from './demuxer/webm/webm-demuxer';
-
-import { TSTrack } from './demuxer/ts/ts-track';
-
-import { IDemuxer, TracksHash } from './demuxer/demuxer';
-import { Track } from './demuxer/track';
-import { Frame } from './demuxer/frame';
-
-import { Atom, ContainerAtom } from './demuxer/mp4/atoms/atom';
-
-import { FrameRate, Size } from './codecs/video-types';
-
import { WebWorker } from './utils/web-worker';
-export type MpegTSDemuxer = MpegTSDemuxer;
-export type TSTrack = TSTrack;
-export type Mp4Demuxer = Mp4Demuxer;
-export type WebMDemuxer = WebMDemuxer;
-export type IDemuxer = IDemuxer;
-export type TracksHash = TracksHash;
-export type Track = Track;
-export type Frame = Frame;
-export type FrameRate = FrameRate;
-export type Size = Size;
-
-export type Atom = Atom;
+export { MpegTSDemuxer } from './demuxer/ts/mpegts-demuxer';
+export { Mp4Demuxer } from './demuxer/mp4/mp4-demuxer';
+export { WebMDemuxer } from './demuxer/webm/webm-demuxer';
+export { FrameRate, Size } from './codecs/video-types';
+export { IDemuxer, TracksHash } from './demuxer/demuxer';
+export { Track } from './demuxer/track';
+export { Frame } from './demuxer/frame';
+export { Atom, ContainerAtom } from './demuxer/mp4/atoms/atom';
+export { TSTrack } from './demuxer/ts/ts-track';
export function createMpegTSDemuxer(): MpegTSDemuxer { // Q: these methods should return IDemuxer to maintain abstraction solid?
return new MpegTSDemuxer();
From 0725c523412460f2db0142ca8db8d9dca09b4313 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 10 Jul 2019 12:48:45 +0200
Subject: [PATCH 042/197] move mp4 sample flags parse function to mp4 subtree
---
src/demuxer/mp4/mp4-sample-flags.ts | 12 ++++++++++++
src/utils/byte-parser-utils.ts | 12 ------------
2 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/src/demuxer/mp4/mp4-sample-flags.ts b/src/demuxer/mp4/mp4-sample-flags.ts
index 92131d0..56cf580 100644
--- a/src/demuxer/mp4/mp4-sample-flags.ts
+++ b/src/demuxer/mp4/mp4-sample-flags.ts
@@ -7,3 +7,15 @@ export class Mp4SampleFlags {
public isNonSyncSample: number;
public degradationPriority: number;
}
+
+export function parseIsoBoxSampleFlags(flags: number): Mp4SampleFlags {
+ return {
+ isLeading: (flags[0] & 0x0c) >>> 2,
+ dependsOn: flags[0] & 0x03,
+ isDependedOn: (flags[1] & 0xc0) >>> 6,
+ hasRedundancy: (flags[1] & 0x30) >>> 4,
+ paddingValue: (flags[1] & 0x0e) >>> 1,
+ isNonSyncSample: flags[1] & 0x01,
+ degradationPriority: (flags[2] << 8) | flags[3]
+ };
+}
diff --git a/src/utils/byte-parser-utils.ts b/src/utils/byte-parser-utils.ts
index c196993..eb7c3d2 100644
--- a/src/utils/byte-parser-utils.ts
+++ b/src/utils/byte-parser-utils.ts
@@ -79,18 +79,6 @@ export default class ByteParserUtils {
return new Date(seconds * 1000 - 2082844800000);
}
- public static parseIsoBoxSampleFlags(flags: number): Mp4SampleFlags {
- return {
- isLeading: (flags[0] & 0x0c) >>> 2,
- dependsOn: flags[0] & 0x03,
- isDependedOn: (flags[1] & 0xc0) >>> 6,
- hasRedundancy: (flags[1] & 0x30) >>> 4,
- paddingValue: (flags[1] & 0x0e) >>> 1,
- isNonSyncSample: flags[1] & 0x01,
- degradationPriority: (flags[2] << 8) | flags[3]
- };
- }
-
public static parseBufferToHex(buffer: Uint8Array, offset: number, end: number): string {
let str: string = '';
for (let i: number = offset; i < end; i++) {
From b34ef5400c3239d578d15767469a7ee8bf2d5a80 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 10 Jul 2019 12:49:28 +0200
Subject: [PATCH 043/197] add PPS parsing support
---
src/codecs/h264/nal-units.ts | 22 +++++-
src/codecs/h264/sps-parser.ts | 122 +++++++++++++++++++++-------------
src/demuxer/mp4/atoms/avcC.ts | 7 +-
3 files changed, 101 insertions(+), 50 deletions(-)
diff --git a/src/codecs/h264/nal-units.ts b/src/codecs/h264/nal-units.ts
index 4bb7251..bb3ad12 100644
--- a/src/codecs/h264/nal-units.ts
+++ b/src/codecs/h264/nal-units.ts
@@ -1,9 +1,25 @@
import { Size, FrameRate } from '../video-types';
export class Sps {
- constructor (public profile: string, public level: string, public bitDepth: number,
- public chromaFormat: number, chromaFormatStr: string, public frameRate: FrameRate,
- public sar: Size, public codecSize: Size, public presentSize: Size ) {
+ constructor (
+ public id: number,
+ public profile: string,
+ public level: string,
+ public bitDepth: number,
+ public chromaFormat: number,
+ public chromaFormatStr: string,
+ public frameRate: FrameRate,
+ public sar: Size,
+ public codecSize: Size,
+ public presentSize: Size ) {
// do nothing
}
}
+
+export class Pps {
+ constructor(
+ public id: number,
+ public spsId: number,
+ public entropyCodingModeFlag: boolean,
+ ) {}
+}
diff --git a/src/codecs/h264/sps-parser.ts b/src/codecs/h264/sps-parser.ts
index 7fba59c..6719e4f 100644
--- a/src/codecs/h264/sps-parser.ts
+++ b/src/codecs/h264/sps-parser.ts
@@ -1,17 +1,70 @@
import { BitReader } from '../../utils/bit-reader';
-import { Sps } from './nal-units';
+import { Sps, Pps } from './nal-units';
import { Size, FrameRate } from '../video-types';
export class SPSParser {
- public static parseSPS(data: Uint8Array): Sps {
+
+ static getProfileString(profile_idc: number): string {
+ switch (profile_idc) {
+ case 66:
+ return 'Baseline';
+ case 77:
+ return 'Main';
+ case 88:
+ return 'Extended';
+ case 100:
+ return 'High';
+ case 110:
+ return 'High10';
+ case 122:
+ return 'High422';
+ case 244:
+ return 'High444';
+ default:
+ return 'Unknown';
+ }
+ }
+
+ static getLevelString(level_idc: number): string {
+ return (level_idc / 10).toFixed(1);
+ }
+
+ static getChromaFormatString(chroma: number): string {
+ switch (chroma) {
+ case 420:
+ return '4:2:0';
+ case 422:
+ return '4:2:2';
+ case 444:
+ return '4:4:4';
+ default:
+ return 'Unknown';
+ }
+ }
+
+ static parsePPS(data: Uint8Array): Pps {
+ const gb: BitReader = new BitReader(data);
+
+ const id: number = gb.readUEG();
+ const spsId: number = gb.readUEG();
+ const entropyCodingMode: boolean = gb.readBool();
+
+ gb.destroy();
+
+ return new Pps(id, spsId, entropyCodingMode);
+ }
+
+ static parseSPS(data: Uint8Array): Sps {
let gb: BitReader = new BitReader(data);
const profile_idc: number = gb.readByte();
gb.readByte();
const level_idc: number = gb.readByte();
- gb.readUEG();
+
+ const seq_parameter_set_id: number = gb.readUEG();
const profile_string: string = SPSParser.getProfileString(profile_idc);
const level_string: string = SPSParser.getLevelString(level_idc);
+
let chroma_format_idc: number = 1;
let chroma_format: number = 420;
let chroma_format_table: number[] = [0, 420, 422, 444];
@@ -84,10 +137,16 @@ export class SPSParser {
frame_crop_bottom_offset = gb.readUEG();
}
- let sar_width: number = 1, sar_height: number = 1;
- let fps: number = 0, fps_fixed: boolean = true, fps_num: number = 0, fps_den: number = 0;
+ let sar_width: number = 1;
+ let sar_height: number = 1;
+
+ let fps: number = 0,
+ fps_fixed: boolean = true,
+ fps_num: number = 0,
+ fps_den: number = 0;
let vui_parameters_present_flag: boolean = gb.readBool();
+
if (vui_parameters_present_flag) {
if (gb.readBool()) { // aspect_ratio_info_present_flag
const aspect_ratio_idc: number = gb.readByte();
@@ -154,47 +213,18 @@ export class SPSParser {
gb.destroy();
gb = null;
- return new Sps(profile_string, level_string, bit_depth, chroma_format,
- SPSParser.getChromaFormatString(chroma_format), new FrameRate(fps_fixed, fps, fps_den, fps_num),
- new Size(sar_width, sar_height), new Size(codec_width, codec_height), new Size(present_width, codec_height));
- }
-
- private static getProfileString(profile_idc: number): string {
- switch (profile_idc) {
- case 66:
- return 'Baseline';
- case 77:
- return 'Main';
- case 88:
- return 'Extended';
- case 100:
- return 'High';
- case 110:
- return 'High10';
- case 122:
- return 'High422';
- case 244:
- return 'High444';
- default:
- return 'Unknown';
- }
- }
-
- private static getLevelString(level_idc: number): string {
- return (level_idc / 10).toFixed(1);
- }
-
- private static getChromaFormatString(chroma: number): string {
- switch (chroma) {
- case 420:
- return '4:2:0';
- case 422:
- return '4:2:2';
- case 444:
- return '4:4:4';
- default:
- return 'Unknown';
- }
+ return new Sps(
+ seq_parameter_set_id,
+ profile_string,
+ level_string,
+ bit_depth,
+ chroma_format,
+ SPSParser.getChromaFormatString(chroma_format),
+ new FrameRate(fps_fixed, fps, fps_den, fps_num),
+ new Size(sar_width, sar_height),
+ new Size(codec_width, codec_height),
+ new Size(present_width, codec_height)
+ );
}
private static skipScalingList(gb: BitReader, count: number): void {
diff --git a/src/demuxer/mp4/atoms/avcC.ts b/src/demuxer/mp4/atoms/avcC.ts
index 3f02c5c..a546560 100644
--- a/src/demuxer/mp4/atoms/avcC.ts
+++ b/src/demuxer/mp4/atoms/avcC.ts
@@ -1,7 +1,7 @@
import ByteParserUtils from '../../../utils/byte-parser-utils';
import { Atom } from './atom';
import { SPSParser } from '../../../codecs/h264/sps-parser';
-import { Sps } from '../../../codecs/h264/nal-units';
+import { Sps, Pps } from '../../../codecs/h264/nal-units';
export class AvcC extends Atom {
public version: number;
@@ -14,6 +14,7 @@ export class AvcC extends Atom {
public sps: Uint8Array[];
public spsParsed: Sps[];
public pps: Uint8Array[];
+ public ppsParsed: Pps[];
public data: Uint8Array;
public static parse(data: Uint8Array): Atom {
@@ -42,12 +43,16 @@ export class AvcC extends Atom {
avcC.numOfPictureParameterSets = data[offset] & 0x1f;
avcC.pps = [];
+ avcC.ppsParsed = [];
offset++;
for (let i: number = 0; i < avcC.numOfPictureParameterSets; i++) {
const ppsSize: number = ByteParserUtils.parseUint16(data, offset);
offset += 2;
+ const pps: Uint8Array = new Uint8Array(data.subarray(offset, offset + ppsSize));
avcC.pps.push(new Uint8Array(data.subarray(offset, offset + ppsSize)));
offset += ppsSize;
+
+ avcC.ppsParsed.push(SPSParser.parsePPS(pps.subarray(1, ppsSize)))
}
return avcC;
From f33c48388a99a7a7ab44b2acbce13da7f1da9708 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 10 Jul 2019 12:49:52 +0200
Subject: [PATCH 044/197] h264 parsing utils cosmetics
---
src/codecs/video-types.ts | 7 ++-
src/demuxer/ts/payload/h264-reader.ts | 55 +++++++++++-------------
src/demuxer/ts/payload/payload-reader.ts | 1 -
3 files changed, 30 insertions(+), 33 deletions(-)
diff --git a/src/codecs/video-types.ts b/src/codecs/video-types.ts
index 3500c89..49c1d51 100644
--- a/src/codecs/video-types.ts
+++ b/src/codecs/video-types.ts
@@ -1,6 +1,9 @@
export class FrameRate {
- constructor (public fixed: boolean, public fps: number,
- public fpsDen: number, public fpsNum: number) {
+ constructor (
+ public fixed: boolean,
+ public fps: number,
+ public fpsDen: number,
+ public fpsNum: number) {
// do nothing
}
}
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index 09596d6..4c749d9 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -34,6 +34,30 @@ export class Fraction {
}
export class H264Reader extends PayloadReader {
+
+ static getNALUnitName(nalType: number): string {
+ switch (nalType) {
+ case NAL_UNIT_TYPE.SLICE:
+ return 'SLICE';
+ case NAL_UNIT_TYPE.SEI:
+ return 'SEI';
+ case NAL_UNIT_TYPE.PPS:
+ return 'PPS';
+ case NAL_UNIT_TYPE.SPS:
+ return 'SPS';
+ case NAL_UNIT_TYPE.AUD:
+ return 'AUD';
+ case NAL_UNIT_TYPE.IDR:
+ return 'IDR';
+ case NAL_UNIT_TYPE.END_SEQUENCE:
+ return 'END SEQUENCE';
+ case NAL_UNIT_TYPE.END_STREAM:
+ return 'END STREAM';
+ default:
+ return 'Unknown';
+ }
+ }
+
public sps: Sps;
public pps: boolean;
public pendingBytes: number;
@@ -72,8 +96,6 @@ export class H264Reader extends PayloadReader {
public consumeData(pts: number): void {
- console.log('consumeData');
-
if (!this.dataBuffer) {
return;
}
@@ -109,8 +131,6 @@ export class H264Reader extends PayloadReader {
offset = nextNalUnit;
}
- console.log('shifting buffer:', offset)
-
this.dataOffset += offset;
this.dataBuffer = this.dataBuffer.subarray(offset);
this.pendingBytes = this.dataBuffer.byteLength;
@@ -129,6 +149,7 @@ export class H264Reader extends PayloadReader {
}
private processNALUnit(start: number, limit: number, nalType: number): void {
+
if (nalType === NAL_UNIT_TYPE.SPS) {
this.parseSPSNALUnit(start, limit);
} else if (nalType === NAL_UNIT_TYPE.PPS) {
@@ -225,33 +246,7 @@ export class H264Reader extends PayloadReader {
}
}
- private getNALUnitName(nalType: number): string {
- switch (nalType) {
- case NAL_UNIT_TYPE.SLICE:
- return 'SLICE';
- case NAL_UNIT_TYPE.SEI:
- return 'SEI';
- case NAL_UNIT_TYPE.PPS:
- return 'PPS';
- case NAL_UNIT_TYPE.SPS:
- return 'SPS';
- case NAL_UNIT_TYPE.AUD:
- return 'AUD';
- case NAL_UNIT_TYPE.IDR:
- return 'IDR';
- case NAL_UNIT_TYPE.END_SEQUENCE:
- return 'END SEQUENCE';
- case NAL_UNIT_TYPE.END_STREAM:
- return 'END STREAM';
- default:
- return 'Unknown';
- }
- }
-
private addNewFrame(frameType: string, frameSize: number, duration: number): void {
-
- console.log('frame bytes offset:', this.frameBytesOffset)
-
const frame = new Frame(frameType, this.timeUs, frameSize, duration, this.frameBytesOffset);
this.frames.push(frame);
}
diff --git a/src/demuxer/ts/payload/payload-reader.ts b/src/demuxer/ts/payload/payload-reader.ts
index 28a82a3..316e777 100644
--- a/src/demuxer/ts/payload/payload-reader.ts
+++ b/src/demuxer/ts/payload/payload-reader.ts
@@ -18,7 +18,6 @@ export abstract class PayloadReader {
public append(packet: BitReader): void {
if (isNaN(this.firstPacketDataOffset)) {
- console.log('first packet data offset:', packet.buffer.byteOffset + packet.bytesOffset())
this.firstPacketDataOffset = packet.buffer.byteOffset + packet.bytesOffset();
}
From 3dab78508d1cab5c7289593c12087e80d9f3ebe5 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 10 Jul 2019 12:50:34 +0200
Subject: [PATCH 045/197] mp4 demuxer: support for fragmented mode without sidx
box to find timescale inside MVHD atom
---
src/demuxer/mp4/mp4-demuxer.ts | 10 +++++++---
src/demuxer/mp4/mp4-track.ts | 11 ++++++++---
2 files changed, 15 insertions(+), 6 deletions(-)
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index 12d3a36..408352e 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -23,11 +23,11 @@ import { Stss } from './atoms/stss';
import { Stco } from './atoms/stco';
import { Mdhd } from './atoms/mdhd';
-import {getLogger} from '../../utils/logger';
+import {getLogger, LoggerLevels} from '../../utils/logger';
import { Mp4SampleTable } from './mp4-sample-table';
import { Esds } from './atoms/esds';
-import { LoggerLevels } from '../../../../../logger';
+import { Mvhd } from './atoms/mvhd';
const {log, warn} = getLogger('Mp4Demuxer', LoggerLevels.OFF);
@@ -203,6 +203,10 @@ export class Mp4Demuxer implements IDemuxer {
this._getLastTrackCreated().addTrunAtom(atom);
break;
+ case Atom.mvhd:
+ this.lastTimescale = (atom as Mvhd).timescale;
+ break;
+
// Plain-old MOV ie unfragmented mode ...
case Atom.mdhd:
@@ -280,7 +284,7 @@ export class Mp4Demuxer implements IDemuxer {
this.lastTrackDataOffset
);
if (this.lastTimescale !== null) {
- log('got timescale:', this.lastTimescale);
+ log('setting parent timescale on track:', this.lastTimescale);
track.setTimescale(this.lastTimescale);
}
this.tracks[this.lastTrackId] = track;
diff --git a/src/demuxer/mp4/mp4-track.ts b/src/demuxer/mp4/mp4-track.ts
index b9d24b4..ec8433c 100644
--- a/src/demuxer/mp4/mp4-track.ts
+++ b/src/demuxer/mp4/mp4-track.ts
@@ -11,10 +11,10 @@ import { Sidx } from './atoms/sidx';
import { Trun, SampleFlags } from './atoms/trun';
import { Avc1 } from './atoms/avc1';
-import {getLogger} from '../../utils/logger';
+import { getLogger, LoggerLevels } from '../../utils/logger';
import { toMicroseconds } from '../../utils/timescale';
-const {log, debug, warn} = getLogger('Mp4Track', 0);
+const {debug, warn} = getLogger('Mp4Track', LoggerLevels.WARN);
export type Mp4TrackDefaults = {
sampleDuration: number;
@@ -23,6 +23,7 @@ export type Mp4TrackDefaults = {
}
export class Mp4Track extends Track {
+
private sidx: Sidx = null;
private trunInfo: Trun[] = [];
private trunInfoReadIndex: number = 0;
@@ -167,7 +168,11 @@ export class Mp4Track extends Track {
return;
}
- const timescale: number = this.sidx ? this.sidx.timescale : 1;
+ const timescale: number = this.sidx ? this.sidx.timescale : this.getTimescale();
+
+ if (!this.sidx) {
+ warn('No sidx found, using parent timescale:', timescale);
+ }
const sampleRunDataOffset: number = trun.dataOffset + this.getFinalSampleDataOffset();
From 673ba498ffb2a35964792c2e4dddd620ea4b43b7 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 10 Jul 2019 14:08:26 +0200
Subject: [PATCH 046/197] rename sps-parser to param-set-parser
---
.../h264/{sps-parser.ts => param-set-parser.ts} | 12 ++++++------
src/demuxer/mp4/atoms/avcC.ts | 6 +++---
src/demuxer/ts/payload/h264-reader.ts | 4 ++--
3 files changed, 11 insertions(+), 11 deletions(-)
rename src/codecs/h264/{sps-parser.ts => param-set-parser.ts} (95%)
diff --git a/src/codecs/h264/sps-parser.ts b/src/codecs/h264/param-set-parser.ts
similarity index 95%
rename from src/codecs/h264/sps-parser.ts
rename to src/codecs/h264/param-set-parser.ts
index 6719e4f..7cac580 100644
--- a/src/codecs/h264/sps-parser.ts
+++ b/src/codecs/h264/param-set-parser.ts
@@ -2,7 +2,7 @@ import { BitReader } from '../../utils/bit-reader';
import { Sps, Pps } from './nal-units';
import { Size, FrameRate } from '../video-types';
-export class SPSParser {
+export class ParameterSetParser {
static getProfileString(profile_idc: number): string {
switch (profile_idc) {
@@ -62,8 +62,8 @@ export class SPSParser {
const seq_parameter_set_id: number = gb.readUEG();
- const profile_string: string = SPSParser.getProfileString(profile_idc);
- const level_string: string = SPSParser.getLevelString(level_idc);
+ const profile_string: string = ParameterSetParser.getProfileString(profile_idc);
+ const level_string: string = ParameterSetParser.getLevelString(level_idc);
let chroma_format_idc: number = 1;
let chroma_format: number = 420;
@@ -91,9 +91,9 @@ export class SPSParser {
for (let i: number = 0; i < scaling_list_count; i++) {
if (gb.readBool()) {
if (i < 6) {
- SPSParser.skipScalingList(gb, 16);
+ ParameterSetParser.skipScalingList(gb, 16);
} else {
- SPSParser.skipScalingList(gb, 64);
+ ParameterSetParser.skipScalingList(gb, 64);
}
}
}
@@ -219,7 +219,7 @@ export class SPSParser {
level_string,
bit_depth,
chroma_format,
- SPSParser.getChromaFormatString(chroma_format),
+ ParameterSetParser.getChromaFormatString(chroma_format),
new FrameRate(fps_fixed, fps, fps_den, fps_num),
new Size(sar_width, sar_height),
new Size(codec_width, codec_height),
diff --git a/src/demuxer/mp4/atoms/avcC.ts b/src/demuxer/mp4/atoms/avcC.ts
index a546560..e5aecf4 100644
--- a/src/demuxer/mp4/atoms/avcC.ts
+++ b/src/demuxer/mp4/atoms/avcC.ts
@@ -1,6 +1,6 @@
import ByteParserUtils from '../../../utils/byte-parser-utils';
import { Atom } from './atom';
-import { SPSParser } from '../../../codecs/h264/sps-parser';
+import { ParameterSetParser } from '../../../codecs/h264/param-set-parser';
import { Sps, Pps } from '../../../codecs/h264/nal-units';
export class AvcC extends Atom {
@@ -38,7 +38,7 @@ export class AvcC extends Atom {
avcC.sps.push(sps);
offset += spsSize;
- avcC.spsParsed.push(SPSParser.parseSPS(sps.subarray(1, spsSize)));
+ avcC.spsParsed.push(ParameterSetParser.parseSPS(sps.subarray(1, spsSize)));
}
avcC.numOfPictureParameterSets = data[offset] & 0x1f;
@@ -52,7 +52,7 @@ export class AvcC extends Atom {
avcC.pps.push(new Uint8Array(data.subarray(offset, offset + ppsSize)));
offset += ppsSize;
- avcC.ppsParsed.push(SPSParser.parsePPS(pps.subarray(1, ppsSize)))
+ avcC.ppsParsed.push(ParameterSetParser.parsePPS(pps.subarray(1, ppsSize)))
}
return avcC;
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index 4c749d9..c005b43 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -2,7 +2,7 @@ import { BitReader } from '../../../utils/bit-reader';
import { PayloadReader } from './payload-reader';
import { Frame } from '../../frame';
import { Track } from '../../track';
-import { SPSParser } from '../../../codecs/h264/sps-parser';
+import { ParameterSetParser } from '../../../codecs/h264/param-set-parser';
import { Sps } from '../../../codecs/h264/nal-units';
enum NAL_UNIT_TYPE {
@@ -166,7 +166,7 @@ export class H264Reader extends PayloadReader {
}
private parseSPSNALUnit(start: number, limit: number): void {
- this.sps = SPSParser.parseSPS(this.dataBuffer.subarray(start + 4, limit));
+ this.sps = ParameterSetParser.parseSPS(this.dataBuffer.subarray(start + 4, limit));
}
private skipScalingList(parser: BitReader, size: number): void {
From 30f14a4ad05ef0cd10844e3c190f2c282dadbd95 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 10 Jul 2019 12:50:34 +0200
Subject: [PATCH 047/197] mp4 demuxer: support for fragmented mode without sidx
box to find timescale inside MVHD atom
---
src/demuxer/mp4/mp4-demuxer.ts | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index 408352e..4a0230c 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -27,7 +27,10 @@ import {getLogger, LoggerLevels} from '../../utils/logger';
import { Mp4SampleTable } from './mp4-sample-table';
import { Esds } from './atoms/esds';
+<<<<<<< HEAD
import { Mvhd } from './atoms/mvhd';
+=======
+>>>>>>> cf60f5e... fix logger usage: was relying on top-project mmjs logger
const {log, warn} = getLogger('Mp4Demuxer', LoggerLevels.OFF);
From d04c4b061741a3f013e0bfc3b686a53eb961bbac Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 14 Jan 2019 15:41:02 +0100
Subject: [PATCH 048/197] mp4-sample-table: loglevel default off
---
src/demuxer/mp4/mp4-sample-table.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/demuxer/mp4/mp4-sample-table.ts b/src/demuxer/mp4/mp4-sample-table.ts
index 5aa8923..b4860dd 100644
--- a/src/demuxer/mp4/mp4-sample-table.ts
+++ b/src/demuxer/mp4/mp4-sample-table.ts
@@ -10,9 +10,9 @@ import { Frame } from '../frame';
import { toMicroseconds } from '../../utils/timescale';
-import {getLogger} from '../../utils/logger';
+import {getLogger, LoggerLevels} from '../../utils/logger';
-const {log, debug, warn} = getLogger('Mp4SampleTable');
+const {log, debug} = getLogger('Mp4SampleTable', LoggerLevels.OFF);
export class Mp4SampleTable {
decodingTimestamps: Stts;
From 4c0bca3f7c1e2218621f067766e03d8b96804f9f Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 26 Feb 2019 02:19:54 +0100
Subject: [PATCH 049/197] sps parser fixes
---
src/codecs/h264/param-set-parser.ts | 22 ++++++++++++++++------
1 file changed, 16 insertions(+), 6 deletions(-)
diff --git a/src/codecs/h264/param-set-parser.ts b/src/codecs/h264/param-set-parser.ts
index 7cac580..474ed1c 100644
--- a/src/codecs/h264/param-set-parser.ts
+++ b/src/codecs/h264/param-set-parser.ts
@@ -145,9 +145,11 @@ export class ParameterSetParser {
fps_num: number = 0,
fps_den: number = 0;
+ // @see https://sourceforge.net/p/h264bitstream/code/HEAD/tree/trunk/h264bitstream/h264_stream.c#l363
let vui_parameters_present_flag: boolean = gb.readBool();
if (vui_parameters_present_flag) {
+
if (gb.readBool()) { // aspect_ratio_info_present_flag
const aspect_ratio_idc: number = gb.readByte();
const sar_w_table: number[] = [1, 12, 10, 16, 40, 24, 20, 32, 80, 18, 15, 64, 160, 4, 3, 2];
@@ -162,22 +164,30 @@ export class ParameterSetParser {
}
}
- if (gb.readBool()) {
+ if (gb.readBool()) { //overscan_info_present
gb.readBool();
}
- if (gb.readBool()) {
- gb.readBits(4);
+
+ if (gb.readBool()) { // video_signal_type_present
+ gb.readBits(3);
+ gb.readBool();
if (gb.readBool()) {
- gb.readBits(24);
+ gb.readBits(8);
+ gb.readBits(8);
+ gb.readBits(8);
}
}
- if (gb.readBool()) {
+
+ if (gb.readBool()) { // chroma_loc_info_present
gb.readUEG();
gb.readUEG();
}
- if (gb.readBool()) {
+
+ if (gb.readBool()) { // timing_info_present
+
const num_units_in_tick: number = gb.readBits(32);
const time_scale: number = gb.readBits(32);
+
fps_fixed = gb.readBool();
fps_num = time_scale;
From a389325118ab685996685ce6deff21b567d68731 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 14 Mar 2019 22:26:26 +0100
Subject: [PATCH 050/197] mp4 sample table: fix corner cases of ctts
decompression. when there is only one entry, we need to compute the amount of
chunks from dividing the number of frames already mapped by the
samplesPerChunk field.
---
src/demuxer/mp4/mp4-sample-table.ts | 42 +++++++++++++++++------------
1 file changed, 25 insertions(+), 17 deletions(-)
diff --git a/src/demuxer/mp4/mp4-sample-table.ts b/src/demuxer/mp4/mp4-sample-table.ts
index b4860dd..ca11d0e 100644
--- a/src/demuxer/mp4/mp4-sample-table.ts
+++ b/src/demuxer/mp4/mp4-sample-table.ts
@@ -38,7 +38,6 @@ export class Mp4SampleTable {
const frames: Frame[] = [];
const chunksDecompressed: {samplesPerChunk: number, sampleDescriptionIndex: number}[] = []
- const chunkOffsetsDecompressed: number[] = [];
this.decodingTimestamps.timeToSamples.forEach((entry: TimeToSampleEntry) => {
@@ -66,44 +65,49 @@ export class Mp4SampleTable {
}
});
- frameCount = 0;
+ if (frameCount !== frames.length) {
+ throw new Error('Sample-to-chunk-list decompression yields inconsistent sample count. Input data may be corrupt.');
+ }
- this.compositionTimestampOffsets && this.compositionTimestampOffsets.cTimeOffsetToSamples.forEach((entry: CTimeOffsetToSampleEntry) => {
- for (let i = 0; i < entry.sampleCount; i++) {
+ frameCount = 0;
- frames[frameCount]
- .setPresentationTimeOffsetUs(toMicroseconds(entry.sampleCTimeOffset, this._track.getTimescale()));
+ // Having a CTO table is not mandatory
+ if (this.compositionTimestampOffsets) {
+ this.compositionTimestampOffsets.cTimeOffsetToSamples.forEach((entry: CTimeOffsetToSampleEntry) => {
+ for (let i = 0; i < entry.sampleCount; i++) {
- frames[frameCount].scaledPresentationTimeOffset = entry.sampleCTimeOffset;
+ frames[frameCount]
+ .setPresentationTimeOffsetUs(toMicroseconds(entry.sampleCTimeOffset, this._track.getTimescale()));
- frameCount++; // note: here we incr the count after using it as an ordinal index
- }
- });
+ frames[frameCount].scaledPresentationTimeOffset = entry.sampleCTimeOffset;
- frameCount = 0;
+ frameCount++; // note: here we incr the count after using it as an ordinal index
+ }
+ });
+ }
this.samplesToChunkBox.sampleToChunks.forEach((sampleToChunkEntry: SampleToChunkEntry, index) => {
+
// the sample-to-chunk box contains a compressed list
// of possibly repeating properties (samplesPerChunk + sampleDescriptionIndex)
// we need to decompress this information by looking at firstChunkIndex
+
let chunksInThisEntry = 1;
if (index < this.samplesToChunkBox.sampleToChunks.length - 1) {
chunksInThisEntry = this.samplesToChunkBox.sampleToChunks[index + 1].firstChunk
- sampleToChunkEntry.firstChunk;
}
- for (let i=0; i < chunksInThisEntry; i++) {
- frameCount += this.samplesToChunkBox.sampleToChunks[index].samplesPerChunk
+ if (this.samplesToChunkBox.sampleToChunks.length === 1) {
+ chunksInThisEntry = frames.length / sampleToChunkEntry.samplesPerChunk;
+ }
+ for (let i=0; i < chunksInThisEntry; i++) {
chunksDecompressed.push(sampleToChunkEntry);
}
});
- if (frameCount !== frames.length) {
- throw new Error('Sample-to-chunk-list decompression yields inconsistent sample count. Input data may be corrupt.');
- }
-
frameCount = 0;
chunksDecompressed.forEach((chunkSampleInfo, index) => {
@@ -124,6 +128,10 @@ export class Mp4SampleTable {
});
+ if (frameCount !== frames.length) {
+ throw new Error('Sample-to-chunk-list decompression yields inconsistent sample count. Input data may be corrupt.');
+ }
+
// Finally, append all frames to our track
frames.forEach((frame) => {
this._track.appendFrame(frame)
From b2a5cf12086c7a6d976138e2664f46873a361e1a Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 19 Mar 2019 23:56:33 +0100
Subject: [PATCH 051/197] mp4 sample table decompression: handle all cases of
chunk-to-sample sets properly + centralize frame count assertion
---
src/demuxer/mp4/mp4-sample-table.ts | 32 +++++++++++++++++------------
1 file changed, 19 insertions(+), 13 deletions(-)
diff --git a/src/demuxer/mp4/mp4-sample-table.ts b/src/demuxer/mp4/mp4-sample-table.ts
index ca11d0e..62b8b03 100644
--- a/src/demuxer/mp4/mp4-sample-table.ts
+++ b/src/demuxer/mp4/mp4-sample-table.ts
@@ -39,6 +39,14 @@ export class Mp4SampleTable {
const frames: Frame[] = [];
const chunksDecompressed: {samplesPerChunk: number, sampleDescriptionIndex: number}[] = []
+ function assertAndResetFrameCount() {
+ if (frameCount !== frames.length) {
+ throw new Error('Sample-to-chunk-list decompression yields inconsistent sample count. Input data may be corrupt.');
+ }
+
+ frameCount = 0;
+ }
+
this.decodingTimestamps.timeToSamples.forEach((entry: TimeToSampleEntry) => {
for (let i = 0; i < entry.sampleCount; i++) {
@@ -65,11 +73,7 @@ export class Mp4SampleTable {
}
});
- if (frameCount !== frames.length) {
- throw new Error('Sample-to-chunk-list decompression yields inconsistent sample count. Input data may be corrupt.');
- }
-
- frameCount = 0;
+ assertAndResetFrameCount();
// Having a CTO table is not mandatory
if (this.compositionTimestampOffsets) {
@@ -84,6 +88,8 @@ export class Mp4SampleTable {
frameCount++; // note: here we incr the count after using it as an ordinal index
}
});
+
+ assertAndResetFrameCount();
}
this.samplesToChunkBox.sampleToChunks.forEach((sampleToChunkEntry: SampleToChunkEntry, index) => {
@@ -93,22 +99,24 @@ export class Mp4SampleTable {
// we need to decompress this information by looking at firstChunkIndex
let chunksInThisEntry = 1;
- if (index < this.samplesToChunkBox.sampleToChunks.length - 1) {
- chunksInThisEntry = this.samplesToChunkBox.sampleToChunks[index + 1].firstChunk
- - sampleToChunkEntry.firstChunk;
- }
if (this.samplesToChunkBox.sampleToChunks.length === 1) {
chunksInThisEntry = frames.length / sampleToChunkEntry.samplesPerChunk;
+ } else if (index < this.samplesToChunkBox.sampleToChunks.length - 1) {
+ chunksInThisEntry = this.samplesToChunkBox.sampleToChunks[index + 1].firstChunk
+ - sampleToChunkEntry.firstChunk;
+ } else { // last chunk when not only chunk
+ chunksInThisEntry = (frames.length - frameCount) / sampleToChunkEntry.samplesPerChunk;
}
for (let i=0; i < chunksInThisEntry; i++) {
chunksDecompressed.push(sampleToChunkEntry);
+ frameCount += sampleToChunkEntry.samplesPerChunk;
}
});
- frameCount = 0;
+ assertAndResetFrameCount();
chunksDecompressed.forEach((chunkSampleInfo, index) => {
@@ -128,9 +136,7 @@ export class Mp4SampleTable {
});
- if (frameCount !== frames.length) {
- throw new Error('Sample-to-chunk-list decompression yields inconsistent sample count. Input data may be corrupt.');
- }
+ assertAndResetFrameCount();
// Finally, append all frames to our track
frames.forEach((frame) => {
From 37ea758591101b774fb2db4744a4ce1b81564a33 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 10 Jul 2019 14:34:02 +0200
Subject: [PATCH 052/197] fix merge conflict
---
src/demuxer/mp4/mp4-demuxer.ts | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index 4a0230c..408352e 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -27,10 +27,7 @@ import {getLogger, LoggerLevels} from '../../utils/logger';
import { Mp4SampleTable } from './mp4-sample-table';
import { Esds } from './atoms/esds';
-<<<<<<< HEAD
import { Mvhd } from './atoms/mvhd';
-=======
->>>>>>> cf60f5e... fix logger usage: was relying on top-project mmjs logger
const {log, warn} = getLogger('Mp4Demuxer', LoggerLevels.OFF);
From 22e9a1f1be5488f4e237e85eec3215242e64fa24 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 10 Jul 2019 15:58:11 +0200
Subject: [PATCH 053/197] add level and profile idc values to SPS object
---
src/codecs/h264/nal-units.ts | 2 ++
src/codecs/h264/param-set-parser.ts | 6 ++++--
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/codecs/h264/nal-units.ts b/src/codecs/h264/nal-units.ts
index bb3ad12..7ee48b0 100644
--- a/src/codecs/h264/nal-units.ts
+++ b/src/codecs/h264/nal-units.ts
@@ -4,7 +4,9 @@ export class Sps {
constructor (
public id: number,
public profile: string,
+ public profileIdc: number,
public level: string,
+ public levelIdc: number,
public bitDepth: number,
public chromaFormat: number,
public chromaFormatStr: string,
diff --git a/src/codecs/h264/param-set-parser.ts b/src/codecs/h264/param-set-parser.ts
index 474ed1c..8cbb688 100644
--- a/src/codecs/h264/param-set-parser.ts
+++ b/src/codecs/h264/param-set-parser.ts
@@ -21,7 +21,7 @@ export class ParameterSetParser {
case 244:
return 'High444';
default:
- return 'Unknown';
+ return 'Unspecified Profile-IDC value: ' + profile_idc;
}
}
@@ -38,7 +38,7 @@ export class ParameterSetParser {
case 444:
return '4:4:4';
default:
- return 'Unknown';
+ return 'Unspecified chroma-format value: ' + chroma;
}
}
@@ -226,7 +226,9 @@ export class ParameterSetParser {
return new Sps(
seq_parameter_set_id,
profile_string,
+ profile_idc,
level_string,
+ level_idc,
bit_depth,
chroma_format,
ParameterSetParser.getChromaFormatString(chroma_format),
From 9b7e6a94654916749ea7414e26f2ddb4eb931608 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 10 Jul 2019 15:59:12 +0200
Subject: [PATCH 054/197] mp4 demux: allow to parse multiple track codec
description reference atoms support this for avcC case
---
src/demuxer/mp4/mp4-demuxer.ts | 41 +++++++++++++++++++++-------------
src/demuxer/mp4/mp4-track.ts | 10 ++++++---
2 files changed, 32 insertions(+), 19 deletions(-)
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index 408352e..aac86fa 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -29,7 +29,7 @@ import { Mp4SampleTable } from './mp4-sample-table';
import { Esds } from './atoms/esds';
import { Mvhd } from './atoms/mvhd';
-const {log, warn} = getLogger('Mp4Demuxer', LoggerLevels.OFF);
+const {log, warn} = getLogger('Mp4Demuxer', LoggerLevels.ON);
export class Mp4Demuxer implements IDemuxer {
public tracks: TracksHash = {};
@@ -273,22 +273,31 @@ export class Mp4Demuxer implements IDemuxer {
}
private _attemptCreateTrack(type: string, mime: string, ref: Atom) {
- if (this.lastTrackId > 0) {
- log('creating new track:', type, mime)
- const track = new Mp4Track(
- this.lastTrackId,
- type,
- mime,
- ref,
- this.lastAudioVideoAtom,
- this.lastTrackDataOffset
- );
- if (this.lastTimescale !== null) {
- log('setting parent timescale on track:', this.lastTimescale);
- track.setTimescale(this.lastTimescale);
- }
- this.tracks[this.lastTrackId] = track;
+ if (!this.lastTrackId) {
+ throw new Error('No track-id set');
}
+
+ if (this.tracks[this.lastTrackId]) {
+ log('adding ref-atom to existing track with id:', this.lastTrackId, 'mime:', mime, 'type:', type);
+ (this.tracks[this.lastTrackId] as Mp4Track).addReferenceAtom(ref);
+ return;
+ }
+
+ log('creating new track:', type, mime, 'id:', this.lastTrackId)
+ const track = new Mp4Track(
+ this.lastTrackId,
+ type,
+ mime,
+ [ref],
+ this.lastAudioVideoAtom,
+ this.lastTrackDataOffset
+ );
+ if (this.lastTimescale !== null) {
+ log('setting parent timescale on track:', this.lastTimescale);
+ track.setTimescale(this.lastTimescale);
+ }
+ this.tracks[this.lastTrackId] = track;
+
}
/**
diff --git a/src/demuxer/mp4/mp4-track.ts b/src/demuxer/mp4/mp4-track.ts
index ec8433c..335f442 100644
--- a/src/demuxer/mp4/mp4-track.ts
+++ b/src/demuxer/mp4/mp4-track.ts
@@ -38,7 +38,7 @@ export class Mp4Track extends Track {
id: number,
type: string,
mimeType: string,
- public referenceAtom: Atom,
+ public referenceAtoms: Atom[],
public metadataAtom: AudioAtom | VideoAtom,
public dataOffset: number
) {
@@ -70,8 +70,12 @@ export class Mp4Track extends Track {
return this.trunInfo;
}
- public getReferenceAtom(): Atom {
- return this.referenceAtom;
+ public getReferenceAtoms(): Atom[] {
+ return this.referenceAtoms;
+ }
+
+ public addReferenceAtom(atom: Atom) {
+ this.referenceAtoms.push(atom);
}
public getMetadataAtom(): VideoAtom | AudioAtom {
From a401c4822c0c3557abf72d8fb344bce2bf9fa610 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 24 Jul 2019 22:09:40 +0200
Subject: [PATCH 055/197] h264: rename class to H264ParamSetParser
---
src/codecs/h264/param-set-parser.ts | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/codecs/h264/param-set-parser.ts b/src/codecs/h264/param-set-parser.ts
index 8cbb688..1488c20 100644
--- a/src/codecs/h264/param-set-parser.ts
+++ b/src/codecs/h264/param-set-parser.ts
@@ -2,7 +2,7 @@ import { BitReader } from '../../utils/bit-reader';
import { Sps, Pps } from './nal-units';
import { Size, FrameRate } from '../video-types';
-export class ParameterSetParser {
+export class H264ParameterSetParser {
static getProfileString(profile_idc: number): string {
switch (profile_idc) {
@@ -62,8 +62,8 @@ export class ParameterSetParser {
const seq_parameter_set_id: number = gb.readUEG();
- const profile_string: string = ParameterSetParser.getProfileString(profile_idc);
- const level_string: string = ParameterSetParser.getLevelString(level_idc);
+ const profile_string: string = H264ParameterSetParser.getProfileString(profile_idc);
+ const level_string: string = H264ParameterSetParser.getLevelString(level_idc);
let chroma_format_idc: number = 1;
let chroma_format: number = 420;
@@ -91,9 +91,9 @@ export class ParameterSetParser {
for (let i: number = 0; i < scaling_list_count; i++) {
if (gb.readBool()) {
if (i < 6) {
- ParameterSetParser.skipScalingList(gb, 16);
+ H264ParameterSetParser.skipScalingList(gb, 16);
} else {
- ParameterSetParser.skipScalingList(gb, 64);
+ H264ParameterSetParser.skipScalingList(gb, 64);
}
}
}
@@ -231,7 +231,7 @@ export class ParameterSetParser {
level_idc,
bit_depth,
chroma_format,
- ParameterSetParser.getChromaFormatString(chroma_format),
+ H264ParameterSetParser.getChromaFormatString(chroma_format),
new FrameRate(fps_fixed, fps, fps_den, fps_num),
new Size(sar_width, sar_height),
new Size(codec_width, codec_height),
From 240c28d9aa44250ca1672d20ecbec3df2538e2d8 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 24 Jul 2019 22:10:04 +0200
Subject: [PATCH 056/197] use renamed param set parset in avcc parser and h264
bitstream reader
---
src/demuxer/mp4/atoms/avcC.ts | 6 +++---
src/demuxer/ts/payload/h264-reader.ts | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/demuxer/mp4/atoms/avcC.ts b/src/demuxer/mp4/atoms/avcC.ts
index e5aecf4..3a34f99 100644
--- a/src/demuxer/mp4/atoms/avcC.ts
+++ b/src/demuxer/mp4/atoms/avcC.ts
@@ -1,6 +1,6 @@
import ByteParserUtils from '../../../utils/byte-parser-utils';
import { Atom } from './atom';
-import { ParameterSetParser } from '../../../codecs/h264/param-set-parser';
+import { H264ParameterSetParser } from '../../../codecs/h264/param-set-parser';
import { Sps, Pps } from '../../../codecs/h264/nal-units';
export class AvcC extends Atom {
@@ -38,7 +38,7 @@ export class AvcC extends Atom {
avcC.sps.push(sps);
offset += spsSize;
- avcC.spsParsed.push(ParameterSetParser.parseSPS(sps.subarray(1, spsSize)));
+ avcC.spsParsed.push(H264ParameterSetParser.parseSPS(sps.subarray(1, spsSize)));
}
avcC.numOfPictureParameterSets = data[offset] & 0x1f;
@@ -52,7 +52,7 @@ export class AvcC extends Atom {
avcC.pps.push(new Uint8Array(data.subarray(offset, offset + ppsSize)));
offset += ppsSize;
- avcC.ppsParsed.push(ParameterSetParser.parsePPS(pps.subarray(1, ppsSize)))
+ avcC.ppsParsed.push(H264ParameterSetParser.parsePPS(pps.subarray(1, ppsSize)))
}
return avcC;
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index c005b43..69828a6 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -2,7 +2,7 @@ import { BitReader } from '../../../utils/bit-reader';
import { PayloadReader } from './payload-reader';
import { Frame } from '../../frame';
import { Track } from '../../track';
-import { ParameterSetParser } from '../../../codecs/h264/param-set-parser';
+import { H264ParameterSetParser } from '../../../codecs/h264/param-set-parser';
import { Sps } from '../../../codecs/h264/nal-units';
enum NAL_UNIT_TYPE {
@@ -166,7 +166,7 @@ export class H264Reader extends PayloadReader {
}
private parseSPSNALUnit(start: number, limit: number): void {
- this.sps = ParameterSetParser.parseSPS(this.dataBuffer.subarray(start + 4, limit));
+ this.sps = H264ParameterSetParser.parseSPS(this.dataBuffer.subarray(start + 4, limit));
}
private skipScalingList(parser: BitReader, size: number): void {
From 37fdc549c6772fde16596e3220d001524eb85607 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 24 Jul 2019 22:10:23 +0200
Subject: [PATCH 057/197] mp4 demuxer: fix missing break statement in
switch-case
---
src/demuxer/mp4/mp4-demuxer.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index aac86fa..7fefcd0 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -182,6 +182,7 @@ export class Mp4Demuxer implements IDemuxer {
case Atom.esds:
this._attemptCreateTrack(Track.TYPE_AUDIO, Track.MIME_TYPE_AAC, atom);
this.lastCodecDataAtom = atom as Esds;
+ break;
// Fragmented-mode ...
From 770651cf056534d7c581db6234069519d7ead098 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 5 Aug 2019 16:05:09 +0200
Subject: [PATCH 058/197] mdat atom: disable broken payload parsing and add
data property for external access
---
src/demuxer/mp4/atoms/mdat.ts | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/demuxer/mp4/atoms/mdat.ts b/src/demuxer/mp4/atoms/mdat.ts
index 4bf3e3a..1515505 100644
--- a/src/demuxer/mp4/atoms/mdat.ts
+++ b/src/demuxer/mp4/atoms/mdat.ts
@@ -6,12 +6,16 @@ const {log, warn} = getLogger('Mdat');
export class Mdat extends Atom {
+ public data: Uint8Array = null;
+
public static parse(data: Uint8Array): Atom {
const mdat: Mdat = new Mdat(Atom.mdat, data.byteLength);
- Mdat.parsePayload(data);
+ mdat.data = data;
+ //Mdat.parsePayload(data);
return mdat;
}
+ /*
private static parsePayload(data: Uint8Array): void {
let length: number;
for (let i: number = 0; i + 4 < data.byteLength; i += length) {
@@ -31,4 +35,5 @@ export class Mdat extends Atom {
// TODO: do something
}
}
+ */
}
From 1517330e955b163efa776b0f7f2268f681ee9211 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 8 Aug 2019 16:50:56 +0200
Subject: [PATCH 059/197] mvhd atom: fix offsets on parsing version 1 box
containing 64-bit fields
---
src/demuxer/mp4/atoms/mvhd.ts | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/demuxer/mp4/atoms/mvhd.ts b/src/demuxer/mp4/atoms/mvhd.ts
index 08e98f1..bed1cff 100644
--- a/src/demuxer/mp4/atoms/mvhd.ts
+++ b/src/demuxer/mp4/atoms/mvhd.ts
@@ -30,10 +30,11 @@ export class Mvhd extends Atom {
mvhd.creationTime = ByteParserUtils.parseIsoBoxDate(ByteParserUtils.parseUint32(data, offset));
offset += 8;
mvhd.modificationTime = ByteParserUtils.parseIsoBoxDate(ByteParserUtils.parseUint32(data, offset));
- offset += 4;
- mvhd.timescale = ByteParserUtils.parseUint32(data, offset);
offset += 8;
+ mvhd.timescale = ByteParserUtils.parseUint32(data, offset);
+ offset += 4;
mvhd.duration = ByteParserUtils.parseUint32(data, offset);
+ offset += 8;
} else {
mvhd.creationTime = ByteParserUtils.parseIsoBoxDate(ByteParserUtils.parseUint32(data, offset));
offset += 4;
@@ -42,8 +43,8 @@ export class Mvhd extends Atom {
mvhd.timescale = ByteParserUtils.parseUint32(data, offset);
offset += 4;
mvhd.duration = ByteParserUtils.parseUint32(data, offset);
+ offset += 4;
}
- offset += 4;
mvhd.rate = ByteParserUtils.parseUint16(data, offset) +
ByteParserUtils.parseUint16(data, offset + 2) / 16;
offset += 4;
From 52ff61a16e1a10a726ae246821707e145e496953 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 8 Aug 2019 16:51:30 +0200
Subject: [PATCH 060/197] rename function parseLong64 to parseUint64
---
src/demuxer/mp4/atoms/sidx.ts | 4 ++--
src/demuxer/mp4/atoms/tfhd.ts | 2 +-
src/utils/byte-parser-utils.ts | 4 ++--
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/demuxer/mp4/atoms/sidx.ts b/src/demuxer/mp4/atoms/sidx.ts
index 3ae6abb..203b654 100644
--- a/src/demuxer/mp4/atoms/sidx.ts
+++ b/src/demuxer/mp4/atoms/sidx.ts
@@ -29,8 +29,8 @@ export class Sidx extends Atom {
sidx.firstOffset = ByteParserUtils.parseUint32(data, 16);
offset = 20;
} else {
- sidx.earliestPresentationTime = ByteParserUtils.parseLong64(data, 12);
- sidx.firstOffset = ByteParserUtils.parseLong64(data, 20);
+ sidx.earliestPresentationTime = ByteParserUtils.parseUint64(data, 12);
+ sidx.firstOffset = ByteParserUtils.parseUint64(data, 20);
offset = 28;
}
diff --git a/src/demuxer/mp4/atoms/tfhd.ts b/src/demuxer/mp4/atoms/tfhd.ts
index 884673c..a03ec0c 100644
--- a/src/demuxer/mp4/atoms/tfhd.ts
+++ b/src/demuxer/mp4/atoms/tfhd.ts
@@ -24,7 +24,7 @@ export class Tfhd extends Atom {
let offset: number = 8;
if (baseDataOffsetPresent) {
- tfhd.baseDataOffset = ByteParserUtils.parseLong64(data, 12);
+ tfhd.baseDataOffset = ByteParserUtils.parseUint64(data, 12);
offset += 8;
}
if (sampleDescriptionIndexPresent) {
diff --git a/src/utils/byte-parser-utils.ts b/src/utils/byte-parser-utils.ts
index eb7c3d2..d89210a 100644
--- a/src/utils/byte-parser-utils.ts
+++ b/src/utils/byte-parser-utils.ts
@@ -62,8 +62,8 @@ export default class ByteParserUtils {
return ByteParserUtils.parseUint(buffer, offset, 4);
}
- public static parseLong64(buffer: Uint8Array, offset: number): number {
- return ByteParserUtils.parseUint(buffer, offset, 8);
+ public static parseUint64(buffer: Uint8Array, offset: number): number {
+ return ByteParserUtils.parseUint(buffer, offset, 8, false); // do NOT allow internal value wrap-over on assumed 64-bit values
}
public static parseIsoBoxType(buffer: Uint8Array, offset: number): string {
From cff6cb01954c5cb0a978fd72f84bbd22767a81a0 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 8 Aug 2019 16:52:45 +0200
Subject: [PATCH 061/197] mp4 box parser: properlly suppprot size=1 case which
indicates usage of long size-value and differing box data offset
---
src/demuxer/mp4/mp4-demuxer.ts | 19 ++++++++++++++-----
1 file changed, 14 insertions(+), 5 deletions(-)
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index 7fefcd0..523134d 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -81,10 +81,19 @@ export class Mp4Demuxer implements IDemuxer {
let dataOffset: number = offset;
while (dataOffset < data.byteLength) {
- const size: number = ByteParserUtils.parseUint32(data, dataOffset);
+ let size: number = ByteParserUtils.parseUint32(data, dataOffset);
const type: string = ByteParserUtils.parseIsoBoxType(data, dataOffset + 4);
- const end: number = size > 1 ? dataOffset + size : data.byteLength;
- const boxData: Uint8Array = data.subarray(dataOffset + 8, end);
+
+ let boxDataOffset: number = dataOffset + 8;
+ if (size === 1) {
+ size = ByteParserUtils.parseUint64(data, boxDataOffset)
+ boxDataOffset = dataOffset + 16;
+ } else if (size === 0) {
+ size = data.byteLength;
+ }
+
+ const end: number = dataOffset + size;
+ const boxData: Uint8Array = data.subarray(boxDataOffset, end);
// parse
let atom: Atom;
@@ -117,6 +126,7 @@ export class Mp4Demuxer implements IDemuxer {
}
private _processAtom(atom: Atom, dataOffset: number): void {
+
switch (atom.type) {
// FIXME !!! `trex` box can contain super based set of default sample-duration/flags/size ...
@@ -244,8 +254,7 @@ export class Mp4Demuxer implements IDemuxer {
this.lastSampleTable.chunkOffsetBox = atom as Stco;
break;
- // Sample data ...
-
+ // payload data
case Atom.mdat:
// in plain old MOV the moov may be at the end of the file (and mdat before)
if (this._getLastTrackCreated()) {
From 14f4cc35ffecde8403f709c7c2fc6f846b51fb95 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 8 Aug 2019 16:54:29 +0200
Subject: [PATCH 062/197] parseUint byte parser function: add optional integer
overflow protection optionnally any value wrap-over can be disabled via the
flag, which we enable in the uint64 case.
---
src/utils/byte-parser-utils.ts | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)
diff --git a/src/utils/byte-parser-utils.ts b/src/utils/byte-parser-utils.ts
index d89210a..722d929 100644
--- a/src/utils/byte-parser-utils.ts
+++ b/src/utils/byte-parser-utils.ts
@@ -46,10 +46,25 @@ export default class ByteParserUtils {
return value;
}
- public static parseUint(buffer: Uint8Array, offset: number, len: number): number {
+ public static parseUint(buffer: Uint8Array, offset: number, len: number, allowOverflow: boolean = true): number {
let value: number = 0;
- for (let i: number = 0; i < len; i++) {
+ let safeValue: number = 0;
+ // we need to walk-back instead of iterating up because otherwise the value-checking to prevent overflow will not work
+ for (let i: number = len - 1; i >= 0; i--) {
value |= (buffer[offset + i] << ((len - i - 1) * 8)) >>> 0;
+ if (!allowOverflow) {
+ /**
+ * NOTE: In JS engines, usually numbers are stored in 32-bit registers using signed types. That leaves 31 bits for the actual value.
+ * As we write into this variable and shift bits, it is to be expected that overflow will happen when we have a number exceeded 2^31 stored inside this buffer.
+ * We are detecting this via this method of storing the last "safe" value and detecting overflow by comparison (it will be either less in positive value or negative then).
+ * The only thing we can do atm is throw this error and bailing out from any use-cases needing actually large numbers as this.
+ * TODO: any solution to handle large 64 bits values
+ */
+ if (value < safeValue) {
+ throw new Error(`Error parsing ${len} bytes-long unsigned integer from buffer: value overflow/wrap-around from previously ${safeValue} to falsely ${value} at byte-index ${i}`)
+ }
+ safeValue = value;
+ }
}
return value;
}
From 03c0d7196a349017c006755599fce3e253b541b7 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Fri, 23 Aug 2019 03:26:49 +0200
Subject: [PATCH 063/197] mp4 demuxer: fix for parsing MOV files containg track
types we do not handle
---
src/demuxer/mp4/mp4-demuxer.ts | 37 ++++++++++++++++++++++------------
1 file changed, 24 insertions(+), 13 deletions(-)
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index 523134d..0b606ed 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -135,10 +135,7 @@ export class Mp4Demuxer implements IDemuxer {
case Atom.trak:
this._digestSampleTable();
- this.lastTrackId = -1;
- this.lastTimescale = null;
- this.lastCodecDataAtom = null;
- this.lastAudioVideoAtom = null;
+ this._resetLastTrackInfos();
case Atom.ftyp:
case Atom.moov:
@@ -151,6 +148,7 @@ export class Mp4Demuxer implements IDemuxer {
// Moov box / "initialization"-data and SIDX
case Atom.sidx:
+ // FIXME: this isn't very nice
this._attemptCreateUnknownTrack();
this._getLastTrackCreated().setSidxAtom(atom);
break;
@@ -231,27 +229,27 @@ export class Mp4Demuxer implements IDemuxer {
break;
case Atom.stts:
this._haveSampleTable();
- this.lastSampleTable.decodingTimestamps = atom as Stts;
+ this.lastSampleTable && (this.lastSampleTable.decodingTimestamps = atom as Stts);
break;
case Atom.stss:
this._haveSampleTable();
- this.lastSampleTable.syncSamples = atom as Stss;
+ this.lastSampleTable && (this.lastSampleTable.syncSamples = atom as Stss);
break;
case Atom.ctts:
this._haveSampleTable();
- this.lastSampleTable.compositionTimestampOffsets = atom as Ctts;
+ this.lastSampleTable && (this.lastSampleTable.compositionTimestampOffsets = atom as Ctts);
break;
case Atom.stsc:
this._haveSampleTable();
- this.lastSampleTable.samplesToChunkBox = atom as Stsc;
+ this.lastSampleTable && (this.lastSampleTable.samplesToChunkBox = atom as Stsc);
break;
case Atom.stsz:
this._haveSampleTable();
- this.lastSampleTable.sampleSizes = atom as Stsz;
+ this.lastSampleTable && (this.lastSampleTable.sampleSizes = atom as Stsz);
break;
case Atom.stco:
this._haveSampleTable();
- this.lastSampleTable.chunkOffsetBox = atom as Stco;
+ this.lastSampleTable && (this.lastSampleTable.chunkOffsetBox = atom as Stco);
break;
// payload data
@@ -271,7 +269,13 @@ export class Mp4Demuxer implements IDemuxer {
if (this.lastSampleTable) {
return;
}
- this.lastSampleTable = new Mp4SampleTable(this._getLastTrackCreated());
+ if (!this._getLastTrackCreated() || this._getLastTrackCreated().isOther()) {
+ this._attemptCreateUnknownTrack();
+ warn('not unpacking sample table for unknown track');
+ } else { // we only create a sample table representation for known track types
+ this.lastSampleTable = new Mp4SampleTable(this._getLastTrackCreated());
+ }
+
}
@@ -316,7 +320,9 @@ export class Mp4Demuxer implements IDemuxer {
private _attemptCreateUnknownTrack(): void {
if (!this.lastTrackId || !this.tracks[this.lastTrackId]) {
warn('creating unknown-typed track');
- this.lastTrackId = 1;
+ if (this.lastTrackId <= 0) {
+ this.lastTrackId = 1
+ }
this.tracks[this.lastTrackId] = new Mp4Track(
this.lastTrackId,
Track.TYPE_UNKNOWN,
@@ -332,8 +338,13 @@ export class Mp4Demuxer implements IDemuxer {
* Should be called everytime we create a track
*/
private _resetLastTrackInfos() {
- this.lastTrackId = 0;
+ this.lastTrackId = -1;
this.lastTrackDataOffset = -1;
+ this.lastSampleTable = null;
+ this.lastCodecDataAtom = null;
+ this.lastTimescale = null;
+ this.lastCodecDataAtom = null;
+ this.lastAudioVideoAtom = null;
}
private _getLastTrackCreated(): Mp4Track {
From 7bfcc538a7d122a7d7bf11b2aca6982a809f1073 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 12 Sep 2019 14:59:58 +0200
Subject: [PATCH 064/197] mp4 demuxer: disable logs
---
src/demuxer/mp4/mp4-demuxer.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index 0b606ed..93e7144 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -29,7 +29,7 @@ import { Mp4SampleTable } from './mp4-sample-table';
import { Esds } from './atoms/esds';
import { Mvhd } from './atoms/mvhd';
-const {log, warn} = getLogger('Mp4Demuxer', LoggerLevels.ON);
+const {log, warn} = getLogger('Mp4Demuxer', LoggerLevels.OFF);
export class Mp4Demuxer implements IDemuxer {
public tracks: TracksHash = {};
From b8060982fb9e115bfb381c8e56c0411da1f571c9 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 11 May 2020 22:19:50 +0200
Subject: [PATCH 065/197] rm console.log
---
src/demuxer/ts/mpegts-demuxer.ts | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index 3cbf63d..b7de20e 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -153,8 +153,6 @@ export class MpegTSDemuxer implements IDemuxer {
private processTSPacket(packet: Uint8Array): void {
- console.log('packet found at offset:', packet.byteOffset)
-
this.packetsCount++;
const packetParser: BitReader = new BitReader(packet);
From 0f3e893199f247543c6310957f4484f663699111 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 30 Jun 2020 15:56:30 +0200
Subject: [PATCH 066/197] rm logger module
---
src/codecs/h264/param-set-parser.ts | 20 +++++++++-
src/demuxer/mp4/mp4-demuxer.ts | 4 +-
src/utils/logger.ts | 60 -----------------------------
3 files changed, 20 insertions(+), 64 deletions(-)
delete mode 100644 src/utils/logger.ts
diff --git a/src/codecs/h264/param-set-parser.ts b/src/codecs/h264/param-set-parser.ts
index 1488c20..72fdda4 100644
--- a/src/codecs/h264/param-set-parser.ts
+++ b/src/codecs/h264/param-set-parser.ts
@@ -191,9 +191,27 @@ export class H264ParameterSetParser {
fps_fixed = gb.readBool();
fps_num = time_scale;
- fps_den = num_units_in_tick * 2;
+ fps_den = num_units_in_tick;
fps = fps_num / fps_den;
}
+
+ /*
+ sps->timing_info_present_flag = get_bits1(gb);
+ if (sps->timing_info_present_flag) {
+ unsigned num_units_in_tick = get_bits_long(gb, 32);
+ unsigned time_scale = get_bits_long(gb, 32);
+ if (!num_units_in_tick || !time_scale) {
+ av_log(avctx, AV_LOG_ERROR,
+ "time_scale/num_units_in_tick invalid or unsupported (%u/%u)\n",
+ time_scale, num_units_in_tick);
+ sps->timing_info_present_flag = 0;
+ } else {
+ sps->num_units_in_tick = num_units_in_tick;
+ sps->time_scale = time_scale;
+ }
+ sps->fixed_frame_rate_flag = get_bits1(gb);
+ }
+ */
}
let sarScale: number = 1;
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index 93e7144..c74c9b4 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -23,13 +23,11 @@ import { Stss } from './atoms/stss';
import { Stco } from './atoms/stco';
import { Mdhd } from './atoms/mdhd';
-import {getLogger, LoggerLevels} from '../../utils/logger';
-
import { Mp4SampleTable } from './mp4-sample-table';
import { Esds } from './atoms/esds';
import { Mvhd } from './atoms/mvhd';
-const {log, warn} = getLogger('Mp4Demuxer', LoggerLevels.OFF);
+const {log, warn} = console;
export class Mp4Demuxer implements IDemuxer {
public tracks: TracksHash = {};
diff --git a/src/utils/logger.ts b/src/utils/logger.ts
deleted file mode 100644
index fe07ad8..0000000
--- a/src/utils/logger.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-// TODO: Move to Objec-TS long-term
-
-const PREFIX_ROOT = '1nspect0r.js'
-
-const noop = () => {};
-
-const getPrefix = function(type: string, category: string): string {
- const prefix = `[${PREFIX_ROOT}]:[${type}]:[${category}] >`
- return prefix
-}
-
-export function checkLogLevel(level: number, catLevel: number) {
- switch(catLevel) {
- case LoggerLevels.INFO: return (level >= LoggerLevels.INFO) && console.info
- case LoggerLevels.LOG: return (level >= LoggerLevels.LOG) && console.log
- case LoggerLevels.DEBUG: return (level >= LoggerLevels.DEBUG) && console.debug
- case LoggerLevels.WARN: return (level >= LoggerLevels.WARN) && console.warn
- case LoggerLevels.ERROR: return (level >= LoggerLevels.ERROR) && console.error
- }
-}
-
-export type LoggerFunc = (...args: any[]) => void
-
-export type Logger = {
- info: LoggerFunc,
- log: LoggerFunc
- debug: LoggerFunc
- warn: LoggerFunc
- error: LoggerFunc
-}
-
-export enum LoggerLevels {
- ON = Infinity,
- LOG = 5,
- INFO = 4,
- DEBUG = 3,
- WARN = 2,
- ERROR = 1,
- OFF = 0
-}
-
-export const getLogger = function(category: string, level: number = LoggerLevels.ON): Logger {
- var window = self; // Needed for WebWorker compat
-
- return {
- info: checkLogLevel(level, LoggerLevels.INFO) ? console.info.bind(window['console'], getPrefix('i', category)) : noop,
- log: checkLogLevel(level, LoggerLevels.LOG) ? console.log.bind(window['console'], getPrefix('l', category)) : noop,
- debug: checkLogLevel(level, LoggerLevels.DEBUG) ? console.debug.bind(window['console'], getPrefix('d', category)) : noop,
- warn: checkLogLevel(level, LoggerLevels.WARN) ? console.warn.bind(window['console'], getPrefix('w', category)) : noop,
- error: checkLogLevel(level, LoggerLevels.ERROR) ? console.error.bind(window['console'], getPrefix('e', category)) : noop
- }
-}
-
-export function makeLogTimestamped(...args): string {
- let message = `[${(new Date()).toISOString()}]`
- args.forEach((arg) => {
- message += ' ' + arg
- })
- return message
-}
From 500d6ef8640470eac48c2e53e4fa3ee3be25abda Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 30 Jun 2020 16:01:53 +0200
Subject: [PATCH 067/197] replace log fns by noops
---
src/demuxer/mp4/mp4-demuxer.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index c74c9b4..521a5d4 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -27,7 +27,8 @@ import { Mp4SampleTable } from './mp4-sample-table';
import { Esds } from './atoms/esds';
import { Mvhd } from './atoms/mvhd';
-const {log, warn} = console;
+const log = () => void 0; // console.log.bind(console);
+const warn = console.warn.bind(console);
export class Mp4Demuxer implements IDemuxer {
public tracks: TracksHash = {};
From d4245cb2b67870b7944371f5fade24277fa11012 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 30 Jun 2020 16:05:19 +0200
Subject: [PATCH 068/197] binding log func properly
---
src/demuxer/mp4/mp4-demuxer.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index 521a5d4..b6d95ee 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -27,7 +27,7 @@ import { Mp4SampleTable } from './mp4-sample-table';
import { Esds } from './atoms/esds';
import { Mvhd } from './atoms/mvhd';
-const log = () => void 0; // console.log.bind(console);
+const log = (...msg: any[]) => void 0; // console.log.bind(console);
const warn = console.warn.bind(console);
export class Mp4Demuxer implements IDemuxer {
From d6303697bf98a68227854d9645179f236f2ae705 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 30 Jun 2020 16:12:47 +0200
Subject: [PATCH 069/197] more rm logger
---
src/demuxer/mp4/atoms/mdat.ts | 3 ---
src/demuxer/mp4/mp4-sample-table.ts | 8 ++------
src/demuxer/mp4/mp4-track.ts | 9 +++------
3 files changed, 5 insertions(+), 15 deletions(-)
diff --git a/src/demuxer/mp4/atoms/mdat.ts b/src/demuxer/mp4/atoms/mdat.ts
index 1515505..58b69c7 100644
--- a/src/demuxer/mp4/atoms/mdat.ts
+++ b/src/demuxer/mp4/atoms/mdat.ts
@@ -1,8 +1,5 @@
import ByteParserUtils from '../../../utils/byte-parser-utils';
import { Atom } from './atom';
-import {getLogger} from '../../../utils/logger';
-
-const {log, warn} = getLogger('Mdat');
export class Mdat extends Atom {
diff --git a/src/demuxer/mp4/mp4-sample-table.ts b/src/demuxer/mp4/mp4-sample-table.ts
index 62b8b03..d6e1c8d 100644
--- a/src/demuxer/mp4/mp4-sample-table.ts
+++ b/src/demuxer/mp4/mp4-sample-table.ts
@@ -10,10 +10,6 @@ import { Frame } from '../frame';
import { toMicroseconds } from '../../utils/timescale';
-import {getLogger, LoggerLevels} from '../../utils/logger';
-
-const {log, debug} = getLogger('Mp4SampleTable', LoggerLevels.OFF);
-
export class Mp4SampleTable {
decodingTimestamps: Stts;
compositionTimestampOffsets: Ctts;
@@ -31,7 +27,7 @@ export class Mp4SampleTable {
digest() {
- debug('digesting sample table');
+ //debug('digesting sample table');
let dts = 0;
let frameCount = 0;
@@ -143,6 +139,6 @@ export class Mp4SampleTable {
this._track.appendFrame(frame)
});
- log(frames)
+ //log(frames)
}
};
diff --git a/src/demuxer/mp4/mp4-track.ts b/src/demuxer/mp4/mp4-track.ts
index 335f442..a876dd3 100644
--- a/src/demuxer/mp4/mp4-track.ts
+++ b/src/demuxer/mp4/mp4-track.ts
@@ -11,11 +11,8 @@ import { Sidx } from './atoms/sidx';
import { Trun, SampleFlags } from './atoms/trun';
import { Avc1 } from './atoms/avc1';
-import { getLogger, LoggerLevels } from '../../utils/logger';
import { toMicroseconds } from '../../utils/timescale';
-const {debug, warn} = getLogger('Mp4Track', LoggerLevels.WARN);
-
export type Mp4TrackDefaults = {
sampleDuration: number;
sampleSize: number;
@@ -175,7 +172,7 @@ export class Mp4Track extends Track {
const timescale: number = this.sidx ? this.sidx.timescale : this.getTimescale();
if (!this.sidx) {
- warn('No sidx found, using parent timescale:', timescale);
+ //warn('No sidx found, using parent timescale:', timescale);
}
const sampleRunDataOffset: number = trun.dataOffset + this.getFinalSampleDataOffset();
@@ -192,7 +189,7 @@ export class Mp4Track extends Track {
const flags = sample.flags || this.defaultSampleFlagsParsed;
if (!flags) {
- warn('no default sample flags in track sample-run');
+ //warn('no default sample flags in track sample-run');
// in fact the trun box parser should provide a fallback instance of flags in this case
//throw new Error('Invalid file, sample has no flags');
}
@@ -217,7 +214,7 @@ export class Mp4Track extends Track {
this.appendFrame(newFrame);
- debug(`frame: @ ${newFrame.timeUs} [us] -> ${newFrame.bytesOffset} / ${newFrame.size}`)
+ //debug(`frame: @ ${newFrame.timeUs} [us] -> ${newFrame.bytesOffset} / ${newFrame.size}`)
bytesOffset += sample.size;
}
From 3dd34839dd307a6224ed5f954c4c5b4d40f382a8 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 5 Oct 2021 00:59:24 +0200
Subject: [PATCH 070/197] mp4 demuxer: code style fix on comment
---
src/demuxer/mp4/mp4-demuxer.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index b6d95ee..f2ada1e 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -194,7 +194,9 @@ export class Mp4Demuxer implements IDemuxer {
// Fragmented-mode ...
case Atom.tfhd:
- // FIXME: should be handled differently by looking at other things inside fragments and mapping eventually to previously parsed moov
+ // FIXME: should be handled differently
+ // by looking at other things inside fragments
+ // and mapping eventually to previously parsed moov
this._attemptCreateUnknownTrack();
const tfhd: Tfhd = atom as Tfhd;
this._getLastTrackCreated().setBaseDataOffset(tfhd.baseDataOffset);
From 860df43ef336fc932c25db4dd05251d1b9a90f9e Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 5 Oct 2021 00:59:49 +0200
Subject: [PATCH 071/197] demuxer frame contructor assertion on size parameter
---
src/demuxer/frame.ts | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/src/demuxer/frame.ts b/src/demuxer/frame.ts
index 9090f1d..5025d16 100644
--- a/src/demuxer/frame.ts
+++ b/src/demuxer/frame.ts
@@ -11,20 +11,25 @@ export class Frame {
// normalized micros value
private presentationTimeUs: number = 0;
- // ideally have unnormalized integer values
+ // ideally have scaled integer values
public timescale: number = NaN;
public scaledDecodingTime: number = NaN;
public scaledPresentationTimeOffset: number = NaN;
public scaledDuration: number = NaN;
constructor (
- public frameType: string,
- public timeUs: number,
- public size: number,
- public duration: number = NaN,
+ public readonly frameType: string,
+ public readonly timeUs: number,
+ public readonly size: number,
+ public readonly duration: number = NaN,
public bytesOffset: number = NaN,
- presentationTimeOffsetUs: number = 0
+ public presentationTimeOffsetUs: number = 0
) {
+
+ if (!Number.isFinite(size)) {
+ throw new Error('Frame has to have sample size');
+ }
+
this.setPresentationTimeOffsetUs(presentationTimeOffsetUs);
}
From 36f65c538715de0f96b9092045877046d2a652da Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 5 Oct 2021 01:00:48 +0200
Subject: [PATCH 072/197] mp4 track: fallback for frame size missing in sample
entry to trak default
---
src/demuxer/mp4/mp4-track.ts | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/demuxer/mp4/mp4-track.ts b/src/demuxer/mp4/mp4-track.ts
index a876dd3..339d6ca 100644
--- a/src/demuxer/mp4/mp4-track.ts
+++ b/src/demuxer/mp4/mp4-track.ts
@@ -198,10 +198,13 @@ export class Mp4Track extends Track {
const timeUs = this.lastPts;
+ const frameSize = sample.size || this.defaults.sampleSize;
+ if (!frameSize) throw new Error('Frame has to have either sample-size of trun-entry or track default');
+
const newFrame = new Frame(
flags ? (flags.isSyncFrame ? Frame.IDR_FRAME : Frame.P_FRAME) : Frame.UNFLAGGED_FRAME,
timeUs,
- sample.size,
+ frameSize,
duration,
bytesOffset,
cto
From bb77284ddafaaec0bf166a96495df3865c95e4ba Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 6 Oct 2021 04:13:31 +0200
Subject: [PATCH 073/197] mp4-demux/track: handle different tfhd default values
for each trun (fragment) fixes input of fragmented data where each fragment
carries different defaults + add flush method to base track and mp4-impl +
demuxer to allow reset (after consumer component has read out demuxed frame
data)
---
src/demuxer/mp4/mp4-demuxer.ts | 26 +++++++++++++--------
src/demuxer/mp4/mp4-track.ts | 42 ++++++++++++++++++++++------------
2 files changed, 44 insertions(+), 24 deletions(-)
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index f2ada1e..a051c71 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -44,16 +44,9 @@ export class Mp4Demuxer implements IDemuxer {
private lastTimescale: number = null;
constructor() {
- this.atoms = [];
- this.tracks = {};
-
this._resetLastTrackInfos();
}
- public getAtoms(): Atom[] {
- return this.atoms;
- }
-
public append(data: Uint8Array): void {
this.atoms = this._parseAtoms(data);
@@ -67,6 +60,19 @@ export class Mp4Demuxer implements IDemuxer {
this._updateTracks();
}
+ public flush() {
+ this.atoms.length = 0;
+ for (const trackId in this.tracks) {
+ if (this.tracks.hasOwnProperty(trackId)) {
+ (this.tracks[trackId] as Mp4Track).flush();
+ }
+ }
+ }
+
+ public getAtoms(): Atom[] {
+ return this.atoms;
+ }
+
private _updateTracks(): void {
for (const trackId in this.tracks) {
if (this.tracks.hasOwnProperty(trackId)) {
@@ -200,7 +206,7 @@ export class Mp4Demuxer implements IDemuxer {
this._attemptCreateUnknownTrack();
const tfhd: Tfhd = atom as Tfhd;
this._getLastTrackCreated().setBaseDataOffset(tfhd.baseDataOffset);
- this._getLastTrackCreated().setDefaults({
+ this._getLastTrackCreated().addDefaults({
sampleDuration: tfhd.defaultSampleDuration,
sampleFlags: tfhd.defaultSampleFlags,
sampleSize: tfhd.defaultSampleSize
@@ -208,7 +214,9 @@ export class Mp4Demuxer implements IDemuxer {
break;
case Atom.trun:
- // FIXME: should be handled differently by looking at other things inside fragments and mapping eventually to previously parsed moov
+ // FIXME: should be handled differently by looking
+ // at other things inside fragments and mapping eventually
+ // to previously parsed moov (see above for tfhds)
this._attemptCreateUnknownTrack();
this._getLastTrackCreated().addTrunAtom(atom);
break;
diff --git a/src/demuxer/mp4/mp4-track.ts b/src/demuxer/mp4/mp4-track.ts
index 339d6ca..3db8b9e 100644
--- a/src/demuxer/mp4/mp4-track.ts
+++ b/src/demuxer/mp4/mp4-track.ts
@@ -24,11 +24,11 @@ export class Mp4Track extends Track {
private sidx: Sidx = null;
private trunInfo: Trun[] = [];
private trunInfoReadIndex: number = 0;
- private lastPts: number = null;
- private lastPtsUnscaledUint: number = null;
+ private lastPts: number;
+ private lastPtsUnscaledUint: number;
private timescale: number = null;
- private defaults: Mp4TrackDefaults = null;
- private defaultSampleFlagsParsed: SampleFlags = null;
+ private defaults: Mp4TrackDefaults[] = [];
+ private defaultSampleFlagsParsed: (SampleFlags | null)[] = [];
private baseDataOffset: number = 0;
constructor(
@@ -50,6 +50,14 @@ export class Mp4Track extends Track {
}
}
+ public flush() {
+ this.trunInfo.length = 0;
+ this.trunInfoReadIndex = 0;
+ this.defaults.length = 0;
+ this.defaultSampleFlagsParsed.length = 0;
+ super.flush();
+ }
+
// TODO: make this abstract on Track class
public getResolution(): [number, number] {
if (!this.isVideo()) {
@@ -91,15 +99,17 @@ export class Mp4Track extends Track {
this.timescale = timescale;
}
- public setDefaults(defaults: Mp4TrackDefaults) {
- this.defaults = defaults;
+ public addDefaults(defaults: Mp4TrackDefaults) {
+ this.defaults.push(defaults);
if (defaults.sampleFlags) {
- this.defaultSampleFlagsParsed = Trun.parseFlags(new Uint8Array([
+ this.defaultSampleFlagsParsed.push(Trun.parseFlags(new Uint8Array([
defaults.sampleFlags & 0xff000000,
defaults.sampleFlags & 0x00ff0000,
defaults.sampleFlags & 0x0000ff00,
defaults.sampleFlags & 0x000000ff,
- ]));
+ ])));
+ } else {
+ this.defaultSampleFlagsParsed.push(null);
}
}
@@ -163,9 +173,9 @@ export class Mp4Track extends Track {
}
public processTrunAtoms() {
- this.trunInfo.forEach((trun: Trun, index) => {
+ this.trunInfo.forEach((trun: Trun, trunIndex) => {
- if (index < this.trunInfoReadIndex) {
+ if (trunIndex < this.trunInfoReadIndex) {
return;
}
@@ -179,15 +189,16 @@ export class Mp4Track extends Track {
let bytesOffset: number = sampleRunDataOffset;
- for (const sample of trun.samples) {
- const sampleDuration = sample.duration || this.defaults.sampleDuration;
+ for (let i = 0; i < trun.samples.length; i++) {
+ const sample = trun.samples[i];
+ const sampleDuration = sample.duration || this.defaults[trunIndex]?.sampleDuration;
if (!sampleDuration) {
throw new Error('Invalid file, samples have no duration');
}
const duration: number = toMicroseconds(sampleDuration, timescale);
- const flags = sample.flags || this.defaultSampleFlagsParsed;
+ const flags = sample.flags || this.defaultSampleFlagsParsed[trunIndex];
if (!flags) {
//warn('no default sample flags in track sample-run');
// in fact the trun box parser should provide a fallback instance of flags in this case
@@ -198,9 +209,10 @@ export class Mp4Track extends Track {
const timeUs = this.lastPts;
- const frameSize = sample.size || this.defaults.sampleSize;
+ const frameSize = sample.size || this.defaults[trunIndex]?.sampleSize;
if (!frameSize) throw new Error('Frame has to have either sample-size of trun-entry or track default');
+
const newFrame = new Frame(
flags ? (flags.isSyncFrame ? Frame.IDR_FRAME : Frame.P_FRAME) : Frame.UNFLAGGED_FRAME,
timeUs,
@@ -219,7 +231,7 @@ export class Mp4Track extends Track {
//debug(`frame: @ ${newFrame.timeUs} [us] -> ${newFrame.bytesOffset} / ${newFrame.size}`)
- bytesOffset += sample.size;
+ bytesOffset += frameSize;
}
})
From f4d2e63feca7c88fb5b299b2b52f3832ccc10f08 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 6 Oct 2021 04:14:23 +0200
Subject: [PATCH 074/197] base track: add flush method impl and rm redundant
init in constructor + move update method up
---
src/demuxer/track.ts | 21 +++++++++++----------
1 file changed, 11 insertions(+), 10 deletions(-)
diff --git a/src/demuxer/track.ts b/src/demuxer/track.ts
index 2f08093..b9faa18 100644
--- a/src/demuxer/track.ts
+++ b/src/demuxer/track.ts
@@ -25,8 +25,17 @@ export class Track {
protected frames: Frame[] = [];
protected duration: number = NaN;
- constructor(public id: number, public type: string /* fixme: make enum type */, public mimeType: string) {
- this.frames = [];
+ constructor(public id: number, public type: string /* fixme: make enum type */, public mimeType: string) {}
+
+ public update(): void {
+ this.frames = this.getFrames().sort((a: Frame, b: Frame): number => {
+ return a.timeUs - b.timeUs;
+ });
+ this.duration = this.getDuration();
+ }
+
+ public flush() {
+ this.frames.length = 0;
}
public isVideo() {
@@ -62,12 +71,4 @@ export class Track {
public getMetadata(): {} { // FIXME: Make this a string-to-any hash
return {};
}
-
- public update(): void {
- this.frames = this.getFrames().sort((a: Frame, b: Frame): number => {
- return a.timeUs - b.timeUs;
- });
-
- this.duration = this.getDuration();
- }
}
From b8dae7063b7ea807d77b76c895545624fbb31ad3 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Fri, 8 Oct 2021 02:10:00 +0200
Subject: [PATCH 075/197] trun box: fix member init to be in declaration + fix
dataOffset 0 init + rename var
---
src/demuxer/mp4/atoms/trun.ts | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/src/demuxer/mp4/atoms/trun.ts b/src/demuxer/mp4/atoms/trun.ts
index a6f44d5..7031e5a 100644
--- a/src/demuxer/mp4/atoms/trun.ts
+++ b/src/demuxer/mp4/atoms/trun.ts
@@ -28,9 +28,9 @@ export class Sample {
export class Trun extends Atom {
public version: number;
public flags: Uint8Array;
- public trackId: number;
- public dataOffset: number;
- public samples: Sample[];
+ public trackId: number; // fixme: not used, why?
+ public dataOffset: number = 0;
+ public samples: Sample[] = [];
public static parse(data: Uint8Array): Atom {
const trun: Trun = new Trun(Atom.trun, data.byteLength);
@@ -46,8 +46,7 @@ export class Trun extends Atom {
let sampleCount: number = ByteParserUtils.parseUint32(data, 4);
let offset: number = 8;
- trun.samples = [];
- let totalSize: number = 0;
+ let totalSizeOfSamples: number = 0; // for debug/test
if (dataOffsetPresent) {
trun.dataOffset = ByteParserUtils.parseUint32(data, offset);
offset += 4;
@@ -62,7 +61,7 @@ export class Trun extends Atom {
}
if (sampleSizePresent) {
sample.size = ByteParserUtils.parseUint32(data, offset);
- totalSize += sample.size;
+ totalSizeOfSamples += sample.size;
offset += 4;
}
if (sampleCompositionTimeOffsetPresent) {
@@ -80,7 +79,7 @@ export class Trun extends Atom {
}
if (sampleSizePresent) {
sample.size = ByteParserUtils.parseUint32(data, offset);
- totalSize += sample.size;
+ totalSizeOfSamples += sample.size;
offset += 4;
}
if (sampleFlagsPresent) {
From 36e0d46589d09ce749d03cc1f21387c9748a533e Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 4 Nov 2021 01:19:52 +0100
Subject: [PATCH 076/197] h264-reader: revert previous changes that were
experimental/wip (but had impact on functinality)
---
src/demuxer/ts/payload/h264-reader.ts | 14 +++++---------
1 file changed, 5 insertions(+), 9 deletions(-)
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index 69828a6..d67bf41 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -62,8 +62,6 @@ export class H264Reader extends PayloadReader {
public pps: boolean;
public pendingBytes: number;
- private frameBytesOffset: number;
-
constructor() {
super();
this.pendingBytes = 0;
@@ -80,7 +78,6 @@ export class H264Reader extends PayloadReader {
if (this.dataBuffer.byteLength > 0) {
const offset: number = this.findNextNALUnit(0);
if (offset < this.dataBuffer.byteLength) {
- this.frameBytesOffset = this.getFirstPacketDataOffset() + this.dataOffset + offset;
this.processNALUnit(offset, this.dataBuffer.byteLength, this.dataBuffer[offset + 3] & 0x1F);
}
}
@@ -109,7 +106,6 @@ export class H264Reader extends PayloadReader {
if (this.pendingBytes > 0) {
nextNalUnit = this.findNextNALUnit(this.pendingBytes);
if (nextNalUnit < this.dataBuffer.byteLength) {
- this.frameBytesOffset = this.getFirstPacketDataOffset() + this.dataOffset + 0;
this.processNALUnit(0, nextNalUnit, this.dataBuffer[offset + 3] & 0x1F);
offset = nextNalUnit;
}
@@ -126,12 +122,12 @@ export class H264Reader extends PayloadReader {
if (this.dataBuffer.byteLength > 0) {
while (nextNalUnit < this.dataBuffer.byteLength) {
nextNalUnit = this.findNextNALUnit(offset + 3);
- this.frameBytesOffset = this.getFirstPacketDataOffset() + this.dataOffset + offset;
- this.processNALUnit(offset, nextNalUnit, this.dataBuffer[offset + 3] & 0x1F);
- offset = nextNalUnit;
+ if (nextNalUnit < this.dataBuffer.byteLength) {
+ this.processNALUnit(offset, nextNalUnit, this.dataBuffer[offset + 3] & 0x1F);
+ offset = nextNalUnit;
+ }
}
- this.dataOffset += offset;
this.dataBuffer = this.dataBuffer.subarray(offset);
this.pendingBytes = this.dataBuffer.byteLength;
}
@@ -247,7 +243,7 @@ export class H264Reader extends PayloadReader {
}
private addNewFrame(frameType: string, frameSize: number, duration: number): void {
- const frame = new Frame(frameType, this.timeUs, frameSize, duration, this.frameBytesOffset);
+ const frame = new Frame(frameType, this.timeUs, frameSize, duration);
this.frames.push(frame);
}
From 10ead851f1d42d5e1fd57d70e703db8e55430e6c Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 4 Nov 2021 01:20:37 +0100
Subject: [PATCH 077/197] rm native console log calls in mpeg-reader and webm
demux
---
src/demuxer/ts/payload/mpeg-reader.ts | 1 -
src/demuxer/webm/webm-demuxer.ts | 1 -
2 files changed, 2 deletions(-)
diff --git a/src/demuxer/ts/payload/mpeg-reader.ts b/src/demuxer/ts/payload/mpeg-reader.ts
index 4984655..e246d68 100644
--- a/src/demuxer/ts/payload/mpeg-reader.ts
+++ b/src/demuxer/ts/payload/mpeg-reader.ts
@@ -116,7 +116,6 @@ export class MpegReader extends PayloadReader {
private parseHeader(header: number): boolean {
if ((header & 0xFFE00000) >>> 0 !== 0xFFE00000) {
- console.log(header);
return false;
}
diff --git a/src/demuxer/webm/webm-demuxer.ts b/src/demuxer/webm/webm-demuxer.ts
index 7fdf0dc..e26667d 100644
--- a/src/demuxer/webm/webm-demuxer.ts
+++ b/src/demuxer/webm/webm-demuxer.ts
@@ -38,7 +38,6 @@ export class WebMDemuxer implements IDemuxer {
}
this.elements = this.parseElements(this.data.byteLength);
- console.log(this.elements);
if (this.dataOffset > 0) {
this.data = this.data.subarray(this.dataOffset);
From f8106ec7b4206b2624352d08066cd131597feb17 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 17 Nov 2021 01:25:41 +0100
Subject: [PATCH 078/197] ts-demuxer: append method called on empty internal
buffer creates copy this is to avoid any side-effects when sharing the
typed-array ref. performance impact should be minimal as it only has to be
done for first chunk on every parsing cycle.
---
src/demuxer/ts/mpegts-demuxer.ts | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index b7de20e..9137988 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -33,8 +33,9 @@ export class MpegTSDemuxer implements IDemuxer {
}
public append(data: Uint8Array): void {
- if (!this.data || this.data.byteLength === 0 || this.dataOffset >= this.data.byteLength) {
- this.data = data;
+ if (!this.data || this.data.byteLength === 0
+ || this.dataOffset >= this.data.byteLength) {
+ this.data = new Uint8Array(data);
this.dataOffset = 0;
} else {
const newLen: number = this.data.byteLength + data.byteLength;
From 741b86fb7e9311677065dd705190e933489d0473 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 17 Nov 2021 01:26:25 +0100
Subject: [PATCH 079/197] ts-demux: append(): rename temp to newBuffer (the ref
is not temp in fact)
---
src/demuxer/ts/mpegts-demuxer.ts | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index 9137988..45ecec4 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -39,10 +39,10 @@ export class MpegTSDemuxer implements IDemuxer {
this.dataOffset = 0;
} else {
const newLen: number = this.data.byteLength + data.byteLength;
- const temp: Uint8Array = new Uint8Array(newLen);
- temp.set(this.data, 0);
- temp.set(data, this.data.byteLength);
- this.data = temp;
+ const newBuffer: Uint8Array = new Uint8Array(newLen);
+ newBuffer.set(this.data, 0);
+ newBuffer.set(data, this.data.byteLength);
+ this.data = newBuffer;
}
this.parse();
From 51f5ef79a96b38fb2ba2076fa7cab14f3463df11 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 17 Nov 2021 01:27:40 +0100
Subject: [PATCH 080/197] ts-demuxer: in end() method, set dataOffset = 0,
otherwise can crash when end/flush() is called during parse and data is
nulled.
---
src/demuxer/ts/mpegts-demuxer.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index 45ecec4..d6af162 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -63,6 +63,7 @@ export class MpegTSDemuxer implements IDemuxer {
}
}
this.data = null;
+ this.dataOffset = 0;
}
private parse(): void {
From 78904fd798e206493054776e612f03dcb159985c Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 17 Nov 2021 01:28:49 +0100
Subject: [PATCH 081/197] ts-demuxer: add onPmtParsed callback for consuming
side to flush accordingly or just to notify track metadata is alevailab.
---
src/demuxer/ts/mpegts-demuxer.ts | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index d6af162..1c5840d 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -66,6 +66,8 @@ export class MpegTSDemuxer implements IDemuxer {
this.dataOffset = 0;
}
+ public onPmtParsed() {};
+
private parse(): void {
this.findContainerType();
@@ -239,5 +241,6 @@ export class MpegTSDemuxer implements IDemuxer {
}
}
this.pmtParsed = true;
+ this.onPmtParsed();
}
}
From d6834f07ac33e2b90237cc5b547ab136622be1ca Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 17 Nov 2021 01:29:35 +0100
Subject: [PATCH 082/197] ts-demuxer: readSamples: add check to fix crash
occuring when flushing data during parse (via pmt cb)
---
src/demuxer/ts/mpegts-demuxer.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index 1c5840d..9bb021c 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -140,7 +140,7 @@ export class MpegTSDemuxer implements IDemuxer {
}
private readSamples(): void {
- while (this.dataOffset < this.data.byteLength - 1) {
+ while (this.data && this.dataOffset < this.data.byteLength - 1) {
const byteRead: number = this.data[this.dataOffset++];
if (byteRead === MpegTSDemuxer.MPEGTS_SYNC
From 84a50da879eef4c2f73f9f41efbab86faea2db20 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 17 Nov 2021 01:29:58 +0100
Subject: [PATCH 083/197] ts-demuxer: rename methods to
parseProgramAllocationTable & parseProgramMapTable
---
src/demuxer/ts/mpegts-demuxer.ts | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index 9bb021c..3a67583 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -175,9 +175,9 @@ export class MpegTSDemuxer implements IDemuxer {
}
if (adaptationField === 1 || adaptationField === 3) {
if (pid === 0) {
- this.parseProgramId(payloadUnitStartIndicator, packetParser);
+ this.parseProgramAllocationTable(payloadUnitStartIndicator, packetParser);
} else if (pid === this.pmtId) {
- this.parseProgramTable(payloadUnitStartIndicator, packetParser);
+ this.parseProgramMapTable(payloadUnitStartIndicator, packetParser);
} else {
const track: TSTrack = this.tracks[pid] as TSTrack;
if (track && track.pes) {
@@ -187,7 +187,7 @@ export class MpegTSDemuxer implements IDemuxer {
}
}
- private parseProgramId(payloadUnitStartIndicator: boolean, packetParser: BitReader): void {
+ private parseProgramAllocationTable(payloadUnitStartIndicator: boolean, packetParser: BitReader): void {
if (payloadUnitStartIndicator) {
packetParser.skipBytes(packetParser.readByte());
}
@@ -195,7 +195,7 @@ export class MpegTSDemuxer implements IDemuxer {
this.pmtId = packetParser.readBits(13);
}
- private parseProgramTable(payloadUnitStartIndicator: boolean, packetParser: BitReader): void {
+ private parseProgramMapTable(payloadUnitStartIndicator: boolean, packetParser: BitReader): void {
if (payloadUnitStartIndicator) {
packetParser.skipBytes(packetParser.readByte());
}
From 9840a91e83fd67ed18e9e75c0c20511feb2a19e2 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 17 Nov 2021 01:30:16 +0100
Subject: [PATCH 084/197] ts-track: fix line break
---
src/demuxer/ts/ts-track.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/demuxer/ts/ts-track.ts b/src/demuxer/ts/ts-track.ts
index adf7e0d..d51dedf 100644
--- a/src/demuxer/ts/ts-track.ts
+++ b/src/demuxer/ts/ts-track.ts
@@ -6,7 +6,9 @@ import { AdtsReader } from './payload/adts-reader';
import { Sps } from '../../codecs/h264/nal-units';
export class TSTrack extends Track {
- constructor(id: number, type: string, mimeType: string, public pes: PESReader) {
+ constructor(id: number, type: string, mimeType: string,
+ public pes: PESReader) {
+
super(id, type, mimeType);
}
From 87ed3d41ee29c2f6446ef67ca738aa6e249e2230 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 17 Nov 2021 01:30:52 +0100
Subject: [PATCH 085/197] h264-reader: add check on dataBuffer to fix crash
occuring on flush calll when dataBuffer = null.
---
src/demuxer/ts/payload/h264-reader.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index d67bf41..b97c6d4 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -72,7 +72,7 @@ export class H264Reader extends PayloadReader {
}
public flush(pts: number): void {
- if (this.dataBuffer.byteLength > 0) {
+ if (this.dataBuffer && this.dataBuffer.byteLength > 0) {
this.consumeData(pts);
if (this.dataBuffer.byteLength > 0) {
From 94b859940b9f93c2b7f4a422f18e23bff5f8e445 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 22 Nov 2021 00:48:09 +0100
Subject: [PATCH 086/197] ts-track & payload-reader: add popFrames method to
consume frames continously
---
src/demuxer/ts/payload/payload-reader.ts | 8 +++++++-
src/demuxer/ts/ts-track.ts | 9 +++++----
2 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/src/demuxer/ts/payload/payload-reader.ts b/src/demuxer/ts/payload/payload-reader.ts
index 316e777..ada745e 100644
--- a/src/demuxer/ts/payload/payload-reader.ts
+++ b/src/demuxer/ts/payload/payload-reader.ts
@@ -35,7 +35,7 @@ export abstract class PayloadReader {
}
public reset(): void {
- this.frames = [];
+ this.frames.length = 0;
this.dataOffset = 0;
this.firstTimestamp = -1;
this.timeUs = -1;
@@ -49,6 +49,12 @@ export abstract class PayloadReader {
this.dataOffset = 0;
}
+ public popFrames(): Frame[] {
+ const frames = this.frames.slice(0);
+ this.frames.length = 0;
+ return frames;
+ }
+
public abstract consumeData(pts: number): void;
public getMimeType(): string {
diff --git a/src/demuxer/ts/ts-track.ts b/src/demuxer/ts/ts-track.ts
index d51dedf..e7b7b10 100644
--- a/src/demuxer/ts/ts-track.ts
+++ b/src/demuxer/ts/ts-track.ts
@@ -20,10 +20,11 @@ export class TSTrack extends Track {
}
public getFrames(): Frame[] {
- if (this.pes && this.pes.payloadReader) {
- return this.pes.payloadReader.frames;
- }
- return [];
+ return this?.pes?.payloadReader.frames || [];
+ }
+
+ public popFrames(): Frame[] | null {
+ return this?.pes?.payloadReader.popFrames() || null;
}
public getMetadata(): {} {
From c382baf436a38a1861b93d25a356bfd8ce1d68fd Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 22 Nov 2021 00:48:33 +0100
Subject: [PATCH 087/197] ts-demux: add get currentBufferSize to check on
current analyze buffer health
---
src/demuxer/ts/mpegts-demuxer.ts | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index 3a67583..d0a0b15 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -32,6 +32,10 @@ export class MpegTSDemuxer implements IDemuxer {
this.tracks = {};
}
+ get currentBufferSize(): number {
+ return this?.data.byteLength || 0;
+ }
+
public append(data: Uint8Array): void {
if (!this.data || this.data.byteLength === 0
|| this.dataOffset >= this.data.byteLength) {
From 3dcc4080878cc40dfd287e1c117f5740850d8148 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 22 Nov 2021 00:51:25 +0100
Subject: [PATCH 088/197] ts-demux: optimize buffer shifting after parse code
to be more explicit (subarray clamps the start/end values but it isn't
intuitive to read that).
---
src/demuxer/ts/mpegts-demuxer.ts | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index d0a0b15..1937942 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -52,7 +52,11 @@ export class MpegTSDemuxer implements IDemuxer {
this.parse();
if (this.dataOffset > 0) {
- this.data = this.data.subarray(this.dataOffset);
+ if (this.dataOffset < this.data.byteLength) {
+ this.data = this.data.subarray(this.dataOffset);
+ } else {
+ this.data = null;
+ }
this.dataOffset = 0;
}
From eda9b7aef53785c3937be79d6718f645f1eabb6d Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 22 Nov 2021 01:56:50 +0100
Subject: [PATCH 089/197] h264-reader: improve code readability, rename local
pts to timeUs (units!)
---
src/demuxer/ts/payload/h264-reader.ts | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index b97c6d4..91db614 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -71,9 +71,9 @@ export class H264Reader extends PayloadReader {
return Track.MIME_TYPE_AVC;
}
- public flush(pts: number): void {
+ public flush(timeUs: number): void {
if (this.dataBuffer && this.dataBuffer.byteLength > 0) {
- this.consumeData(pts);
+ this.consumeData(timeUs);
if (this.dataBuffer.byteLength > 0) {
const offset: number = this.findNextNALUnit(0);
@@ -91,13 +91,13 @@ export class H264Reader extends PayloadReader {
this.pps = false;
}
- public consumeData(pts: number): void {
+ public consumeData(timeUs: number): void {
if (!this.dataBuffer) {
return;
}
if (this.firstTimestamp === -1) {
- this.timeUs = this.firstTimestamp = pts;
+ this.timeUs = this.firstTimestamp = timeUs;
}
// process any possible remaining data
@@ -115,8 +115,8 @@ export class H264Reader extends PayloadReader {
}
// process next nal units in the buffer
- if (pts !== -1) {
- this.timeUs = pts;
+ if (timeUs !== -1) {
+ this.timeUs = timeUs;
}
if (this.dataBuffer.byteLength > 0) {
From fe3afe4cab2072d92734bcf141d28ebd54b27fa5 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 22 Nov 2021 01:57:08 +0100
Subject: [PATCH 090/197] pes-reader: rename private lastPts to lastPtsUs
---
src/demuxer/ts/pes-reader.ts | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/demuxer/ts/pes-reader.ts b/src/demuxer/ts/pes-reader.ts
index 48b2f64..89b3e83 100644
--- a/src/demuxer/ts/pes-reader.ts
+++ b/src/demuxer/ts/pes-reader.ts
@@ -16,13 +16,13 @@ export class PESReader {
public payloadReader: PayloadReader;
- private lastPts: number;
+ private lastPtsUs: number;
private pesLength: number;
constructor(public pid: number, public type: number) {
this.pid = pid;
this.type = type;
- this.lastPts = -1;
+ this.lastPtsUs = -1;
this.pesLength = 0;
if (type === PESReader.TS_STREAM_TYPE_AAC) {
@@ -47,7 +47,7 @@ export class PESReader {
public appendData(payloadUnitStartIndicator: boolean, packet: BitReader): void {
if (payloadUnitStartIndicator) {
if (this.payloadReader) {
- this.payloadReader.consumeData(this.lastPts);
+ this.payloadReader.consumeData(this.lastPtsUs);
}
this.parsePESHeader(packet);
}
@@ -71,7 +71,7 @@ export class PESReader {
pts |= (val & 0xFE) >>> 3;
pts = pts << 2;
pts += (val & 0x06) >>> 1;
- this.lastPts = PESReader.ptsToTimeUs(pts);
+ this.lastPtsUs = PESReader.ptsToTimeUs(pts);
}
}
@@ -83,7 +83,7 @@ export class PESReader {
public flush(): void {
if (this.payloadReader) {
- this.payloadReader.flush(this.lastPts);
+ this.payloadReader.flush(this.lastPtsUs);
}
}
}
From b86e53291c33554b21761965c42b9d1546cad188 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 22 Nov 2021 01:57:57 +0100
Subject: [PATCH 091/197] ts-track: ensure to return non-null array on frame
getters & shorten checks
---
src/demuxer/ts/ts-track.ts | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/src/demuxer/ts/ts-track.ts b/src/demuxer/ts/ts-track.ts
index e7b7b10..55c1cbe 100644
--- a/src/demuxer/ts/ts-track.ts
+++ b/src/demuxer/ts/ts-track.ts
@@ -13,22 +13,19 @@ export class TSTrack extends Track {
}
public getDuration(): number {
- if (this.pes && this.pes.payloadReader) {
- return this.pes.payloadReader.getDuration();
- }
- return 0;
+ return this?.pes?.payloadReader.getDuration() || 0;
}
public getFrames(): Frame[] {
return this?.pes?.payloadReader.frames || [];
}
- public popFrames(): Frame[] | null {
- return this?.pes?.payloadReader.popFrames() || null;
+ public popFrames(): Frame[] {
+ return this?.pes?.payloadReader.popFrames() || [];
}
public getMetadata(): {} {
- if (this.pes && this.pes.payloadReader) {
+ if (this?.pes.payloadReader) {
if (this.pes.payloadReader instanceof H264Reader && (this.pes.payloadReader as H264Reader).sps) {
const sps: Sps = (this.pes.payloadReader as H264Reader).sps;
return {
From bb20b0e037e286702babb212cd300897e4c3249b Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 22 Nov 2021 01:58:35 +0100
Subject: [PATCH 092/197] payload-reader: popFrames: optimize on empty frames
state (dont do any copy)
---
src/demuxer/ts/payload/payload-reader.ts | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/demuxer/ts/payload/payload-reader.ts b/src/demuxer/ts/payload/payload-reader.ts
index ada745e..abd6e85 100644
--- a/src/demuxer/ts/payload/payload-reader.ts
+++ b/src/demuxer/ts/payload/payload-reader.ts
@@ -50,6 +50,9 @@ export abstract class PayloadReader {
}
public popFrames(): Frame[] {
+ if (this.frames.length === 0) {
+ return this.frames;
+ }
const frames = this.frames.slice(0);
this.frames.length = 0;
return frames;
From 4fdd78fb092d8c86b1f34c1cb219950312b0b367 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 23 Nov 2021 11:27:51 +0100
Subject: [PATCH 093/197] ts-demuxer: parsing logic refactor to support
properly partial/unsegmented packet data input from either network stream
input. - remove redundant readHeader/readSamples methods to unify in one
readPackets method.
---
src/demuxer/ts/mpegts-demuxer.ts | 61 ++++++++++++--------------------
1 file changed, 22 insertions(+), 39 deletions(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index 1937942..d880eff 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -13,7 +13,8 @@ enum CONTAINER_TYPE {
export class MpegTSDemuxer implements IDemuxer {
private static MPEGTS_SYNC: number = 0x47;
- private static MPEGTS_PACKET_SIZE: number = 187;
+ private static MPEGTS_PACKET_SIZE: number = 188;
+ private static MPEGTS_PACKET_SIZE_MINUS_ONE: number = 187;
public tracks: { [id: number] : TSTrack; };
@@ -77,16 +78,17 @@ export class MpegTSDemuxer implements IDemuxer {
public onPmtParsed() {};
private parse(): void {
+
this.findContainerType();
if (this.containerType === CONTAINER_TYPE.MPEG_TS) {
- this.readHeader();
- this.readSamples();
- } else { // FIXME: support raw mpeg audio
- const dataParser: BitReader = new BitReader(this.data);
- this.tracks[0] = new TSTrack(0, Track.TYPE_AUDIO, Track.MIME_TYPE_AAC,
+ this.readPackets();
+ } else {
+ const streamReader: BitReader = new BitReader(this.data);
+ this.tracks[0] = new TSTrack(0,
+ Track.TYPE_AUDIO, Track.MIME_TYPE_AAC,
new PESReader(0, PESReader.TS_STREAM_TYPE_AAC));
- (this.tracks[0] as TSTrack).pes.appendData(false, dataParser);
+ this.tracks[0].pes.appendData(false, streamReader);
}
}
@@ -126,40 +128,21 @@ export class MpegTSDemuxer implements IDemuxer {
}
}
- private readHeader(): void {
- while (this.dataOffset < this.data.byteLength - 1) {
- const byteRead: number = this.data[this.dataOffset];
- this.dataOffset++;
-
- if (byteRead === MpegTSDemuxer.MPEGTS_SYNC
- && (this.data.byteLength - this.dataOffset) >= MpegTSDemuxer.MPEGTS_PACKET_SIZE) {
-
- const packet: Uint8Array = this.data.subarray(this.dataOffset,
- this.dataOffset + MpegTSDemuxer.MPEGTS_PACKET_SIZE);
- this.dataOffset += MpegTSDemuxer.MPEGTS_PACKET_SIZE;
-
- this.processTSPacket(packet);
-
- if (this.pmtParsed) {
- break;
- }
- }
- }
- }
-
- private readSamples(): void {
- while (this.data && this.dataOffset < this.data.byteLength - 1) {
- const byteRead: number = this.data[this.dataOffset++];
-
- if (byteRead === MpegTSDemuxer.MPEGTS_SYNC
- && (this.data.byteLength - this.dataOffset) >= MpegTSDemuxer.MPEGTS_PACKET_SIZE) {
-
- const packet: Uint8Array = this.data.subarray(this.dataOffset, this.dataOffset
- + MpegTSDemuxer.MPEGTS_PACKET_SIZE);
- this.dataOffset += MpegTSDemuxer.MPEGTS_PACKET_SIZE;
+ private readPackets(): void {
+ // run as long as there is at least a full packet in buffer
+ while ((this.data.byteLength - this.dataOffset) >= MpegTSDemuxer.MPEGTS_PACKET_SIZE) {
- this.processTSPacket(packet);
+ // check for sync-byte
+ const currentByte: number = this.data[this.dataOffset];
+ if (currentByte !== MpegTSDemuxer.MPEGTS_SYNC) {
+ // keep looking if we are out of sync
+ this.dataOffset++;
+ continue;
}
+ const packet: Uint8Array = this.data.subarray(this.dataOffset + 1,
+ this.dataOffset + MpegTSDemuxer.MPEGTS_PACKET_SIZE_MINUS_ONE);
+ this.dataOffset += MpegTSDemuxer.MPEGTS_PACKET_SIZE;
+ this.processTsPacket(packet);
}
}
From cc85f9741aa2fa59043ff63fb932b23db4853f7f Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 23 Nov 2021 11:29:05 +0100
Subject: [PATCH 094/197] ts-demuxer: add getters currentPacketCount and
isPmtParsed
---
src/demuxer/ts/mpegts-demuxer.ts | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index d880eff..b168e5c 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -37,6 +37,13 @@ export class MpegTSDemuxer implements IDemuxer {
return this?.data.byteLength || 0;
}
+ get currentPacketCount(): number {
+ return this.packetsCount;
+ }
+
+ get isPmtParsed(): boolean {
+ return this.isPmtParsed;
+ }
public append(data: Uint8Array): void {
if (!this.data || this.data.byteLength === 0
|| this.dataOffset >= this.data.byteLength) {
From 8e7f5e93969970a5c445408a69c54043e6341bb1 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 23 Nov 2021 11:30:07 +0100
Subject: [PATCH 095/197] ts-demux: rename method to processTsPackets and
internal locals
---
src/demuxer/ts/mpegts-demuxer.ts | 26 +++++++++++++-------------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index b168e5c..08f2238 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -153,33 +153,33 @@ export class MpegTSDemuxer implements IDemuxer {
}
}
- private processTSPacket(packet: Uint8Array): void {
+ private processTsPacket(packet: Uint8Array): void {
this.packetsCount++;
- const packetParser: BitReader = new BitReader(packet);
- packetParser.skipBits(1);
+ const packetReader: BitReader = new BitReader(packet);
+ packetReader.skipBits(1);
- const payloadUnitStartIndicator: boolean = (packetParser.readBits(1) !== 0);
- packetParser.skipBits(1);
+ const payloadUnitStartIndicator: boolean = (packetReader.readBits(1) !== 0);
+ packetReader.skipBits(1);
- const pid: number = packetParser.readBits(13);
- const adaptationField: number = (packetParser.readByte() & 0x30) >> 4;
+ const pid: number = packetReader.readBits(13);
+ const adaptationField: number = (packetReader.readByte() & 0x30) >> 4;
if (adaptationField > 1) {
- const length: number = packetParser.readByte();
+ const length: number = packetReader.readByte();
if (length > 0) {
- packetParser.skipBytes(length);
+ packetReader.skipBytes(length);
}
}
if (adaptationField === 1 || adaptationField === 3) {
if (pid === 0) {
- this.parseProgramAllocationTable(payloadUnitStartIndicator, packetParser);
+ this.parseProgramAllocationTable(payloadUnitStartIndicator, packetReader);
} else if (pid === this.pmtId) {
- this.parseProgramMapTable(payloadUnitStartIndicator, packetParser);
+ this.parseProgramMapTable(payloadUnitStartIndicator, packetReader);
} else {
- const track: TSTrack = this.tracks[pid] as TSTrack;
+ const track: TSTrack = this.tracks[pid];
if (track && track.pes) {
- track.pes.appendData(payloadUnitStartIndicator, packetParser);
+ track.pes.appendData(payloadUnitStartIndicator, packetReader);
}
}
}
From be7fd70c4024b1316bb51723278fbbbd042368d3 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 23 Nov 2021 12:06:18 +0100
Subject: [PATCH 096/197] ts-demuxer: spec append method to return parsing
buffer pruned remainder, this can allow to operate the parser in a packet
streaming fashion, so that we can pass in any data and get out the most
recently updating data of track frames from any parsing pass.
---
src/demuxer/ts/mpegts-demuxer.ts | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index 08f2238..db2543d 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -44,9 +44,9 @@ export class MpegTSDemuxer implements IDemuxer {
get isPmtParsed(): boolean {
return this.isPmtParsed;
}
- public append(data: Uint8Array): void {
- if (!this.data || this.data.byteLength === 0
- || this.dataOffset >= this.data.byteLength) {
+
+ public append(data: Uint8Array): Uint8Array | null {
+ if (!this.data || this.data.byteLength === 0) {
this.data = new Uint8Array(data);
this.dataOffset = 0;
} else {
@@ -59,16 +59,24 @@ export class MpegTSDemuxer implements IDemuxer {
this.parse();
+ let parsedBuf: Uint8Array = null;
+ // prune off parsing remainder from buffer
if (this.dataOffset > 0) {
- if (this.dataOffset < this.data.byteLength) {
+ // we might have dropped the data already
+ // through a parsing callback calling end() for example.
+ if (this.data) {
+ if (this.dataOffset >= this.data.byteLength) {
+ throw new Error('Reader offset is out of buffer range');
+ }
+ parsedBuf = this.data.subarray(0, this.dataOffset);
this.data = this.data.subarray(this.dataOffset);
- } else {
- this.data = null;
}
this.dataOffset = 0;
}
this.updateTracks();
+
+ return parsedBuf;
}
public end(): void {
From 8a868dd8a0bacf6a441c647f56eda126f3d4e66f Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 23 Nov 2021 12:23:02 +0100
Subject: [PATCH 097/197] ts-demuxer: fix check on data offset assertion in
buffer pruning
---
src/demuxer/ts/mpegts-demuxer.ts | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index db2543d..5c34c33 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -65,10 +65,14 @@ export class MpegTSDemuxer implements IDemuxer {
// we might have dropped the data already
// through a parsing callback calling end() for example.
if (this.data) {
- if (this.dataOffset >= this.data.byteLength) {
+ // the offset is expected to go +1 the buffer range
+ // thus the > instead of >=
+ if (this.dataOffset > this.data.byteLength) {
throw new Error('Reader offset is out of buffer range');
}
+ // second arg of .subarray is exclusive range
parsedBuf = this.data.subarray(0, this.dataOffset);
+ // the first argument yields to an empty array when out-of-range
this.data = this.data.subarray(this.dataOffset);
}
this.dataOffset = 0;
From fc1ef6dabf7fdcc324f270874340c97c92fbea97 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 23 Nov 2021 16:08:12 +0100
Subject: [PATCH 098/197] pes-reader: fix PTS/DTS header parsing, see comments,
was failing > 31 bits we were getting negative numbers therefore in some
data.
---
src/demuxer/ts/pes-reader.ts | 61 +++++++++++++++++++++++++++---------
1 file changed, 47 insertions(+), 14 deletions(-)
diff --git a/src/demuxer/ts/pes-reader.ts b/src/demuxer/ts/pes-reader.ts
index 89b3e83..ee7291f 100644
--- a/src/demuxer/ts/pes-reader.ts
+++ b/src/demuxer/ts/pes-reader.ts
@@ -59,20 +59,9 @@ export class PESReader {
public parsePESHeader(packet: BitReader): void {
packet.skipBytes(7);
- const timingFlags: number = packet.readByte();
- if (timingFlags & 0xC0) {
- packet.skipBytes(1);
- let pts: number;
- pts = (packet.readByte() & 0x0E) << 27 |
- (packet.readByte() & 0xFF) << 20 |
- (packet.readByte() & 0xFE) << 12 |
- (packet.readByte() & 0xFF) << 5;
- const val: number = packet.readByte();
- pts |= (val & 0xFE) >>> 3;
- pts = pts << 2;
- pts += (val & 0x06) >>> 1;
- this.lastPtsUs = PESReader.ptsToTimeUs(pts);
- }
+
+ const [dts, pts] = this.readPesTimingInfo(packet);
+ this.lastPtsUs = PESReader.ptsToTimeUs(pts);
}
public reset(): void {
@@ -86,4 +75,48 @@ export class PESReader {
this.payloadReader.flush(this.lastPtsUs);
}
}
+
+ private readPesTimingInfo(packet: BitReader): [number, number] {
+ /**
+ * Thanks to Videojs/Muxjs for this bit, which does well the
+ * trick around 32-bit unary bit-ops and 33 bit numbers :)
+ * -> See https://github.com/videojs/mux.js/blob/87f777f718b264df69a063847fe0fb9b5e0aaa6c/lib/m2ts/m2ts.js#L333
+ */
+ // PTS and DTS are normally stored as a 33-bit number. Javascript
+ // performs all bitwise operations on 32-bit integers but javascript
+ // supports a much greater range (52-bits) of integer using standard
+ // mathematical operations.
+ // We construct a 31-bit value using bitwise operators over the 31
+ // most significant bits and then multiply by 4 (equal to a left-shift
+ // of 2) before we add the final 2 least significant bits of the
+ // timestamp (equal to an OR.)
+ const ptsDtsFlags = packet.readByte();
+ packet.skipBytes(1);
+ let pts = NaN;
+ let dts = NaN;
+ if (ptsDtsFlags & 0xC0) {
+ // the PTS and DTS are not written out directly. For information
+ // on how they are encoded, see
+ // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
+ pts = (packet.readByte() & 0x0E) << 27 |
+ (packet.readByte() & 0xFF) << 20 |
+ (packet.readByte() & 0xFE) << 12 |
+ (packet.readByte() & 0xFF) << 5 |
+ (packet.readByte() & 0xFE) >>> 3;
+ pts *= 4; // Left shift by 2
+ pts += (packet.readByte() & 0x06) >>> 1; // OR by the two LSBs
+ dts = pts;
+ if (ptsDtsFlags & 0x40) {
+ let lastByte;
+ dts = (packet.readByte() & 0x0E) << 27 |
+ (packet.readByte() & 0xFF) << 20 |
+ (packet.readByte() & 0xFE) << 12 |
+ (packet.readByte() & 0xFF) << 5 |
+ (lastByte = packet.readByte() & 0xFE) >>> 3;
+ dts *= 4; // Left shift by 2
+ dts += (lastByte & 0x06) >>> 1; // OR by the two LSBs
+ }
+ }
+ return [dts, pts];
+ }
}
From 17041e9340e25e47746ef586b29f30db1a22d605 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 25 Nov 2021 11:47:09 +0100
Subject: [PATCH 099/197] ts-demuxer: add prune method to expose pop`ing off
data on demand from parse buffer + make the pruning after parse in append
method call optional
---
src/demuxer/ts/mpegts-demuxer.ts | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index 5c34c33..2b49f6c 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -45,7 +45,7 @@ export class MpegTSDemuxer implements IDemuxer {
return this.isPmtParsed;
}
- public append(data: Uint8Array): Uint8Array | null {
+ public append(data: Uint8Array, pruneAfterParse: boolean = false): Uint8Array | null {
if (!this.data || this.data.byteLength === 0) {
this.data = new Uint8Array(data);
this.dataOffset = 0;
@@ -58,7 +58,15 @@ export class MpegTSDemuxer implements IDemuxer {
}
this.parse();
+ this.updateTracks();
+
+ if (pruneAfterParse) {
+ return this.prune();
+ }
+ return null;
+ }
+ public prune(): Uint8Array | null {
let parsedBuf: Uint8Array = null;
// prune off parsing remainder from buffer
if (this.dataOffset > 0) {
@@ -77,9 +85,6 @@ export class MpegTSDemuxer implements IDemuxer {
}
this.dataOffset = 0;
}
-
- this.updateTracks();
-
return parsedBuf;
}
From b81a9105c71c002bc2921d499fc9f71d015e45d1 Mon Sep 17 00:00:00 2001
From: "J. Oliva"
Date: Wed, 22 Dec 2021 14:30:24 +0100
Subject: [PATCH 100/197] Update h264-reader.ts
Add frames even though sps/pps were not read yet.
---
src/demuxer/ts/payload/h264-reader.ts | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index 91db614..981aa6f 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -208,11 +208,8 @@ export class H264Reader extends PayloadReader {
sliceParser.readUEG();
const sliceType: number = sliceParser.readUEG();
const type: string = this.getSliceTypeName(sliceType);
- if (this.sps && this.pps) {
- this.addNewFrame(type, limit - start, NaN);
- } else {
- // console.warn('Slice ' + type + ' received without sps/pps been set');
- }
+ this.addNewFrame(type, limit - start, NaN);
+
sliceParser.destroy();
sliceParser = null;
}
From aa7b083a18a3e28f4da31429324879106d20ea5c Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 6 Jan 2022 04:32:19 +0100
Subject: [PATCH 101/197] mp4-demux: handle base-media-decode-time properly +
improve track internals
---
src/demuxer/mp4/mp4-demuxer.ts | 19 ++++++----
src/demuxer/mp4/mp4-track.ts | 63 +++++++++++++++++-----------------
src/demuxer/track.ts | 6 ++--
3 files changed, 47 insertions(+), 41 deletions(-)
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index a051c71..6bff0a3 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -1,12 +1,10 @@
import ByteParserUtils from '../../utils/byte-parser-utils';
-import { Track } from '../track';
-import { Mp4Track } from './mp4-track';
-
import { IDemuxer, TracksHash } from '../demuxer';
-import { AudioAtom } from './atoms/helpers/audio-atom';
-import { VideoAtom } from './atoms/helpers/video-atom';
+import { Track } from '../track';
+import { Mp4Track } from './mp4-track';
+import { Mp4SampleTable } from './mp4-sample-table';
import { boxesParsers } from './atoms';
import { Atom, ContainerAtom } from './atoms/atom';
@@ -22,10 +20,12 @@ import { Ctts } from './atoms/ctts';
import { Stss } from './atoms/stss';
import { Stco } from './atoms/stco';
import { Mdhd } from './atoms/mdhd';
-
-import { Mp4SampleTable } from './mp4-sample-table';
import { Esds } from './atoms/esds';
import { Mvhd } from './atoms/mvhd';
+import { Tfdt } from './atoms/tfdt';
+
+import { AudioAtom } from './atoms/helpers/audio-atom';
+import { VideoAtom } from './atoms/helpers/video-atom';
const log = (...msg: any[]) => void 0; // console.log.bind(console);
const warn = console.warn.bind(console);
@@ -199,6 +199,11 @@ export class Mp4Demuxer implements IDemuxer {
// Fragmented-mode ...
+ case Atom.tfdt:
+ const tfdt: Tfdt = atom as Tfdt;
+ this._getLastTrackCreated().setBaseMediaDecodeTime(tfdt.baseMediaDecodeTime);
+ break;
+
case Atom.tfhd:
// FIXME: should be handled differently
// by looking at other things inside fragments
diff --git a/src/demuxer/mp4/mp4-track.ts b/src/demuxer/mp4/mp4-track.ts
index 3db8b9e..952214a 100644
--- a/src/demuxer/mp4/mp4-track.ts
+++ b/src/demuxer/mp4/mp4-track.ts
@@ -11,7 +11,7 @@ import { Sidx } from './atoms/sidx';
import { Trun, SampleFlags } from './atoms/trun';
import { Avc1 } from './atoms/avc1';
-import { toMicroseconds } from '../../utils/timescale';
+import { MICROSECOND_TIMESCALE, toMicroseconds } from '../../utils/timescale';
export type Mp4TrackDefaults = {
sampleDuration: number;
@@ -24,12 +24,13 @@ export class Mp4Track extends Track {
private sidx: Sidx = null;
private trunInfo: Trun[] = [];
private trunInfoReadIndex: number = 0;
- private lastPts: number;
- private lastPtsUnscaledUint: number;
+ private lastDtsUs: number = 0;
+ private lastDtsScaled: number = 0;
private timescale: number = null;
private defaults: Mp4TrackDefaults[] = [];
private defaultSampleFlagsParsed: (SampleFlags | null)[] = [];
private baseDataOffset: number = 0;
+ private baseMediaDecodeTime: number = 0;
constructor(
id: number,
@@ -41,9 +42,8 @@ export class Mp4Track extends Track {
) {
super(id, type, mimeType);
- this.lastPts = 0;
- this.lastPtsUnscaledUint = 0;
- this.duration = 0;
+ // declared in base-class
+ this.durationUs = 0;
if (this.dataOffset < 0) {
throw new Error('Invalid file, no sample-data base-offset can be determined');
@@ -87,14 +87,20 @@ export class Mp4Track extends Track {
return this.metadataAtom;
}
- public getLastPts(): number {
- return this.lastPts;
- }
-
public getTimescale(): number {
return this.timescale;
}
+ public setBaseMediaDecodeTime(baseDts: number) {
+ this.baseMediaDecodeTime = baseDts;
+ this.lastDtsScaled = baseDts;
+ this.lastDtsUs = toMicroseconds(baseDts, this.timescale);
+ }
+
+ public getBaseMediaDecodeTime(): number {
+ return this.baseMediaDecodeTime;
+ }
+
public setTimescale(timescale: number) {
this.timescale = timescale;
}
@@ -150,8 +156,8 @@ export class Mp4Track extends Track {
public setSidxAtom(atom: Atom): void {
this.sidx = atom as Sidx;
- this.lastPtsUnscaledUint = this.sidx.earliestPresentationTime;
- this.lastPts = 1000000 * this.sidx.earliestPresentationTime / this.sidx.timescale;
+ this.lastDtsScaled = this.sidx.earliestPresentationTime;
+ this.lastDtsUs = toMicroseconds(this.sidx.earliestPresentationTime, this.sidx.timescale);
this.timescale = this.sidx.timescale;
}
@@ -159,9 +165,9 @@ export class Mp4Track extends Track {
if (!frame.hasUnnormalizedIntegerTiming()) {
throw new Error('Frame must have unscaled-int sample timing');
}
- this.lastPtsUnscaledUint += frame.scaledDuration;
- this.lastPts += frame.duration;
- this.duration += frame.duration;
+ this.lastDtsScaled += frame.scaledDuration;
+ this.lastDtsUs += frame.duration;
+ this.durationUs += frame.duration;
this.frames.push(frame);
}
@@ -191,46 +197,41 @@ export class Mp4Track extends Track {
for (let i = 0; i < trun.samples.length; i++) {
const sample = trun.samples[i];
- const sampleDuration = sample.duration || this.defaults[trunIndex]?.sampleDuration;
- if (!sampleDuration) {
- throw new Error('Invalid file, samples have no duration');
- }
-
- const duration: number = toMicroseconds(sampleDuration, timescale);
const flags = sample.flags || this.defaultSampleFlagsParsed[trunIndex];
if (!flags) {
//warn('no default sample flags in track sample-run');
// in fact the trun box parser should provide a fallback instance of flags in this case
- //throw new Error('Invalid file, sample has no flags');
}
- const cto: number = toMicroseconds((sample.compositionTimeOffset || 0), timescale);
+ const sampleDuration = sample.duration || this.defaults[trunIndex]?.sampleDuration;
+ if (!sampleDuration) {
+ throw new Error('Invalid file, samples have no duration');
+ }
- const timeUs = this.lastPts;
+ const durationUs: number = toMicroseconds(sampleDuration, timescale);
+ const ctoUs: number = toMicroseconds((sample.compositionTimeOffset || 0), timescale);
+ const dtsUs = this.lastDtsUs;
const frameSize = sample.size || this.defaults[trunIndex]?.sampleSize;
if (!frameSize) throw new Error('Frame has to have either sample-size of trun-entry or track default');
-
const newFrame = new Frame(
flags ? (flags.isSyncFrame ? Frame.IDR_FRAME : Frame.P_FRAME) : Frame.UNFLAGGED_FRAME,
- timeUs,
+ dtsUs,
frameSize,
- duration,
+ durationUs,
bytesOffset,
- cto
+ ctoUs
);
newFrame.scaledDuration = sampleDuration;
- newFrame.scaledDecodingTime = this.lastPtsUnscaledUint;
+ newFrame.scaledDecodingTime = this.lastDtsScaled;
newFrame.scaledPresentationTimeOffset = sample.compositionTimeOffset || 0;
newFrame.timescale = timescale;
this.appendFrame(newFrame);
- //debug(`frame: @ ${newFrame.timeUs} [us] -> ${newFrame.bytesOffset} / ${newFrame.size}`)
-
bytesOffset += frameSize;
}
})
diff --git a/src/demuxer/track.ts b/src/demuxer/track.ts
index b9faa18..2780030 100644
--- a/src/demuxer/track.ts
+++ b/src/demuxer/track.ts
@@ -23,7 +23,7 @@ export class Track {
public static MIME_TYPE_UNKNOWN: string = 'unknown';
protected frames: Frame[] = [];
- protected duration: number = NaN;
+ protected durationUs: number = NaN;
constructor(public id: number, public type: string /* fixme: make enum type */, public mimeType: string) {}
@@ -31,7 +31,7 @@ export class Track {
this.frames = this.getFrames().sort((a: Frame, b: Frame): number => {
return a.timeUs - b.timeUs;
});
- this.duration = this.getDuration();
+ this.durationUs = this.getDuration();
}
public flush() {
@@ -61,7 +61,7 @@ export class Track {
}
public getDuration(): number {
- return this.duration;
+ return this.durationUs;
}
public getDurationInSeconds(): number {
From ba78d7d47a49d0bbbb6d47a5533459049108d8ac Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 10 Jan 2022 22:10:10 +0100
Subject: [PATCH 102/197] h264-reader: safely initialize sps/pps member props
on construction
---
src/demuxer/ts/payload/h264-reader.ts | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index 981aa6f..2374030 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -58,8 +58,9 @@ export class H264Reader extends PayloadReader {
}
}
- public sps: Sps;
- public pps: boolean;
+ public sps: Sps = null;
+ public pps: boolean = false;
+
public pendingBytes: number;
constructor() {
From 220f45288f168c4447822dd6fa0d0aa27e87fcab Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Sun, 16 Jan 2022 02:55:24 +0100
Subject: [PATCH 103/197] h264-payload-reader: rm getNALUnitName (unused, can
be done via enum keying)
---
src/demuxer/ts/payload/h264-reader.ts | 25 +------------------------
1 file changed, 1 insertion(+), 24 deletions(-)
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index 2374030..741c5e9 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -35,29 +35,6 @@ export class Fraction {
export class H264Reader extends PayloadReader {
- static getNALUnitName(nalType: number): string {
- switch (nalType) {
- case NAL_UNIT_TYPE.SLICE:
- return 'SLICE';
- case NAL_UNIT_TYPE.SEI:
- return 'SEI';
- case NAL_UNIT_TYPE.PPS:
- return 'PPS';
- case NAL_UNIT_TYPE.SPS:
- return 'SPS';
- case NAL_UNIT_TYPE.AUD:
- return 'AUD';
- case NAL_UNIT_TYPE.IDR:
- return 'IDR';
- case NAL_UNIT_TYPE.END_SEQUENCE:
- return 'END SEQUENCE';
- case NAL_UNIT_TYPE.END_STREAM:
- return 'END STREAM';
- default:
- return 'Unknown';
- }
- }
-
public sps: Sps = null;
public pps: boolean = false;
@@ -210,7 +187,7 @@ export class H264Reader extends PayloadReader {
const sliceType: number = sliceParser.readUEG();
const type: string = this.getSliceTypeName(sliceType);
this.addNewFrame(type, limit - start, NaN);
-
+
sliceParser.destroy();
sliceParser = null;
}
From 2c03d157865cfcfe86aad2ded5da9f874e0525db Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Sun, 16 Jan 2022 03:17:31 +0100
Subject: [PATCH 104/197] h264-reader/nal-units: centralize enums and mapping
funcs + move private instance method to plain helper function (is static
mapping) + rm unused "Fraction" class decl
---
src/codecs/h264/nal-units.ts | 51 ++++++++++++++++++++++++++
src/demuxer/ts/payload/h264-reader.ts | 52 ++-------------------------
2 files changed, 53 insertions(+), 50 deletions(-)
diff --git a/src/codecs/h264/nal-units.ts b/src/codecs/h264/nal-units.ts
index 7ee48b0..3c91884 100644
--- a/src/codecs/h264/nal-units.ts
+++ b/src/codecs/h264/nal-units.ts
@@ -1,5 +1,56 @@
import { Size, FrameRate } from '../video-types';
+export enum NAL_UNIT_TYPE {
+ SLICE = 1,
+ DPA,
+ DPB,
+ DPC,
+ IDR,
+ SEI,
+ SPS,
+ PPS,
+ AUD,
+ END_SEQUENCE,
+ END_STREAM
+}
+
+export enum SLICE_TYPE {
+ P = 0,
+ B,
+ I,
+ SP,
+ SI
+}
+
+export enum FRAME_TYPE {
+ I = 'I',
+ P = 'P',
+ B = 'B',
+ SI = 'SI',
+ SP = 'SP',
+ UNKNOWN = ''
+}
+
+export function mapNaluSliceToFrameType(sliceType: number): FRAME_TYPE {
+ if (sliceType > 4) {
+ sliceType = sliceType - 5;
+ }
+ switch (sliceType) {
+ case SLICE_TYPE.B:
+ return FRAME_TYPE.B;
+ case SLICE_TYPE.I:
+ return FRAME_TYPE.I;
+ case SLICE_TYPE.P:
+ return FRAME_TYPE.P;
+ case SLICE_TYPE.SI:
+ return FRAME_TYPE.SI;
+ case SLICE_TYPE.SP:
+ return FRAME_TYPE.SP;
+ default:
+ return FRAME_TYPE.UNKNOWN;
+ }
+}
+
export class Sps {
constructor (
public id: number,
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index 741c5e9..0ab81da 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -3,35 +3,7 @@ import { PayloadReader } from './payload-reader';
import { Frame } from '../../frame';
import { Track } from '../../track';
import { H264ParameterSetParser } from '../../../codecs/h264/param-set-parser';
-import { Sps } from '../../../codecs/h264/nal-units';
-
-enum NAL_UNIT_TYPE {
- SLICE = 1,
- DPA,
- DPB,
- DPC,
- IDR,
- SEI,
- SPS,
- PPS,
- AUD,
- END_SEQUENCE,
- END_STREAM
-}
-
-enum SLICE_TYPE {
- P = 0,
- B,
- I,
- SP,
- SI
-}
-
-export class Fraction {
- constructor(public num: number, public den: number) {
- // do nothing
- }
-}
+import { mapNaluSliceToFrameType, NAL_UNIT_TYPE, Sps } from '../../../codecs/h264/nal-units';
export class H264Reader extends PayloadReader {
@@ -185,7 +157,7 @@ export class H264Reader extends PayloadReader {
sliceParser.skipBytes(4);
sliceParser.readUEG();
const sliceType: number = sliceParser.readUEG();
- const type: string = this.getSliceTypeName(sliceType);
+ const type: string = mapNaluSliceToFrameType(sliceType);
this.addNewFrame(type, limit - start, NaN);
sliceParser.destroy();
@@ -197,26 +169,6 @@ export class H264Reader extends PayloadReader {
// audParser.skipBytes(4);
}
- private getSliceTypeName(sliceType: number): string {
- if (sliceType > 4) {
- sliceType = sliceType - 5;
- }
- switch (sliceType) {
- case SLICE_TYPE.B:
- return Frame.B_FRAME;
- case SLICE_TYPE.I:
- return Frame.IDR_FRAME;
- case SLICE_TYPE.P:
- return Frame.P_FRAME;
- case SLICE_TYPE.SI:
- return 'SI';
- case SLICE_TYPE.SP:
- return 'SP';
- default:
- return 'Unknown';
- }
- }
-
private addNewFrame(frameType: string, frameSize: number, duration: number): void {
const frame = new Frame(frameType, this.timeUs, frameSize, duration);
this.frames.push(frame);
From 8ff4003b443ee13bb650f6d4252a5bc57e0b3f21 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Sun, 16 Jan 2022 03:22:23 +0100
Subject: [PATCH 105/197] h264-reader: slice-frame parse/mapping param
typesafety fix
---
src/codecs/h264/nal-units.ts | 2 +-
src/demuxer/ts/payload/h264-reader.ts | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/codecs/h264/nal-units.ts b/src/codecs/h264/nal-units.ts
index 3c91884..33775aa 100644
--- a/src/codecs/h264/nal-units.ts
+++ b/src/codecs/h264/nal-units.ts
@@ -31,7 +31,7 @@ export enum FRAME_TYPE {
UNKNOWN = ''
}
-export function mapNaluSliceToFrameType(sliceType: number): FRAME_TYPE {
+export function mapNaluSliceToFrameType(sliceType: SLICE_TYPE): FRAME_TYPE {
if (sliceType > 4) {
sliceType = sliceType - 5;
}
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index 0ab81da..2bc75ea 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -3,7 +3,7 @@ import { PayloadReader } from './payload-reader';
import { Frame } from '../../frame';
import { Track } from '../../track';
import { H264ParameterSetParser } from '../../../codecs/h264/param-set-parser';
-import { mapNaluSliceToFrameType, NAL_UNIT_TYPE, Sps } from '../../../codecs/h264/nal-units';
+import { mapNaluSliceToFrameType, NAL_UNIT_TYPE, SLICE_TYPE, Sps } from '../../../codecs/h264/nal-units';
export class H264Reader extends PayloadReader {
@@ -156,7 +156,7 @@ export class H264Reader extends PayloadReader {
let sliceParser: BitReader = new BitReader(this.dataBuffer.subarray(start, limit));
sliceParser.skipBytes(4);
sliceParser.readUEG();
- const sliceType: number = sliceParser.readUEG();
+ const sliceType: SLICE_TYPE = sliceParser.readUEG();
const type: string = mapNaluSliceToFrameType(sliceType);
this.addNewFrame(type, limit - start, NaN);
From 2db6542e95cd6360da73df5c376733bc886bdcad Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Fri, 28 Jan 2022 08:40:51 +0100
Subject: [PATCH 106/197] h264/nal-units: rename FRAME_TYPE.UNKNOWN to NONE
---
src/codecs/h264/nal-units.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/codecs/h264/nal-units.ts b/src/codecs/h264/nal-units.ts
index 33775aa..25787f2 100644
--- a/src/codecs/h264/nal-units.ts
+++ b/src/codecs/h264/nal-units.ts
@@ -28,7 +28,7 @@ export enum FRAME_TYPE {
B = 'B',
SI = 'SI',
SP = 'SP',
- UNKNOWN = ''
+ NONE = ''
}
export function mapNaluSliceToFrameType(sliceType: SLICE_TYPE): FRAME_TYPE {
@@ -47,7 +47,7 @@ export function mapNaluSliceToFrameType(sliceType: SLICE_TYPE): FRAME_TYPE {
case SLICE_TYPE.SP:
return FRAME_TYPE.SP;
default:
- return FRAME_TYPE.UNKNOWN;
+ return FRAME_TYPE.NONE;
}
}
From c1201c4c794d2423808ef7bea102fdbca8903435 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Fri, 28 Jan 2022 08:42:09 +0100
Subject: [PATCH 107/197] frame base class: change frameType member to
FRAME_TYPE + rm static values
---
src/demuxer/frame.ts | 9 ++-------
1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/src/demuxer/frame.ts b/src/demuxer/frame.ts
index 5025d16..1f99b7b 100644
--- a/src/demuxer/frame.ts
+++ b/src/demuxer/frame.ts
@@ -1,13 +1,8 @@
+import { FRAME_TYPE } from "../codecs/h264/nal-units";
import { toSecondsFromMicros } from "../utils/timescale";
export class Frame {
- // fixme: should be an enum
- public static IDR_FRAME: string = 'I';
- public static P_FRAME: string = 'P';
- public static B_FRAME: string = 'B';
- public static UNFLAGGED_FRAME: string = '/';
-
// normalized micros value
private presentationTimeUs: number = 0;
@@ -18,7 +13,7 @@ export class Frame {
public scaledDuration: number = NaN;
constructor (
- public readonly frameType: string,
+ public readonly frameType: FRAME_TYPE,
public readonly timeUs: number,
public readonly size: number,
public readonly duration: number = NaN,
From 1d204c6b441b6b1d67bee9452325db35d88b120d Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Fri, 28 Jan 2022 08:46:15 +0100
Subject: [PATCH 108/197] mp4 & webm Frame constructor calls: replace arg by
FRAME_TYPE enum usage + reorg imports
---
src/demuxer/mp4/mp4-sample-table.ts | 11 ++++++-----
src/demuxer/mp4/mp4-track.ts | 10 ++++------
src/demuxer/webm/webm-track.ts | 9 ++++++---
3 files changed, 16 insertions(+), 14 deletions(-)
diff --git a/src/demuxer/mp4/mp4-sample-table.ts b/src/demuxer/mp4/mp4-sample-table.ts
index d6e1c8d..9bf3634 100644
--- a/src/demuxer/mp4/mp4-sample-table.ts
+++ b/src/demuxer/mp4/mp4-sample-table.ts
@@ -1,3 +1,8 @@
+import { toMicroseconds } from '../../utils/timescale';
+import { FRAME_TYPE } from '../../codecs/h264/nal-units';
+
+import { Frame } from '../frame';
+
import { Stts, TimeToSampleEntry } from './atoms/stts';
import { Stsc, SampleToChunkEntry } from './atoms/stsc';
import { Stsz } from './atoms/stsz';
@@ -6,10 +11,6 @@ import { Mp4Track } from './mp4-track';
import { Stss } from './atoms/stss';
import { Stco } from './atoms/stco';
-import { Frame } from '../frame';
-
-import { toMicroseconds } from '../../utils/timescale';
-
export class Mp4SampleTable {
decodingTimestamps: Stts;
compositionTimestampOffsets: Ctts;
@@ -50,7 +51,7 @@ export class Mp4SampleTable {
const isSyncFrame = this.syncSamples ? (this.syncSamples.syncSampleNumbers.indexOf(frameCount + 1) >= 0) : false;
const newFrame = new Frame(
- isSyncFrame ? Frame.IDR_FRAME : Frame.P_FRAME,
+ isSyncFrame ? FRAME_TYPE.I : FRAME_TYPE.P,
toMicroseconds(dts, this._track.getTimescale()),
this.sampleSizes.sampleSize || this.sampleSizes.entries[frameCount],
toMicroseconds(entry.sampleDelta, this._track.getTimescale())
diff --git a/src/demuxer/mp4/mp4-track.ts b/src/demuxer/mp4/mp4-track.ts
index 952214a..f67f265 100644
--- a/src/demuxer/mp4/mp4-track.ts
+++ b/src/demuxer/mp4/mp4-track.ts
@@ -1,18 +1,16 @@
-import { Track } from '../track';
+import { toMicroseconds } from '../../utils/timescale';
+import { FRAME_TYPE } from '../../codecs/h264/nal-units';
+import { Track } from '../track';
import { Frame } from '../frame';
import { Atom } from './atoms/atom';
-
import { AudioAtom } from './atoms/helpers/audio-atom';
import { VideoAtom } from './atoms/helpers/video-atom';
-
import { Sidx } from './atoms/sidx';
import { Trun, SampleFlags } from './atoms/trun';
import { Avc1 } from './atoms/avc1';
-import { MICROSECOND_TIMESCALE, toMicroseconds } from '../../utils/timescale';
-
export type Mp4TrackDefaults = {
sampleDuration: number;
sampleSize: number;
@@ -217,7 +215,7 @@ export class Mp4Track extends Track {
if (!frameSize) throw new Error('Frame has to have either sample-size of trun-entry or track default');
const newFrame = new Frame(
- flags ? (flags.isSyncFrame ? Frame.IDR_FRAME : Frame.P_FRAME) : Frame.UNFLAGGED_FRAME,
+ flags ? (flags.isSyncFrame ? FRAME_TYPE.I : FRAME_TYPE.P) : FRAME_TYPE.NONE,
dtsUs,
frameSize,
durationUs,
diff --git a/src/demuxer/webm/webm-track.ts b/src/demuxer/webm/webm-track.ts
index 885cbdc..6cf370a 100644
--- a/src/demuxer/webm/webm-track.ts
+++ b/src/demuxer/webm/webm-track.ts
@@ -1,8 +1,11 @@
+import ByteParserUtils from '../../utils/byte-parser-utils';
+import { FRAME_TYPE } from '../../codecs/h264/nal-units';
+
import { Track } from '../track';
import { Frame } from '../frame';
+
import { ITrackInfo } from './elements/track-info';
import { Vint, EbmlElement } from './ebml/ebml-types';
-import ByteParserUtils from '../../utils/byte-parser-utils';
export class WebMTrack extends Track {
private lastPts: number;
@@ -104,9 +107,9 @@ export class WebMTrack extends Track {
this.lastPts = 1000 * ((this.lastTimecodeBase + timecode) / (this.timecodeScale > 0 ? this.timecodeScale : 1));
if (element.name === 'SimpleBlock' && flags & 0x80) {
- this.frames.push(new Frame(Frame.IDR_FRAME, this.lastPts, buffer.length));
+ this.frames.push(new Frame(FRAME_TYPE.I, this.lastPts, buffer.length));
} else {
- this.frames.push(new Frame(Frame.P_FRAME, this.lastPts, buffer.length));
+ this.frames.push(new Frame(FRAME_TYPE.P, this.lastPts, buffer.length));
}
}
}
From c9867b0e067a65771c22e9619d83496d14a83006 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Fri, 28 Jan 2022 08:49:04 +0100
Subject: [PATCH 109/197] payload-reader: rename abstract consumeData to read +
rename in subclasses + use shorter member init
---
src/demuxer/ts/payload/id3-reader.ts | 2 +-
src/demuxer/ts/payload/mpeg-reader.ts | 5 +++--
src/demuxer/ts/payload/payload-reader.ts | 12 +++++++-----
src/demuxer/ts/payload/unknown-reader.ts | 2 +-
src/demuxer/ts/pes-reader.ts | 2 +-
5 files changed, 13 insertions(+), 10 deletions(-)
diff --git a/src/demuxer/ts/payload/id3-reader.ts b/src/demuxer/ts/payload/id3-reader.ts
index ad71d00..a9fb0d6 100644
--- a/src/demuxer/ts/payload/id3-reader.ts
+++ b/src/demuxer/ts/payload/id3-reader.ts
@@ -7,7 +7,7 @@ export class ID3Reader extends PayloadReader {
return Track.MIME_TYPE_ID3;
}
- public consumeData(pts: number): void {
+ public read(pts: number): void {
// do nothing
}
}
diff --git a/src/demuxer/ts/payload/mpeg-reader.ts b/src/demuxer/ts/payload/mpeg-reader.ts
index e246d68..75dd241 100644
--- a/src/demuxer/ts/payload/mpeg-reader.ts
+++ b/src/demuxer/ts/payload/mpeg-reader.ts
@@ -1,6 +1,7 @@
import ByteParserUtils from '../../../utils/byte-parser-utils';
import { PayloadReader } from './payload-reader';
import { Frame } from '../../frame';
+import { FRAME_TYPE } from '../../../codecs/h264/nal-units';
enum State {
FIND_SYNC = 1,
@@ -51,7 +52,7 @@ export class MpegReader extends PayloadReader {
return 'audio/' + this.mimeType;
}
- public consumeData(pts: number): void {
+ public read(pts: number): void {
if (!this.dataBuffer) {
return;
}
@@ -175,7 +176,7 @@ export class MpegReader extends PayloadReader {
return 0;
}
this.state = State.FIND_SYNC;
- this.frames.push(new Frame(Frame.IDR_FRAME, this.timeUs, this.currentFrameSize));
+ this.frames.push(new Frame(FRAME_TYPE.NONE, this.timeUs, this.currentFrameSize));
this.timeUs = this.timeUs + this.frameDuration;
return MpegReader.HEADER_SIZE + this.currentFrameSize;
}
diff --git a/src/demuxer/ts/payload/payload-reader.ts b/src/demuxer/ts/payload/payload-reader.ts
index abd6e85..a4f06f2 100644
--- a/src/demuxer/ts/payload/payload-reader.ts
+++ b/src/demuxer/ts/payload/payload-reader.ts
@@ -7,7 +7,7 @@ export abstract class PayloadReader {
public frames: Frame[] = [];
public dataBuffer: Uint8Array;
- protected dataOffset: number;
+ protected dataOffset: number = 0;
private firstPacketDataOffset: number;
@@ -15,6 +15,10 @@ export abstract class PayloadReader {
this.reset();
}
+ public abstract read(pts: number): void;
+
+ public onData(data: Uint8Array) {}
+
public append(packet: BitReader): void {
if (isNaN(this.firstPacketDataOffset)) {
@@ -43,7 +47,7 @@ export abstract class PayloadReader {
public flush(pts: number): void {
if (this.dataBuffer && this.dataBuffer.byteLength > 0) {
- this.consumeData(pts);
+ this.read(pts);
this.dataBuffer = null;
}
this.dataOffset = 0;
@@ -51,15 +55,13 @@ export abstract class PayloadReader {
public popFrames(): Frame[] {
if (this.frames.length === 0) {
- return this.frames;
+ return [];
}
const frames = this.frames.slice(0);
this.frames.length = 0;
return frames;
}
- public abstract consumeData(pts: number): void;
-
public getMimeType(): string {
return 'Unknown';
}
diff --git a/src/demuxer/ts/payload/unknown-reader.ts b/src/demuxer/ts/payload/unknown-reader.ts
index 554d89d..d1a89d1 100644
--- a/src/demuxer/ts/payload/unknown-reader.ts
+++ b/src/demuxer/ts/payload/unknown-reader.ts
@@ -6,7 +6,7 @@ export class UnknownReader extends PayloadReader {
return 'unknown';
}
- public consumeData(pts: number): void {
+ public read(pts: number): void {
// do nothing
}
}
diff --git a/src/demuxer/ts/pes-reader.ts b/src/demuxer/ts/pes-reader.ts
index ee7291f..d5cd347 100644
--- a/src/demuxer/ts/pes-reader.ts
+++ b/src/demuxer/ts/pes-reader.ts
@@ -47,7 +47,7 @@ export class PESReader {
public appendData(payloadUnitStartIndicator: boolean, packet: BitReader): void {
if (payloadUnitStartIndicator) {
if (this.payloadReader) {
- this.payloadReader.consumeData(this.lastPtsUs);
+ this.payloadReader.read(this.lastPtsUs);
}
this.parsePESHeader(packet);
}
From 564528a3d159fa9a3063cb072d38edcee550e07b Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Fri, 28 Jan 2022 08:50:44 +0100
Subject: [PATCH 110/197] adts & h264 reader: use of novel onData callback to
do demuxing + streamline some logic and perf opts + shorten member inits + rm
dead things
---
src/demuxer/ts/payload/adts-reader.ts | 80 +++++-----
src/demuxer/ts/payload/h264-reader.ts | 201 +++++++++++++-------------
2 files changed, 141 insertions(+), 140 deletions(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index ecf05df..2b03fc8 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -2,43 +2,40 @@ import { BitReader } from '../../../utils/bit-reader';
import { PayloadReader } from './payload-reader';
import { Frame } from '../../frame';
import { Track } from '../../track';
+import { FRAME_TYPE } from '../../../codecs/h264/nal-units';
-enum State {
- FIND_SYNC = 1,
- READ_HEADER = 2,
- READ_FRAME = 3
+enum AdtsReaderState {
+ FIND_SYNC,
+ READ_HEADER,
+ READ_FRAME
}
export class AdtsReader extends PayloadReader {
private static ADTS_HEADER_SIZE: number = 5;
private static ADTS_SYNC_SIZE: number = 2;
+ private static ADTS_SYNC_AND_HEADER_LEN = AdtsReader.ADTS_HEADER_SIZE + AdtsReader.ADTS_SYNC_SIZE;
+
private static ADTS_CRC_SIZE: number = 2;
private static ADTS_SAMPLE_RATES: number[] = [96000, 88200, 64000, 48000,
44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
- public channels: number;
- public sampleRate: number;
- public frameDuration: number;
- public currentFrameSize: number;
+ public channels: number = 0;
+ public sampleRate: number = 0;
+ public frameDuration: number = 0;
+ public currentPayloadUnitLen: number = 0;
- private state: State;
+ private state: AdtsReaderState = AdtsReaderState.FIND_SYNC;
constructor () {
super();
- this.channels = 0;
- this.sampleRate = 0;
- this.frameDuration = 0;
- this.currentFrameSize = 0;
- this.state = State.FIND_SYNC;
- this.dataOffset = 0;
}
public getMimeType(): string {
return Track.MIME_TYPE_AAC;
}
- public consumeData(pts: number): void {
+ public read(pts: number): void {
if (!this.dataBuffer) {
return;
}
@@ -51,26 +48,35 @@ export class AdtsReader extends PayloadReader {
}
while (this.dataOffset < this.dataBuffer.byteLength) {
- if (this.state === State.FIND_SYNC) {
+ if (this.state === AdtsReaderState.FIND_SYNC) {
this.findNextSync();
- } else if (this.state === State.READ_HEADER) {
- if (this.dataBuffer.byteLength - this.dataOffset < (AdtsReader.ADTS_HEADER_SIZE +
- AdtsReader.ADTS_SYNC_SIZE)) {
+ } else if (this.state === AdtsReaderState.READ_HEADER) {
+ if (this.dataBuffer.byteLength - this.dataOffset
+ < AdtsReader.ADTS_SYNC_AND_HEADER_LEN) {
break;
}
this.parseAACHeader();
- } else if (this.state === State.READ_FRAME) {
- if ((this.dataBuffer.byteLength - this.dataOffset) < (AdtsReader.ADTS_SYNC_SIZE +
- AdtsReader.ADTS_HEADER_SIZE + this.currentFrameSize)) {
+ } else if (this.state === AdtsReaderState.READ_FRAME) {
+ if (this.dataBuffer.byteLength - this.dataOffset
+ < this.currentPayloadUnitLen) {
break;
}
- this.frames.push(new Frame(Frame.IDR_FRAME, this.timeUs, this.currentFrameSize,
- this.frameDuration, this.dataOffset));
- this.timeUs = this.timeUs + this.frameDuration;
- this.dataOffset += (AdtsReader.ADTS_SYNC_SIZE + AdtsReader.ADTS_HEADER_SIZE +
- this.currentFrameSize);
- this.state = State.FIND_SYNC;
+ this.frames.push(new Frame(
+ FRAME_TYPE.NONE,
+ this.timeUs,
+ this.currentPayloadUnitLen,
+ this.frameDuration,
+ this.dataOffset));
+
+ this.onData(this.dataBuffer.subarray(
+ this.dataOffset + AdtsReader.ADTS_SYNC_AND_HEADER_LEN,
+ this.dataOffset + this.currentPayloadUnitLen));
+
+ this.dataOffset += this.currentPayloadUnitLen;
+
+ this.timeUs = this.timeUs + this.frameDuration;
+ this.state = AdtsReaderState.FIND_SYNC;
}
}
@@ -85,7 +91,7 @@ export class AdtsReader extends PayloadReader {
if ((dataRead & 0xfff6) === 0xfff0) {
this.dataOffset = i;
if (this.dataOffset < this.dataBuffer.byteLength) {
- this.state = State.READ_HEADER;
+ this.state = AdtsReaderState.READ_HEADER;
}
return;
}
@@ -95,8 +101,10 @@ export class AdtsReader extends PayloadReader {
}
private parseAACHeader(): void {
- const aacHeaderParser: BitReader = new BitReader(this.dataBuffer.subarray(this.dataOffset,
- this.dataOffset + AdtsReader.ADTS_SYNC_SIZE + AdtsReader.ADTS_HEADER_SIZE));
+ const aacHeaderParser: BitReader = new BitReader(
+ this.dataBuffer.subarray(
+ this.dataOffset,
+ this.dataOffset + AdtsReader.ADTS_SYNC_AND_HEADER_LEN));
aacHeaderParser.skipBits(15);
const hasCrc: boolean = !aacHeaderParser.readBool();
@@ -114,13 +122,13 @@ export class AdtsReader extends PayloadReader {
this.channels = aacHeaderParser.readBits(3);
aacHeaderParser.skipBits(4);
- this.currentFrameSize = aacHeaderParser.readBits(13) - AdtsReader.ADTS_HEADER_SIZE
- - AdtsReader.ADTS_SYNC_SIZE;
+
+ this.currentPayloadUnitLen = aacHeaderParser.readBits(13);
if (hasCrc) {
- this.currentFrameSize -= AdtsReader.ADTS_CRC_SIZE;
+ this.currentPayloadUnitLen -= AdtsReader.ADTS_CRC_SIZE;
}
- this.state = State.READ_FRAME;
+ this.state = AdtsReaderState.READ_FRAME;
}
}
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index 2bc75ea..06926be 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -3,18 +3,19 @@ import { PayloadReader } from './payload-reader';
import { Frame } from '../../frame';
import { Track } from '../../track';
import { H264ParameterSetParser } from '../../../codecs/h264/param-set-parser';
-import { mapNaluSliceToFrameType, NAL_UNIT_TYPE, SLICE_TYPE, Sps } from '../../../codecs/h264/nal-units';
+import { FRAME_TYPE, mapNaluSliceToFrameType, NAL_UNIT_TYPE, SLICE_TYPE, Sps } from '../../../codecs/h264/nal-units';
+
+const NALU_DELIM_LEN = 3;
export class H264Reader extends PayloadReader {
public sps: Sps = null;
public pps: boolean = false;
- public pendingBytes: number;
+ public pendingBytes: number = 0;
constructor() {
super();
- this.pendingBytes = 0;
}
public getMimeType(): string {
@@ -22,16 +23,19 @@ export class H264Reader extends PayloadReader {
}
public flush(timeUs: number): void {
- if (this.dataBuffer && this.dataBuffer.byteLength > 0) {
- this.consumeData(timeUs);
-
- if (this.dataBuffer.byteLength > 0) {
- const offset: number = this.findNextNALUnit(0);
- if (offset < this.dataBuffer.byteLength) {
- this.processNALUnit(offset, this.dataBuffer.byteLength, this.dataBuffer[offset + 3] & 0x1F);
- }
- }
+
+ this.read(timeUs);
+
+ // enforced process any last data after
+ // a nalu-delim to be processed
+ // (most likely partial NALUs).
+ const nextNalUnit: number = this.findNextNalu(0);
+ if (!Number.isFinite(nextNalUnit)) {
+ return;
}
+ this.processNalu(nextNalUnit,
+ this.dataBuffer.byteLength,
+ this.readNaluHeadAt(nextNalUnit));
}
public reset(): void {
@@ -41,135 +45,124 @@ export class H264Reader extends PayloadReader {
this.pps = false;
}
- public consumeData(timeUs: number): void {
-
- if (!this.dataBuffer) {
- return;
- }
- if (this.firstTimestamp === -1) {
- this.timeUs = this.firstTimestamp = timeUs;
- }
+ public read(timeUs: number): void {
- // process any possible remaining data
+ // process pending remainder data
+ let firstNalUnit: number = 0;
let nextNalUnit: number = 0;
- let offset: number = 0;
+
if (this.pendingBytes > 0) {
- nextNalUnit = this.findNextNALUnit(this.pendingBytes);
- if (nextNalUnit < this.dataBuffer.byteLength) {
- this.processNALUnit(0, nextNalUnit, this.dataBuffer[offset + 3] & 0x1F);
- offset = nextNalUnit;
+ nextNalUnit = this.findNextNalu(this.pendingBytes);
+ // if we cant find a next NALU-delim from the remainder data,
+ // we can already give-up here.
+ if (!Number.isFinite(nextNalUnit)) {
+ return;
}
- this.pendingBytes = 0;
+ this.processNalu(0, nextNalUnit, this.readNaluHeadAt(firstNalUnit));
+ firstNalUnit = nextNalUnit;
} else {
- offset = this.findNextNALUnit(0);
+ firstNalUnit = this.findNextNalu();
+ if (!Number.isFinite(firstNalUnit)) {
+ return;
+ }
}
- // process next nal units in the buffer
+ // post: firstNalUnit is finite number
+
+ // Q: for what else do we need this timeUs param, and
+ // why do we need to set this superclass prop here
+ // as it gets passed in from the call arg... ?
+
+ // FIXME: use NaN instead of -1
+ if (this.firstTimestamp === -1) {
+ this.timeUs = this.firstTimestamp = timeUs;
+ }
if (timeUs !== -1) {
this.timeUs = timeUs;
}
- if (this.dataBuffer.byteLength > 0) {
- while (nextNalUnit < this.dataBuffer.byteLength) {
- nextNalUnit = this.findNextNALUnit(offset + 3);
- if (nextNalUnit < this.dataBuffer.byteLength) {
- this.processNALUnit(offset, nextNalUnit, this.dataBuffer[offset + 3] & 0x1F);
- offset = nextNalUnit;
- }
+ // process next nal units in the buffer
+ while (true) {
+ // w/o the +3 we would end up again with the input offset!
+ nextNalUnit = this.findNextNalu(firstNalUnit + NALU_DELIM_LEN);
+ if (!Number.isFinite(nextNalUnit)) {
+ break;
}
- this.dataBuffer = this.dataBuffer.subarray(offset);
- this.pendingBytes = this.dataBuffer.byteLength;
- }
- }
+ this.processNalu(firstNalUnit, nextNalUnit,
+ this.readNaluHeadAt(firstNalUnit));
- private findNextNALUnit(index: number): number {
- const limit: number = this.dataBuffer.byteLength - 3;
- for (let i: number = index; i < limit; i++) {
- if (this.dataBuffer[i] === 0 && this.dataBuffer[i + 1] === 0 && this.dataBuffer[i + 2] === 1) {
- return i;
- }
+ firstNalUnit = nextNalUnit;
}
- return this.dataBuffer.byteLength;
- }
-
- private processNALUnit(start: number, limit: number, nalType: number): void {
+ // prune data-buffer
+ this.dataBuffer = this.dataBuffer.subarray(firstNalUnit);
- if (nalType === NAL_UNIT_TYPE.SPS) {
- this.parseSPSNALUnit(start, limit);
- } else if (nalType === NAL_UNIT_TYPE.PPS) {
- this.pps = true;
- } else if (nalType === NAL_UNIT_TYPE.AUD) {
- this.parseAUDNALUnit(start, limit);
- } else if (nalType === NAL_UNIT_TYPE.IDR) {
- this.addNewFrame(Frame.IDR_FRAME, limit - start, NaN);
- } else if (nalType === NAL_UNIT_TYPE.SEI) {
- this.parseSEINALUnit(start, limit);
- } else if (nalType === NAL_UNIT_TYPE.SLICE) {
- this.parseSliceNALUnit(start, limit);
- }
+ // we need to make sure the next read starts off
+ // ahead the last parsed NALU-delimiter.
+ this.pendingBytes = this.dataBuffer.byteLength;
}
- private parseSPSNALUnit(start: number, limit: number): void {
- this.sps = H264ParameterSetParser.parseSPS(this.dataBuffer.subarray(start + 4, limit));
- }
+ private findNextNalu(offset: number = 0): number {
+ if (!(this?.dataBuffer?.byteLength)) {
+ return NaN;
+ }
- private skipScalingList(parser: BitReader, size: number): void {
- let lastScale: number = 8;
- let nextScale: number = 8;
- for (let i: number = 0; i < size; i++) {
- if (nextScale !== 0) {
- const deltaScale: number = parser.readSEG();
- nextScale = (lastScale + deltaScale + 256) % 256;
- }
- if (nextScale !== 0) {
- lastScale = nextScale;
+ const length: number = this.dataBuffer.byteLength - NALU_DELIM_LEN;
+ for (let i: number = offset; i < length; i++) {
+ if (this.dataBuffer[i] === 0
+ && this.dataBuffer[i + 1] === 0
+ && this.dataBuffer[i + 2] === 1) {
+ return i;
}
}
+ return NaN;
}
- private parseSEINALUnit(start: number, limit: number): void {
- let seiParser: BitReader = new BitReader(this.dataBuffer.subarray(start, limit));
- seiParser.skipBytes(4);
+ private readNaluHeadAt(offset: number): number {
+ // TODO: check for invalid values
+ // (can be if buffer begin/remainder is garbage)
+ return this.dataBuffer[offset + NALU_DELIM_LEN] & 0x1F;
+ }
- while (seiParser.remainingBytes() > 0) {
- const data: number = seiParser.readByte();
- if (data !== 0xFF) {
- break;
- }
+ private processNalu(start: number, end: number, nalType: number): void {
+
+ switch(nalType) {
+ case NAL_UNIT_TYPE.SLICE:
+ this.parseSliceNALUnit(start, end);
+ break;
+ case NAL_UNIT_TYPE.IDR:
+ this.addFrame(FRAME_TYPE.I, end - start - NALU_DELIM_LEN, NaN);
+ break;
+ case NAL_UNIT_TYPE.SPS:
+ this.parseSPSNALUnit(start, end);
+ break;
+ case NAL_UNIT_TYPE.PPS:
+ this.pps = true;
+ break;
+ default:
+ break;
}
- // parse payload size
- while (seiParser.remainingBytes() > 0) {
- const data: number = seiParser.readByte();
- if (data !== 0xFF) {
- break;
- }
- }
+ this.onData(this.dataBuffer.subarray(start + NALU_DELIM_LEN, end));
+ }
- seiParser.destroy();
- seiParser = null;
+ private parseSPSNALUnit(start: number, end: number): void {
+ this.sps = H264ParameterSetParser.parseSPS(this.dataBuffer.subarray(start + 4, end));
}
- private parseSliceNALUnit(start: number, limit: number): void {
- let sliceParser: BitReader = new BitReader(this.dataBuffer.subarray(start, limit));
+ private parseSliceNALUnit(start: number, end: number): void {
+ const sliceParser: BitReader = new BitReader(this.dataBuffer.subarray(start, end));
sliceParser.skipBytes(4);
sliceParser.readUEG();
const sliceType: SLICE_TYPE = sliceParser.readUEG();
- const type: string = mapNaluSliceToFrameType(sliceType);
- this.addNewFrame(type, limit - start, NaN);
-
sliceParser.destroy();
- sliceParser = null;
- }
- private parseAUDNALUnit(start: number, limit: number): void {
- // const audParser: BitReader = new BitReader(this.dataBuffer.subarray(start, limit));
- // audParser.skipBytes(4);
+ const frameType: FRAME_TYPE = mapNaluSliceToFrameType(sliceType);
+ this.addFrame(frameType, end - start - NALU_DELIM_LEN, NaN);
}
- private addNewFrame(frameType: string, frameSize: number, duration: number): void {
+ private addFrame(frameType: FRAME_TYPE, frameSize: number, duration: number): void {
const frame = new Frame(frameType, this.timeUs, frameSize, duration);
this.frames.push(frame);
}
From bc2c8bf5ec898c7498f82e079e112de4fcf1c9fa Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 9 Feb 2022 03:57:33 +0100
Subject: [PATCH 111/197] timescale: add mpegClockTimeToMicroSecs
---
src/utils/timescale.ts | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/utils/timescale.ts b/src/utils/timescale.ts
index 97da9bb..3647af3 100644
--- a/src/utils/timescale.ts
+++ b/src/utils/timescale.ts
@@ -8,3 +8,7 @@ export function toSecondsFromMicros(us) {
return us / MICROSECOND_TIMESCALE;
}
+export function mpegClockTimeToMicroSecs(time: number): number {
+ return toMicroseconds(time, 90000);
+}
+
From d9c42facab185c186151ea3858bcd49ee6c2651b Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 9 Feb 2022 03:58:11 +0100
Subject: [PATCH 112/197] payload-reader: change flush param name to "time", as
it can also be DTS
---
src/demuxer/ts/payload/payload-reader.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/demuxer/ts/payload/payload-reader.ts b/src/demuxer/ts/payload/payload-reader.ts
index a4f06f2..7159c4e 100644
--- a/src/demuxer/ts/payload/payload-reader.ts
+++ b/src/demuxer/ts/payload/payload-reader.ts
@@ -45,9 +45,9 @@ export abstract class PayloadReader {
this.timeUs = -1;
}
- public flush(pts: number): void {
+ public flush(time: number): void {
if (this.dataBuffer && this.dataBuffer.byteLength > 0) {
- this.read(pts);
+ this.read(time);
this.dataBuffer = null;
}
this.dataOffset = 0;
From c019611ca6886a2a0451323c61abc479ba083f72 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 9 Feb 2022 03:59:54 +0100
Subject: [PATCH 113/197] pes-reader: add onPayloadReaderData handler for
onData of payload-reader + replace static method for timescale conversion +
rm dead pesLength member
---
src/demuxer/ts/pes-reader.ts | 25 +++++++++++++++----------
1 file changed, 15 insertions(+), 10 deletions(-)
diff --git a/src/demuxer/ts/pes-reader.ts b/src/demuxer/ts/pes-reader.ts
index d5cd347..7aebb37 100644
--- a/src/demuxer/ts/pes-reader.ts
+++ b/src/demuxer/ts/pes-reader.ts
@@ -1,4 +1,5 @@
import { BitReader } from '../../utils/bit-reader';
+import { mpegClockTimeToMicroSecs } from '../../utils/timescale';
import { PayloadReader } from './payload/payload-reader';
import { UnknownReader } from './payload/unknown-reader';
import { AdtsReader } from './payload/adts-reader';
@@ -16,14 +17,12 @@ export class PESReader {
public payloadReader: PayloadReader;
- private lastPtsUs: number;
- private pesLength: number;
+ private lastDtsUs: number;
constructor(public pid: number, public type: number) {
this.pid = pid;
this.type = type;
- this.lastPtsUs = -1;
- this.pesLength = 0;
+ this.lastDtsUs = -1;
if (type === PESReader.TS_STREAM_TYPE_AAC) {
this.payloadReader = new AdtsReader();
@@ -38,16 +37,14 @@ export class PESReader {
} else {
this.payloadReader = new UnknownReader();
}
- }
- public static ptsToTimeUs(pts: number): number {
- return (pts * 1000000) / 90000;
+ this.payloadReader.onData = this.onPayloadReaderData.bind(this);
}
public appendData(payloadUnitStartIndicator: boolean, packet: BitReader): void {
if (payloadUnitStartIndicator) {
if (this.payloadReader) {
- this.payloadReader.read(this.lastPtsUs);
+ this.payloadReader.read(this.lastDtsUs);
}
this.parsePESHeader(packet);
}
@@ -60,8 +57,12 @@ export class PESReader {
public parsePESHeader(packet: BitReader): void {
packet.skipBytes(7);
+
const [dts, pts] = this.readPesTimingInfo(packet);
- this.lastPtsUs = PESReader.ptsToTimeUs(pts);
+
+
+ // Note: Using DTS here, not PTS, to avoid ordering issues.
+ this.lastDtsUs = mpegClockTimeToMicroSecs(dts);
}
public reset(): void {
@@ -72,10 +73,14 @@ export class PESReader {
public flush(): void {
if (this.payloadReader) {
- this.payloadReader.flush(this.lastPtsUs);
+ this.payloadReader.flush(this.lastDtsUs);
}
}
+ private onPayloadReaderData(data: Uint8Array) {
+
+ }
+
private readPesTimingInfo(packet: BitReader): [number, number] {
/**
* Thanks to Videojs/Muxjs for this bit, which does well the
From c124850a0ad89e0905aae47858e6e67ce2bdc6ca Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 9 Feb 2022 04:02:04 +0100
Subject: [PATCH 114/197] pes-reader: make readPesTimingInfo static
---
src/demuxer/ts/pes-reader.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/demuxer/ts/pes-reader.ts b/src/demuxer/ts/pes-reader.ts
index 7aebb37..4017103 100644
--- a/src/demuxer/ts/pes-reader.ts
+++ b/src/demuxer/ts/pes-reader.ts
@@ -58,7 +58,7 @@ export class PESReader {
packet.skipBytes(7);
- const [dts, pts] = this.readPesTimingInfo(packet);
+ const [dts, pts] = PESReader.readTimingInfo(packet);
// Note: Using DTS here, not PTS, to avoid ordering issues.
@@ -81,7 +81,7 @@ export class PESReader {
}
- private readPesTimingInfo(packet: BitReader): [number, number] {
+ private static readTimingInfo(packet: BitReader): [number, number] {
/**
* Thanks to Videojs/Muxjs for this bit, which does well the
* trick around 32-bit unary bit-ops and 33 bit numbers :)
From 6e6cc6b2d6835b5e4b7598aaebe4c031c808738c Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 9 Feb 2022 04:48:34 +0100
Subject: [PATCH 115/197] bit-reader: rm destroy method, doesn't influece
GC-ing behavior in any way
---
src/codecs/h264/param-set-parser.ts | 5 -----
src/demuxer/ts/payload/h264-reader.ts | 1 -
src/utils/bit-reader.ts | 4 ----
3 files changed, 10 deletions(-)
diff --git a/src/codecs/h264/param-set-parser.ts b/src/codecs/h264/param-set-parser.ts
index 72fdda4..7e72157 100644
--- a/src/codecs/h264/param-set-parser.ts
+++ b/src/codecs/h264/param-set-parser.ts
@@ -49,8 +49,6 @@ export class H264ParameterSetParser {
const spsId: number = gb.readUEG();
const entropyCodingMode: boolean = gb.readBool();
- gb.destroy();
-
return new Pps(id, spsId, entropyCodingMode);
}
@@ -238,9 +236,6 @@ export class H264ParameterSetParser {
const present_width: number = Math.ceil(codec_width * sarScale);
- gb.destroy();
- gb = null;
-
return new Sps(
seq_parameter_set_id,
profile_string,
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index 06926be..0130550 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -156,7 +156,6 @@ export class H264Reader extends PayloadReader {
sliceParser.skipBytes(4);
sliceParser.readUEG();
const sliceType: SLICE_TYPE = sliceParser.readUEG();
- sliceParser.destroy();
const frameType: FRAME_TYPE = mapNaluSliceToFrameType(sliceType);
this.addFrame(frameType, end - start - NALU_DELIM_LEN, NaN);
diff --git a/src/utils/bit-reader.ts b/src/utils/bit-reader.ts
index 1b97aa3..80a6687 100644
--- a/src/utils/bit-reader.ts
+++ b/src/utils/bit-reader.ts
@@ -11,10 +11,6 @@ export class BitReader {
this.loadWord();
}
- public destroy(): void {
- this.buffer = null;
- }
-
public remainingBytes(): number {
return this.workingBytesAvailable + this.workingBitsAvailable / 8;
}
From 502875cc1dade87948a8cd225ed6843e8e02f409 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 9 Feb 2022 04:51:12 +0100
Subject: [PATCH 116/197] h264-reader: optimize NALU subarray-ing (only once),
modify parseSliceNALUnit input param
---
src/demuxer/ts/payload/h264-reader.ts | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index 0130550..097d827 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -127,9 +127,11 @@ export class H264Reader extends PayloadReader {
private processNalu(start: number, end: number, nalType: number): void {
+ const naluData = this.dataBuffer.subarray(start + NALU_DELIM_LEN, end);
+
switch(nalType) {
case NAL_UNIT_TYPE.SLICE:
- this.parseSliceNALUnit(start, end);
+ this.parseSliceNALUnit(naluData);
break;
case NAL_UNIT_TYPE.IDR:
this.addFrame(FRAME_TYPE.I, end - start - NALU_DELIM_LEN, NaN);
@@ -144,21 +146,22 @@ export class H264Reader extends PayloadReader {
break;
}
- this.onData(this.dataBuffer.subarray(start + NALU_DELIM_LEN, end));
+ this.onData(naluData);
}
private parseSPSNALUnit(start: number, end: number): void {
this.sps = H264ParameterSetParser.parseSPS(this.dataBuffer.subarray(start + 4, end));
}
- private parseSliceNALUnit(start: number, end: number): void {
- const sliceParser: BitReader = new BitReader(this.dataBuffer.subarray(start, end));
- sliceParser.skipBytes(4);
+ private parseSliceNALUnit(naluData: Uint8Array): void {
+ const sliceParser: BitReader = new BitReader(naluData);
+
+ sliceParser.skipBytes(1);
sliceParser.readUEG();
const sliceType: SLICE_TYPE = sliceParser.readUEG();
-
const frameType: FRAME_TYPE = mapNaluSliceToFrameType(sliceType);
- this.addFrame(frameType, end - start - NALU_DELIM_LEN, NaN);
+
+ this.addFrame(frameType, naluData.byteLength, NaN);
}
private addFrame(frameType: FRAME_TYPE, frameSize: number, duration: number): void {
From b722abb8dfac2e1b6c5d9c182f78b0e895135770 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 9 Feb 2022 05:04:16 +0100
Subject: [PATCH 117/197] payload-reader: streamline append method logic (rm
BitReader.remainingBytes usage) + rm
firstPacketDataOffset/getFirstPacketDataOffset (not used anywhere)
---
src/demuxer/ts/payload/payload-reader.ts | 15 +++------------
1 file changed, 3 insertions(+), 12 deletions(-)
diff --git a/src/demuxer/ts/payload/payload-reader.ts b/src/demuxer/ts/payload/payload-reader.ts
index 7159c4e..602b5c1 100644
--- a/src/demuxer/ts/payload/payload-reader.ts
+++ b/src/demuxer/ts/payload/payload-reader.ts
@@ -9,8 +9,6 @@ export abstract class PayloadReader {
protected dataOffset: number = 0;
- private firstPacketDataOffset: number;
-
constructor() {
this.reset();
}
@@ -21,16 +19,13 @@ export abstract class PayloadReader {
public append(packet: BitReader): void {
- if (isNaN(this.firstPacketDataOffset)) {
- this.firstPacketDataOffset = packet.buffer.byteOffset + packet.bytesOffset();
- }
-
- const dataToAppend: Uint8Array = packet.buffer.subarray(packet.bytesOffset());
+ const packetReaderOffset = packet.bytesOffset();
+ const dataToAppend: Uint8Array = packet.buffer.subarray(packetReaderOffset);
if (!this.dataBuffer) {
this.dataBuffer = dataToAppend;
} else {
- const newLen: number = this.dataBuffer.byteLength + packet.remainingBytes();
+ const newLen: number = this.dataBuffer.byteLength + dataToAppend.byteLength;
const temp: Uint8Array = new Uint8Array(newLen);
temp.set(this.dataBuffer, 0);
temp.set(dataToAppend, this.dataBuffer.byteLength);
@@ -77,8 +72,4 @@ export abstract class PayloadReader {
public getLastPTS(): number {
return this.timeUs;
}
-
- public getFirstPacketDataOffset(): number {
- return this.firstPacketDataOffset;
- }
}
From d78b2aad03274fca8a0fa9f6b48c56795b61c701 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 9 Feb 2022 05:33:07 +0100
Subject: [PATCH 118/197] h264-reader: use nalu-buffer directly, avoid any
redundant subarray usage
---
src/demuxer/ts/payload/h264-reader.ts | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index 097d827..4829825 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -134,10 +134,10 @@ export class H264Reader extends PayloadReader {
this.parseSliceNALUnit(naluData);
break;
case NAL_UNIT_TYPE.IDR:
- this.addFrame(FRAME_TYPE.I, end - start - NALU_DELIM_LEN, NaN);
+ this.addFrame(FRAME_TYPE.I, naluData, NaN);
break;
case NAL_UNIT_TYPE.SPS:
- this.parseSPSNALUnit(start, end);
+ this.parseSPSNALUnit(naluData);
break;
case NAL_UNIT_TYPE.PPS:
this.pps = true;
@@ -149,8 +149,9 @@ export class H264Reader extends PayloadReader {
this.onData(naluData);
}
- private parseSPSNALUnit(start: number, end: number): void {
- this.sps = H264ParameterSetParser.parseSPS(this.dataBuffer.subarray(start + 4, end));
+ private parseSPSNALUnit(naluData: Uint8Array): void {
+ // skip first byte NALU-header for SPS-parser func input (expects only payload)
+ this.sps = H264ParameterSetParser.parseSPS(naluData.subarray(1));
}
private parseSliceNALUnit(naluData: Uint8Array): void {
@@ -159,13 +160,12 @@ export class H264Reader extends PayloadReader {
sliceParser.skipBytes(1);
sliceParser.readUEG();
const sliceType: SLICE_TYPE = sliceParser.readUEG();
- const frameType: FRAME_TYPE = mapNaluSliceToFrameType(sliceType);
- this.addFrame(frameType, naluData.byteLength, NaN);
+ this.addFrame(mapNaluSliceToFrameType(sliceType), naluData, NaN);
}
- private addFrame(frameType: FRAME_TYPE, frameSize: number, duration: number): void {
- const frame = new Frame(frameType, this.timeUs, frameSize, duration);
+ private addFrame(frameType: FRAME_TYPE, naluData: Uint8Array, duration: number): void {
+ const frame = new Frame(frameType, this.timeUs, naluData.byteLength, duration);
this.frames.push(frame);
}
From 5867eaf96f72fffe995d5c79b6897e1caa69b2e5 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 9 Feb 2022 05:33:50 +0100
Subject: [PATCH 119/197] nal-units: extend enum to all specified NAL_UNIT_TYPE
values
---
src/codecs/h264/nal-units.ts | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/src/codecs/h264/nal-units.ts b/src/codecs/h264/nal-units.ts
index 25787f2..2f023df 100644
--- a/src/codecs/h264/nal-units.ts
+++ b/src/codecs/h264/nal-units.ts
@@ -11,7 +11,19 @@ export enum NAL_UNIT_TYPE {
PPS,
AUD,
END_SEQUENCE,
- END_STREAM
+ END_STREAM,
+ FILLER_DATA,
+ SPS_EXT,
+ PREFIX,
+ SUBSET_SPS,
+ RESERVED_16,
+ RESERVED_17,
+ RESERVED_18,
+ SLICE_AUX_PIC,
+ SLICE_EXT,
+ SLICE_EXT_DEPTH,
+ RESERVED_22,
+ RESERVED_23
}
export enum SLICE_TYPE {
From 51578a33cc0608b6eb349402ef8b22dee581aa54 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 9 Feb 2022 06:06:33 +0100
Subject: [PATCH 120/197] pes-reader: rm unnecessary assertion of payloadReader
init + create unknown-reader in factory on metadata-type stream as fallback +
make private & rename readHeader method
---
src/demuxer/ts/pes-reader.ts | 39 ++++++++++++++----------------------
1 file changed, 15 insertions(+), 24 deletions(-)
diff --git a/src/demuxer/ts/pes-reader.ts b/src/demuxer/ts/pes-reader.ts
index 4017103..98013c3 100644
--- a/src/demuxer/ts/pes-reader.ts
+++ b/src/demuxer/ts/pes-reader.ts
@@ -33,7 +33,7 @@ export class PESReader {
} else if (type === PESReader.TS_STREAM_TYPE_MPA || type === PESReader.TS_STREAM_TYPE_MPA_LSF) {
this.payloadReader = new MpegReader();
} else if (type === PESReader.TS_STREAM_TYPE_METADATA) {
- // do nothing
+ this.payloadReader = new UnknownReader();
} else {
this.payloadReader = new UnknownReader();
}
@@ -43,45 +43,36 @@ export class PESReader {
public appendData(payloadUnitStartIndicator: boolean, packet: BitReader): void {
if (payloadUnitStartIndicator) {
- if (this.payloadReader) {
- this.payloadReader.read(this.lastDtsUs);
- }
- this.parsePESHeader(packet);
+ this.payloadReader.read(this.lastDtsUs);
+ this.readHeader(packet);
}
- if (this.payloadReader) {
- this.payloadReader.append(packet);
- }
+ this.payloadReader.append(packet);
}
- public parsePESHeader(packet: BitReader): void {
- packet.skipBytes(7);
+ public reset(): void {
+ this.payloadReader.reset();
+ }
+
+ public flush(): void {
+ this.payloadReader.flush(this.lastDtsUs);
+ }
+ private readHeader(packet: BitReader): void {
+ packet.skipBytes(7);
- const [dts, pts] = PESReader.readTimingInfo(packet);
+ const [dts, pts] = PESReader.parseTimingInfo(packet);
// Note: Using DTS here, not PTS, to avoid ordering issues.
this.lastDtsUs = mpegClockTimeToMicroSecs(dts);
}
- public reset(): void {
- if (this.payloadReader) {
- this.payloadReader.reset();
- }
- }
-
- public flush(): void {
- if (this.payloadReader) {
- this.payloadReader.flush(this.lastDtsUs);
- }
- }
-
private onPayloadReaderData(data: Uint8Array) {
}
- private static readTimingInfo(packet: BitReader): [number, number] {
+ private static parseTimingInfo(packet: BitReader): [number, number] {
/**
* Thanks to Videojs/Muxjs for this bit, which does well the
* trick around 32-bit unary bit-ops and 33 bit numbers :)
From 6eb41ae3ab0f92d58c734147b5b31fd5ab3d8c5e Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 9 Feb 2022 06:21:34 +0100
Subject: [PATCH 121/197] h264-reader: streamline internal
nalu-reading/parsing, rm readNaluHeadAt + rm empty constructor
---
src/demuxer/ts/payload/h264-reader.ts | 33 +++++++++++----------------
1 file changed, 13 insertions(+), 20 deletions(-)
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index 4829825..95af4ec 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -14,10 +14,6 @@ export class H264Reader extends PayloadReader {
public pendingBytes: number = 0;
- constructor() {
- super();
- }
-
public getMimeType(): string {
return Track.MIME_TYPE_AVC;
}
@@ -33,9 +29,8 @@ export class H264Reader extends PayloadReader {
if (!Number.isFinite(nextNalUnit)) {
return;
}
- this.processNalu(nextNalUnit,
- this.dataBuffer.byteLength,
- this.readNaluHeadAt(nextNalUnit));
+
+ this.readNaluData(nextNalUnit, this.dataBuffer.byteLength);
}
public reset(): void {
@@ -58,8 +53,7 @@ export class H264Reader extends PayloadReader {
if (!Number.isFinite(nextNalUnit)) {
return;
}
- this.processNalu(0, nextNalUnit, this.readNaluHeadAt(firstNalUnit));
- firstNalUnit = nextNalUnit;
+ firstNalUnit = this.readNaluData(firstNalUnit, nextNalUnit);
} else {
firstNalUnit = this.findNextNalu();
if (!Number.isFinite(firstNalUnit)) {
@@ -89,10 +83,7 @@ export class H264Reader extends PayloadReader {
break;
}
- this.processNalu(firstNalUnit, nextNalUnit,
- this.readNaluHeadAt(firstNalUnit));
-
- firstNalUnit = nextNalUnit;
+ firstNalUnit = this.readNaluData(firstNalUnit, nextNalUnit);
}
// prune data-buffer
@@ -119,15 +110,15 @@ export class H264Reader extends PayloadReader {
return NaN;
}
- private readNaluHeadAt(offset: number): number {
- // TODO: check for invalid values
- // (can be if buffer begin/remainder is garbage)
- return this.dataBuffer[offset + NALU_DELIM_LEN] & 0x1F;
- }
-
- private processNalu(start: number, end: number, nalType: number): void {
+ /**
+ * @returns end offset
+ */
+ private readNaluData(start: number, end: number): number {
const naluData = this.dataBuffer.subarray(start + NALU_DELIM_LEN, end);
+ // TODO: check for invalid values
+ // (can be if buffer begin/remainder is garbage)
+ const nalType = naluData[0] & 0x1F;
switch(nalType) {
case NAL_UNIT_TYPE.SLICE:
@@ -147,6 +138,8 @@ export class H264Reader extends PayloadReader {
}
this.onData(naluData);
+
+ return end;
}
private parseSPSNALUnit(naluData: Uint8Array): void {
From 331e02014b3402a3132eb0334cf973b40a03f7c1 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 10 Feb 2022 18:15:23 +0100
Subject: [PATCH 122/197] pes/payload-reader: add time & nalu-type args to
onData callback + add private lastCtoUs in pes-reader & set on pts/dts parse
result + move timestamp parsing to plain function + restructure/rename/doc
some things better in h264-reader + pass nalu-type in acc/h264-reader cbs
---
src/demuxer/ts/payload/adts-reader.ts | 3 +-
src/demuxer/ts/payload/h264-reader.ts | 34 ++++----
src/demuxer/ts/payload/payload-reader.ts | 6 +-
src/demuxer/ts/pes-reader.ts | 101 +++++++++++------------
4 files changed, 71 insertions(+), 73 deletions(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index 2b03fc8..22f0b1c 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -71,7 +71,8 @@ export class AdtsReader extends PayloadReader {
this.onData(this.dataBuffer.subarray(
this.dataOffset + AdtsReader.ADTS_SYNC_AND_HEADER_LEN,
- this.dataOffset + this.currentPayloadUnitLen));
+ this.dataOffset + this.currentPayloadUnitLen),
+ this.timeUs);
this.dataOffset += this.currentPayloadUnitLen;
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index 95af4ec..48318ff 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -67,7 +67,6 @@ export class H264Reader extends PayloadReader {
// why do we need to set this superclass prop here
// as it gets passed in from the call arg... ?
- // FIXME: use NaN instead of -1
if (this.firstTimestamp === -1) {
this.timeUs = this.firstTimestamp = timeUs;
}
@@ -111,24 +110,24 @@ export class H264Reader extends PayloadReader {
}
/**
- * @returns end offset
+ * @param begin offset (inclusive)
+ * @param end offset (exclusive)
+ * @returns end offset (exclusive) as input
*/
- private readNaluData(start: number, end: number): number {
+ private readNaluData(begin: number, end: number): number {
- const naluData = this.dataBuffer.subarray(start + NALU_DELIM_LEN, end);
- // TODO: check for invalid values
- // (can be if buffer begin/remainder is garbage)
- const nalType = naluData[0] & 0x1F;
+ const naluData = this.dataBuffer.subarray(begin + NALU_DELIM_LEN, end);
- switch(nalType) {
+ const naluType = naluData[0] & 0x1F;
+ switch(naluType) {
case NAL_UNIT_TYPE.SLICE:
- this.parseSliceNALUnit(naluData);
+ this.parseNonIdrPicSlice(naluData);
break;
case NAL_UNIT_TYPE.IDR:
- this.addFrame(FRAME_TYPE.I, naluData, NaN);
+ this.addFrame(FRAME_TYPE.I, naluData);
break;
case NAL_UNIT_TYPE.SPS:
- this.parseSPSNALUnit(naluData);
+ this.parseSps(naluData);
break;
case NAL_UNIT_TYPE.PPS:
this.pps = true;
@@ -137,28 +136,27 @@ export class H264Reader extends PayloadReader {
break;
}
- this.onData(naluData);
-
+ this.onData(naluData, this.timeUs, naluType);
return end;
}
- private parseSPSNALUnit(naluData: Uint8Array): void {
+ private parseSps(naluData: Uint8Array): void {
// skip first byte NALU-header for SPS-parser func input (expects only payload)
this.sps = H264ParameterSetParser.parseSPS(naluData.subarray(1));
}
- private parseSliceNALUnit(naluData: Uint8Array): void {
+ private parseNonIdrPicSlice(naluData: Uint8Array): void {
const sliceParser: BitReader = new BitReader(naluData);
sliceParser.skipBytes(1);
sliceParser.readUEG();
const sliceType: SLICE_TYPE = sliceParser.readUEG();
- this.addFrame(mapNaluSliceToFrameType(sliceType), naluData, NaN);
+ this.addFrame(mapNaluSliceToFrameType(sliceType), naluData);
}
- private addFrame(frameType: FRAME_TYPE, naluData: Uint8Array, duration: number): void {
- const frame = new Frame(frameType, this.timeUs, naluData.byteLength, duration);
+ private addFrame(frameType: FRAME_TYPE, naluData: Uint8Array): void {
+ const frame = new Frame(frameType, this.timeUs, naluData.byteLength, NaN);
this.frames.push(frame);
}
diff --git a/src/demuxer/ts/payload/payload-reader.ts b/src/demuxer/ts/payload/payload-reader.ts
index 602b5c1..7db83ff 100644
--- a/src/demuxer/ts/payload/payload-reader.ts
+++ b/src/demuxer/ts/payload/payload-reader.ts
@@ -3,7 +3,7 @@ import { Frame } from '../../frame';
export abstract class PayloadReader {
public firstTimestamp: number = -1;
- public timeUs: number = -1;
+ public timeUs: number = -1; // FIXME: use NaN instead of -1 !
public frames: Frame[] = [];
public dataBuffer: Uint8Array;
@@ -13,9 +13,9 @@ export abstract class PayloadReader {
this.reset();
}
- public abstract read(pts: number): void;
+ public abstract read(time: number): void;
- public onData(data: Uint8Array) {}
+ public onData(data: Uint8Array, time: number, naluType?: number) {}
public append(packet: BitReader): void {
diff --git a/src/demuxer/ts/pes-reader.ts b/src/demuxer/ts/pes-reader.ts
index 98013c3..1a2612d 100644
--- a/src/demuxer/ts/pes-reader.ts
+++ b/src/demuxer/ts/pes-reader.ts
@@ -7,6 +7,50 @@ import { H264Reader } from './payload/h264-reader';
import { ID3Reader } from './payload/id3-reader';
import { MpegReader } from './payload/mpeg-reader';
+function parsePesHeaderTimestamps(packet: BitReader): [number, number] {
+ /**
+ * Thanks to Videojs/Muxjs for this bit, which does well the
+ * trick around 32-bit unary bit-ops and 33 bit numbers :)
+ * -> See https://github.com/videojs/mux.js/blob/87f777f718b264df69a063847fe0fb9b5e0aaa6c/lib/m2ts/m2ts.js#L333
+ */
+ // PTS and DTS are normally stored as a 33-bit number. Javascript
+ // performs all bitwise operations on 32-bit integers but javascript
+ // supports a much greater range (52-bits) of integer using standard
+ // mathematical operations.
+ // We construct a 31-bit value using bitwise operators over the 31
+ // most significant bits and then multiply by 4 (equal to a left-shift
+ // of 2) before we add the final 2 least significant bits of the
+ // timestamp (equal to an OR.)
+ const ptsDtsFlags = packet.readByte();
+ packet.skipBytes(1);
+ let pts = NaN;
+ let dts = NaN;
+ if (ptsDtsFlags & 0xC0) {
+ // the PTS and DTS are not written out directly. For information
+ // on how they are encoded, see
+ // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
+ pts = (packet.readByte() & 0x0E) << 27 |
+ (packet.readByte() & 0xFF) << 20 |
+ (packet.readByte() & 0xFE) << 12 |
+ (packet.readByte() & 0xFF) << 5 |
+ (packet.readByte() & 0xFE) >>> 3;
+ pts *= 4; // Left shift by 2
+ pts += (packet.readByte() & 0x06) >>> 1; // OR by the two LSBs
+ dts = pts;
+ if (ptsDtsFlags & 0x40) {
+ let lastByte;
+ dts = (packet.readByte() & 0x0E) << 27 |
+ (packet.readByte() & 0xFF) << 20 |
+ (packet.readByte() & 0xFE) << 12 |
+ (packet.readByte() & 0xFF) << 5 |
+ (lastByte = packet.readByte() & 0xFE) >>> 3;
+ dts *= 4; // Left shift by 2
+ dts += (lastByte & 0x06) >>> 1; // OR by the two LSBs
+ }
+ }
+ return [dts, pts];
+}
+
export class PESReader {
public static TS_STREAM_TYPE_AAC: number = 0x0F;
public static TS_STREAM_TYPE_H264: number = 0x1B;
@@ -17,12 +61,11 @@ export class PESReader {
public payloadReader: PayloadReader;
- private lastDtsUs: number;
+ // FIXME: use NaN instead of -1 !
+ private lastDtsUs: number = -1; // TODO: migrate to integer/timescale values, causing FLOP-precision errors !!!
+ private lastCtoUs: number = NaN;
constructor(public pid: number, public type: number) {
- this.pid = pid;
- this.type = type;
- this.lastDtsUs = -1;
if (type === PESReader.TS_STREAM_TYPE_AAC) {
this.payloadReader = new AdtsReader();
@@ -61,58 +104,14 @@ export class PESReader {
private readHeader(packet: BitReader): void {
packet.skipBytes(7);
- const [dts, pts] = PESReader.parseTimingInfo(packet);
+ const [dts, pts] = parsePesHeaderTimestamps(packet);
// Note: Using DTS here, not PTS, to avoid ordering issues.
this.lastDtsUs = mpegClockTimeToMicroSecs(dts);
+ this.lastCtoUs = mpegClockTimeToMicroSecs(pts - dts);
}
- private onPayloadReaderData(data: Uint8Array) {
-
- }
-
- private static parseTimingInfo(packet: BitReader): [number, number] {
- /**
- * Thanks to Videojs/Muxjs for this bit, which does well the
- * trick around 32-bit unary bit-ops and 33 bit numbers :)
- * -> See https://github.com/videojs/mux.js/blob/87f777f718b264df69a063847fe0fb9b5e0aaa6c/lib/m2ts/m2ts.js#L333
- */
- // PTS and DTS are normally stored as a 33-bit number. Javascript
- // performs all bitwise operations on 32-bit integers but javascript
- // supports a much greater range (52-bits) of integer using standard
- // mathematical operations.
- // We construct a 31-bit value using bitwise operators over the 31
- // most significant bits and then multiply by 4 (equal to a left-shift
- // of 2) before we add the final 2 least significant bits of the
- // timestamp (equal to an OR.)
- const ptsDtsFlags = packet.readByte();
- packet.skipBytes(1);
- let pts = NaN;
- let dts = NaN;
- if (ptsDtsFlags & 0xC0) {
- // the PTS and DTS are not written out directly. For information
- // on how they are encoded, see
- // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
- pts = (packet.readByte() & 0x0E) << 27 |
- (packet.readByte() & 0xFF) << 20 |
- (packet.readByte() & 0xFE) << 12 |
- (packet.readByte() & 0xFF) << 5 |
- (packet.readByte() & 0xFE) >>> 3;
- pts *= 4; // Left shift by 2
- pts += (packet.readByte() & 0x06) >>> 1; // OR by the two LSBs
- dts = pts;
- if (ptsDtsFlags & 0x40) {
- let lastByte;
- dts = (packet.readByte() & 0x0E) << 27 |
- (packet.readByte() & 0xFF) << 20 |
- (packet.readByte() & 0xFE) << 12 |
- (packet.readByte() & 0xFF) << 5 |
- (lastByte = packet.readByte() & 0xFE) >>> 3;
- dts *= 4; // Left shift by 2
- dts += (lastByte & 0x06) >>> 1; // OR by the two LSBs
- }
- }
- return [dts, pts];
+ private onPayloadReaderData(data: Uint8Array, timeUs: number, naluType: number) {
}
}
From 3376722f5fda0c8fa24825bccb028d14e20efa96 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 10 Mar 2022 21:46:30 +0100
Subject: [PATCH 123/197] payload-reader: add internal PUSI tracking (count and
frame-list length) + popFrames to support default wholePayloadUnits flag,
splicing frame list on last tracked PUSI (optionally) + re-org private
methods order
---
src/demuxer/ts/payload/payload-reader.ts | 59 +++++++++++++++---------
1 file changed, 36 insertions(+), 23 deletions(-)
diff --git a/src/demuxer/ts/payload/payload-reader.ts b/src/demuxer/ts/payload/payload-reader.ts
index 7db83ff..6833e35 100644
--- a/src/demuxer/ts/payload/payload-reader.ts
+++ b/src/demuxer/ts/payload/payload-reader.ts
@@ -2,6 +2,7 @@ import { BitReader } from '../../../utils/bit-reader';
import { Frame } from '../../frame';
export abstract class PayloadReader {
+
public firstTimestamp: number = -1;
public timeUs: number = -1; // FIXME: use NaN instead of -1 !
public frames: Frame[] = [];
@@ -9,6 +10,9 @@ export abstract class PayloadReader {
protected dataOffset: number = 0;
+ private framesPusiCount: number = 0;
+ private framesCurrentPusiLen: number = 0;
+
constructor() {
this.reset();
}
@@ -17,7 +21,28 @@ export abstract class PayloadReader {
public onData(data: Uint8Array, time: number, naluType?: number) {}
- public append(packet: BitReader): void {
+ public getMimeType(): string {
+ return 'Unknown';
+ }
+
+ public getDuration(): number {
+ return this.getLastPTS() - this.getFirstPTS();
+ }
+
+ public getFirstPTS(): number {
+ return this.firstTimestamp;
+ }
+
+ public getLastPTS(): number {
+ return this.timeUs;
+ }
+
+ public append(packet: BitReader, payloadUnitStartIndicator: boolean): void {
+
+ if (payloadUnitStartIndicator) {
+ this.framesPusiCount++;
+ this.framesCurrentPusiLen = this.frames.length;
+ }
const packetReaderOffset = packet.bytesOffset();
const dataToAppend: Uint8Array = packet.buffer.subarray(packetReaderOffset);
@@ -35,6 +60,8 @@ export abstract class PayloadReader {
public reset(): void {
this.frames.length = 0;
+ this.framesPusiCount = 0;
+ this.framesCurrentPusiLen = 0;
this.dataOffset = 0;
this.firstTimestamp = -1;
this.timeUs = -1;
@@ -48,28 +75,14 @@ export abstract class PayloadReader {
this.dataOffset = 0;
}
- public popFrames(): Frame[] {
- if (this.frames.length === 0) {
- return [];
- }
- const frames = this.frames.slice(0);
- this.frames.length = 0;
+ public popFrames(wholePayloadUnits: boolean = true): Frame[] {
+ let numFrames = wholePayloadUnits ? this.framesCurrentPusiLen : this.frames.length;
+ if (numFrames === 0) return [];
+ // split-slice frame-list:
+ // returns slice to pop, mutates list to remainder (deletes sliced items)
+ const frames = this.frames.splice(0, numFrames);
+ // set current payload-unit frame-count to remainder length
+ this.framesCurrentPusiLen = this.frames.length;
return frames;
}
-
- public getMimeType(): string {
- return 'Unknown';
- }
-
- public getDuration(): number {
- return this.getLastPTS() - this.getFirstPTS();
- }
-
- public getFirstPTS(): number {
- return this.firstTimestamp;
- }
-
- public getLastPTS(): number {
- return this.timeUs;
- }
}
From 2a7f78e6b497f8c4f008950e6b26dd23f6e246aa Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 10 Mar 2022 21:47:56 +0100
Subject: [PATCH 124/197] ts-track: popFrames addd wholePayloadUnits flag
propagated to payload-reader
---
src/demuxer/ts/ts-track.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/demuxer/ts/ts-track.ts b/src/demuxer/ts/ts-track.ts
index 55c1cbe..8c54a4e 100644
--- a/src/demuxer/ts/ts-track.ts
+++ b/src/demuxer/ts/ts-track.ts
@@ -20,8 +20,8 @@ export class TSTrack extends Track {
return this?.pes?.payloadReader.frames || [];
}
- public popFrames(): Frame[] {
- return this?.pes?.payloadReader.popFrames() || [];
+ public popFrames(wholePayloadUnits: boolean = true): Frame[] {
+ return this.pes?.payloadReader?.popFrames(wholePayloadUnits) || [];
}
public getMetadata(): {} {
From 3259b78119f188913492beabea5468d967002985 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 10 Mar 2022 21:48:38 +0100
Subject: [PATCH 125/197] utils: timescale: add MPEG_CLOCK_HZ const
---
src/utils/timescale.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/utils/timescale.ts b/src/utils/timescale.ts
index 3647af3..c32e096 100644
--- a/src/utils/timescale.ts
+++ b/src/utils/timescale.ts
@@ -8,7 +8,9 @@ export function toSecondsFromMicros(us) {
return us / MICROSECOND_TIMESCALE;
}
+export const MPEG_CLOCK_HZ = 90000;
+
export function mpegClockTimeToMicroSecs(time: number): number {
- return toMicroseconds(time, 90000);
+ return toMicroseconds(time, MPEG_CLOCK_HZ);
}
From 2deac3dd16d90facf6126141d566289a3a540895 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 10 Mar 2022 21:51:46 +0100
Subject: [PATCH 126/197] pes-reader: convert stream-types lookup to enum + fix
type in constructor + add onPayloadData public callback prop + pass
PUSI-found flag to payload-reader append + implement handlePayloadReadData to
call onPayloadData
---
src/demuxer/ts/pes-reader.ts | 42 +++++++++++++++++++++---------------
1 file changed, 25 insertions(+), 17 deletions(-)
diff --git a/src/demuxer/ts/pes-reader.ts b/src/demuxer/ts/pes-reader.ts
index 1a2612d..9202bde 100644
--- a/src/demuxer/ts/pes-reader.ts
+++ b/src/demuxer/ts/pes-reader.ts
@@ -1,11 +1,12 @@
import { BitReader } from '../../utils/bit-reader';
-import { mpegClockTimeToMicroSecs } from '../../utils/timescale';
+import { mpegClockTimeToMicroSecs, MPEG_CLOCK_HZ, toSecondsFromMicros } from '../../utils/timescale';
import { PayloadReader } from './payload/payload-reader';
import { UnknownReader } from './payload/unknown-reader';
import { AdtsReader } from './payload/adts-reader';
import { H264Reader } from './payload/h264-reader';
import { ID3Reader } from './payload/id3-reader';
import { MpegReader } from './payload/mpeg-reader';
+import { NAL_UNIT_TYPE } from '../../codecs/h264/nal-units';
function parsePesHeaderTimestamps(packet: BitReader): [number, number] {
/**
@@ -51,13 +52,16 @@ function parsePesHeaderTimestamps(packet: BitReader): [number, number] {
return [dts, pts];
}
+export enum MptsElementaryStreamType {
+ TS_STREAM_TYPE_AAC = 0x0F,
+ TS_STREAM_TYPE_H264 = 0x1B,
+ TS_STREAM_TYPE_ID3 = 0x15,
+ TS_STREAM_TYPE_MPA = 0x03,
+ TS_STREAM_TYPE_MPA_LSF = 0x04,
+ TS_STREAM_TYPE_METADATA = 0x06
+}
+
export class PESReader {
- public static TS_STREAM_TYPE_AAC: number = 0x0F;
- public static TS_STREAM_TYPE_H264: number = 0x1B;
- public static TS_STREAM_TYPE_ID3: number = 0x15;
- public static TS_STREAM_TYPE_MPA: number = 0x03;
- public static TS_STREAM_TYPE_MPA_LSF: number = 0x04;
- public static TS_STREAM_TYPE_METADATA: number = 0x06;
public payloadReader: PayloadReader;
@@ -65,32 +69,33 @@ export class PESReader {
private lastDtsUs: number = -1; // TODO: migrate to integer/timescale values, causing FLOP-precision errors !!!
private lastCtoUs: number = NaN;
- constructor(public pid: number, public type: number) {
+ constructor(public pid: number, public type: MptsElementaryStreamType) {
- if (type === PESReader.TS_STREAM_TYPE_AAC) {
+ if (type === MptsElementaryStreamType.TS_STREAM_TYPE_AAC) {
this.payloadReader = new AdtsReader();
- } else if (type === PESReader.TS_STREAM_TYPE_H264) {
+ } else if (type === MptsElementaryStreamType.TS_STREAM_TYPE_H264) {
this.payloadReader = new H264Reader();
- } else if (type === PESReader.TS_STREAM_TYPE_ID3) {
+ } else if (type === MptsElementaryStreamType.TS_STREAM_TYPE_ID3) {
this.payloadReader = new ID3Reader();
- } else if (type === PESReader.TS_STREAM_TYPE_MPA || type === PESReader.TS_STREAM_TYPE_MPA_LSF) {
+ } else if (type === MptsElementaryStreamType.TS_STREAM_TYPE_MPA || type === MptsElementaryStreamType.TS_STREAM_TYPE_MPA_LSF) {
this.payloadReader = new MpegReader();
- } else if (type === PESReader.TS_STREAM_TYPE_METADATA) {
+ } else if (type === MptsElementaryStreamType.TS_STREAM_TYPE_METADATA) {
this.payloadReader = new UnknownReader();
} else {
this.payloadReader = new UnknownReader();
}
- this.payloadReader.onData = this.onPayloadReaderData.bind(this);
+ this.payloadReader.onData = this.handlePayloadReadData.bind(this);
}
+ public onPayloadData(data: Uint8Array, timeUs: number) {}
+
public appendData(payloadUnitStartIndicator: boolean, packet: BitReader): void {
if (payloadUnitStartIndicator) {
this.payloadReader.read(this.lastDtsUs);
this.readHeader(packet);
}
-
- this.payloadReader.append(packet);
+ this.payloadReader.append(packet, payloadUnitStartIndicator);
}
public reset(): void {
@@ -112,6 +117,9 @@ export class PESReader {
this.lastCtoUs = mpegClockTimeToMicroSecs(pts - dts);
}
- private onPayloadReaderData(data: Uint8Array, timeUs: number, naluType: number) {
+ private handlePayloadReadData(data: Uint8Array, timeUs: number, naluType: number = NaN) {
+ if (!this.payloadReader.frames.length) return;
+ const timeSecs = toSecondsFromMicros(timeUs);
+ this.onPayloadData(data, timeSecs);
}
}
From d8b72a5ed77375fe2f9e0a4d1120364f82dfea1c Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 10 Mar 2022 21:55:02 +0100
Subject: [PATCH 127/197] ts-demuxer: use new MptsElementaryStreamType enum in
PES-reader factory + findContainerType: no-op early return if containerType
already defined + re-org member declaration & rm redundant assignments in
constructor
---
src/demuxer/ts/mpegts-demuxer.ts | 36 ++++++++++++++------------------
1 file changed, 16 insertions(+), 20 deletions(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index 2b49f6c..68776ec 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -1,5 +1,5 @@
import { BitReader } from '../../utils/bit-reader';
-import { PESReader } from './pes-reader';
+import { MptsElementaryStreamType, PESReader } from './pes-reader';
import { TSTrack } from './ts-track';
import { Track } from '../track';
import { IDemuxer } from '../demuxer';
@@ -16,22 +16,16 @@ export class MpegTSDemuxer implements IDemuxer {
private static MPEGTS_PACKET_SIZE: number = 188;
private static MPEGTS_PACKET_SIZE_MINUS_ONE: number = 187;
- public tracks: { [id: number] : TSTrack; };
+ public tracks: { [id: number] : TSTrack; } = {};
+
+ private containerType: CONTAINER_TYPE = CONTAINER_TYPE.UNKNOWN;
private data: Uint8Array;
private dataOffset: number;
- private containerType: number;
- private pmtParsed: boolean;
- private packetsCount: number;
- private pmtId: number;
-
- constructor () {
- this.containerType = CONTAINER_TYPE.UNKNOWN;
- this.pmtParsed = false;
- this.packetsCount = 0;
- this.pmtId = -1;
- this.tracks = {};
- }
+
+ private packetsCount: number = 0;
+ private pmtId: number = -1;
+ private pmtParsed: boolean = false;
get currentBufferSize(): number {
return this?.data.byteLength || 0;
@@ -111,7 +105,7 @@ export class MpegTSDemuxer implements IDemuxer {
const streamReader: BitReader = new BitReader(this.data);
this.tracks[0] = new TSTrack(0,
Track.TYPE_AUDIO, Track.MIME_TYPE_AAC,
- new PESReader(0, PESReader.TS_STREAM_TYPE_AAC));
+ new PESReader(0, MptsElementaryStreamType.TS_STREAM_TYPE_AAC));
this.tracks[0].pes.appendData(false, streamReader);
}
}
@@ -133,6 +127,8 @@ export class MpegTSDemuxer implements IDemuxer {
}
private findContainerType(): void {
+ if (this.containerType !== CONTAINER_TYPE.UNKNOWN) return;
+
while (this.dataOffset < this.data.byteLength) {
if (this.data[this.dataOffset] === MpegTSDemuxer.MPEGTS_SYNC) {
this.containerType = CONTAINER_TYPE.MPEG_TS;
@@ -234,19 +230,19 @@ export class MpegTSDemuxer implements IDemuxer {
const pes: PESReader = new PESReader(elementaryPid, streamType);
let type: string;
let mimeType: string;
- if (streamType === PESReader.TS_STREAM_TYPE_AAC) {
+ if (streamType === MptsElementaryStreamType.TS_STREAM_TYPE_AAC) {
type = Track.TYPE_AUDIO;
mimeType = Track.MIME_TYPE_AAC;
- } else if (streamType === PESReader.TS_STREAM_TYPE_H264) {
+ } else if (streamType === MptsElementaryStreamType.TS_STREAM_TYPE_H264) {
type = Track.TYPE_VIDEO;
mimeType = Track.MIME_TYPE_AVC;
- } else if (streamType === PESReader.TS_STREAM_TYPE_ID3) {
+ } else if (streamType === MptsElementaryStreamType.TS_STREAM_TYPE_ID3) {
type = Track.TYPE_TEXT;
mimeType = Track.MIME_TYPE_ID3;
- } else if (streamType === PESReader.TS_STREAM_TYPE_MPA || streamType === PESReader.TS_STREAM_TYPE_MPA_LSF) {
+ } else if (streamType === MptsElementaryStreamType.TS_STREAM_TYPE_MPA || streamType === MptsElementaryStreamType.TS_STREAM_TYPE_MPA_LSF) {
type = Track.TYPE_AUDIO;
mimeType = Track.MIME_TYPE_MPEG;
- } else if (streamType === PESReader.TS_STREAM_TYPE_METADATA) {
+ } else if (streamType === MptsElementaryStreamType.TS_STREAM_TYPE_METADATA) {
// do nothing
} else {
type = Track.TYPE_UNKNOWN;
From cdd0e84198f0c728132f645cb590268cdf071bbb Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Fri, 1 Apr 2022 23:10:29 +0200
Subject: [PATCH 128/197] ts-demuxer: init pmtId with Nan instead -1 & rename
cb to onProgramMapUpdate + getter to isProgramMapUpdated + order imports
---
src/demuxer/ts/mpegts-demuxer.ts | 19 +++++++++++--------
1 file changed, 11 insertions(+), 8 deletions(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index 68776ec..0a2d167 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -1,8 +1,9 @@
-import { BitReader } from '../../utils/bit-reader';
+import { IDemuxer } from '../demuxer';
+import { Track } from '../track';
+
import { MptsElementaryStreamType, PESReader } from './pes-reader';
import { TSTrack } from './ts-track';
-import { Track } from '../track';
-import { IDemuxer } from '../demuxer';
+import { BitReader } from '../../utils/bit-reader';
enum CONTAINER_TYPE {
UNKNOWN = 1,
@@ -24,7 +25,8 @@ export class MpegTSDemuxer implements IDemuxer {
private dataOffset: number;
private packetsCount: number = 0;
- private pmtId: number = -1;
+
+ private pmtId: number = NaN;
private pmtParsed: boolean = false;
get currentBufferSize(): number {
@@ -35,8 +37,8 @@ export class MpegTSDemuxer implements IDemuxer {
return this.packetsCount;
}
- get isPmtParsed(): boolean {
- return this.isPmtParsed;
+ get isProgramMapUpdated(): boolean {
+ return this.pmtParsed;
}
public append(data: Uint8Array, pruneAfterParse: boolean = false): Uint8Array | null {
@@ -93,7 +95,7 @@ export class MpegTSDemuxer implements IDemuxer {
this.dataOffset = 0;
}
- public onPmtParsed() {};
+ public onProgramMapUpdate() {};
private parse(): void {
@@ -191,6 +193,7 @@ export class MpegTSDemuxer implements IDemuxer {
this.parseProgramMapTable(payloadUnitStartIndicator, packetReader);
} else {
const track: TSTrack = this.tracks[pid];
+ // handle case where PID not found?
if (track && track.pes) {
track.pes.appendData(payloadUnitStartIndicator, packetReader);
}
@@ -252,6 +255,6 @@ export class MpegTSDemuxer implements IDemuxer {
}
}
this.pmtParsed = true;
- this.onPmtParsed();
+ this.onProgramMapUpdate();
}
}
From df3ce0c2bb5e19cc8c86212325ec2a522861a4c7 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Fri, 1 Apr 2022 23:14:25 +0200
Subject: [PATCH 129/197] pes/payload-reader: pass nalu-type in payload cb +
add getPusiCount method + rename internal counter members to pusiCount &
lastPusiFramesLen + reset pusiCount to 0 in popFrames() (allows to gather
count via getter prior eventual call to the latter payload-reader method)
---
src/demuxer/ts/payload/payload-reader.ts | 21 +++++++++++++--------
src/demuxer/ts/pes-reader.ts | 4 ++--
2 files changed, 15 insertions(+), 10 deletions(-)
diff --git a/src/demuxer/ts/payload/payload-reader.ts b/src/demuxer/ts/payload/payload-reader.ts
index 6833e35..4c50d53 100644
--- a/src/demuxer/ts/payload/payload-reader.ts
+++ b/src/demuxer/ts/payload/payload-reader.ts
@@ -10,8 +10,8 @@ export abstract class PayloadReader {
protected dataOffset: number = 0;
- private framesPusiCount: number = 0;
- private framesCurrentPusiLen: number = 0;
+ private pusiCount: number = 0;
+ private lastPusiFramesLen: number = 0;
constructor() {
this.reset();
@@ -37,11 +37,15 @@ export abstract class PayloadReader {
return this.timeUs;
}
+ public getPusiCount() {
+ return this.pusiCount;
+ }
+
public append(packet: BitReader, payloadUnitStartIndicator: boolean): void {
if (payloadUnitStartIndicator) {
- this.framesPusiCount++;
- this.framesCurrentPusiLen = this.frames.length;
+ this.pusiCount++;
+ this.lastPusiFramesLen = this.frames.length;
}
const packetReaderOffset = packet.bytesOffset();
@@ -60,8 +64,8 @@ export abstract class PayloadReader {
public reset(): void {
this.frames.length = 0;
- this.framesPusiCount = 0;
- this.framesCurrentPusiLen = 0;
+ this.pusiCount = 0;
+ this.lastPusiFramesLen = 0;
this.dataOffset = 0;
this.firstTimestamp = -1;
this.timeUs = -1;
@@ -76,13 +80,14 @@ export abstract class PayloadReader {
}
public popFrames(wholePayloadUnits: boolean = true): Frame[] {
- let numFrames = wholePayloadUnits ? this.framesCurrentPusiLen : this.frames.length;
+ let numFrames = wholePayloadUnits ? this.lastPusiFramesLen : this.frames.length;
if (numFrames === 0) return [];
// split-slice frame-list:
// returns slice to pop, mutates list to remainder (deletes sliced items)
const frames = this.frames.splice(0, numFrames);
// set current payload-unit frame-count to remainder length
- this.framesCurrentPusiLen = this.frames.length;
+ this.lastPusiFramesLen = this.frames.length;
+ this.pusiCount = 0;
return frames;
}
}
diff --git a/src/demuxer/ts/pes-reader.ts b/src/demuxer/ts/pes-reader.ts
index 9202bde..ef13eaa 100644
--- a/src/demuxer/ts/pes-reader.ts
+++ b/src/demuxer/ts/pes-reader.ts
@@ -88,7 +88,7 @@ export class PESReader {
this.payloadReader.onData = this.handlePayloadReadData.bind(this);
}
- public onPayloadData(data: Uint8Array, timeUs: number) {}
+ public onPayloadData(data: Uint8Array, timeUs: number, naluType: number) {}
public appendData(payloadUnitStartIndicator: boolean, packet: BitReader): void {
if (payloadUnitStartIndicator) {
@@ -120,6 +120,6 @@ export class PESReader {
private handlePayloadReadData(data: Uint8Array, timeUs: number, naluType: number = NaN) {
if (!this.payloadReader.frames.length) return;
const timeSecs = toSecondsFromMicros(timeUs);
- this.onPayloadData(data, timeSecs);
+ this.onPayloadData(data, timeSecs, naluType);
}
}
From 7b14ee3afdf0d5c169837d60ca1c70ecd7d29431 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 25 Apr 2022 01:57:34 +0200
Subject: [PATCH 130/197] h264-reader: rm Q comment
---
src/demuxer/ts/payload/h264-reader.ts | 4 ----
1 file changed, 4 deletions(-)
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index 48318ff..b2bd232 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -63,10 +63,6 @@ export class H264Reader extends PayloadReader {
// post: firstNalUnit is finite number
- // Q: for what else do we need this timeUs param, and
- // why do we need to set this superclass prop here
- // as it gets passed in from the call arg... ?
-
if (this.firstTimestamp === -1) {
this.timeUs = this.firstTimestamp = timeUs;
}
From f6edb5b224c96273e0e11aaa5efe7c68aa4461dd Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 25 Apr 2022 02:03:42 +0200
Subject: [PATCH 131/197] adts-reader: refactor parsing control flow
(findNextSync return result) + add header parsing & internal state fields for
streamlining logic & completeness + add validation runtime
assertions/exceptions in header parsing + fix layout style of sampling freqs
LUT + rm redundant static const members + use MICROSECOND_TIMESCALE util
const instead of literal val + ensure onData callback invoked after state
transition mutations fulfilled
---
src/demuxer/ts/payload/adts-reader.ts | 182 ++++++++++++++++++--------
1 file changed, 131 insertions(+), 51 deletions(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index 22f0b1c..6e0a7f3 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -1,3 +1,4 @@
+import { MICROSECOND_TIMESCALE } from '../../../utils/timescale';
import { BitReader } from '../../../utils/bit-reader';
import { PayloadReader } from './payload-reader';
import { Frame } from '../../frame';
@@ -11,19 +12,33 @@ enum AdtsReaderState {
}
export class AdtsReader extends PayloadReader {
- private static ADTS_HEADER_SIZE: number = 5;
- private static ADTS_SYNC_SIZE: number = 2;
- private static ADTS_SYNC_AND_HEADER_LEN = AdtsReader.ADTS_HEADER_SIZE + AdtsReader.ADTS_SYNC_SIZE;
- private static ADTS_CRC_SIZE: number = 2;
-
- private static ADTS_SAMPLE_RATES: number[] = [96000, 88200, 64000, 48000,
- 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
-
- public channels: number = 0;
- public sampleRate: number = 0;
- public frameDuration: number = 0;
- public currentPayloadUnitLen: number = 0;
+ private static ADTS_HEADER_LEN = 7 as const;
+ private static ADTS_CRC_SIZE: number = 2 as const;
+
+ // TODO: use centralized table
+ private static ADTS_SAMPLE_RATES: number[] = [
+ 96000,
+ 88200,
+ 64000,
+ 48000,
+ 44100,
+ 32000,
+ 24000,
+ 22050,
+ 16000,
+ 12000,
+ 11025,
+ 8000,
+ 7350
+ ];
+
+ public profile: number = NaN;
+ public channels: number = NaN;
+ public sampleRate: number = NaN;
+ public frameDuration: number = NaN;
+ public currentAdtsHeaderLen: number = NaN;
+ public currentAccessUnitSize: number = NaN;
private state: AdtsReaderState = AdtsReaderState.FIND_SYNC;
@@ -47,37 +62,47 @@ export class AdtsReader extends PayloadReader {
this.firstTimestamp = this.timeUs;
}
- while (this.dataOffset < this.dataBuffer.byteLength) {
- if (this.state === AdtsReaderState.FIND_SYNC) {
- this.findNextSync();
- } else if (this.state === AdtsReaderState.READ_HEADER) {
- if (this.dataBuffer.byteLength - this.dataOffset
- < AdtsReader.ADTS_SYNC_AND_HEADER_LEN) {
+ let needMoreData = false;
+ while (!needMoreData && this.dataOffset < this.dataBuffer.byteLength) {
+
+ switch (this.state) {
+ case AdtsReaderState.FIND_SYNC:
+ needMoreData = ! this.findNextSync(); // returns true when sync found
+ break;
+
+ case AdtsReaderState.READ_HEADER:
+ if (this.dataBuffer.byteLength - this.dataOffset < AdtsReader.ADTS_HEADER_LEN) {
+ needMoreData = true;
break;
}
- this.parseAACHeader();
- } else if (this.state === AdtsReaderState.READ_FRAME) {
- if (this.dataBuffer.byteLength - this.dataOffset
- < this.currentPayloadUnitLen) {
+ this.parseHeader();
+ break;
+
+ case AdtsReaderState.READ_FRAME:
+ if (this.dataBuffer.byteLength - this.dataOffset < this.currentAdtsHeaderLen + this.currentAccessUnitSize) {
+ needMoreData = true;
break;
}
this.frames.push(new Frame(
FRAME_TYPE.NONE,
this.timeUs,
- this.currentPayloadUnitLen,
+ this.currentAccessUnitSize,
this.frameDuration,
this.dataOffset));
- this.onData(this.dataBuffer.subarray(
- this.dataOffset + AdtsReader.ADTS_SYNC_AND_HEADER_LEN,
- this.dataOffset + this.currentPayloadUnitLen),
- this.timeUs);
-
- this.dataOffset += this.currentPayloadUnitLen;
+ const frameDataStart = this.dataOffset + this.currentAdtsHeaderLen;
+ const frameDataEnd = frameDataStart + this.currentAccessUnitSize;
+ const frameData = this.dataBuffer.subarray(frameDataStart, frameDataEnd);
+ this.dataOffset = frameDataEnd;
this.timeUs = this.timeUs + this.frameDuration;
+
this.state = AdtsReaderState.FIND_SYNC;
+
+ this.onData(frameData, this.timeUs);
+ break;
+
}
}
@@ -85,49 +110,104 @@ export class AdtsReader extends PayloadReader {
this.dataOffset = 0;
}
- private findNextSync(): void {
- const limit: number = this.dataBuffer.byteLength - 1;
- for (let i: number = this.dataOffset; i < limit; i++) {
- const dataRead: number = (((this.dataBuffer[i]) << 8) | (this.dataBuffer[i + 1]));
+ /**
+ *
+ * @returns
+ * - true when found (post: state = READ_HEADER)
+ * - false when more data needed (post: dataOffset = first byte after inclusive end of scan window)
+ */
+ private findNextSync(): boolean {
+ const nextDataOffset: number = this.dataBuffer.byteLength - 1; // sync-word spans 2 bytes (12 bits)
+ for (let i: number = this.dataOffset; i < nextDataOffset; i++) {
+ const dataRead: number = ((this.dataBuffer[i]) << 8) | (this.dataBuffer[i + 1]);
+ // 0b6 = 0110 mask to ignore the mpeg-version and CRC bit,
+ // and assert check for layer bits to be zero
+ /**
+ * A 12 syncword 0xFFF, all bits must be 1
+ * B 1 MPEG Version: 0 for MPEG-4, 1 for MPEG-2
+ * C 2 Layer: always 0
+ * D 1 protection absent, Warning, set to 1 if there is no CRC and 0 if there is CRC
+ */
if ((dataRead & 0xfff6) === 0xfff0) {
this.dataOffset = i;
if (this.dataOffset < this.dataBuffer.byteLength) {
this.state = AdtsReaderState.READ_HEADER;
}
- return;
+ return true;
}
}
- this.dataOffset = this.dataBuffer.byteLength;
+ this.dataOffset = nextDataOffset;
+ return false;
}
- private parseAACHeader(): void {
- const aacHeaderParser: BitReader = new BitReader(
+ private parseHeader(): void {
+ const br: BitReader = new BitReader(
this.dataBuffer.subarray(
this.dataOffset,
- this.dataOffset + AdtsReader.ADTS_SYNC_AND_HEADER_LEN));
+ this.dataOffset + AdtsReader.ADTS_HEADER_LEN));
+
+ br.skipBits(12);
+
+ const mpegVersion: number = br.readBool() ? 1 : 0; // MPEG Version: 0 for MPEG-4, 1 for MPEG-2
- aacHeaderParser.skipBits(15);
- const hasCrc: boolean = !aacHeaderParser.readBool();
- aacHeaderParser.skipBits(2);
- const sampleRateIndex: number = aacHeaderParser.readBits(4);
- if (sampleRateIndex < AdtsReader.ADTS_SAMPLE_RATES.length) {
+ br.skipBits(2);
+
+ const hasCrc: boolean = ! br.readBool();
+
+ /**
+ * 1: AAC Main
+ * 2: AAC LC (Low Complexity)
+ * 3: AAC SSR (Scalable Sample Rate)
+ * 4: AAC LTP (Long Term Prediction)
+ */
+ // profile, the MPEG-4 Audio Object Type minus 1
+ const audioCodecProfile = br.readBits(2) + 1;
+ if (audioCodecProfile <= 0 || audioCodecProfile >= 5) {
+ throw new Error(`Unsupported or likely invalid AAC profile (MPEG-4 Audio Object Type): ${audioCodecProfile}`);
+ }
+ this.profile = audioCodecProfile;
+
+ const sampleRateIndex: number = br.readBits(4);
+
+ if (sampleRateIndex >= 0 && sampleRateIndex < AdtsReader.ADTS_SAMPLE_RATES.length) {
this.sampleRate = AdtsReader.ADTS_SAMPLE_RATES[sampleRateIndex];
} else {
- this.sampleRate = sampleRateIndex;
+ throw new Error(`Invalid AAC sampling-frequency index: ${sampleRateIndex}`);
}
+ this.frameDuration = (MICROSECOND_TIMESCALE * 1024) / this.sampleRate;
- this.frameDuration = (1000000 * 1024) / this.sampleRate;
+ // private bit (unused by spec)
+ br.skipBits(1);
- aacHeaderParser.skipBits(1);
- this.channels = aacHeaderParser.readBits(3);
+ const channelsConf = br.readBits(3);
+ if (channelsConf <= 0 || channelsConf >= 8) {
+ throw new Error(`Channel configuration invalid value: ${channelsConf}`);
+ }
- aacHeaderParser.skipBits(4);
+ this.channels = channelsConf;
- this.currentPayloadUnitLen = aacHeaderParser.readBits(13);
+ // originality/home/copyright bits (ignoring)
+ br.skipBits(4);
+
+ const adtsFrameLen = br.readBits(13); // always including the header itself (w/ opt CRC 2 bytes)
+ if (adtsFrameLen <= 0) throw new Error(`Invalid ADTS-frame byte-length: ${adtsFrameLen}`);
+
+ this.currentAdtsHeaderLen = hasCrc ?
+ AdtsReader.ADTS_HEADER_LEN + AdtsReader.ADTS_CRC_SIZE : AdtsReader.ADTS_HEADER_LEN;
+ this.currentAccessUnitSize = adtsFrameLen - this.currentAdtsHeaderLen;
+
+ // buffer fullness (ignoring so far, spec not clear about what it is really yet)
+ br.skipBits(11);
+
+ // 1 ADTS frame can contain up to 4 AAC frames !
+ const nbOfAacFrames = br.readBits(2) + 1;
+ if (nbOfAacFrames <= 0) {
+ throw new Error(`Invalid AAC frame-number in ADTS header: ${nbOfAacFrames}`);
+ }
- if (hasCrc) {
- this.currentPayloadUnitLen -= AdtsReader.ADTS_CRC_SIZE;
+ if (nbOfAacFrames !== 1) {
+ throw new Error(`Can not have AAC frame-number in ADTS header: ${nbOfAacFrames} (only 1 is supported in this compatibility mode)`);
}
this.state = AdtsReaderState.READ_FRAME;
From 5e3ab0d05a9877860cb0d19701839ed6464fe8e8 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 28 Apr 2022 11:18:04 +0200
Subject: [PATCH 132/197] adts-reader: add generic error debugger around
parseHeader call
---
src/demuxer/ts/payload/adts-reader.ts | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index 6e0a7f3..6292bb5 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -75,7 +75,12 @@ export class AdtsReader extends PayloadReader {
needMoreData = true;
break;
}
- this.parseHeader();
+ try {
+ this.parseHeader();
+ } catch (err) {
+ console.debug(this);
+ throw new Error(`Error parsing header at ${this.dataOffset} / ${this.dataBuffer.byteLength}: ${(err as Error).message}`);
+ }
break;
case AdtsReaderState.READ_FRAME:
From 8daec962b849049bf7bb9a5ba18ae89082029812 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 11 May 2022 19:45:37 +0200
Subject: [PATCH 133/197] adts parse err: rm hard debug log
---
src/demuxer/ts/payload/adts-reader.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index 6292bb5..2497047 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -78,7 +78,7 @@ export class AdtsReader extends PayloadReader {
try {
this.parseHeader();
} catch (err) {
- console.debug(this);
+ // console.debug(this);
throw new Error(`Error parsing header at ${this.dataOffset} / ${this.dataBuffer.byteLength}: ${(err as Error).message}`);
}
break;
From 9c2aac40278d19af08310df0dfdefd82490bfaa7 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 12 May 2022 03:06:34 +0200
Subject: [PATCH 134/197] payload-reader: fix reset() to null dataBuffer as
well
---
src/demuxer/ts/payload/payload-reader.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/demuxer/ts/payload/payload-reader.ts b/src/demuxer/ts/payload/payload-reader.ts
index 4c50d53..86d2578 100644
--- a/src/demuxer/ts/payload/payload-reader.ts
+++ b/src/demuxer/ts/payload/payload-reader.ts
@@ -67,6 +67,7 @@ export abstract class PayloadReader {
this.pusiCount = 0;
this.lastPusiFramesLen = 0;
this.dataOffset = 0;
+ this.dataBuffer = null;
this.firstTimestamp = -1;
this.timeUs = -1;
}
From b43162307f6c611cadf30153486cee028332a19f Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 12 May 2022 03:08:46 +0200
Subject: [PATCH 135/197] adts-reader reader: fix handling partial/broken
payload in header parsing by calling parent class reset and setting state
back to FIND_SYNC + throw error with appropriate info + remove assertion on
nb-of-frames in header parsing (not necessary and our payload has
multi-frames units, but that is ok for the way we consume the parser so far)
---
src/demuxer/ts/payload/adts-reader.ts | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index 2497047..df6159f 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -78,8 +78,12 @@ export class AdtsReader extends PayloadReader {
try {
this.parseHeader();
} catch (err) {
- // console.debug(this);
- throw new Error(`Error parsing header at ${this.dataOffset} / ${this.dataBuffer.byteLength}: ${(err as Error).message}`);
+ // data pointers will be nulled by reset call, so we need to make string first
+ const errMsg = `Error parsing header at ${this.dataOffset} / ${this.dataBuffer.byteLength}: ${(err as Error).message}`;
+ // console.debug(this); // only for debug !!
+ this.reset();
+ this.state = AdtsReaderState.FIND_SYNC;
+ throw new Error(errMsg);
}
break;
@@ -211,9 +215,11 @@ export class AdtsReader extends PayloadReader {
throw new Error(`Invalid AAC frame-number in ADTS header: ${nbOfAacFrames}`);
}
+ /*
if (nbOfAacFrames !== 1) {
throw new Error(`Can not have AAC frame-number in ADTS header: ${nbOfAacFrames} (only 1 is supported in this compatibility mode)`);
}
+ //*/
this.state = AdtsReaderState.READ_FRAME;
}
From 6e1b3e075d965b36cfc865b64a6c947a2f3c5762 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 12 May 2022 03:56:33 +0200
Subject: [PATCH 136/197] adts-reader: fix init BitReader with full buffer
instead non-crc header len
---
src/demuxer/ts/payload/adts-reader.ts | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index df6159f..5191b8f 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -152,9 +152,7 @@ export class AdtsReader extends PayloadReader {
private parseHeader(): void {
const br: BitReader = new BitReader(
- this.dataBuffer.subarray(
- this.dataOffset,
- this.dataOffset + AdtsReader.ADTS_HEADER_LEN));
+ this.dataBuffer.subarray(this.dataOffset, this.dataBuffer.byteLength));
br.skipBits(12);
From 308322e5048f4758aab2fd16a7de8a8302294fd7 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 12 May 2022 03:57:59 +0200
Subject: [PATCH 137/197] adts-reader: flatten header-parser assertions
ctrl-flow
---
src/demuxer/ts/payload/adts-reader.ts | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index 5191b8f..dc79cc2 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -176,12 +176,10 @@ export class AdtsReader extends PayloadReader {
this.profile = audioCodecProfile;
const sampleRateIndex: number = br.readBits(4);
-
- if (sampleRateIndex >= 0 && sampleRateIndex < AdtsReader.ADTS_SAMPLE_RATES.length) {
- this.sampleRate = AdtsReader.ADTS_SAMPLE_RATES[sampleRateIndex];
- } else {
+ if (sampleRateIndex < 0 && sampleRateIndex >= AdtsReader.ADTS_SAMPLE_RATES.length) {
throw new Error(`Invalid AAC sampling-frequency index: ${sampleRateIndex}`);
}
+ this.sampleRate = AdtsReader.ADTS_SAMPLE_RATES[sampleRateIndex];
this.frameDuration = (MICROSECOND_TIMESCALE * 1024) / this.sampleRate;
// private bit (unused by spec)
@@ -191,17 +189,19 @@ export class AdtsReader extends PayloadReader {
if (channelsConf <= 0 || channelsConf >= 8) {
throw new Error(`Channel configuration invalid value: ${channelsConf}`);
}
-
this.channels = channelsConf;
// originality/home/copyright bits (ignoring)
br.skipBits(4);
const adtsFrameLen = br.readBits(13); // always including the header itself (w/ opt CRC 2 bytes)
- if (adtsFrameLen <= 0) throw new Error(`Invalid ADTS-frame byte-length: ${adtsFrameLen}`);
+ if (adtsFrameLen <= 0) {
+ throw new Error(`Invalid ADTS-frame byte-length: ${adtsFrameLen}`);
+ }
this.currentAdtsHeaderLen = hasCrc ?
- AdtsReader.ADTS_HEADER_LEN + AdtsReader.ADTS_CRC_SIZE : AdtsReader.ADTS_HEADER_LEN;
+ AdtsReader.ADTS_HEADER_LEN + AdtsReader.ADTS_CRC_SIZE
+ : AdtsReader.ADTS_HEADER_LEN;
this.currentAccessUnitSize = adtsFrameLen - this.currentAdtsHeaderLen;
// buffer fullness (ignoring so far, spec not clear about what it is really yet)
From b11f4cdf0dad14aea535bfe2c41d67065ac49036 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 12 May 2022 03:59:31 +0200
Subject: [PATCH 138/197] adts-reader: add fixme related to parser semantic
Frames vs AAC frames nb (non critical or blocker however)
---
src/demuxer/ts/payload/adts-reader.ts | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index dc79cc2..9a5cb77 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -207,12 +207,15 @@ export class AdtsReader extends PayloadReader {
// buffer fullness (ignoring so far, spec not clear about what it is really yet)
br.skipBits(11);
- // 1 ADTS frame can contain up to 4 AAC frames !
+ // 1 ADTS frame can contain up to 4 AAC frames
const nbOfAacFrames = br.readBits(2) + 1;
if (nbOfAacFrames <= 0) {
throw new Error(`Invalid AAC frame-number in ADTS header: ${nbOfAacFrames}`);
}
+ // FIXME: semantically our Frame info parsed represents potentially several AAC ones,
+ // with the same container PTS value however (precision could be inferred from
+ // sampling freq however).
/*
if (nbOfAacFrames !== 1) {
throw new Error(`Can not have AAC frame-number in ADTS header: ${nbOfAacFrames} (only 1 is supported in this compatibility mode)`);
From 255e51076ed3defb450614b9bfcb20ba9f8e2f3d Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Sat, 14 May 2022 03:48:39 +0200
Subject: [PATCH 139/197] adts-reader: add timing info to exception on
read/parse call + impr generally
---
src/demuxer/ts/payload/adts-reader.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index 9a5cb77..9bc8859 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -1,4 +1,4 @@
-import { MICROSECOND_TIMESCALE } from '../../../utils/timescale';
+import { MICROSECOND_TIMESCALE, toSecondsFromMicros } from '../../../utils/timescale';
import { BitReader } from '../../../utils/bit-reader';
import { PayloadReader } from './payload-reader';
import { Frame } from '../../frame';
@@ -79,7 +79,7 @@ export class AdtsReader extends PayloadReader {
this.parseHeader();
} catch (err) {
// data pointers will be nulled by reset call, so we need to make string first
- const errMsg = `Error parsing header at ${this.dataOffset} / ${this.dataBuffer.byteLength}: ${(err as Error).message}`;
+ const errMsg = `Error parsing header at ${this.dataOffset}/${this.dataBuffer.byteLength} [B]; t=${toSecondsFromMicros(this.timeUs)} [s]; \nException: ${(err as Error).message}`;
// console.debug(this); // only for debug !!
this.reset();
this.state = AdtsReaderState.FIND_SYNC;
From 8c4aa9cb88e0f2e627905065369502b3665dfe76 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 30 May 2022 17:04:11 +0200
Subject: [PATCH 140/197] ts-track: make getMetadata impl trivial (should be
deprecated in superclass)
---
src/demuxer/track.ts | 4 ++--
src/demuxer/ts/ts-track.ts | 27 ---------------------------
2 files changed, 2 insertions(+), 29 deletions(-)
diff --git a/src/demuxer/track.ts b/src/demuxer/track.ts
index 2780030..514c23f 100644
--- a/src/demuxer/track.ts
+++ b/src/demuxer/track.ts
@@ -68,7 +68,7 @@ export class Track {
return toSecondsFromMicros(this.getDuration());
}
- public getMetadata(): {} { // FIXME: Make this a string-to-any hash
- return {};
+ public getMetadata() {
+ return {}
}
}
diff --git a/src/demuxer/ts/ts-track.ts b/src/demuxer/ts/ts-track.ts
index 8c54a4e..da2d206 100644
--- a/src/demuxer/ts/ts-track.ts
+++ b/src/demuxer/ts/ts-track.ts
@@ -23,31 +23,4 @@ export class TSTrack extends Track {
public popFrames(wholePayloadUnits: boolean = true): Frame[] {
return this.pes?.payloadReader?.popFrames(wholePayloadUnits) || [];
}
-
- public getMetadata(): {} {
- if (this?.pes.payloadReader) {
- if (this.pes.payloadReader instanceof H264Reader && (this.pes.payloadReader as H264Reader).sps) {
- const sps: Sps = (this.pes.payloadReader as H264Reader).sps;
- return {
- profile: sps.profile,
- level: sps.level,
- bitDepth: sps.bitDepth,
- chromaFormat: sps.chromaFormat,
- frameRate: sps.frameRate,
- sar: sps.sar,
- codecSize: sps.codecSize,
- presentSize:
- sps.presentSize,
- };
- } else if (this.pes.payloadReader instanceof AdtsReader) {
- const adtsReader: AdtsReader = this.pes.payloadReader as AdtsReader;
- return {
- channels: adtsReader.channels,
- sampleRate: adtsReader.sampleRate,
- frameDuration: adtsReader.frameDuration,
- };
- }
- }
- return {};
- }
}
From cda4b141d140ffb5a77b9feaa225aa1ca10e7e18 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 30 May 2022 17:08:56 +0200
Subject: [PATCH 141/197] track: @deprecated getMetadata
---
src/demuxer/track.ts | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/demuxer/track.ts b/src/demuxer/track.ts
index 514c23f..2d9cf5e 100644
--- a/src/demuxer/track.ts
+++ b/src/demuxer/track.ts
@@ -68,6 +68,9 @@ export class Track {
return toSecondsFromMicros(this.getDuration());
}
+ /**
+ * @deprecated
+ */
public getMetadata() {
return {}
}
From 23494a844b5b880bf132b5d9acd080a8cff3e699 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 30 May 2022 17:10:37 +0200
Subject: [PATCH 142/197] adts-reader: intro AdtsFrameInfo iface and use for
reader int state + put all consts into own module
---
src/demuxer/ts/payload/adts-consts.ts | 18 +++++
src/demuxer/ts/payload/adts-reader.ts | 99 ++++++++++++++-------------
2 files changed, 68 insertions(+), 49 deletions(-)
create mode 100644 src/demuxer/ts/payload/adts-consts.ts
diff --git a/src/demuxer/ts/payload/adts-consts.ts b/src/demuxer/ts/payload/adts-consts.ts
new file mode 100644
index 0000000..607e6df
--- /dev/null
+++ b/src/demuxer/ts/payload/adts-consts.ts
@@ -0,0 +1,18 @@
+export const ADTS_HEADER_LEN = 7 as const;
+export const ADTS_CRC_SIZE: number = 2 as const;
+
+export const ADTS_SAMPLE_RATES: number[] = [
+ 96000,
+ 88200,
+ 64000,
+ 48000,
+ 44100,
+ 32000,
+ 24000,
+ 22050,
+ 16000,
+ 12000,
+ 11025,
+ 8000,
+ 7350
+];
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index 9bc8859..67ba30d 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -1,44 +1,30 @@
-import { MICROSECOND_TIMESCALE, toSecondsFromMicros } from '../../../utils/timescale';
-import { BitReader } from '../../../utils/bit-reader';
import { PayloadReader } from './payload-reader';
-import { Frame } from '../../frame';
import { Track } from '../../track';
+import { Frame } from '../../frame';
import { FRAME_TYPE } from '../../../codecs/h264/nal-units';
+import { BitReader } from '../../../utils/bit-reader';
+import { MICROSECOND_TIMESCALE, toSecondsFromMicros } from '../../../utils/timescale';
+import { ADTS_CRC_SIZE, ADTS_HEADER_LEN, ADTS_SAMPLE_RATES } from './adts-consts';
+
enum AdtsReaderState {
FIND_SYNC,
READ_HEADER,
READ_FRAME
}
+export interface AdtsFrameInfo {
+ profile: number;
+ channels: number;
+ sampleRate: number;
+ headerLen: number;
+ accessUnitSize: number;
+ numFrames: number
+}
+
export class AdtsReader extends PayloadReader {
- private static ADTS_HEADER_LEN = 7 as const;
- private static ADTS_CRC_SIZE: number = 2 as const;
-
- // TODO: use centralized table
- private static ADTS_SAMPLE_RATES: number[] = [
- 96000,
- 88200,
- 64000,
- 48000,
- 44100,
- 32000,
- 24000,
- 22050,
- 16000,
- 12000,
- 11025,
- 8000,
- 7350
- ];
-
- public profile: number = NaN;
- public channels: number = NaN;
- public sampleRate: number = NaN;
- public frameDuration: number = NaN;
- public currentAdtsHeaderLen: number = NaN;
- public currentAccessUnitSize: number = NaN;
+ private currentFrame: AdtsFrameInfo | null = null;
private state: AdtsReaderState = AdtsReaderState.FIND_SYNC;
@@ -71,7 +57,7 @@ export class AdtsReader extends PayloadReader {
break;
case AdtsReaderState.READ_HEADER:
- if (this.dataBuffer.byteLength - this.dataOffset < AdtsReader.ADTS_HEADER_LEN) {
+ if (this.dataBuffer.byteLength - this.dataOffset < ADTS_HEADER_LEN) {
needMoreData = true;
break;
}
@@ -88,7 +74,10 @@ export class AdtsReader extends PayloadReader {
break;
case AdtsReaderState.READ_FRAME:
- if (this.dataBuffer.byteLength - this.dataOffset < this.currentAdtsHeaderLen + this.currentAccessUnitSize) {
+ const { headerLen, accessUnitSize, sampleRate } = this.currentFrame;
+ const frameDurationUs = (MICROSECOND_TIMESCALE * 1024) / sampleRate;
+ if (this.dataBuffer.byteLength - this.dataOffset
+ < headerLen + accessUnitSize) {
needMoreData = true;
break;
}
@@ -96,16 +85,17 @@ export class AdtsReader extends PayloadReader {
this.frames.push(new Frame(
FRAME_TYPE.NONE,
this.timeUs,
- this.currentAccessUnitSize,
- this.frameDuration,
- this.dataOffset));
+ accessUnitSize,
+ frameDurationUs,
+ this.dataOffset
+ ));
- const frameDataStart = this.dataOffset + this.currentAdtsHeaderLen;
- const frameDataEnd = frameDataStart + this.currentAccessUnitSize;
+ const frameDataStart = this.dataOffset + headerLen;
+ const frameDataEnd = frameDataStart + accessUnitSize;
const frameData = this.dataBuffer.subarray(frameDataStart, frameDataEnd);
this.dataOffset = frameDataEnd;
- this.timeUs = this.timeUs + this.frameDuration;
+ this.timeUs = this.timeUs + frameDurationUs;
this.state = AdtsReaderState.FIND_SYNC;
@@ -151,6 +141,11 @@ export class AdtsReader extends PayloadReader {
}
private parseHeader(): void {
+
+ // first, clear current frame state. in case of exception during header parse,
+ // it will keep null state, as we only set the frame after success.
+ this.currentFrame = null;
+
const br: BitReader = new BitReader(
this.dataBuffer.subarray(this.dataOffset, this.dataBuffer.byteLength));
@@ -173,14 +168,13 @@ export class AdtsReader extends PayloadReader {
if (audioCodecProfile <= 0 || audioCodecProfile >= 5) {
throw new Error(`Unsupported or likely invalid AAC profile (MPEG-4 Audio Object Type): ${audioCodecProfile}`);
}
- this.profile = audioCodecProfile;
+ const profile = audioCodecProfile;
const sampleRateIndex: number = br.readBits(4);
- if (sampleRateIndex < 0 && sampleRateIndex >= AdtsReader.ADTS_SAMPLE_RATES.length) {
+ if (sampleRateIndex < 0 && sampleRateIndex >= ADTS_SAMPLE_RATES.length) {
throw new Error(`Invalid AAC sampling-frequency index: ${sampleRateIndex}`);
}
- this.sampleRate = AdtsReader.ADTS_SAMPLE_RATES[sampleRateIndex];
- this.frameDuration = (MICROSECOND_TIMESCALE * 1024) / this.sampleRate;
+ const sampleRate = ADTS_SAMPLE_RATES[sampleRateIndex];
// private bit (unused by spec)
br.skipBits(1);
@@ -189,7 +183,7 @@ export class AdtsReader extends PayloadReader {
if (channelsConf <= 0 || channelsConf >= 8) {
throw new Error(`Channel configuration invalid value: ${channelsConf}`);
}
- this.channels = channelsConf;
+ const channels = channelsConf;
// originality/home/copyright bits (ignoring)
br.skipBits(4);
@@ -199,18 +193,16 @@ export class AdtsReader extends PayloadReader {
throw new Error(`Invalid ADTS-frame byte-length: ${adtsFrameLen}`);
}
- this.currentAdtsHeaderLen = hasCrc ?
- AdtsReader.ADTS_HEADER_LEN + AdtsReader.ADTS_CRC_SIZE
- : AdtsReader.ADTS_HEADER_LEN;
- this.currentAccessUnitSize = adtsFrameLen - this.currentAdtsHeaderLen;
+ const headerLen = hasCrc ? ADTS_HEADER_LEN + ADTS_CRC_SIZE : ADTS_HEADER_LEN;
+ const accessUnitSize = adtsFrameLen - headerLen;
// buffer fullness (ignoring so far, spec not clear about what it is really yet)
br.skipBits(11);
// 1 ADTS frame can contain up to 4 AAC frames
- const nbOfAacFrames = br.readBits(2) + 1;
- if (nbOfAacFrames <= 0) {
- throw new Error(`Invalid AAC frame-number in ADTS header: ${nbOfAacFrames}`);
+ const numFrames = br.readBits(2) + 1;
+ if (numFrames <= 0) {
+ throw new Error(`Invalid AAC frame-number in ADTS header: ${numFrames}`);
}
// FIXME: semantically our Frame info parsed represents potentially several AAC ones,
@@ -222,6 +214,15 @@ export class AdtsReader extends PayloadReader {
}
//*/
+ this.currentFrame = {
+ headerLen,
+ accessUnitSize,
+ channels,
+ profile,
+ sampleRate,
+ numFrames
+ };
+
this.state = AdtsReaderState.READ_FRAME;
}
}
From 67986b706073dcd5f3a0b0d2d21c13fabc058bd4 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 31 May 2022 13:04:27 +0200
Subject: [PATCH 143/197] adts-reader: fix in error message for parsing
exception
---
src/demuxer/ts/payload/adts-reader.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index 67ba30d..c1ba78d 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -202,15 +202,15 @@ export class AdtsReader extends PayloadReader {
// 1 ADTS frame can contain up to 4 AAC frames
const numFrames = br.readBits(2) + 1;
if (numFrames <= 0) {
- throw new Error(`Invalid AAC frame-number in ADTS header: ${numFrames}`);
+ throw new Error(`Invalid number of AAC frames for ADTS header: ${numFrames}`);
}
// FIXME: semantically our Frame info parsed represents potentially several AAC ones,
// with the same container PTS value however (precision could be inferred from
// sampling freq however).
/*
- if (nbOfAacFrames !== 1) {
- throw new Error(`Can not have AAC frame-number in ADTS header: ${nbOfAacFrames} (only 1 is supported in this compatibility mode)`);
+ if (numFrames !== 1) {
+ throw new Error(`Can not have AAC frame-number in ADTS header: ${numFrames} (only 1 is supported in this compatibility mode)`);
}
//*/
From cc0d22ce91d6c0d80ab33b33de4afd210f5c7303 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 1 Jun 2022 01:48:05 +0200
Subject: [PATCH 144/197] adts-reader: add currentFrameInfo public getter + rm
trivial constructor
---
src/demuxer/ts/payload/adts-reader.ts | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index c1ba78d..33335c0 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -24,12 +24,12 @@ export interface AdtsFrameInfo {
export class AdtsReader extends PayloadReader {
- private currentFrame: AdtsFrameInfo | null = null;
-
private state: AdtsReaderState = AdtsReaderState.FIND_SYNC;
- constructor () {
- super();
+ private currentFrame: AdtsFrameInfo | null = null;
+
+ get currentFrameInfo(): AdtsFrameInfo | null {
+ return this.currentFrame;
}
public getMimeType(): string {
From 852b36ecef90168be0542629a3688e3665124ff8 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 21 Jun 2022 05:24:05 +0200
Subject: [PATCH 145/197] pes-reader: fix using ms time in onPayloadData call
args
---
src/demuxer/ts/pes-reader.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/demuxer/ts/pes-reader.ts b/src/demuxer/ts/pes-reader.ts
index ef13eaa..e76c4ea 100644
--- a/src/demuxer/ts/pes-reader.ts
+++ b/src/demuxer/ts/pes-reader.ts
@@ -120,6 +120,6 @@ export class PESReader {
private handlePayloadReadData(data: Uint8Array, timeUs: number, naluType: number = NaN) {
if (!this.payloadReader.frames.length) return;
const timeSecs = toSecondsFromMicros(timeUs);
- this.onPayloadData(data, timeSecs, naluType);
+ this.onPayloadData(data, timeUs, naluType);
}
}
From 961c73699821ae87a86dab6f0320f631046cfd96 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 21 Jun 2022 05:25:16 +0200
Subject: [PATCH 146/197] adts-reader: silently accept handling channelConf=0
case for now
---
src/demuxer/ts/payload/adts-reader.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index 33335c0..f622821 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -181,7 +181,9 @@ export class AdtsReader extends PayloadReader {
const channelsConf = br.readBits(3);
if (channelsConf <= 0 || channelsConf >= 8) {
- throw new Error(`Channel configuration invalid value: ${channelsConf}`);
+ // ignorining this for now, todo see why comes up in ffmpeg payload
+ if (channelsConf != 0)
+ throw new Error(`Channel configuration invalid value: ${channelsConf}`);
}
const channels = channelsConf;
From f98800f5a06df1279d2aad858ef8d9afcdcfe23f Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 29 Jun 2022 18:36:48 +0200
Subject: [PATCH 147/197] demuxer: track/frame: refactor some basic concepts to
allow scaled time by default, instead of using microseconds everywhere.
remove custom stuff added on top before for timescale support.
---
src/demuxer/frame.ts | 72 +++++++++++++++++++-------------------------
src/demuxer/track.ts | 44 ++++++++++++++++-----------
2 files changed, 57 insertions(+), 59 deletions(-)
diff --git a/src/demuxer/frame.ts b/src/demuxer/frame.ts
index 1f99b7b..1346445 100644
--- a/src/demuxer/frame.ts
+++ b/src/demuxer/frame.ts
@@ -3,59 +3,49 @@ import { toSecondsFromMicros } from "../utils/timescale";
export class Frame {
- // normalized micros value
- private presentationTimeUs: number = 0;
-
- // ideally have scaled integer values
- public timescale: number = NaN;
- public scaledDecodingTime: number = NaN;
- public scaledPresentationTimeOffset: number = NaN;
- public scaledDuration: number = NaN;
-
constructor (
public readonly frameType: FRAME_TYPE,
- public readonly timeUs: number,
+ public readonly dts: number,
+ private _cto: number,
+ public readonly duration: number,
public readonly size: number,
- public readonly duration: number = NaN,
- public bytesOffset: number = NaN,
- public presentationTimeOffsetUs: number = 0
+ private _bytesOffset: number = NaN
) {
-
- if (!Number.isFinite(size)) {
- throw new Error('Frame has to have sample size');
+ if (dts < 0 || !Number.isSafeInteger(dts)) {
+ throw new Error(`Frame: DTS has to be positive safe-integer value`);
}
-
- this.setPresentationTimeOffsetUs(presentationTimeOffsetUs);
- }
-
- hasUnnormalizedIntegerTiming() {
- return Number.isFinite(this.timescale)
- && Number.isFinite(this.scaledDecodingTime)
- && Number.isFinite(this.scaledPresentationTimeOffset)
- && Number.isFinite(this.scaledDuration);
- }
-
- getDecodingTimeUs() {
- return this.timeUs;
- }
-
- getPresentationTimeUs(): number {
- return this.presentationTimeUs;
+ if (size < 0 || !Number.isSafeInteger(size)) {
+ throw new Error(`Frame: Size has to be positive safe-integer value`);
+ }
+ if (duration < 0 || !Number.isSafeInteger(duration)) {
+ throw new Error(`Frame: Duration has to be positive safe-integer value`);
+ }
+ this.setPresentationTimeOffset(_cto);
}
- setPresentationTimeOffsetUs(presentationTimeOffsetUs: number) {
- this.presentationTimeUs = this.timeUs + presentationTimeOffsetUs;
+ get bytesOffset() {
+ return this._bytesOffset;
}
- getPresentationTimestampInSeconds(): number {
- return toSecondsFromMicros(this.getPresentationTimeUs())
+ get cto() {
+ return this._cto;
}
- getDecodingTimestampInSeconds() {
- return toSecondsFromMicros(this.getDecodingTimeUs());
+ /**
+ * aka "CTO"
+ * @param cto
+ */
+ setPresentationTimeOffset(cto: number) {
+ if (cto < 0 || !Number.isSafeInteger(cto)) {
+ throw new Error(`Frame: CTO has to be positive safe-integer value`);
+ }
+ this._cto = cto;
}
- getDurationInSeconds() {
- return toSecondsFromMicros(this.duration);
+ setBytesOffset(bytesOffset: number) {
+ if (bytesOffset < 0 || !Number.isSafeInteger(bytesOffset)) {
+ throw new Error(`Frame: Bytes-offset has to be positive safe-integer value`);
+ }
+ this._bytesOffset = bytesOffset;
}
}
diff --git a/src/demuxer/track.ts b/src/demuxer/track.ts
index 2d9cf5e..0ea5047 100644
--- a/src/demuxer/track.ts
+++ b/src/demuxer/track.ts
@@ -1,7 +1,8 @@
import { Frame } from './frame';
import { toSecondsFromMicros } from '../utils/timescale';
-export class Track {
+export abstract class Track {
+
// FIXME: should be an enum type
public static TYPE_VIDEO: string = 'video';
public static TYPE_AUDIO: string = 'audio';
@@ -23,20 +24,12 @@ export class Track {
public static MIME_TYPE_UNKNOWN: string = 'unknown';
protected frames: Frame[] = [];
- protected durationUs: number = NaN;
-
- constructor(public id: number, public type: string /* fixme: make enum type */, public mimeType: string) {}
- public update(): void {
- this.frames = this.getFrames().sort((a: Frame, b: Frame): number => {
- return a.timeUs - b.timeUs;
- });
- this.durationUs = this.getDuration();
- }
+ private _timeScale: number = NaN;
- public flush() {
- this.frames.length = 0;
- }
+ constructor(public id: number,
+ public type: string /* fixme: make enum type */,
+ public mimeType: string) {}
public isVideo() {
return this.type === Track.TYPE_VIDEO;
@@ -56,22 +49,37 @@ export class Track {
&& this.type !== Track.TYPE_VIDEO;
}
+ public update(): void {
+ this.frames = this.getFrames().sort((a: Frame, b: Frame): number => {
+ return a.dts - b.dts;
+ });
+ }
+
+ public flush() {
+ this.frames.length = 0;
+ }
+
+ abstract getResolution(): [number, number];
+
public getFrames(): Frame[] {
return this.frames;
}
- public getDuration(): number {
- return this.durationUs;
+ public getTimescale() {
+ return this._timeScale;
}
- public getDurationInSeconds(): number {
- return toSecondsFromMicros(this.getDuration());
+ public setTimescale(timeScale: number) {
+ if (!Number.isSafeInteger(timeScale)) {
+ throw new Error(`Track timescale has to be safe-integer value`);
+ }
+ this._timeScale = timeScale;
}
/**
* @deprecated
*/
- public getMetadata() {
+ public getMetadata() {
return {}
}
}
From 76d366b2b8425482c4beb577c62ceb31c009728e Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 29 Jun 2022 18:44:23 +0200
Subject: [PATCH 148/197] demuxer mpts: pes-reader cleanup payload-unit timing
handling + add PTO support + move pes header parsing to own file
---
src/demuxer/ts/payload/pes-header.ts | 45 ++++++++++++++++++
src/demuxer/ts/pes-reader.ts | 71 +++++-----------------------
src/demuxer/ts/ts-track.ts | 14 +++---
3 files changed, 65 insertions(+), 65 deletions(-)
create mode 100644 src/demuxer/ts/payload/pes-header.ts
diff --git a/src/demuxer/ts/payload/pes-header.ts b/src/demuxer/ts/payload/pes-header.ts
new file mode 100644
index 0000000..e3df234
--- /dev/null
+++ b/src/demuxer/ts/payload/pes-header.ts
@@ -0,0 +1,45 @@
+import { BitReader } from "../../../utils/bit-reader";
+
+export function parsePesHeaderTimestamps(packet: BitReader): [number, number] {
+ /**
+ * Thanks to Videojs/Muxjs for this bit, which does well the
+ * trick around 32-bit unary bit-ops and 33 bit numbers :)
+ * -> See https://github.com/videojs/mux.js/blob/87f777f718b264df69a063847fe0fb9b5e0aaa6c/lib/m2ts/m2ts.js#L333
+ */
+ // PTS and DTS are normally stored as a 33-bit number. Javascript
+ // performs all bitwise operations on 32-bit integers but javascript
+ // supports a much greater range (52-bits) of integer using standard
+ // mathematical operations.
+ // We construct a 31-bit value using bitwise operators over the 31
+ // most significant bits and then multiply by 4 (equal to a left-shift
+ // of 2) before we add the final 2 least significant bits of the
+ // timestamp (equal to an OR.)
+ const ptsDtsFlags = packet.readByte();
+ packet.skipBytes(1);
+ let pts = NaN;
+ let dts = NaN;
+ if (ptsDtsFlags & 0xC0) {
+ // the PTS and DTS are not written out directly. For information
+ // on how they are encoded, see
+ // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
+ pts = (packet.readByte() & 0x0E) << 27 |
+ (packet.readByte() & 0xFF) << 20 |
+ (packet.readByte() & 0xFE) << 12 |
+ (packet.readByte() & 0xFF) << 5 |
+ (packet.readByte() & 0xFE) >>> 3;
+ pts *= 4; // Left shift by 2
+ pts += (packet.readByte() & 0x06) >>> 1; // OR by the two LSBs
+ dts = pts;
+ if (ptsDtsFlags & 0x40) {
+ let lastByte;
+ dts = (packet.readByte() & 0x0E) << 27 |
+ (packet.readByte() & 0xFF) << 20 |
+ (packet.readByte() & 0xFE) << 12 |
+ (packet.readByte() & 0xFF) << 5 |
+ (lastByte = packet.readByte() & 0xFE) >>> 3;
+ dts *= 4; // Left shift by 2
+ dts += (lastByte & 0x06) >>> 1; // OR by the two LSBs
+ }
+ }
+ return [dts, pts];
+}
diff --git a/src/demuxer/ts/pes-reader.ts b/src/demuxer/ts/pes-reader.ts
index e76c4ea..4736879 100644
--- a/src/demuxer/ts/pes-reader.ts
+++ b/src/demuxer/ts/pes-reader.ts
@@ -1,56 +1,12 @@
import { BitReader } from '../../utils/bit-reader';
-import { mpegClockTimeToMicroSecs, MPEG_CLOCK_HZ, toSecondsFromMicros } from '../../utils/timescale';
+
import { PayloadReader } from './payload/payload-reader';
import { UnknownReader } from './payload/unknown-reader';
import { AdtsReader } from './payload/adts-reader';
import { H264Reader } from './payload/h264-reader';
import { ID3Reader } from './payload/id3-reader';
import { MpegReader } from './payload/mpeg-reader';
-import { NAL_UNIT_TYPE } from '../../codecs/h264/nal-units';
-
-function parsePesHeaderTimestamps(packet: BitReader): [number, number] {
- /**
- * Thanks to Videojs/Muxjs for this bit, which does well the
- * trick around 32-bit unary bit-ops and 33 bit numbers :)
- * -> See https://github.com/videojs/mux.js/blob/87f777f718b264df69a063847fe0fb9b5e0aaa6c/lib/m2ts/m2ts.js#L333
- */
- // PTS and DTS are normally stored as a 33-bit number. Javascript
- // performs all bitwise operations on 32-bit integers but javascript
- // supports a much greater range (52-bits) of integer using standard
- // mathematical operations.
- // We construct a 31-bit value using bitwise operators over the 31
- // most significant bits and then multiply by 4 (equal to a left-shift
- // of 2) before we add the final 2 least significant bits of the
- // timestamp (equal to an OR.)
- const ptsDtsFlags = packet.readByte();
- packet.skipBytes(1);
- let pts = NaN;
- let dts = NaN;
- if (ptsDtsFlags & 0xC0) {
- // the PTS and DTS are not written out directly. For information
- // on how they are encoded, see
- // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
- pts = (packet.readByte() & 0x0E) << 27 |
- (packet.readByte() & 0xFF) << 20 |
- (packet.readByte() & 0xFE) << 12 |
- (packet.readByte() & 0xFF) << 5 |
- (packet.readByte() & 0xFE) >>> 3;
- pts *= 4; // Left shift by 2
- pts += (packet.readByte() & 0x06) >>> 1; // OR by the two LSBs
- dts = pts;
- if (ptsDtsFlags & 0x40) {
- let lastByte;
- dts = (packet.readByte() & 0x0E) << 27 |
- (packet.readByte() & 0xFF) << 20 |
- (packet.readByte() & 0xFE) << 12 |
- (packet.readByte() & 0xFF) << 5 |
- (lastByte = packet.readByte() & 0xFE) >>> 3;
- dts *= 4; // Left shift by 2
- dts += (lastByte & 0x06) >>> 1; // OR by the two LSBs
- }
- }
- return [dts, pts];
-}
+import { parsePesHeaderTimestamps } from './payload/pes-header';
export enum MptsElementaryStreamType {
TS_STREAM_TYPE_AAC = 0x0F,
@@ -65,9 +21,8 @@ export class PESReader {
public payloadReader: PayloadReader;
- // FIXME: use NaN instead of -1 !
- private lastDtsUs: number = -1; // TODO: migrate to integer/timescale values, causing FLOP-precision errors !!!
- private lastCtoUs: number = NaN;
+ private currentDts: number = NaN;
+ private currentCto: number = NaN;
constructor(public pid: number, public type: MptsElementaryStreamType) {
@@ -88,11 +43,11 @@ export class PESReader {
this.payloadReader.onData = this.handlePayloadReadData.bind(this);
}
- public onPayloadData(data: Uint8Array, timeUs: number, naluType: number) {}
+ public onPayloadData(data: Uint8Array, dts: number, cto: number, naluType: number) {}
public appendData(payloadUnitStartIndicator: boolean, packet: BitReader): void {
if (payloadUnitStartIndicator) {
- this.payloadReader.read(this.lastDtsUs);
+ this.payloadReader.read(this.currentDts, this.currentCto);
this.readHeader(packet);
}
this.payloadReader.append(packet, payloadUnitStartIndicator);
@@ -103,7 +58,7 @@ export class PESReader {
}
public flush(): void {
- this.payloadReader.flush(this.lastDtsUs);
+ this.payloadReader.flush(this.currentDts, this.currentCto);
}
private readHeader(packet: BitReader): void {
@@ -111,15 +66,15 @@ export class PESReader {
const [dts, pts] = parsePesHeaderTimestamps(packet);
+ // TODO: assert CTO >= 0 ?
- // Note: Using DTS here, not PTS, to avoid ordering issues.
- this.lastDtsUs = mpegClockTimeToMicroSecs(dts);
- this.lastCtoUs = mpegClockTimeToMicroSecs(pts - dts);
+ this.currentDts = dts;
+ this.currentCto = pts - dts;
}
- private handlePayloadReadData(data: Uint8Array, timeUs: number, naluType: number = NaN) {
+ private handlePayloadReadData(data: Uint8Array, dts: number, cto: number, naluType: number = NaN) {
if (!this.payloadReader.frames.length) return;
- const timeSecs = toSecondsFromMicros(timeUs);
- this.onPayloadData(data, timeUs, naluType);
+
+ this.onPayloadData(data, dts, cto, naluType);
}
}
diff --git a/src/demuxer/ts/ts-track.ts b/src/demuxer/ts/ts-track.ts
index da2d206..8bf808b 100644
--- a/src/demuxer/ts/ts-track.ts
+++ b/src/demuxer/ts/ts-track.ts
@@ -1,26 +1,26 @@
import { Track } from '../track';
import { Frame } from '../frame';
import { PESReader } from './pes-reader';
-import { H264Reader } from './payload/h264-reader';
-import { AdtsReader } from './payload/adts-reader';
-import { Sps } from '../../codecs/h264/nal-units';
export class TSTrack extends Track {
+
constructor(id: number, type: string, mimeType: string,
public pes: PESReader) {
super(id, type, mimeType);
}
- public getDuration(): number {
- return this?.pes?.payloadReader.getDuration() || 0;
+ getResolution(): [number, number] {
+ return [0, 0];
}
- public getFrames(): Frame[] {
+ getFrames(): Frame[] {
return this?.pes?.payloadReader.frames || [];
}
- public popFrames(wholePayloadUnits: boolean = true): Frame[] {
+ popFrames(wholePayloadUnits: boolean = true): Frame[] {
return this.pes?.payloadReader?.popFrames(wholePayloadUnits) || [];
}
+
+
}
From a6540978023487497cd11ac77e37f0fb5ad1bb93 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 29 Jun 2022 18:45:28 +0200
Subject: [PATCH 149/197] mpts payload unit parsers: generalize current-time
concept and ease read() impl
---
src/demuxer/ts/payload/adts-consts.ts | 2 +
src/demuxer/ts/payload/adts-reader.ts | 30 ++++++-------
src/demuxer/ts/payload/h264-reader.ts | 41 ++++++++++--------
src/demuxer/ts/payload/mpeg-reader.ts | 27 +++++++-----
src/demuxer/ts/payload/payload-reader.ts | 55 ++++++++++++------------
5 files changed, 80 insertions(+), 75 deletions(-)
diff --git a/src/demuxer/ts/payload/adts-consts.ts b/src/demuxer/ts/payload/adts-consts.ts
index 607e6df..1caa2d6 100644
--- a/src/demuxer/ts/payload/adts-consts.ts
+++ b/src/demuxer/ts/payload/adts-consts.ts
@@ -16,3 +16,5 @@ export const ADTS_SAMPLE_RATES: number[] = [
8000,
7350
];
+
+export const AAC_FRAME_SAMPLES_NUM = 1024;
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index f622821..dd9285c 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -4,8 +4,8 @@ import { Frame } from '../../frame';
import { FRAME_TYPE } from '../../../codecs/h264/nal-units';
import { BitReader } from '../../../utils/bit-reader';
-import { MICROSECOND_TIMESCALE, toSecondsFromMicros } from '../../../utils/timescale';
-import { ADTS_CRC_SIZE, ADTS_HEADER_LEN, ADTS_SAMPLE_RATES } from './adts-consts';
+import { MPEG_CLOCK_HZ } from '../../../utils/timescale';
+import { AAC_FRAME_SAMPLES_NUM, ADTS_CRC_SIZE, ADTS_HEADER_LEN, ADTS_SAMPLE_RATES } from './adts-consts';
enum AdtsReaderState {
FIND_SYNC,
@@ -36,17 +36,11 @@ export class AdtsReader extends PayloadReader {
return Track.MIME_TYPE_AAC;
}
- public read(pts: number): void {
+ public read(dts: number, cto: number): void {
if (!this.dataBuffer) {
- return;
- }
- if (pts >= 0) {
- this.timeUs = pts;
- }
-
- if (this.firstTimestamp === -1) {
- this.firstTimestamp = this.timeUs;
+ throw new Error('read() should not be called without priorly data appended');
}
+ this.setCurrentTime(dts, cto);
let needMoreData = false;
while (!needMoreData && this.dataOffset < this.dataBuffer.byteLength) {
@@ -65,7 +59,7 @@ export class AdtsReader extends PayloadReader {
this.parseHeader();
} catch (err) {
// data pointers will be nulled by reset call, so we need to make string first
- const errMsg = `Error parsing header at ${this.dataOffset}/${this.dataBuffer.byteLength} [B]; t=${toSecondsFromMicros(this.timeUs)} [s]; \nException: ${(err as Error).message}`;
+ const errMsg = `Error parsing header at ${this.dataOffset}/${this.dataBuffer.byteLength} [B]; t=${JSON.stringify(this.getCurrentTime())} [s]; \nException: ${(err as Error).message}`;
// console.debug(this); // only for debug !!
this.reset();
this.state = AdtsReaderState.FIND_SYNC;
@@ -75,7 +69,8 @@ export class AdtsReader extends PayloadReader {
case AdtsReaderState.READ_FRAME:
const { headerLen, accessUnitSize, sampleRate } = this.currentFrame;
- const frameDurationUs = (MICROSECOND_TIMESCALE * 1024) / sampleRate;
+ // use MPTS timescale here too
+ const frameDuration = Math.round(AAC_FRAME_SAMPLES_NUM * MPEG_CLOCK_HZ / sampleRate)
if (this.dataBuffer.byteLength - this.dataOffset
< headerLen + accessUnitSize) {
needMoreData = true;
@@ -84,9 +79,10 @@ export class AdtsReader extends PayloadReader {
this.frames.push(new Frame(
FRAME_TYPE.NONE,
- this.timeUs,
+ this.dts,
+ this.cto,
+ frameDuration,
accessUnitSize,
- frameDurationUs,
this.dataOffset
));
@@ -95,13 +91,11 @@ export class AdtsReader extends PayloadReader {
const frameData = this.dataBuffer.subarray(frameDataStart, frameDataEnd);
this.dataOffset = frameDataEnd;
- this.timeUs = this.timeUs + frameDurationUs;
this.state = AdtsReaderState.FIND_SYNC;
- this.onData(frameData, this.timeUs);
+ this.onData(frameData, this.dts, this.cto);
break;
-
}
}
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index b2bd232..d8295fc 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -9,18 +9,18 @@ const NALU_DELIM_LEN = 3;
export class H264Reader extends PayloadReader {
+ private _pendingBytes: number = 0;
+
public sps: Sps = null;
public pps: boolean = false;
- public pendingBytes: number = 0;
-
public getMimeType(): string {
return Track.MIME_TYPE_AVC;
}
- public flush(timeUs: number): void {
+ public flush(dts: number, cto: number): void {
- this.read(timeUs);
+ this.read(dts, cto);
// enforced process any last data after
// a nalu-delim to be processed
@@ -40,14 +40,18 @@ export class H264Reader extends PayloadReader {
this.pps = false;
}
- public read(timeUs: number): void {
+ public read(dts: number, cto: number): void {
+ if (!this.dataBuffer) {
+ throw new Error('read() should not be called without priorly data appended');
+ }
+ this.setCurrentTime(dts, cto);
// process pending remainder data
let firstNalUnit: number = 0;
let nextNalUnit: number = 0;
- if (this.pendingBytes > 0) {
- nextNalUnit = this.findNextNalu(this.pendingBytes);
+ if (this._pendingBytes > 0) {
+ nextNalUnit = this.findNextNalu(this._pendingBytes);
// if we cant find a next NALU-delim from the remainder data,
// we can already give-up here.
if (!Number.isFinite(nextNalUnit)) {
@@ -61,15 +65,6 @@ export class H264Reader extends PayloadReader {
}
}
- // post: firstNalUnit is finite number
-
- if (this.firstTimestamp === -1) {
- this.timeUs = this.firstTimestamp = timeUs;
- }
- if (timeUs !== -1) {
- this.timeUs = timeUs;
- }
-
// process next nal units in the buffer
while (true) {
// w/o the +3 we would end up again with the input offset!
@@ -86,7 +81,7 @@ export class H264Reader extends PayloadReader {
// we need to make sure the next read starts off
// ahead the last parsed NALU-delimiter.
- this.pendingBytes = this.dataBuffer.byteLength;
+ this._pendingBytes = this.dataBuffer.byteLength;
}
private findNextNalu(offset: number = 0): number {
@@ -114,6 +109,8 @@ export class H264Reader extends PayloadReader {
const naluData = this.dataBuffer.subarray(begin + NALU_DELIM_LEN, end);
+ // TODO: check for invalid values
+ // (can happen if buffer begin/remainder is garbage)
const naluType = naluData[0] & 0x1F;
switch(naluType) {
case NAL_UNIT_TYPE.SLICE:
@@ -132,7 +129,7 @@ export class H264Reader extends PayloadReader {
break;
}
- this.onData(naluData, this.timeUs, naluType);
+ this.onData(naluData, this.dts, naluType);
return end;
}
@@ -152,7 +149,13 @@ export class H264Reader extends PayloadReader {
}
private addFrame(frameType: FRAME_TYPE, naluData: Uint8Array): void {
- const frame = new Frame(frameType, this.timeUs, naluData.byteLength, NaN);
+ const frame = new Frame(
+ frameType,
+ this.dts,
+ this.cto,
+ 0,
+ naluData.byteLength,
+ );
this.frames.push(frame);
}
diff --git a/src/demuxer/ts/payload/mpeg-reader.ts b/src/demuxer/ts/payload/mpeg-reader.ts
index 75dd241..b013bbc 100644
--- a/src/demuxer/ts/payload/mpeg-reader.ts
+++ b/src/demuxer/ts/payload/mpeg-reader.ts
@@ -2,6 +2,7 @@ import ByteParserUtils from '../../../utils/byte-parser-utils';
import { PayloadReader } from './payload-reader';
import { Frame } from '../../frame';
import { FRAME_TYPE } from '../../../codecs/h264/nal-units';
+import { MPEG_CLOCK_HZ } from '../../../utils/timescale';
enum State {
FIND_SYNC = 1,
@@ -52,16 +53,12 @@ export class MpegReader extends PayloadReader {
return 'audio/' + this.mimeType;
}
- public read(pts: number): void {
+ public read(dts: number, cto: number): void {
if (!this.dataBuffer) {
- return;
- }
- if (pts >= 0) {
- this.timeUs = pts;
- }
- if (this.firstTimestamp === -1) {
- this.firstTimestamp = this.timeUs;
+ throw new Error('read() should not be called without priorly data appended');
}
+ this.setCurrentTime(dts, cto);
+
while (this.dataOffset < this.dataBuffer.byteLength) {
if (this.state === State.FIND_SYNC) {
this.findHeader();
@@ -166,7 +163,8 @@ export class MpegReader extends PayloadReader {
this.currentFrameSize = Math.floor(this.samplesPerFrame * (this.bitrate * 1000 / 8) / this.sampleRate) + padding;
}
}
- this.frameDuration = (1000000 * this.samplesPerFrame) / this.sampleRate;
+
+ this.frameDuration = (MPEG_CLOCK_HZ * this.samplesPerFrame) / this.sampleRate;
return true;
}
@@ -175,9 +173,16 @@ export class MpegReader extends PayloadReader {
if ((this.dataBuffer.byteLength - this.dataOffset) < (MpegReader.HEADER_SIZE + this.currentFrameSize)) {
return 0;
}
+
+ this.frames.push(new Frame(
+ FRAME_TYPE.NONE,
+ this.dts,
+ this.cto,
+ this.frameDuration,
+ this.currentFrameSize
+ ));
+
this.state = State.FIND_SYNC;
- this.frames.push(new Frame(FRAME_TYPE.NONE, this.timeUs, this.currentFrameSize));
- this.timeUs = this.timeUs + this.frameDuration;
return MpegReader.HEADER_SIZE + this.currentFrameSize;
}
}
diff --git a/src/demuxer/ts/payload/payload-reader.ts b/src/demuxer/ts/payload/payload-reader.ts
index 86d2578..8823b82 100644
--- a/src/demuxer/ts/payload/payload-reader.ts
+++ b/src/demuxer/ts/payload/payload-reader.ts
@@ -3,49 +3,52 @@ import { Frame } from '../../frame';
export abstract class PayloadReader {
- public firstTimestamp: number = -1;
- public timeUs: number = -1; // FIXME: use NaN instead of -1 !
public frames: Frame[] = [];
public dataBuffer: Uint8Array;
protected dataOffset: number = 0;
- private pusiCount: number = 0;
- private lastPusiFramesLen: number = 0;
+ private _currentTime: [number, number] = [NaN, NaN];
+ private _firstDts: number = NaN;
+
+ private _pusiCount: number = 0;
+ private _lastPusiFramesLen: number = 0;
constructor() {
this.reset();
}
- public abstract read(time: number): void;
+ get dts() { return this._currentTime[0] }
+ get cto() { return this._currentTime[1] }
+
+ public abstract read(dts: number, cto: number): void;
- public onData(data: Uint8Array, time: number, naluType?: number) {}
+ public onData(data: Uint8Array, dts: number, cto: number, naluType?: number) {}
public getMimeType(): string {
return 'Unknown';
}
- public getDuration(): number {
- return this.getLastPTS() - this.getFirstPTS();
- }
-
- public getFirstPTS(): number {
- return this.firstTimestamp;
+ public getPusiCount() {
+ return this._pusiCount;
}
- public getLastPTS(): number {
- return this.timeUs;
+ public setCurrentTime(dts: number, cto: number) {
+ if (Number.isNaN(this._firstDts)) {
+ this._firstDts = dts;
+ }
+ this._currentTime = [dts, cto];
}
- public getPusiCount() {
- return this.pusiCount;
+ public getCurrentTime() {
+ return this._currentTime;
}
public append(packet: BitReader, payloadUnitStartIndicator: boolean): void {
if (payloadUnitStartIndicator) {
- this.pusiCount++;
- this.lastPusiFramesLen = this.frames.length;
+ this._pusiCount++;
+ this._lastPusiFramesLen = this.frames.length;
}
const packetReaderOffset = packet.bytesOffset();
@@ -64,31 +67,29 @@ export abstract class PayloadReader {
public reset(): void {
this.frames.length = 0;
- this.pusiCount = 0;
- this.lastPusiFramesLen = 0;
+ this._pusiCount = 0;
+ this._lastPusiFramesLen = 0;
this.dataOffset = 0;
this.dataBuffer = null;
- this.firstTimestamp = -1;
- this.timeUs = -1;
}
- public flush(time: number): void {
+ public flush(dts: number, cto: number): void {
if (this.dataBuffer && this.dataBuffer.byteLength > 0) {
- this.read(time);
+ this.read(dts, cto);
this.dataBuffer = null;
}
this.dataOffset = 0;
}
public popFrames(wholePayloadUnits: boolean = true): Frame[] {
- let numFrames = wholePayloadUnits ? this.lastPusiFramesLen : this.frames.length;
+ let numFrames = wholePayloadUnits ? this._lastPusiFramesLen : this.frames.length;
if (numFrames === 0) return [];
// split-slice frame-list:
// returns slice to pop, mutates list to remainder (deletes sliced items)
const frames = this.frames.splice(0, numFrames);
// set current payload-unit frame-count to remainder length
- this.lastPusiFramesLen = this.frames.length;
- this.pusiCount = 0;
+ this._lastPusiFramesLen = this.frames.length;
+ this._pusiCount = 0;
return frames;
}
}
From a0043a44b7cdbf526bbe9a77c8e07f5da47ea3c0 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 29 Jun 2022 18:46:10 +0200
Subject: [PATCH 150/197] mp4: adapt with regard to prior refactoring of
generic track model (mostly simplifies things in fact)
---
src/demuxer/mp4/mp4-sample-table.ts | 25 ++++----
src/demuxer/mp4/mp4-track.ts | 99 +++++++++++++----------------
2 files changed, 56 insertions(+), 68 deletions(-)
diff --git a/src/demuxer/mp4/mp4-sample-table.ts b/src/demuxer/mp4/mp4-sample-table.ts
index 9bf3634..b1ce54e 100644
--- a/src/demuxer/mp4/mp4-sample-table.ts
+++ b/src/demuxer/mp4/mp4-sample-table.ts
@@ -48,20 +48,17 @@ export class Mp4SampleTable {
for (let i = 0; i < entry.sampleCount; i++) {
- const isSyncFrame = this.syncSamples ? (this.syncSamples.syncSampleNumbers.indexOf(frameCount + 1) >= 0) : false;
+ const isSyncFrame = this.syncSamples ?
+ (this.syncSamples.syncSampleNumbers.indexOf(frameCount + 1) >= 0) : false;
const newFrame = new Frame(
isSyncFrame ? FRAME_TYPE.I : FRAME_TYPE.P,
- toMicroseconds(dts, this._track.getTimescale()),
- this.sampleSizes.sampleSize || this.sampleSizes.entries[frameCount],
- toMicroseconds(entry.sampleDelta, this._track.getTimescale())
+ dts,
+ 0, // PTO/CTO table is optional, zero is default obviously.
+ entry.sampleDelta,
+ this.sampleSizes.sampleSize || this.sampleSizes.entries[frameCount]
);
- newFrame.scaledDuration = entry.sampleDelta;
- newFrame.scaledDecodingTime = dts;
- newFrame.scaledPresentationTimeOffset = 0;
- newFrame.timescale = this._track.getTimescale();
-
frames.push(newFrame);
frameCount++; // note: here we incr the count after using it as an ordinal index
@@ -78,9 +75,7 @@ export class Mp4SampleTable {
for (let i = 0; i < entry.sampleCount; i++) {
frames[frameCount]
- .setPresentationTimeOffsetUs(toMicroseconds(entry.sampleCTimeOffset, this._track.getTimescale()));
-
- frames[frameCount].scaledPresentationTimeOffset = entry.sampleCTimeOffset;
+ .setPresentationTimeOffset(entry.sampleCTimeOffset);
frameCount++; // note: here we incr the count after using it as an ordinal index
}
@@ -123,8 +118,10 @@ export class Mp4SampleTable {
const frame = frames[frameCount];
- frame.bytesOffset = this.chunkOffsetBox.chunkOffsets[index];
- frame.bytesOffset += sampleOffsetInChunk;
+ const bytesOffset = this.chunkOffsetBox.chunkOffsets[index];
+ + sampleOffsetInChunk;
+
+ frame.setBytesOffset(bytesOffset)
sampleOffsetInChunk += frame.size;
diff --git a/src/demuxer/mp4/mp4-track.ts b/src/demuxer/mp4/mp4-track.ts
index f67f265..8d98599 100644
--- a/src/demuxer/mp4/mp4-track.ts
+++ b/src/demuxer/mp4/mp4-track.ts
@@ -19,16 +19,16 @@ export type Mp4TrackDefaults = {
export class Mp4Track extends Track {
+ private baseDataOffset: number = 0;
+ private baseMediaDecodeTime: number = 0;
+
+ private endDts: number = 0;
+
+ private defaults: Mp4TrackDefaults[] = [];
+ private defaultSampleFlagsParsed: (SampleFlags | null)[] = [];
private sidx: Sidx = null;
private trunInfo: Trun[] = [];
private trunInfoReadIndex: number = 0;
- private lastDtsUs: number = 0;
- private lastDtsScaled: number = 0;
- private timescale: number = null;
- private defaults: Mp4TrackDefaults[] = [];
- private defaultSampleFlagsParsed: (SampleFlags | null)[] = [];
- private baseDataOffset: number = 0;
- private baseMediaDecodeTime: number = 0;
constructor(
id: number,
@@ -38,17 +38,27 @@ export class Mp4Track extends Track {
public metadataAtom: AudioAtom | VideoAtom,
public dataOffset: number
) {
-
super(id, type, mimeType);
- // declared in base-class
- this.durationUs = 0;
if (this.dataOffset < 0) {
throw new Error('Invalid file, no sample-data base-offset can be determined');
}
}
+ /**
+ * post: endDts ie duration incremented by frame duration
+ * @param frame
+ *
+ */
+ public appendFrame(frame: Frame) {
+ this.endDts += frame.duration;
+ this.frames.push(frame);
+ }
+
public flush() {
+
+ this.endDts = 0;
+
this.trunInfo.length = 0;
this.trunInfoReadIndex = 0;
this.defaults.length = 0;
@@ -56,7 +66,17 @@ export class Mp4Track extends Track {
super.flush();
}
- // TODO: make this abstract on Track class
+ public getDuration() {
+ return this.frames.length ?
+ this.endDts - this.frames[0].dts : 0;
+ }
+
+ public getTimescale(): number {
+ const timescale: number = this.sidx ?
+ this.sidx.timescale : super.getTimescale();
+ return timescale;
+ }
+
public getResolution(): [number, number] {
if (!this.isVideo()) {
throw new Error('Can not get resolution of non-video track');
@@ -85,24 +105,15 @@ export class Mp4Track extends Track {
return this.metadataAtom;
}
- public getTimescale(): number {
- return this.timescale;
- }
-
public setBaseMediaDecodeTime(baseDts: number) {
this.baseMediaDecodeTime = baseDts;
- this.lastDtsScaled = baseDts;
- this.lastDtsUs = toMicroseconds(baseDts, this.timescale);
+ this.endDts = baseDts;
}
public getBaseMediaDecodeTime(): number {
return this.baseMediaDecodeTime;
}
- public setTimescale(timescale: number) {
- this.timescale = timescale;
- }
-
public addDefaults(defaults: Mp4TrackDefaults) {
this.defaults.push(defaults);
if (defaults.sampleFlags) {
@@ -154,19 +165,8 @@ export class Mp4Track extends Track {
public setSidxAtom(atom: Atom): void {
this.sidx = atom as Sidx;
- this.lastDtsScaled = this.sidx.earliestPresentationTime;
- this.lastDtsUs = toMicroseconds(this.sidx.earliestPresentationTime, this.sidx.timescale);
- this.timescale = this.sidx.timescale;
- }
-
- public appendFrame(frame: Frame) {
- if (!frame.hasUnnormalizedIntegerTiming()) {
- throw new Error('Frame must have unscaled-int sample timing');
- }
- this.lastDtsScaled += frame.scaledDuration;
- this.lastDtsUs += frame.duration;
- this.durationUs += frame.duration;
- this.frames.push(frame);
+ this.endDts = this.sidx.earliestPresentationTime;
+ this.setTimescale(this.sidx.timescale);
}
// TODO: move the truns array and processTrunAtoms to a own container class (like sample-table)
@@ -183,12 +183,6 @@ export class Mp4Track extends Track {
return;
}
- const timescale: number = this.sidx ? this.sidx.timescale : this.getTimescale();
-
- if (!this.sidx) {
- //warn('No sidx found, using parent timescale:', timescale);
- }
-
const sampleRunDataOffset: number = trun.dataOffset + this.getFinalSampleDataOffset();
let bytesOffset: number = sampleRunDataOffset;
@@ -207,32 +201,29 @@ export class Mp4Track extends Track {
throw new Error('Invalid file, samples have no duration');
}
- const durationUs: number = toMicroseconds(sampleDuration, timescale);
- const ctoUs: number = toMicroseconds((sample.compositionTimeOffset || 0), timescale);
- const dtsUs = this.lastDtsUs;
+ const duration: number = sampleDuration;
+ const dts = this.endDts;
+ const cto: number = sample.compositionTimeOffset || 0;
const frameSize = sample.size || this.defaults[trunIndex]?.sampleSize;
if (!frameSize) throw new Error('Frame has to have either sample-size of trun-entry or track default');
+ const frameType = flags ? (flags.isSyncFrame ? FRAME_TYPE.I : FRAME_TYPE.P) : FRAME_TYPE.NONE
+
const newFrame = new Frame(
- flags ? (flags.isSyncFrame ? FRAME_TYPE.I : FRAME_TYPE.P) : FRAME_TYPE.NONE,
- dtsUs,
+ frameType,
+ dts,
+ cto,
+ duration,
frameSize,
- durationUs,
- bytesOffset,
- ctoUs
+ bytesOffset
);
- newFrame.scaledDuration = sampleDuration;
- newFrame.scaledDecodingTime = this.lastDtsScaled;
- newFrame.scaledPresentationTimeOffset = sample.compositionTimeOffset || 0;
- newFrame.timescale = timescale;
-
this.appendFrame(newFrame);
bytesOffset += frameSize;
}
- })
+ });
this.trunInfoReadIndex = this.trunInfo.length;
}
From 03c6ec8671d7311eaed8e44be0be050145eb45ff Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 29 Jun 2022 18:47:21 +0200
Subject: [PATCH 151/197] webm-track: slight adaptations due to prior
refactoring on track model (but still using the "webm implementation"
timescale here, whatever that would be)
---
src/demuxer/webm/webm-track.ts | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/src/demuxer/webm/webm-track.ts b/src/demuxer/webm/webm-track.ts
index 6cf370a..8d338a4 100644
--- a/src/demuxer/webm/webm-track.ts
+++ b/src/demuxer/webm/webm-track.ts
@@ -8,6 +8,7 @@ import { ITrackInfo } from './elements/track-info';
import { Vint, EbmlElement } from './ebml/ebml-types';
export class WebMTrack extends Track {
+
private lastPts: number;
private nsPerFrame: number;
private lastTimecodeBase: number;
@@ -68,6 +69,10 @@ export class WebMTrack extends Track {
return codecID.substr(pos + 1);
}
+ public getResolution(): [number, number] {
+ throw new Error('Method not implemented.');
+ }
+
public getFrames(): Frame[] {
return this.frames;
}
@@ -104,12 +109,21 @@ export class WebMTrack extends Track {
const buffer: Uint8Array = element.data as Uint8Array;
const timecode: number = ByteParserUtils.parseUint16(buffer, trackId.length);
const flags: number = ByteParserUtils.parseUint(buffer, trackId.length + 2, 1);
+
this.lastPts = 1000 * ((this.lastTimecodeBase + timecode) / (this.timecodeScale > 0 ? this.timecodeScale : 1));
if (element.name === 'SimpleBlock' && flags & 0x80) {
- this.frames.push(new Frame(FRAME_TYPE.I, this.lastPts, buffer.length));
+ this.frames.push(new Frame(
+ FRAME_TYPE.I,
+ this.lastPts, 0, 0,
+ buffer.length
+ ));
} else {
- this.frames.push(new Frame(FRAME_TYPE.P, this.lastPts, buffer.length));
+ this.frames.push(new Frame(
+ FRAME_TYPE.P,
+ this.lastPts, 0, 0,
+ buffer.length
+ ));
}
}
}
From 173882b63765d6e4dbfd4a312008d5e9a100edab Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 6 Jul 2022 00:34:22 +0200
Subject: [PATCH 152/197] frame: print values to assertion error messages
---
src/demuxer/frame.ts | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/demuxer/frame.ts b/src/demuxer/frame.ts
index 1346445..036b05a 100644
--- a/src/demuxer/frame.ts
+++ b/src/demuxer/frame.ts
@@ -12,13 +12,13 @@ export class Frame {
private _bytesOffset: number = NaN
) {
if (dts < 0 || !Number.isSafeInteger(dts)) {
- throw new Error(`Frame: DTS has to be positive safe-integer value`);
+ throw new Error(`Frame: DTS has to be positive safe-integer value but is ${dts}`);
}
if (size < 0 || !Number.isSafeInteger(size)) {
- throw new Error(`Frame: Size has to be positive safe-integer value`);
+ throw new Error(`Frame: Size has to be positive safe-integer value but is ${size}`);
}
if (duration < 0 || !Number.isSafeInteger(duration)) {
- throw new Error(`Frame: Duration has to be positive safe-integer value`);
+ throw new Error(`Frame: Duration has to be positive safe-integer value but is ${duration}`);
}
this.setPresentationTimeOffset(_cto);
}
@@ -37,14 +37,14 @@ export class Frame {
*/
setPresentationTimeOffset(cto: number) {
if (cto < 0 || !Number.isSafeInteger(cto)) {
- throw new Error(`Frame: CTO has to be positive safe-integer value`);
+ throw new Error(`Frame: CTO has to be positive safe-integer value but is ${cto}`);
}
this._cto = cto;
}
setBytesOffset(bytesOffset: number) {
if (bytesOffset < 0 || !Number.isSafeInteger(bytesOffset)) {
- throw new Error(`Frame: Bytes-offset has to be positive safe-integer value`);
+ throw new Error(`Frame: Bytes-offset has to be positive safe-integer value but is ${bytesOffset}`);
}
this._bytesOffset = bytesOffset;
}
From 34f12827445a5d181934674fa7e72b15e4b4d139 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 6 Jul 2022 00:34:54 +0200
Subject: [PATCH 153/197] style fix
---
src/demuxer/ts/payload/adts-reader.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index dd9285c..6168629 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -70,7 +70,8 @@ export class AdtsReader extends PayloadReader {
case AdtsReaderState.READ_FRAME:
const { headerLen, accessUnitSize, sampleRate } = this.currentFrame;
// use MPTS timescale here too
- const frameDuration = Math.round(AAC_FRAME_SAMPLES_NUM * MPEG_CLOCK_HZ / sampleRate)
+ const frameDuration = Math.round(AAC_FRAME_SAMPLES_NUM * MPEG_CLOCK_HZ / sampleRate);
+
if (this.dataBuffer.byteLength - this.dataOffset
< headerLen + accessUnitSize) {
needMoreData = true;
From df15faf3851e42814c02f5f6946f1877728702c9 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 6 Jul 2022 00:35:28 +0200
Subject: [PATCH 154/197] adts-reader: fix assertion check logic in header
parsing
---
src/demuxer/ts/payload/adts-reader.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index 6168629..fe756a3 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -166,7 +166,7 @@ export class AdtsReader extends PayloadReader {
const profile = audioCodecProfile;
const sampleRateIndex: number = br.readBits(4);
- if (sampleRateIndex < 0 && sampleRateIndex >= ADTS_SAMPLE_RATES.length) {
+ if (sampleRateIndex < 0 || sampleRateIndex >= ADTS_SAMPLE_RATES.length) {
throw new Error(`Invalid AAC sampling-frequency index: ${sampleRateIndex}`);
}
const sampleRate = ADTS_SAMPLE_RATES[sampleRateIndex];
From 14a92f1eb524b915ccef56310e6590d68478a2df Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 6 Jul 2022 00:36:26 +0200
Subject: [PATCH 155/197] payload readers: replace assertion by early return in
read (see comment)
---
src/demuxer/ts/payload/adts-reader.ts | 4 +++-
src/demuxer/ts/payload/h264-reader.ts | 4 +++-
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index fe756a3..b90a70f 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -37,8 +37,10 @@ export class AdtsReader extends PayloadReader {
}
public read(dts: number, cto: number): void {
+ // it is expected after this check a dataBuffer exists
if (!this.dataBuffer) {
- throw new Error('read() should not be called without priorly data appended');
+ return;
+ //throw new Error('read() should not be called without priorly data appended');
}
this.setCurrentTime(dts, cto);
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index d8295fc..d09c5cf 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -41,8 +41,10 @@ export class H264Reader extends PayloadReader {
}
public read(dts: number, cto: number): void {
+ // it is expected after this check a dataBuffer exists
if (!this.dataBuffer) {
- throw new Error('read() should not be called without priorly data appended');
+ return;
+ // throw new Error('read() should not be called without priorly data appended');
}
this.setCurrentTime(dts, cto);
From ab701a706ec83b1b4d16ec6ae028be2e383dd282 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 6 Jul 2022 00:37:46 +0200
Subject: [PATCH 156/197] ts-demuxer: disable deprecated method resetTracks
(unused yet)
---
src/demuxer/ts/mpegts-demuxer.ts | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index 0a2d167..3dc00ca 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -120,6 +120,7 @@ export class MpegTSDemuxer implements IDemuxer {
}
}
+ /*
private resetTracks(): void {
for (let id in this.tracks) {
if (this.tracks.hasOwnProperty(id)) {
@@ -127,6 +128,7 @@ export class MpegTSDemuxer implements IDemuxer {
}
}
}
+ */
private findContainerType(): void {
if (this.containerType !== CONTAINER_TYPE.UNKNOWN) return;
From bdb183c54076569353e5036a1a4b78f48b2c12ae Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 6 Jul 2022 00:39:05 +0200
Subject: [PATCH 157/197] track: add hasTimescale and add assertion sign cond
for setTimescale param
---
src/demuxer/track.ts | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/demuxer/track.ts b/src/demuxer/track.ts
index 0ea5047..e75faf0 100644
--- a/src/demuxer/track.ts
+++ b/src/demuxer/track.ts
@@ -65,13 +65,17 @@ export abstract class Track {
return this.frames;
}
+ public hasTimescale() {
+ return Number.isFinite(this.getTimescale());
+ }
+
public getTimescale() {
return this._timeScale;
}
public setTimescale(timeScale: number) {
- if (!Number.isSafeInteger(timeScale)) {
- throw new Error(`Track timescale has to be safe-integer value`);
+ if (timeScale <= 0 || !Number.isSafeInteger(timeScale)) {
+ throw new Error(`Track timescale has to be strictly positive safe-integer value`);
}
this._timeScale = timeScale;
}
From a9d21ade42cab54ef2c283519a067e3b4d0d7019 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 6 Jul 2022 00:39:40 +0200
Subject: [PATCH 158/197] mp4-track: add getDurationInSeconds convenience
accessor (needs hasTimescale)
---
src/demuxer/mp4/mp4-track.ts | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/src/demuxer/mp4/mp4-track.ts b/src/demuxer/mp4/mp4-track.ts
index 8d98599..4bc735c 100644
--- a/src/demuxer/mp4/mp4-track.ts
+++ b/src/demuxer/mp4/mp4-track.ts
@@ -71,6 +71,13 @@ export class Mp4Track extends Track {
this.endDts - this.frames[0].dts : 0;
}
+ public getDurationInSeconds() {
+ if (!this.hasTimescale()) {
+ throw new Error(`Track ${this.type} ${this.id} timescale is not present (has not been set or determined on parsing) to convert track-duration in seconds value!`);
+ }
+ return this.getDuration() / this.getTimescale();
+ }
+
public getTimescale(): number {
const timescale: number = this.sidx ?
this.sidx.timescale : super.getTimescale();
From fa37a9f36015f85faa8027e0857c71a645acbd01 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 6 Jul 2022 00:44:09 +0200
Subject: [PATCH 159/197] rm dead debug log
---
src/demuxer/ts/payload/adts-reader.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index b90a70f..650ba71 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -62,7 +62,7 @@ export class AdtsReader extends PayloadReader {
} catch (err) {
// data pointers will be nulled by reset call, so we need to make string first
const errMsg = `Error parsing header at ${this.dataOffset}/${this.dataBuffer.byteLength} [B]; t=${JSON.stringify(this.getCurrentTime())} [s]; \nException: ${(err as Error).message}`;
- // console.debug(this); // only for debug !!
+
this.reset();
this.state = AdtsReaderState.FIND_SYNC;
throw new Error(errMsg);
From 032a4f8dc061d5a2813ee6bb544763b9ba72d849 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Fri, 8 Jul 2022 09:31:39 +0200
Subject: [PATCH 160/197] ts-demuxer: fix bug in packet parsing subarray
indexing offset
---
src/demuxer/ts/mpegts-demuxer.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index 3dc00ca..c995a3a 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -164,7 +164,7 @@ export class MpegTSDemuxer implements IDemuxer {
continue;
}
const packet: Uint8Array = this.data.subarray(this.dataOffset + 1,
- this.dataOffset + MpegTSDemuxer.MPEGTS_PACKET_SIZE_MINUS_ONE);
+ this.dataOffset + MpegTSDemuxer.MPEGTS_PACKET_SIZE);
this.dataOffset += MpegTSDemuxer.MPEGTS_PACKET_SIZE;
this.processTsPacket(packet);
}
From d72dd5938e6239dfd2ace7c161187fd3ab9eae46 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Fri, 8 Jul 2022 09:33:22 +0200
Subject: [PATCH 161/197] h264-reader: read(): add length assertion on data
buffer using ?. accessor
---
src/demuxer/ts/payload/h264-reader.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index d09c5cf..901d2c5 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -42,7 +42,7 @@ export class H264Reader extends PayloadReader {
public read(dts: number, cto: number): void {
// it is expected after this check a dataBuffer exists
- if (!this.dataBuffer) {
+ if (!(this?.dataBuffer?.byteLength)) {
return;
// throw new Error('read() should not be called without priorly data appended');
}
From 0ed9aaedca86c3d62f8da59245ddc890d24d8282 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Fri, 8 Jul 2022 09:35:42 +0200
Subject: [PATCH 162/197] h264-reader: pass CTO in onData callback + rm
readNaluData return end offset
---
src/demuxer/ts/payload/h264-reader.ts | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index 901d2c5..f9f1d3a 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -48,7 +48,6 @@ export class H264Reader extends PayloadReader {
}
this.setCurrentTime(dts, cto);
- // process pending remainder data
let firstNalUnit: number = 0;
let nextNalUnit: number = 0;
@@ -59,7 +58,8 @@ export class H264Reader extends PayloadReader {
if (!Number.isFinite(nextNalUnit)) {
return;
}
- firstNalUnit = this.readNaluData(firstNalUnit, nextNalUnit);
+ this.readNaluData(firstNalUnit, nextNalUnit);
+ firstNalUnit = nextNalUnit;
} else {
firstNalUnit = this.findNextNalu();
if (!Number.isFinite(firstNalUnit)) {
@@ -75,7 +75,8 @@ export class H264Reader extends PayloadReader {
break;
}
- firstNalUnit = this.readNaluData(firstNalUnit, nextNalUnit);
+ this.readNaluData(firstNalUnit, nextNalUnit);
+ firstNalUnit = nextNalUnit;
}
// prune data-buffer
@@ -107,7 +108,7 @@ export class H264Reader extends PayloadReader {
* @param end offset (exclusive)
* @returns end offset (exclusive) as input
*/
- private readNaluData(begin: number, end: number): number {
+ private readNaluData(begin: number, end: number) {
const naluData = this.dataBuffer.subarray(begin + NALU_DELIM_LEN, end);
@@ -115,6 +116,8 @@ export class H264Reader extends PayloadReader {
// (can happen if buffer begin/remainder is garbage)
const naluType = naluData[0] & 0x1F;
switch(naluType) {
+ case NAL_UNIT_TYPE.AUD:
+ break;
case NAL_UNIT_TYPE.SLICE:
this.parseNonIdrPicSlice(naluData);
break;
@@ -131,8 +134,7 @@ export class H264Reader extends PayloadReader {
break;
}
- this.onData(naluData, this.dts, naluType);
- return end;
+ this.onData(naluData, this.dts, this.cto, naluType);
}
private parseSps(naluData: Uint8Array): void {
From 6fbafcd0ccb929ab478f3fc59996e1439f2af511 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Fri, 8 Jul 2022 09:35:56 +0200
Subject: [PATCH 163/197] adts-reader improve comment
---
src/demuxer/ts/payload/adts-reader.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index 650ba71..d22a1c5 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -60,7 +60,7 @@ export class AdtsReader extends PayloadReader {
try {
this.parseHeader();
} catch (err) {
- // data pointers will be nulled by reset call, so we need to make string first
+ // data pointers will be nulled by reset call, so we need to make err string first
const errMsg = `Error parsing header at ${this.dataOffset}/${this.dataBuffer.byteLength} [B]; t=${JSON.stringify(this.getCurrentTime())} [s]; \nException: ${(err as Error).message}`;
this.reset();
From b23adae904bbd1d6d3e66437e568daed31da8485 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 12 Jul 2022 18:42:00 +0200
Subject: [PATCH 164/197] h264-reader: prefix private method names for better
readability
---
src/demuxer/ts/payload/h264-reader.ts | 36 ++++++++++++++-------------
1 file changed, 19 insertions(+), 17 deletions(-)
diff --git a/src/demuxer/ts/payload/h264-reader.ts b/src/demuxer/ts/payload/h264-reader.ts
index f9f1d3a..ddacc33 100644
--- a/src/demuxer/ts/payload/h264-reader.ts
+++ b/src/demuxer/ts/payload/h264-reader.ts
@@ -25,12 +25,12 @@ export class H264Reader extends PayloadReader {
// enforced process any last data after
// a nalu-delim to be processed
// (most likely partial NALUs).
- const nextNalUnit: number = this.findNextNalu(0);
+ const nextNalUnit: number = this._findNextNalu(0);
if (!Number.isFinite(nextNalUnit)) {
return;
}
- this.readNaluData(nextNalUnit, this.dataBuffer.byteLength);
+ this._readNaluData(nextNalUnit, this.dataBuffer.byteLength);
}
public reset(): void {
@@ -52,16 +52,16 @@ export class H264Reader extends PayloadReader {
let nextNalUnit: number = 0;
if (this._pendingBytes > 0) {
- nextNalUnit = this.findNextNalu(this._pendingBytes);
+ nextNalUnit = this._findNextNalu(this._pendingBytes);
// if we cant find a next NALU-delim from the remainder data,
// we can already give-up here.
if (!Number.isFinite(nextNalUnit)) {
return;
}
- this.readNaluData(firstNalUnit, nextNalUnit);
+ this._readNaluData(firstNalUnit, nextNalUnit);
firstNalUnit = nextNalUnit;
} else {
- firstNalUnit = this.findNextNalu();
+ firstNalUnit = this._findNextNalu();
if (!Number.isFinite(firstNalUnit)) {
return;
}
@@ -70,12 +70,12 @@ export class H264Reader extends PayloadReader {
// process next nal units in the buffer
while (true) {
// w/o the +3 we would end up again with the input offset!
- nextNalUnit = this.findNextNalu(firstNalUnit + NALU_DELIM_LEN);
+ nextNalUnit = this._findNextNalu(firstNalUnit + NALU_DELIM_LEN);
if (!Number.isFinite(nextNalUnit)) {
break;
}
- this.readNaluData(firstNalUnit, nextNalUnit);
+ this._readNaluData(firstNalUnit, nextNalUnit);
firstNalUnit = nextNalUnit;
}
@@ -87,7 +87,7 @@ export class H264Reader extends PayloadReader {
this._pendingBytes = this.dataBuffer.byteLength;
}
- private findNextNalu(offset: number = 0): number {
+ private _findNextNalu(offset: number = 0): number {
if (!(this?.dataBuffer?.byteLength)) {
return NaN;
}
@@ -108,24 +108,26 @@ export class H264Reader extends PayloadReader {
* @param end offset (exclusive)
* @returns end offset (exclusive) as input
*/
- private readNaluData(begin: number, end: number) {
+ private _readNaluData(begin: number, end: number) {
const naluData = this.dataBuffer.subarray(begin + NALU_DELIM_LEN, end);
// TODO: check for invalid values
- // (can happen if buffer begin/remainder is garbage)
+ // (can happen if buffer begin/remainder is garbage,
+ // assert transport parsing is correct, but also handle packet loss)
+
const naluType = naluData[0] & 0x1F;
switch(naluType) {
case NAL_UNIT_TYPE.AUD:
break;
case NAL_UNIT_TYPE.SLICE:
- this.parseNonIdrPicSlice(naluData);
+ this._parseNonIdrPicSlice(naluData);
break;
case NAL_UNIT_TYPE.IDR:
- this.addFrame(FRAME_TYPE.I, naluData);
+ this._addFrame(FRAME_TYPE.I, naluData);
break;
case NAL_UNIT_TYPE.SPS:
- this.parseSps(naluData);
+ this._parseSps(naluData);
break;
case NAL_UNIT_TYPE.PPS:
this.pps = true;
@@ -137,22 +139,22 @@ export class H264Reader extends PayloadReader {
this.onData(naluData, this.dts, this.cto, naluType);
}
- private parseSps(naluData: Uint8Array): void {
+ private _parseSps(naluData: Uint8Array): void {
// skip first byte NALU-header for SPS-parser func input (expects only payload)
this.sps = H264ParameterSetParser.parseSPS(naluData.subarray(1));
}
- private parseNonIdrPicSlice(naluData: Uint8Array): void {
+ private _parseNonIdrPicSlice(naluData: Uint8Array): void {
const sliceParser: BitReader = new BitReader(naluData);
sliceParser.skipBytes(1);
sliceParser.readUEG();
const sliceType: SLICE_TYPE = sliceParser.readUEG();
- this.addFrame(mapNaluSliceToFrameType(sliceType), naluData);
+ this._addFrame(mapNaluSliceToFrameType(sliceType), naluData);
}
- private addFrame(frameType: FRAME_TYPE, naluData: Uint8Array): void {
+ private _addFrame(frameType: FRAME_TYPE, naluData: Uint8Array): void {
const frame = new Frame(
frameType,
this.dts,
From 60ed91e96fbe9efb541dfe98f0f73528823fa68e Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 27 Jul 2022 20:12:50 +0200
Subject: [PATCH 165/197] ts-demux: style fixup and remove dead parts
---
src/demuxer/ts/mpegts-demuxer.ts | 129 ++++++++++++++-----------------
1 file changed, 60 insertions(+), 69 deletions(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index c995a3a..fdb451f 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -13,48 +13,48 @@ enum CONTAINER_TYPE {
}
export class MpegTSDemuxer implements IDemuxer {
+
private static MPEGTS_SYNC: number = 0x47;
private static MPEGTS_PACKET_SIZE: number = 188;
- private static MPEGTS_PACKET_SIZE_MINUS_ONE: number = 187;
public tracks: { [id: number] : TSTrack; } = {};
- private containerType: CONTAINER_TYPE = CONTAINER_TYPE.UNKNOWN;
+ private _containerType: CONTAINER_TYPE = CONTAINER_TYPE.UNKNOWN;
- private data: Uint8Array;
- private dataOffset: number;
+ private _data: Uint8Array;
+ private _dataOffset: number;
- private packetsCount: number = 0;
+ private _packetsCount: number = 0;
- private pmtId: number = NaN;
- private pmtParsed: boolean = false;
+ private _pmtId: number = NaN;
+ private _pmtParsed: boolean = false;
get currentBufferSize(): number {
- return this?.data.byteLength || 0;
+ return this?._data.byteLength || 0;
}
get currentPacketCount(): number {
- return this.packetsCount;
+ return this._packetsCount;
}
get isProgramMapUpdated(): boolean {
- return this.pmtParsed;
+ return this._pmtParsed;
}
public append(data: Uint8Array, pruneAfterParse: boolean = false): Uint8Array | null {
- if (!this.data || this.data.byteLength === 0) {
- this.data = new Uint8Array(data);
- this.dataOffset = 0;
+ if (!this._data || this._data.byteLength === 0) {
+ this._data = new Uint8Array(data);
+ this._dataOffset = 0;
} else {
- const newLen: number = this.data.byteLength + data.byteLength;
+ const newLen: number = this._data.byteLength + data.byteLength;
const newBuffer: Uint8Array = new Uint8Array(newLen);
- newBuffer.set(this.data, 0);
- newBuffer.set(data, this.data.byteLength);
- this.data = newBuffer;
+ newBuffer.set(this._data, 0);
+ newBuffer.set(data, this._data.byteLength);
+ this._data = newBuffer;
}
- this.parse();
- this.updateTracks();
+ this._parse();
+ this._updateTracks();
if (pruneAfterParse) {
return this.prune();
@@ -65,21 +65,21 @@ export class MpegTSDemuxer implements IDemuxer {
public prune(): Uint8Array | null {
let parsedBuf: Uint8Array = null;
// prune off parsing remainder from buffer
- if (this.dataOffset > 0) {
+ if (this._dataOffset > 0) {
// we might have dropped the data already
// through a parsing callback calling end() for example.
- if (this.data) {
+ if (this._data) {
// the offset is expected to go +1 the buffer range
// thus the > instead of >=
- if (this.dataOffset > this.data.byteLength) {
+ if (this._dataOffset > this._data.byteLength) {
throw new Error('Reader offset is out of buffer range');
}
// second arg of .subarray is exclusive range
- parsedBuf = this.data.subarray(0, this.dataOffset);
+ parsedBuf = this._data.subarray(0, this._dataOffset);
// the first argument yields to an empty array when out-of-range
- this.data = this.data.subarray(this.dataOffset);
+ this._data = this._data.subarray(this._dataOffset);
}
- this.dataOffset = 0;
+ this._dataOffset = 0;
}
return parsedBuf;
}
@@ -91,20 +91,20 @@ export class MpegTSDemuxer implements IDemuxer {
this.tracks[trackId].update();
}
}
- this.data = null;
- this.dataOffset = 0;
+ this._data = null;
+ this._dataOffset = 0;
}
public onProgramMapUpdate() {};
- private parse(): void {
+ private _parse(): void {
- this.findContainerType();
+ this._findContainerType();
- if (this.containerType === CONTAINER_TYPE.MPEG_TS) {
- this.readPackets();
+ if (this._containerType === CONTAINER_TYPE.MPEG_TS) {
+ this._readPackets();
} else {
- const streamReader: BitReader = new BitReader(this.data);
+ const streamReader: BitReader = new BitReader(this._data);
this.tracks[0] = new TSTrack(0,
Track.TYPE_AUDIO, Track.MIME_TYPE_AAC,
new PESReader(0, MptsElementaryStreamType.TS_STREAM_TYPE_AAC));
@@ -112,7 +112,7 @@ export class MpegTSDemuxer implements IDemuxer {
}
}
- private updateTracks(): void {
+ private _updateTracks(): void {
for (const trackId in this.tracks) {
if (this.tracks.hasOwnProperty(trackId)) {
this.tracks[trackId].update();
@@ -120,59 +120,50 @@ export class MpegTSDemuxer implements IDemuxer {
}
}
- /*
- private resetTracks(): void {
- for (let id in this.tracks) {
- if (this.tracks.hasOwnProperty(id)) {
- (this.tracks[id] as TSTrack).pes.reset();
- }
- }
- }
- */
+ private _findContainerType(): void {
- private findContainerType(): void {
- if (this.containerType !== CONTAINER_TYPE.UNKNOWN) return;
+ if (this._containerType !== CONTAINER_TYPE.UNKNOWN) return;
- while (this.dataOffset < this.data.byteLength) {
- if (this.data[this.dataOffset] === MpegTSDemuxer.MPEGTS_SYNC) {
- this.containerType = CONTAINER_TYPE.MPEG_TS;
+ while (this._dataOffset < this._data.byteLength) {
+ if (this._data[this._dataOffset] === MpegTSDemuxer.MPEGTS_SYNC) {
+ this._containerType = CONTAINER_TYPE.MPEG_TS;
break;
- } else if ((this.data.byteLength - this.dataOffset) >= 4) {
- const dataRead: number = (this.data[this.dataOffset] << 8) | (this.data[this.dataOffset + 1]);
+ } else if ((this._data.byteLength - this._dataOffset) >= 4) {
+ const dataRead: number = (this._data[this._dataOffset] << 8) | (this._data[this._dataOffset + 1]);
if (dataRead === 0x4944 || (dataRead & 0xfff6) === 0xfff0) {
- this.containerType = CONTAINER_TYPE.RAW_AAC;
+ this._containerType = CONTAINER_TYPE.RAW_AAC;
break;
}
}
- this.dataOffset++;
+ this._dataOffset++;
}
- if (this.containerType === CONTAINER_TYPE.UNKNOWN) {
+ if (this._containerType === CONTAINER_TYPE.UNKNOWN) {
throw new Error('Format not supported');
}
}
- private readPackets(): void {
+ private _readPackets(): void {
// run as long as there is at least a full packet in buffer
- while ((this.data.byteLength - this.dataOffset) >= MpegTSDemuxer.MPEGTS_PACKET_SIZE) {
+ while ((this._data.byteLength - this._dataOffset) >= MpegTSDemuxer.MPEGTS_PACKET_SIZE) {
// check for sync-byte
- const currentByte: number = this.data[this.dataOffset];
+ const currentByte: number = this._data[this._dataOffset];
if (currentByte !== MpegTSDemuxer.MPEGTS_SYNC) {
// keep looking if we are out of sync
- this.dataOffset++;
+ this._dataOffset++;
continue;
}
- const packet: Uint8Array = this.data.subarray(this.dataOffset + 1,
- this.dataOffset + MpegTSDemuxer.MPEGTS_PACKET_SIZE);
- this.dataOffset += MpegTSDemuxer.MPEGTS_PACKET_SIZE;
- this.processTsPacket(packet);
+ const packet: Uint8Array = this._data.subarray(this._dataOffset + 1,
+ this._dataOffset + MpegTSDemuxer.MPEGTS_PACKET_SIZE);
+ this._dataOffset += MpegTSDemuxer.MPEGTS_PACKET_SIZE;
+ this._processTsPacket(packet);
}
}
- private processTsPacket(packet: Uint8Array): void {
+ private _processTsPacket(packet: Uint8Array): void {
- this.packetsCount++;
+ this._packetsCount++;
const packetReader: BitReader = new BitReader(packet);
packetReader.skipBits(1);
@@ -190,9 +181,9 @@ export class MpegTSDemuxer implements IDemuxer {
}
if (adaptationField === 1 || adaptationField === 3) {
if (pid === 0) {
- this.parseProgramAllocationTable(payloadUnitStartIndicator, packetReader);
- } else if (pid === this.pmtId) {
- this.parseProgramMapTable(payloadUnitStartIndicator, packetReader);
+ this._parseProgramAllocationTable(payloadUnitStartIndicator, packetReader);
+ } else if (pid === this._pmtId) {
+ this._parseProgramMapTable(payloadUnitStartIndicator, packetReader);
} else {
const track: TSTrack = this.tracks[pid];
// handle case where PID not found?
@@ -203,15 +194,15 @@ export class MpegTSDemuxer implements IDemuxer {
}
}
- private parseProgramAllocationTable(payloadUnitStartIndicator: boolean, packetParser: BitReader): void {
+ private _parseProgramAllocationTable(payloadUnitStartIndicator: boolean, packetParser: BitReader): void {
if (payloadUnitStartIndicator) {
packetParser.skipBytes(packetParser.readByte());
}
packetParser.skipBits(27 + 7 * 8);
- this.pmtId = packetParser.readBits(13);
+ this._pmtId = packetParser.readBits(13);
}
- private parseProgramMapTable(payloadUnitStartIndicator: boolean, packetParser: BitReader): void {
+ private _parseProgramMapTable(payloadUnitStartIndicator: boolean, packetParser: BitReader): void {
if (payloadUnitStartIndicator) {
packetParser.skipBytes(packetParser.readByte());
}
@@ -256,7 +247,7 @@ export class MpegTSDemuxer implements IDemuxer {
this.tracks[elementaryPid] = new TSTrack(elementaryPid, type, mimeType, pes);
}
}
- this.pmtParsed = true;
+ this._pmtParsed = true;
this.onProgramMapUpdate();
}
}
From be729f7b733adb66c5937dc625d00a52a04403aa Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 1 Aug 2022 04:27:01 +0200
Subject: [PATCH 166/197] adts-reader: add dtsOffset to track timing of several
frames within one PES packet (that shares one timing across the contained
frames)
fixes AAC output frames having same DTS over one PES packet
---
src/demuxer/ts/payload/adts-reader.ts | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index d22a1c5..2e7e2f6 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -28,6 +28,8 @@ export class AdtsReader extends PayloadReader {
private currentFrame: AdtsFrameInfo | null = null;
+ private dtsOffset: number = 0;
+
get currentFrameInfo(): AdtsFrameInfo | null {
return this.currentFrame;
}
@@ -42,6 +44,11 @@ export class AdtsReader extends PayloadReader {
return;
//throw new Error('read() should not be called without priorly data appended');
}
+
+ if (this.dts !== dts) {
+ this.dtsOffset = 0;
+ }
+
this.setCurrentTime(dts, cto);
let needMoreData = false;
@@ -71,7 +78,7 @@ export class AdtsReader extends PayloadReader {
case AdtsReaderState.READ_FRAME:
const { headerLen, accessUnitSize, sampleRate } = this.currentFrame;
- // use MPTS timescale here too
+
const frameDuration = Math.round(AAC_FRAME_SAMPLES_NUM * MPEG_CLOCK_HZ / sampleRate);
if (this.dataBuffer.byteLength - this.dataOffset
@@ -80,15 +87,19 @@ export class AdtsReader extends PayloadReader {
break;
}
+ const frameDts = this.dts + this.dtsOffset;
+
this.frames.push(new Frame(
FRAME_TYPE.NONE,
- this.dts,
+ frameDts,
this.cto,
frameDuration,
accessUnitSize,
this.dataOffset
));
+ this.dtsOffset += frameDuration;
+
const frameDataStart = this.dataOffset + headerLen;
const frameDataEnd = frameDataStart + accessUnitSize;
const frameData = this.dataBuffer.subarray(frameDataStart, frameDataEnd);
@@ -97,7 +108,7 @@ export class AdtsReader extends PayloadReader {
this.state = AdtsReaderState.FIND_SYNC;
- this.onData(frameData, this.dts, this.cto);
+ this.onData(frameData, frameDts, this.cto);
break;
}
}
From 13aee7696d63e487fae8dde860f5477d6904311c Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 3 Aug 2022 23:21:32 +0200
Subject: [PATCH 167/197] mpeg-ts demux: use unsigned right shift to read
adaptationField + add data ? check on currentBufferSize + add various
comments for explanation
---
src/demuxer/ts/mpegts-demuxer.ts | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index fdb451f..c2b5b21 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -30,7 +30,7 @@ export class MpegTSDemuxer implements IDemuxer {
private _pmtParsed: boolean = false;
get currentBufferSize(): number {
- return this?._data.byteLength || 0;
+ return this._data?.byteLength || 0;
}
get currentPacketCount(): number {
@@ -172,13 +172,22 @@ export class MpegTSDemuxer implements IDemuxer {
packetReader.skipBits(1);
const pid: number = packetReader.readBits(13);
- const adaptationField: number = (packetReader.readByte() & 0x30) >> 4;
+
+ // use unsigned right shift?
+ const adaptationField: number = (packetReader.readByte() & 0x30) >>> 4;
+
+ // todo: read continuity counter
+
+ // Adaptation field present
if (adaptationField > 1) {
+ // adaptation field len
const length: number = packetReader.readByte();
if (length > 0) {
packetReader.skipBytes(length);
}
}
+
+ // Payload data present
if (adaptationField === 1 || adaptationField === 3) {
if (pid === 0) {
this._parseProgramAllocationTable(payloadUnitStartIndicator, packetReader);
From 49efd93f949fc06070e4c78794fdc71bc806d96d Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 3 Aug 2022 23:23:51 +0200
Subject: [PATCH 168/197] parsePesHeaderTimestamps: compute and return header
remainder length (needed to skip forward to data section of payload after
parsing ext fields)
---
src/demuxer/ts/payload/pes-header.ts | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/src/demuxer/ts/payload/pes-header.ts b/src/demuxer/ts/payload/pes-header.ts
index e3df234..2757c0a 100644
--- a/src/demuxer/ts/payload/pes-header.ts
+++ b/src/demuxer/ts/payload/pes-header.ts
@@ -1,6 +1,6 @@
import { BitReader } from "../../../utils/bit-reader";
-export function parsePesHeaderTimestamps(packet: BitReader): [number, number] {
+export function parsePesHeaderTimestamps(packet: BitReader): [number, number, number] {
/**
* Thanks to Videojs/Muxjs for this bit, which does well the
* trick around 32-bit unary bit-ops and 33 bit numbers :)
@@ -14,10 +14,18 @@ export function parsePesHeaderTimestamps(packet: BitReader): [number, number] {
// most significant bits and then multiply by 4 (equal to a left-shift
// of 2) before we add the final 2 least significant bits of the
// timestamp (equal to an OR.)
+
+ packet.skipBytes(1); // todo: parse the data-alignment idc
+
const ptsDtsFlags = packet.readByte();
- packet.skipBytes(1);
+
+ const pesHeaderRemainderLen = packet.readByte();
+
let pts = NaN;
let dts = NaN;
+
+ let packetRemainder = packet.remainingBytes();
+
if (ptsDtsFlags & 0xC0) {
// the PTS and DTS are not written out directly. For information
// on how they are encoded, see
@@ -41,5 +49,8 @@ export function parsePesHeaderTimestamps(packet: BitReader): [number, number] {
dts += (lastByte & 0x06) >>> 1; // OR by the two LSBs
}
}
- return [dts, pts];
+
+ let bytesRead = packetRemainder - packet.remainingBytes()
+
+ return [dts, pts, pesHeaderRemainderLen - bytesRead];
}
From eab5a4a9b68c0bcd0f67a904bdbf7782b8739583 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 3 Aug 2022 23:27:15 +0200
Subject: [PATCH 169/197] adts-reader: use audio-samplerate accurate timing,
support multiple frames per PES, fix timing alignment on PUSI + code style
fixes and naming precision
---
src/demuxer/ts/payload/adts-reader.ts | 138 +++++++++++++++-----------
1 file changed, 80 insertions(+), 58 deletions(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index 2e7e2f6..70e7e21 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -14,7 +14,7 @@ enum AdtsReaderState {
}
export interface AdtsFrameInfo {
- profile: number;
+ aacObjectType: number;
channels: number;
sampleRate: number;
headerLen: number;
@@ -24,39 +24,45 @@ export interface AdtsFrameInfo {
export class AdtsReader extends PayloadReader {
- private state: AdtsReaderState = AdtsReaderState.FIND_SYNC;
+ private _state: AdtsReaderState = AdtsReaderState.FIND_SYNC;
- private currentFrame: AdtsFrameInfo | null = null;
+ private _currentFrame: AdtsFrameInfo | null = null;
+ private _currentFrameDuration: number = NaN;
- private dtsOffset: number = 0;
+ private _frameDtsOffset: number = 0;
get currentFrameInfo(): AdtsFrameInfo | null {
- return this.currentFrame;
+ return this._currentFrame;
+ }
+
+ get currentSampleRate(): number {
+ return this.currentFrameInfo?.sampleRate;
}
public getMimeType(): string {
return Track.MIME_TYPE_AAC;
}
- public read(dts: number, cto: number): void {
+ public read(dts: number): void {
// it is expected after this check a dataBuffer exists
if (!this.dataBuffer) {
return;
//throw new Error('read() should not be called without priorly data appended');
}
- if (this.dts !== dts) {
- this.dtsOffset = 0;
- }
-
- this.setCurrentTime(dts, cto);
-
let needMoreData = false;
while (!needMoreData && this.dataOffset < this.dataBuffer.byteLength) {
- switch (this.state) {
+ if (this._state === AdtsReaderState.FIND_SYNC) {
+ if (this.dts !== dts) {
+ this._frameDtsOffset = 0;
+ }
+ this.setCurrentTime(dts, 0);
+ }
+
+ switch (this._state) {
case AdtsReaderState.FIND_SYNC:
- needMoreData = ! this.findNextSync(); // returns true when sync found
+ needMoreData = ! this._findNextSync(); // returns true when sync found
break;
case AdtsReaderState.READ_HEADER:
@@ -65,21 +71,19 @@ export class AdtsReader extends PayloadReader {
break;
}
try {
- this.parseHeader();
+ this._parseHeader();
} catch (err) {
// data pointers will be nulled by reset call, so we need to make err string first
const errMsg = `Error parsing header at ${this.dataOffset}/${this.dataBuffer.byteLength} [B]; t=${JSON.stringify(this.getCurrentTime())} [s]; \nException: ${(err as Error).message}`;
this.reset();
- this.state = AdtsReaderState.FIND_SYNC;
+ this._state = AdtsReaderState.FIND_SYNC;
throw new Error(errMsg);
}
break;
case AdtsReaderState.READ_FRAME:
- const { headerLen, accessUnitSize, sampleRate } = this.currentFrame;
-
- const frameDuration = Math.round(AAC_FRAME_SAMPLES_NUM * MPEG_CLOCK_HZ / sampleRate);
+ const { headerLen, accessUnitSize, sampleRate, numFrames } = this._currentFrame;
if (this.dataBuffer.byteLength - this.dataOffset
< headerLen + accessUnitSize) {
@@ -87,28 +91,34 @@ export class AdtsReader extends PayloadReader {
break;
}
- const frameDts = this.dts + this.dtsOffset;
+ let frameDtsAudioRate
+ = Math.round(sampleRate * this.dts / MPEG_CLOCK_HZ);
+ frameDtsAudioRate += this._frameDtsOffset;
+
+ const frameDuration = numFrames * AAC_FRAME_SAMPLES_NUM;
+ this._frameDtsOffset += frameDuration;
+ // actually using sample-rate accurate timebase
this.frames.push(new Frame(
FRAME_TYPE.NONE,
- frameDts,
- this.cto,
+ frameDtsAudioRate,
+ 0, // CTO actually always 0 with AAC
frameDuration,
accessUnitSize,
this.dataOffset
));
- this.dtsOffset += frameDuration;
-
const frameDataStart = this.dataOffset + headerLen;
const frameDataEnd = frameDataStart + accessUnitSize;
const frameData = this.dataBuffer.subarray(frameDataStart, frameDataEnd);
this.dataOffset = frameDataEnd;
- this.state = AdtsReaderState.FIND_SYNC;
+ // note: intentionally setting state before invoke external callback
+ // that may have any unspecified side-effects or recursion.
+ this._state = AdtsReaderState.FIND_SYNC;
- this.onData(frameData, frameDts, this.cto);
+ this.onData(frameData, frameDtsAudioRate, 0);
break;
}
}
@@ -123,36 +133,49 @@ export class AdtsReader extends PayloadReader {
* - true when found (post: state = READ_HEADER)
* - false when more data needed (post: dataOffset = first byte after inclusive end of scan window)
*/
- private findNextSync(): boolean {
- const nextDataOffset: number = this.dataBuffer.byteLength - 1; // sync-word spans 2 bytes (12 bits)
+ private _findNextSync(): boolean {
+
+ // sync-word spans 2 bytes (12 bits)
+ if (this.dataBuffer.byteLength - this.dataOffset <= 1) return false;
+
+ // nextDataOffset should be be > 1, ensured by above check
+ const nextDataOffset: number = this.dataBuffer.byteLength - 1;
+
+ // we iterate until the second-last byte only, as we need to access i+1.
+ // cond is false if buffer byteLength = 1 also (guarded from that case early
+ // as it is expected further below nextDataOffset >= 0).
for (let i: number = this.dataOffset; i < nextDataOffset; i++) {
const dataRead: number = ((this.dataBuffer[i]) << 8) | (this.dataBuffer[i + 1]);
- // 0b6 = 0110 mask to ignore the mpeg-version and CRC bit,
- // and assert check for layer bits to be zero
/**
* A 12 syncword 0xFFF, all bits must be 1
* B 1 MPEG Version: 0 for MPEG-4, 1 for MPEG-2
* C 2 Layer: always 0
* D 1 protection absent, Warning, set to 1 if there is no CRC and 0 if there is CRC
*/
+
+ // 0b6 = 0110 mask to ignore the mpeg-version and CRC bit,
+ // and assert check for layer bits to be zero
if ((dataRead & 0xfff6) === 0xfff0) {
this.dataOffset = i;
- if (this.dataOffset < this.dataBuffer.byteLength) {
- this.state = AdtsReaderState.READ_HEADER;
- }
+ this._state = AdtsReaderState.READ_HEADER;
return true;
}
+
+ // handle/notify lost sync ? (should only happen on broken packet)
}
+ // start further read at current second-last
+ // attention: this assignment assumes nextDataOffset was computed
+ // via a non-zero byteLength of read buffer!
this.dataOffset = nextDataOffset;
return false;
}
- private parseHeader(): void {
+ private _parseHeader(): void {
// first, clear current frame state. in case of exception during header parse,
// it will keep null state, as we only set the frame after success.
- this.currentFrame = null;
+ this._currentFrame = null;
const br: BitReader = new BitReader(
this.dataBuffer.subarray(this.dataOffset, this.dataBuffer.byteLength));
@@ -160,10 +183,16 @@ export class AdtsReader extends PayloadReader {
br.skipBits(12);
const mpegVersion: number = br.readBool() ? 1 : 0; // MPEG Version: 0 for MPEG-4, 1 for MPEG-2
+ /*
+ if (mpegVersion !== 0) {
+ throw new Error(`Expected in header-data MPEG-version flag = 0 (only MP4-audio supported), but signals MPEG-2!`);
+ }
+ */
br.skipBits(2);
const hasCrc: boolean = ! br.readBool();
+ const headerLen = hasCrc ? ADTS_HEADER_LEN + ADTS_CRC_SIZE : ADTS_HEADER_LEN;
/**
* 1: AAC Main
@@ -172,17 +201,12 @@ export class AdtsReader extends PayloadReader {
* 4: AAC LTP (Long Term Prediction)
*/
// profile, the MPEG-4 Audio Object Type minus 1
- const audioCodecProfile = br.readBits(2) + 1;
- if (audioCodecProfile <= 0 || audioCodecProfile >= 5) {
- throw new Error(`Unsupported or likely invalid AAC profile (MPEG-4 Audio Object Type): ${audioCodecProfile}`);
+ const aacObjectType = br.readBits(2) + 1; // bits value range 0-3
+ if (aacObjectType <= 0 || aacObjectType >= 5) {
+ throw new Error(`Unsupported or likely invalid AAC profile (MPEG-4 Audio Object Type): ${aacObjectType}`);
}
- const profile = audioCodecProfile;
const sampleRateIndex: number = br.readBits(4);
- if (sampleRateIndex < 0 || sampleRateIndex >= ADTS_SAMPLE_RATES.length) {
- throw new Error(`Invalid AAC sampling-frequency index: ${sampleRateIndex}`);
- }
- const sampleRate = ADTS_SAMPLE_RATES[sampleRateIndex];
// private bit (unused by spec)
br.skipBits(1);
@@ -198,41 +222,39 @@ export class AdtsReader extends PayloadReader {
// originality/home/copyright bits (ignoring)
br.skipBits(4);
- const adtsFrameLen = br.readBits(13); // always including the header itself (w/ opt CRC 2 bytes)
+ // ADTS frame len including the header itself (also opt CRC 2 bytes).
+ const adtsFrameLen = br.readBits(13);
+
if (adtsFrameLen <= 0) {
throw new Error(`Invalid ADTS-frame byte-length: ${adtsFrameLen}`);
}
- const headerLen = hasCrc ? ADTS_HEADER_LEN + ADTS_CRC_SIZE : ADTS_HEADER_LEN;
const accessUnitSize = adtsFrameLen - headerLen;
- // buffer fullness (ignoring so far, spec not clear about what it is really yet)
+ // Buffer fullness, states the bit-reservoir per frame.
br.skipBits(11);
+ if (sampleRateIndex < 0 || sampleRateIndex >= ADTS_SAMPLE_RATES.length) {
+ throw new Error(`Invalid AAC sampling-frequency index: ${sampleRateIndex}`);
+ }
+ const sampleRate = ADTS_SAMPLE_RATES[sampleRateIndex];
+
+ // Number of AAC frames (RDBs (Raw Data Blocks)) in ADTS frame minus 1.
// 1 ADTS frame can contain up to 4 AAC frames
const numFrames = br.readBits(2) + 1;
if (numFrames <= 0) {
throw new Error(`Invalid number of AAC frames for ADTS header: ${numFrames}`);
}
- // FIXME: semantically our Frame info parsed represents potentially several AAC ones,
- // with the same container PTS value however (precision could be inferred from
- // sampling freq however).
- /*
- if (numFrames !== 1) {
- throw new Error(`Can not have AAC frame-number in ADTS header: ${numFrames} (only 1 is supported in this compatibility mode)`);
- }
- //*/
-
- this.currentFrame = {
+ this._currentFrame = {
headerLen,
accessUnitSize,
channels,
- profile,
+ aacObjectType,
sampleRate,
numFrames
};
- this.state = AdtsReaderState.READ_FRAME;
+ this._state = AdtsReaderState.READ_FRAME;
}
}
From 620bac57aaef7e452304b192b0cd8a832288b4b6 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 3 Aug 2022 23:31:17 +0200
Subject: [PATCH 170/197] pes-reader: fix skipping PES header extension
remainder after parsing timestamps (was causing garbage to get into payload
buffers and skip frames / loose sync)
+ change appendData impl to read right after every append instead of only on next PUSI (would increase demux latency by one frame unnecessarily)
+ add getStreamTypeName to resolve ES type enum to string name
+ assert PES header start code (to verify TS packet completeness)
---
src/demuxer/ts/pes-reader.ts | 20 ++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/src/demuxer/ts/pes-reader.ts b/src/demuxer/ts/pes-reader.ts
index 4736879..88e6518 100644
--- a/src/demuxer/ts/pes-reader.ts
+++ b/src/demuxer/ts/pes-reader.ts
@@ -43,14 +43,18 @@ export class PESReader {
this.payloadReader.onData = this.handlePayloadReadData.bind(this);
}
+ public getStreamTypeName(): string {
+ return MptsElementaryStreamType[this.type];
+ }
+
public onPayloadData(data: Uint8Array, dts: number, cto: number, naluType: number) {}
public appendData(payloadUnitStartIndicator: boolean, packet: BitReader): void {
if (payloadUnitStartIndicator) {
- this.payloadReader.read(this.currentDts, this.currentCto);
this.readHeader(packet);
}
this.payloadReader.append(packet, payloadUnitStartIndicator);
+ this.payloadReader.read(this.currentDts, this.currentCto);
}
public reset(): void {
@@ -62,14 +66,22 @@ export class PESReader {
}
private readHeader(packet: BitReader): void {
- packet.skipBytes(7);
- const [dts, pts] = parsePesHeaderTimestamps(packet);
+ const readStartCode = packet.readBits(24) === 1;
+ if (!readStartCode) {
+ throw new Error(`No start-code found parsing PES header`);
+ }
+
+ const streamId = packet.readByte();
+
+ const pesPacketLen = ((packet.readByte() << 8) | packet.readByte());
- // TODO: assert CTO >= 0 ?
+ const [dts, pts, remainderLen] = parsePesHeaderTimestamps(packet);
this.currentDts = dts;
this.currentCto = pts - dts;
+
+ packet.skipBytes(remainderLen);
}
private handlePayloadReadData(data: Uint8Array, dts: number, cto: number, naluType: number = NaN) {
From da92292f782956ab42a6cc51593419bda7d418e4 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 3 Aug 2022 23:52:54 +0200
Subject: [PATCH 171/197] pes header parse: skip remainder inside
parsePesHeader (renamed to express this function parses not only timestamps
but handle whole ext header)
---
src/demuxer/ts/payload/pes-header.ts | 27 +++++++++++++++++++++------
src/demuxer/ts/pes-reader.ts | 8 ++------
2 files changed, 23 insertions(+), 12 deletions(-)
diff --git a/src/demuxer/ts/payload/pes-header.ts b/src/demuxer/ts/payload/pes-header.ts
index 2757c0a..750b722 100644
--- a/src/demuxer/ts/payload/pes-header.ts
+++ b/src/demuxer/ts/payload/pes-header.ts
@@ -1,6 +1,11 @@
import { BitReader } from "../../../utils/bit-reader";
-export function parsePesHeaderTimestamps(packet: BitReader): [number, number, number] {
+/**
+ *
+ * @param packet
+ * @returns [dts, pts]
+ */
+export function parsePesHeader(packet: BitReader): [number, number] {
/**
* Thanks to Videojs/Muxjs for this bit, which does well the
* trick around 32-bit unary bit-ops and 33 bit numbers :)
@@ -19,13 +24,18 @@ export function parsePesHeaderTimestamps(packet: BitReader): [number, number, nu
const ptsDtsFlags = packet.readByte();
- const pesHeaderRemainderLen = packet.readByte();
+ // number of bytes following this bytes until payload data section
+ let headerRemainderLen = packet.readByte();
+
+ // The extension header size has variable length based on the present flags.
+ // We need to keep track how many bytes we will effectively read,
+ // as this will vary, in order to skip the remaining non-read bytes in an easy way,
+ // without having to treat all the possible flags and field lengths cases.
+ let packetBytesRemaining = packet.remainingBytes();
let pts = NaN;
let dts = NaN;
- let packetRemainder = packet.remainingBytes();
-
if (ptsDtsFlags & 0xC0) {
// the PTS and DTS are not written out directly. For information
// on how they are encoded, see
@@ -50,7 +60,12 @@ export function parsePesHeaderTimestamps(packet: BitReader): [number, number, nu
}
}
- let bytesRead = packetRemainder - packet.remainingBytes()
+ // count the bytes read since the timing section start
+ packetBytesRemaining -= packet.remainingBytes();
+ // subtract the read bytes from the header len read before
+ headerRemainderLen -= packetBytesRemaining;
+ // skip the bytes to point packet to data section
+ packet.skipBytes(headerRemainderLen);
- return [dts, pts, pesHeaderRemainderLen - bytesRead];
+ return [dts, pts];
}
diff --git a/src/demuxer/ts/pes-reader.ts b/src/demuxer/ts/pes-reader.ts
index 88e6518..85730fd 100644
--- a/src/demuxer/ts/pes-reader.ts
+++ b/src/demuxer/ts/pes-reader.ts
@@ -6,7 +6,7 @@ import { AdtsReader } from './payload/adts-reader';
import { H264Reader } from './payload/h264-reader';
import { ID3Reader } from './payload/id3-reader';
import { MpegReader } from './payload/mpeg-reader';
-import { parsePesHeaderTimestamps } from './payload/pes-header';
+import { parsePesHeader } from './payload/pes-header';
export enum MptsElementaryStreamType {
TS_STREAM_TYPE_AAC = 0x0F,
@@ -71,17 +71,13 @@ export class PESReader {
if (!readStartCode) {
throw new Error(`No start-code found parsing PES header`);
}
-
const streamId = packet.readByte();
-
const pesPacketLen = ((packet.readByte() << 8) | packet.readByte());
- const [dts, pts, remainderLen] = parsePesHeaderTimestamps(packet);
+ const [dts, pts] = parsePesHeader(packet);
this.currentDts = dts;
this.currentCto = pts - dts;
-
- packet.skipBytes(remainderLen);
}
private handlePayloadReadData(data: Uint8Array, dts: number, cto: number, naluType: number = NaN) {
From 3dbfcc12d6d4002adfe51c6657185d942288abe3 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 4 Aug 2022 00:19:07 +0200
Subject: [PATCH 172/197] adts-reader: re-enable assertion on mpegVersion field
and put sample-rate lookup back in order instead of deferred
---
src/demuxer/ts/payload/adts-reader.ts | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index 70e7e21..ab0bb51 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -183,11 +183,9 @@ export class AdtsReader extends PayloadReader {
br.skipBits(12);
const mpegVersion: number = br.readBool() ? 1 : 0; // MPEG Version: 0 for MPEG-4, 1 for MPEG-2
- /*
if (mpegVersion !== 0) {
throw new Error(`Expected in header-data MPEG-version flag = 0 (only MP4-audio supported), but signals MPEG-2!`);
}
- */
br.skipBits(2);
@@ -207,6 +205,10 @@ export class AdtsReader extends PayloadReader {
}
const sampleRateIndex: number = br.readBits(4);
+ if (sampleRateIndex < 0 || sampleRateIndex >= ADTS_SAMPLE_RATES.length) {
+ throw new Error(`Invalid AAC sampling-frequency index: ${sampleRateIndex}`);
+ }
+ const sampleRate = ADTS_SAMPLE_RATES[sampleRateIndex];
// private bit (unused by spec)
br.skipBits(1);
@@ -234,11 +236,6 @@ export class AdtsReader extends PayloadReader {
// Buffer fullness, states the bit-reservoir per frame.
br.skipBits(11);
- if (sampleRateIndex < 0 || sampleRateIndex >= ADTS_SAMPLE_RATES.length) {
- throw new Error(`Invalid AAC sampling-frequency index: ${sampleRateIndex}`);
- }
- const sampleRate = ADTS_SAMPLE_RATES[sampleRateIndex];
-
// Number of AAC frames (RDBs (Raw Data Blocks)) in ADTS frame minus 1.
// 1 ADTS frame can contain up to 4 AAC frames
const numFrames = br.readBits(2) + 1;
From adabc3c663ba714b97ffd2207d7dcf7c8fefd8f8 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 4 Aug 2022 00:19:53 +0200
Subject: [PATCH 173/197] pes-header optional fields parsing: add jsdocs and
other comments and documentation of header data specs
---
src/demuxer/ts/payload/pes-header.ts | 55 +++++++++++++++++++---------
1 file changed, 38 insertions(+), 17 deletions(-)
diff --git a/src/demuxer/ts/payload/pes-header.ts b/src/demuxer/ts/payload/pes-header.ts
index 750b722..b573b29 100644
--- a/src/demuxer/ts/payload/pes-header.ts
+++ b/src/demuxer/ts/payload/pes-header.ts
@@ -2,29 +2,33 @@ import { BitReader } from "../../../utils/bit-reader";
/**
*
- * @param packet
+ * @param {BitReader} packet PES packet-reader aligned to start of optional header section.
* @returns [dts, pts]
*/
-export function parsePesHeader(packet: BitReader): [number, number] {
- /**
- * Thanks to Videojs/Muxjs for this bit, which does well the
- * trick around 32-bit unary bit-ops and 33 bit numbers :)
- * -> See https://github.com/videojs/mux.js/blob/87f777f718b264df69a063847fe0fb9b5e0aaa6c/lib/m2ts/m2ts.js#L333
- */
- // PTS and DTS are normally stored as a 33-bit number. Javascript
- // performs all bitwise operations on 32-bit integers but javascript
- // supports a much greater range (52-bits) of integer using standard
- // mathematical operations.
- // We construct a 31-bit value using bitwise operators over the 31
- // most significant bits and then multiply by 4 (equal to a left-shift
- // of 2) before we add the final 2 least significant bits of the
- // timestamp (equal to an OR.)
+export function parsePesHeaderOptionalFields(packet: BitReader): [number, number] {
+ /*
+ Marker bits 2 10 binary or 0x2 hex
+ Scrambling control 2 00 implies not scrambled
+ Priority 1
+ Data alignment indicator 1 1 indicates that the PES packet header is immediately followed by the video start code or audio syncword
+ Copyright 1 1 implies copyrighted
+ Original or Copy 1 1 implies original
+ */
packet.skipBytes(1); // todo: parse the data-alignment idc
+ /*
+ PTS DTS indicator 2 11 = both present, 01 is forbidden, 10 = only PTS, 00 = no PTS or DTS
+ ESCR flag 1
+ ES rate flag 1
+ DSM trick mode flag 1
+ Additional copy info flag 1
+ CRC flag 1
+ extension flag 1
+ */
const ptsDtsFlags = packet.readByte();
- // number of bytes following this bytes until payload data section
+ // PES header length 8 gives the length of the remainder of the PES header in bytes
let headerRemainderLen = packet.readByte();
// The extension header size has variable length based on the present flags.
@@ -33,9 +37,26 @@ export function parsePesHeader(packet: BitReader): [number, number] {
// without having to treat all the possible flags and field lengths cases.
let packetBytesRemaining = packet.remainingBytes();
+ /*
+ Optional fields variable length presence is determined by flag bits above
+ Stuffing Bytes variable length 0xff
+ */
+
+ /**
+ * Thanks to Videojs/Muxjs for this bit, which does well the
+ * trick around 32-bit unary bit-ops and 33 bit numbers :)
+ * -> See https://github.com/videojs/mux.js/blob/87f777f718b264df69a063847fe0fb9b5e0aaa6c/lib/m2ts/m2ts.js#L333
+ */
+ // PTS and DTS are normally stored as a 33-bit number. Javascript
+ // performs all bitwise operations on 32-bit integers but javascript
+ // supports a much greater range (52-bits) of integer using standard
+ // mathematical operations.
+ // We construct a 31-bit value using bitwise operators over the 31
+ // most significant bits and then multiply by 4 (equal to a left-shift
+ // of 2) before we add the final 2 least significant bits of the
+ // timestamp (equal to an OR.)
let pts = NaN;
let dts = NaN;
-
if (ptsDtsFlags & 0xC0) {
// the PTS and DTS are not written out directly. For information
// on how they are encoded, see
From 9fc6552354c163251551674457c5bb146224ceab Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 4 Aug 2022 00:20:54 +0200
Subject: [PATCH 174/197] pes-reader rename method to appendPacket, improve
order of methods in class, add comments and docs and code style fixes
(private method prefix)
---
src/demuxer/ts/mpegts-demuxer.ts | 4 +--
src/demuxer/ts/pes-reader.ts | 47 ++++++++++++++++++++++----------
2 files changed, 34 insertions(+), 17 deletions(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index c2b5b21..476ad62 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -108,7 +108,7 @@ export class MpegTSDemuxer implements IDemuxer {
this.tracks[0] = new TSTrack(0,
Track.TYPE_AUDIO, Track.MIME_TYPE_AAC,
new PESReader(0, MptsElementaryStreamType.TS_STREAM_TYPE_AAC));
- this.tracks[0].pes.appendData(false, streamReader);
+ this.tracks[0].pes.appendPacket(false, streamReader);
}
}
@@ -197,7 +197,7 @@ export class MpegTSDemuxer implements IDemuxer {
const track: TSTrack = this.tracks[pid];
// handle case where PID not found?
if (track && track.pes) {
- track.pes.appendData(payloadUnitStartIndicator, packetReader);
+ track.pes.appendPacket(payloadUnitStartIndicator, packetReader);
}
}
}
diff --git a/src/demuxer/ts/pes-reader.ts b/src/demuxer/ts/pes-reader.ts
index 85730fd..e7699b9 100644
--- a/src/demuxer/ts/pes-reader.ts
+++ b/src/demuxer/ts/pes-reader.ts
@@ -6,7 +6,7 @@ import { AdtsReader } from './payload/adts-reader';
import { H264Reader } from './payload/h264-reader';
import { ID3Reader } from './payload/id3-reader';
import { MpegReader } from './payload/mpeg-reader';
-import { parsePesHeader } from './payload/pes-header';
+import { parsePesHeaderOptionalFields } from './payload/pes-header';
export enum MptsElementaryStreamType {
TS_STREAM_TYPE_AAC = 0x0F,
@@ -40,23 +40,13 @@ export class PESReader {
this.payloadReader = new UnknownReader();
}
- this.payloadReader.onData = this.handlePayloadReadData.bind(this);
+ this.payloadReader.onData = this._handlePayloadReadData.bind(this);
}
public getStreamTypeName(): string {
return MptsElementaryStreamType[this.type];
}
- public onPayloadData(data: Uint8Array, dts: number, cto: number, naluType: number) {}
-
- public appendData(payloadUnitStartIndicator: boolean, packet: BitReader): void {
- if (payloadUnitStartIndicator) {
- this.readHeader(packet);
- }
- this.payloadReader.append(packet, payloadUnitStartIndicator);
- this.payloadReader.read(this.currentDts, this.currentCto);
- }
-
public reset(): void {
this.payloadReader.reset();
}
@@ -65,7 +55,32 @@ export class PESReader {
this.payloadReader.flush(this.currentDts, this.currentCto);
}
- private readHeader(packet: BitReader): void {
+ /**
+ * Can be overriden instance-wise in userland as in "cheap and fast" event-target.
+ */
+ public onPayloadData(data: Uint8Array, dts: number, cto: number, naluType: number) {}
+
+ /**
+ * Expects a TS packet-reader aligned on its respective payload section.
+ */
+ public appendPacket(payloadUnitStartIndicator: boolean, packet: BitReader): void {
+ // a packet with PUSI flag starts with a PES header.
+ // reading it will update our internal DTS/CTO timing state to the current
+ // payload unit ie frame(s) contained within.
+ if (payloadUnitStartIndicator) {
+ // Q: call read before (if data buffer is filled)
+ // to take out timing alignment concern from payloadReader ?
+
+ // post: dts/cto updated, packet-reader aligned to payload data section
+ this._readHeader(packet);
+ }
+ // append to payload buffer (super-class generic method)
+ this.payloadReader.append(packet, payloadUnitStartIndicator);
+ // call the reader impl
+ this.payloadReader.read(this.currentDts, this.currentCto);
+ }
+
+ private _readHeader(packet: BitReader): void {
const readStartCode = packet.readBits(24) === 1;
if (!readStartCode) {
@@ -74,13 +89,15 @@ export class PESReader {
const streamId = packet.readByte();
const pesPacketLen = ((packet.readByte() << 8) | packet.readByte());
- const [dts, pts] = parsePesHeader(packet);
+ // parses the optional header section.
+ // reads the packet up to the data section in every case.
+ const [dts, pts] = parsePesHeaderOptionalFields(packet);
this.currentDts = dts;
this.currentCto = pts - dts;
}
- private handlePayloadReadData(data: Uint8Array, dts: number, cto: number, naluType: number = NaN) {
+ private _handlePayloadReadData(data: Uint8Array, dts: number, cto: number, naluType: number = NaN) {
if (!this.payloadReader.frames.length) return;
this.onPayloadData(data, dts, cto, naluType);
From 457051932825499dbf43b70aaae12e4aa463e40f Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 4 Aug 2022 00:25:53 +0200
Subject: [PATCH 175/197] adts-reader: remove ignoring channel-conf assertion
failed (re-enable the assertion for all values to throw)
---
src/demuxer/ts/payload/adts-reader.ts | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index ab0bb51..1053cff 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -215,9 +215,7 @@ export class AdtsReader extends PayloadReader {
const channelsConf = br.readBits(3);
if (channelsConf <= 0 || channelsConf >= 8) {
- // ignorining this for now, todo see why comes up in ffmpeg payload
- if (channelsConf != 0)
- throw new Error(`Channel configuration invalid value: ${channelsConf}`);
+ throw new Error(`Channel configuration invalid value: ${channelsConf}`);
}
const channels = channelsConf;
From 5fe5c86481f585d6d6a11763a9c4572909c762ac Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 4 Aug 2022 02:48:32 +0200
Subject: [PATCH 176/197] refactor track type to number-based enum (propagate
change in all container impls etc). better for perf than doing string
comparison also.
+ remove getter for types in track to make the class more lightweight
---
src/demuxer/mp4/mp4-demuxer.ts | 16 +++++------
src/demuxer/mp4/mp4-track.ts | 7 ++---
src/demuxer/track.ts | 48 +++++++++++--------------------
src/demuxer/ts/mpegts-demuxer.ts | 20 +++++++------
src/demuxer/ts/ts-track.ts | 4 +--
src/demuxer/webm/webm-track.ts | 49 ++++++++++++++------------------
6 files changed, 62 insertions(+), 82 deletions(-)
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index 6bff0a3..dc6eba6 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -2,7 +2,7 @@ import ByteParserUtils from '../../utils/byte-parser-utils';
import { IDemuxer, TracksHash } from '../demuxer';
-import { Track } from '../track';
+import { Track, TrackType } from '../track';
import { Mp4Track } from './mp4-track';
import { Mp4SampleTable } from './mp4-sample-table';
@@ -184,16 +184,16 @@ export class Mp4Demuxer implements IDemuxer {
// AVC/HEVC -> H264/5
case Atom.hvcC:
this.lastCodecDataAtom = atom as Hev1;
- this._attemptCreateTrack(Track.TYPE_VIDEO, Track.MIME_TYPE_HEVC, atom);
+ this._attemptCreateTrack(TrackType.VIDEO, Track.MIME_TYPE_HEVC, atom);
break;
case Atom.avcC:
this.lastCodecDataAtom = atom as AvcC;
- this._attemptCreateTrack(Track.TYPE_VIDEO, Track.MIME_TYPE_AVC, atom);
+ this._attemptCreateTrack(TrackType.VIDEO, Track.MIME_TYPE_AVC, atom);
break;
case Atom.esds:
- this._attemptCreateTrack(Track.TYPE_AUDIO, Track.MIME_TYPE_AAC, atom);
+ this._attemptCreateTrack(TrackType.AUDIO, Track.MIME_TYPE_AAC, atom);
this.lastCodecDataAtom = atom as Esds;
break;
@@ -283,9 +283,9 @@ export class Mp4Demuxer implements IDemuxer {
if (this.lastSampleTable) {
return;
}
- if (!this._getLastTrackCreated() || this._getLastTrackCreated().isOther()) {
+ if (!this._getLastTrackCreated() || !this._getLastTrackCreated().isAv()) {
this._attemptCreateUnknownTrack();
- warn('not unpacking sample table for unknown track');
+ warn('not unpacking sample table for non-AV track');
} else { // we only create a sample table representation for known track types
this.lastSampleTable = new Mp4SampleTable(this._getLastTrackCreated());
}
@@ -300,7 +300,7 @@ export class Mp4Demuxer implements IDemuxer {
}
}
- private _attemptCreateTrack(type: string, mime: string, ref: Atom) {
+ private _attemptCreateTrack(type: TrackType, mime: string, ref: Atom) {
if (!this.lastTrackId) {
throw new Error('No track-id set');
}
@@ -339,7 +339,7 @@ export class Mp4Demuxer implements IDemuxer {
}
this.tracks[this.lastTrackId] = new Mp4Track(
this.lastTrackId,
- Track.TYPE_UNKNOWN,
+ TrackType.UNKNOWN,
Track.MIME_TYPE_UNKNOWN,
null,
null,
diff --git a/src/demuxer/mp4/mp4-track.ts b/src/demuxer/mp4/mp4-track.ts
index 4bc735c..5722fa1 100644
--- a/src/demuxer/mp4/mp4-track.ts
+++ b/src/demuxer/mp4/mp4-track.ts
@@ -1,7 +1,6 @@
-import { toMicroseconds } from '../../utils/timescale';
import { FRAME_TYPE } from '../../codecs/h264/nal-units';
-import { Track } from '../track';
+import { Track, TrackType } from '../track';
import { Frame } from '../frame';
import { Atom } from './atoms/atom';
@@ -32,7 +31,7 @@ export class Mp4Track extends Track {
constructor(
id: number,
- type: string,
+ type: TrackType,
mimeType: string,
public referenceAtoms: Atom[],
public metadataAtom: AudioAtom | VideoAtom,
@@ -85,7 +84,7 @@ export class Mp4Track extends Track {
}
public getResolution(): [number, number] {
- if (!this.isVideo()) {
+ if (this.type !== TrackType.VIDEO) {
throw new Error('Can not get resolution of non-video track');
}
const avc1 = this.metadataAtom as Avc1;
diff --git a/src/demuxer/track.ts b/src/demuxer/track.ts
index e75faf0..bb8dd42 100644
--- a/src/demuxer/track.ts
+++ b/src/demuxer/track.ts
@@ -1,19 +1,19 @@
import { Frame } from './frame';
-import { toSecondsFromMicros } from '../utils/timescale';
-export abstract class Track {
+export enum TrackType {
+ VIDEO,
+ AUDIO,
+ TEXT,
+ COMPLEX,
+ LOGO,
+ BUTTONS,
+ CONTROL,
+ METADATA,
+ UNKNOWN
+}
- // FIXME: should be an enum type
- public static TYPE_VIDEO: string = 'video';
- public static TYPE_AUDIO: string = 'audio';
- public static TYPE_TEXT: string = 'text';
- public static TYPE_COMPLEX: string = 'complex';
- public static TYPE_LOGO: string = 'logo';
- public static TYPE_BUTTONS: string = 'buttons';
- public static TYPE_CONTROL: string = 'control';
- public static TYPE_UNKNOWN: string = 'unknown';
+export abstract class Track {
- // Here we don't need an enum
public static MIME_TYPE_AAC: string = 'audio/mp4a-latm';
public static MIME_TYPE_AVC: string = 'video/avc';
public static MIME_TYPE_HEVC: string = 'video/hevc';
@@ -28,25 +28,11 @@ export abstract class Track {
private _timeScale: number = NaN;
constructor(public id: number,
- public type: string /* fixme: make enum type */,
+ public type: TrackType,
public mimeType: string) {}
- public isVideo() {
- return this.type === Track.TYPE_VIDEO;
- }
-
- public isAudio() {
- return this.type === Track.TYPE_AUDIO;
- }
-
- public isText() {
- return this.type === Track.TYPE_TEXT;
- }
-
- public isOther() {
- return this.type !== Track.TYPE_TEXT
- && this.type !== Track.TYPE_AUDIO
- && this.type !== Track.TYPE_VIDEO;
+ public isAv() {
+ return this.type === TrackType.AUDIO || this.type === TrackType.VIDEO;
}
public update(): void {
@@ -59,8 +45,6 @@ export abstract class Track {
this.frames.length = 0;
}
- abstract getResolution(): [number, number];
-
public getFrames(): Frame[] {
return this.frames;
}
@@ -86,4 +70,6 @@ export abstract class Track {
public getMetadata() {
return {}
}
+
+ public abstract getResolution(): [number, number];
}
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index 476ad62..299e517 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -1,5 +1,5 @@
import { IDemuxer } from '../demuxer';
-import { Track } from '../track';
+import { Track, TrackType } from '../track';
import { MptsElementaryStreamType, PESReader } from './pes-reader';
import { TSTrack } from './ts-track';
@@ -106,7 +106,7 @@ export class MpegTSDemuxer implements IDemuxer {
} else {
const streamReader: BitReader = new BitReader(this._data);
this.tracks[0] = new TSTrack(0,
- Track.TYPE_AUDIO, Track.MIME_TYPE_AAC,
+ TrackType.AUDIO, Track.MIME_TYPE_AAC,
new PESReader(0, MptsElementaryStreamType.TS_STREAM_TYPE_AAC));
this.tracks[0].pes.appendPacket(false, streamReader);
}
@@ -233,24 +233,26 @@ export class MpegTSDemuxer implements IDemuxer {
bytesRemaining -= infoLength + 5;
if (!this.tracks[elementaryPid]) {
const pes: PESReader = new PESReader(elementaryPid, streamType);
- let type: string;
+ let type: TrackType;
let mimeType: string;
if (streamType === MptsElementaryStreamType.TS_STREAM_TYPE_AAC) {
- type = Track.TYPE_AUDIO;
+ type = TrackType.AUDIO;
mimeType = Track.MIME_TYPE_AAC;
} else if (streamType === MptsElementaryStreamType.TS_STREAM_TYPE_H264) {
- type = Track.TYPE_VIDEO;
+ type = TrackType.VIDEO;
mimeType = Track.MIME_TYPE_AVC;
} else if (streamType === MptsElementaryStreamType.TS_STREAM_TYPE_ID3) {
- type = Track.TYPE_TEXT;
+ type = TrackType.TEXT;
mimeType = Track.MIME_TYPE_ID3;
} else if (streamType === MptsElementaryStreamType.TS_STREAM_TYPE_MPA || streamType === MptsElementaryStreamType.TS_STREAM_TYPE_MPA_LSF) {
- type = Track.TYPE_AUDIO;
+ type = TrackType.AUDIO;
mimeType = Track.MIME_TYPE_MPEG;
} else if (streamType === MptsElementaryStreamType.TS_STREAM_TYPE_METADATA) {
- // do nothing
+
+ // todo: add support reading custom metadata
+ type = TrackType.METADATA;
} else {
- type = Track.TYPE_UNKNOWN;
+ type = TrackType.UNKNOWN;
mimeType = Track.MIME_TYPE_UNKNOWN;
}
this.tracks[elementaryPid] = new TSTrack(elementaryPid, type, mimeType, pes);
diff --git a/src/demuxer/ts/ts-track.ts b/src/demuxer/ts/ts-track.ts
index 8bf808b..0918049 100644
--- a/src/demuxer/ts/ts-track.ts
+++ b/src/demuxer/ts/ts-track.ts
@@ -1,10 +1,10 @@
-import { Track } from '../track';
+import { Track, TrackType } from '../track';
import { Frame } from '../frame';
import { PESReader } from './pes-reader';
export class TSTrack extends Track {
- constructor(id: number, type: string, mimeType: string,
+ constructor(id: number, type: TrackType, mimeType: string,
public pes: PESReader) {
super(id, type, mimeType);
diff --git a/src/demuxer/webm/webm-track.ts b/src/demuxer/webm/webm-track.ts
index 8d338a4..c971435 100644
--- a/src/demuxer/webm/webm-track.ts
+++ b/src/demuxer/webm/webm-track.ts
@@ -1,7 +1,7 @@
import ByteParserUtils from '../../utils/byte-parser-utils';
import { FRAME_TYPE } from '../../codecs/h264/nal-units';
-import { Track } from '../track';
+import { Track, TrackType } from '../track';
import { Frame } from '../frame';
import { ITrackInfo } from './elements/track-info';
@@ -17,7 +17,7 @@ export class WebMTrack extends Track {
private metadata: any;
constructor(info: ITrackInfo, metadata: any) {
- const type: string = WebMTrack.getType(info.TrackType);
+ const type = WebMTrack.getType(info.TrackType);
const codec: string = info.CodecName || WebMTrack.getCodecNameFromID(info.CodecID);
super(info.TrackNumber, type, type + '/' + codec);
@@ -30,31 +30,24 @@ export class WebMTrack extends Track {
this.timecodeScale = info.TrackTimecodeScale;
}
- private static getType(type: number): string {
+ private static getType(type: number): TrackType {
switch (type) {
- case 1:
- return Track.TYPE_VIDEO;
-
- case 2:
- return Track.TYPE_AUDIO;
-
- case 3:
- return Track.TYPE_COMPLEX;
-
- case 0x10:
- return Track.TYPE_LOGO;
-
- case 0x11:
- return Track.TYPE_TEXT;
-
- case 0x12:
- return Track.TYPE_BUTTONS;
-
- case 0x20:
- return Track.TYPE_CONTROL;
-
- default:
- return Track.TYPE_UNKNOWN;
+ case 1:
+ return TrackType.VIDEO;
+ case 2:
+ return TrackType.AUDIO;
+ case 3:
+ return TrackType.COMPLEX;
+ case 0x10:
+ return TrackType.LOGO;
+ case 0x11:
+ return TrackType.TEXT;
+ case 0x12:
+ return TrackType.BUTTONS;
+ case 0x20:
+ return TrackType.CONTROL;
+ default:
+ return TrackType.UNKNOWN;
}
}
@@ -83,7 +76,7 @@ export class WebMTrack extends Track {
if (!this.metadata) {
return null;
}
- if (this.type === Track.TYPE_VIDEO) {
+ if (this.type === TrackType.VIDEO) {
return {
codecSize: {
height: this.metadata.PixelHeight,
@@ -94,7 +87,7 @@ export class WebMTrack extends Track {
width: this.metadata.DisplayWidth,
}
};
- } else if (this.type === Track.TYPE_AUDIO) {
+ } else if (this.type === TrackType.AUDIO) {
return {
sampleRate: this.metadata.SamplingFrequency
};
From d737ad68b4c30feb30ce3201a90b2339b22c5351 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 4 Aug 2022 02:50:35 +0200
Subject: [PATCH 177/197] adts-reader fix buffer fill check prior header
parsing to safely support CRC case + misc code quality improvements without
functional changes
+ various comments for comprehension sharing
---
src/demuxer/ts/payload/adts-reader.ts | 79 ++++++++++++++++++---------
1 file changed, 53 insertions(+), 26 deletions(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index 1053cff..5353653 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -1,11 +1,18 @@
import { PayloadReader } from './payload-reader';
import { Track } from '../../track';
import { Frame } from '../../frame';
-import { FRAME_TYPE } from '../../../codecs/h264/nal-units';
import { BitReader } from '../../../utils/bit-reader';
import { MPEG_CLOCK_HZ } from '../../../utils/timescale';
-import { AAC_FRAME_SAMPLES_NUM, ADTS_CRC_SIZE, ADTS_HEADER_LEN, ADTS_SAMPLE_RATES } from './adts-consts';
+
+import {
+ AAC_FRAME_SAMPLES_NUM,
+ ADTS_CRC_SIZE,
+ ADTS_HEADER_LEN,
+ ADTS_SAMPLE_RATES
+} from './adts-consts';
+
+import { FRAME_TYPE } from '../../../codecs/h264/nal-units';
enum AdtsReaderState {
FIND_SYNC,
@@ -13,6 +20,8 @@ enum AdtsReaderState {
READ_FRAME
}
+const ADTS_HEADER_LEN_WITH_CRC = ADTS_HEADER_LEN + ADTS_CRC_SIZE;
+
export interface AdtsFrameInfo {
aacObjectType: number;
channels: number;
@@ -27,7 +36,6 @@ export class AdtsReader extends PayloadReader {
private _state: AdtsReaderState = AdtsReaderState.FIND_SYNC;
private _currentFrame: AdtsFrameInfo | null = null;
- private _currentFrameDuration: number = NaN;
private _frameDtsOffset: number = 0;
@@ -53,7 +61,11 @@ export class AdtsReader extends PayloadReader {
let needMoreData = false;
while (!needMoreData && this.dataOffset < this.dataBuffer.byteLength) {
+ // only update our clock upon next sync-word,
+ // as we may get fed PUSI packets
+ // with partially read prior timed frames in buffer.
if (this._state === AdtsReaderState.FIND_SYNC) {
+ // reset offset when PES timing updated
if (this.dts !== dts) {
this._frameDtsOffset = 0;
}
@@ -62,11 +74,17 @@ export class AdtsReader extends PayloadReader {
switch (this._state) {
case AdtsReaderState.FIND_SYNC:
- needMoreData = ! this._findNextSync(); // returns true when sync found
+ // true when sync when
+ if (this._findNextSync()) {
+ this._state = AdtsReaderState.READ_HEADER;
+ } else {
+ needMoreData = true;
+ }
break;
case AdtsReaderState.READ_HEADER:
- if (this.dataBuffer.byteLength - this.dataOffset < ADTS_HEADER_LEN) {
+ if (this.dataBuffer.byteLength - this.dataOffset
+ < ADTS_HEADER_LEN_WITH_CRC) { // assume longest possible header
needMoreData = true;
break;
}
@@ -80,10 +98,16 @@ export class AdtsReader extends PayloadReader {
this._state = AdtsReaderState.FIND_SYNC;
throw new Error(errMsg);
}
+ this._state = AdtsReaderState.READ_FRAME;
break;
case AdtsReaderState.READ_FRAME:
- const { headerLen, accessUnitSize, sampleRate, numFrames } = this._currentFrame;
+ const {
+ headerLen,
+ accessUnitSize,
+ sampleRate,
+ numFrames // AAC frames in ADTS payload
+ } = this._currentFrame;
if (this.dataBuffer.byteLength - this.dataOffset
< headerLen + accessUnitSize) {
@@ -95,6 +119,8 @@ export class AdtsReader extends PayloadReader {
= Math.round(sampleRate * this.dts / MPEG_CLOCK_HZ);
frameDtsAudioRate += this._frameDtsOffset;
+ // effective frame duration results in amount of AAC frames
+ // contained by the current ADTS header (up to 4).
const frameDuration = numFrames * AAC_FRAME_SAMPLES_NUM;
this._frameDtsOffset += frameDuration;
@@ -123,6 +149,7 @@ export class AdtsReader extends PayloadReader {
}
}
+ // prune buffer to remaining data
this.dataBuffer = this.dataBuffer.subarray(this.dataOffset);
this.dataOffset = 0;
}
@@ -145,19 +172,20 @@ export class AdtsReader extends PayloadReader {
// cond is false if buffer byteLength = 1 also (guarded from that case early
// as it is expected further below nextDataOffset >= 0).
for (let i: number = this.dataOffset; i < nextDataOffset; i++) {
- const dataRead: number = ((this.dataBuffer[i]) << 8) | (this.dataBuffer[i + 1]);
+
+ const wordData: number = ((this.dataBuffer[i]) << 8) | (this.dataBuffer[i + 1]);
+
/**
* A 12 syncword 0xFFF, all bits must be 1
* B 1 MPEG Version: 0 for MPEG-4, 1 for MPEG-2
* C 2 Layer: always 0
* D 1 protection absent, Warning, set to 1 if there is no CRC and 0 if there is CRC
*/
-
// 0b6 = 0110 mask to ignore the mpeg-version and CRC bit,
// and assert check for layer bits to be zero
- if ((dataRead & 0xfff6) === 0xfff0) {
+ // (additional assertion with regard to broken packet data or misc parser errors).
+ if ((wordData & 0xfff6) === 0xfff0) {
this.dataOffset = i;
- this._state = AdtsReaderState.READ_HEADER;
return true;
}
@@ -177,20 +205,21 @@ export class AdtsReader extends PayloadReader {
// it will keep null state, as we only set the frame after success.
this._currentFrame = null;
- const br: BitReader = new BitReader(
+ const reader: BitReader = new BitReader(
this.dataBuffer.subarray(this.dataOffset, this.dataBuffer.byteLength));
- br.skipBits(12);
+ // skip sync-word
+ reader.skipBits(12);
- const mpegVersion: number = br.readBool() ? 1 : 0; // MPEG Version: 0 for MPEG-4, 1 for MPEG-2
+ const mpegVersion: number = reader.readBool() ? 1 : 0; // MPEG Version: 0 for MPEG-4, 1 for MPEG-2
if (mpegVersion !== 0) {
throw new Error(`Expected in header-data MPEG-version flag = 0 (only MP4-audio supported), but signals MPEG-2!`);
}
- br.skipBits(2);
+ reader.skipBits(2);
- const hasCrc: boolean = ! br.readBool();
- const headerLen = hasCrc ? ADTS_HEADER_LEN + ADTS_CRC_SIZE : ADTS_HEADER_LEN;
+ const hasCrc: boolean = ! reader.readBool();
+ const headerLen = hasCrc ? ADTS_HEADER_LEN_WITH_CRC : ADTS_HEADER_LEN;
/**
* 1: AAC Main
@@ -199,31 +228,31 @@ export class AdtsReader extends PayloadReader {
* 4: AAC LTP (Long Term Prediction)
*/
// profile, the MPEG-4 Audio Object Type minus 1
- const aacObjectType = br.readBits(2) + 1; // bits value range 0-3
+ const aacObjectType = reader.readBits(2) + 1; // bits value range 0-3
if (aacObjectType <= 0 || aacObjectType >= 5) {
throw new Error(`Unsupported or likely invalid AAC profile (MPEG-4 Audio Object Type): ${aacObjectType}`);
}
- const sampleRateIndex: number = br.readBits(4);
+ const sampleRateIndex: number = reader.readBits(4);
if (sampleRateIndex < 0 || sampleRateIndex >= ADTS_SAMPLE_RATES.length) {
throw new Error(`Invalid AAC sampling-frequency index: ${sampleRateIndex}`);
}
const sampleRate = ADTS_SAMPLE_RATES[sampleRateIndex];
// private bit (unused by spec)
- br.skipBits(1);
+ reader.skipBits(1);
- const channelsConf = br.readBits(3);
+ const channelsConf = reader.readBits(3);
if (channelsConf <= 0 || channelsConf >= 8) {
throw new Error(`Channel configuration invalid value: ${channelsConf}`);
}
const channels = channelsConf;
// originality/home/copyright bits (ignoring)
- br.skipBits(4);
+ reader.skipBits(4);
// ADTS frame len including the header itself (also opt CRC 2 bytes).
- const adtsFrameLen = br.readBits(13);
+ const adtsFrameLen = reader.readBits(13);
if (adtsFrameLen <= 0) {
throw new Error(`Invalid ADTS-frame byte-length: ${adtsFrameLen}`);
@@ -232,11 +261,11 @@ export class AdtsReader extends PayloadReader {
const accessUnitSize = adtsFrameLen - headerLen;
// Buffer fullness, states the bit-reservoir per frame.
- br.skipBits(11);
+ reader.skipBits(11);
// Number of AAC frames (RDBs (Raw Data Blocks)) in ADTS frame minus 1.
// 1 ADTS frame can contain up to 4 AAC frames
- const numFrames = br.readBits(2) + 1;
+ const numFrames = reader.readBits(2) + 1;
if (numFrames <= 0) {
throw new Error(`Invalid number of AAC frames for ADTS header: ${numFrames}`);
}
@@ -249,7 +278,5 @@ export class AdtsReader extends PayloadReader {
sampleRate,
numFrames
};
-
- this._state = AdtsReaderState.READ_FRAME;
}
}
From cbe4d6ad67b6d439e9c3d392be367e0407225003 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 4 Aug 2022 23:58:00 +0200
Subject: [PATCH 178/197] adts-reader: fix comment
---
src/demuxer/ts/payload/adts-reader.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index 5353653..1ff132c 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -194,7 +194,7 @@ export class AdtsReader extends PayloadReader {
// start further read at current second-last
// attention: this assignment assumes nextDataOffset was computed
- // via a non-zero byteLength of read buffer!
+ // via a non-zero byteLength of read buffer, which we assert at top.
this.dataOffset = nextDataOffset;
return false;
}
From f64b87ca8c9f5d7f47797c332c62b5773c7210e1 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Fri, 5 Aug 2022 03:08:35 +0200
Subject: [PATCH 179/197] pes-reader: refactor constructor stream-type handling
by switch-case + add TS_STREAM_TYPE_PACKETIZED_DATA + correct value for
METADATA entry
(value for metadata type was the one actually used for packetized-data)
---
src/demuxer/ts/pes-reader.ts | 25 +++++++++++++++++--------
1 file changed, 17 insertions(+), 8 deletions(-)
diff --git a/src/demuxer/ts/pes-reader.ts b/src/demuxer/ts/pes-reader.ts
index e7699b9..fb54ffd 100644
--- a/src/demuxer/ts/pes-reader.ts
+++ b/src/demuxer/ts/pes-reader.ts
@@ -14,7 +14,8 @@ export enum MptsElementaryStreamType {
TS_STREAM_TYPE_ID3 = 0x15,
TS_STREAM_TYPE_MPA = 0x03,
TS_STREAM_TYPE_MPA_LSF = 0x04,
- TS_STREAM_TYPE_METADATA = 0x06
+ TS_STREAM_TYPE_METADATA = 0x15,
+ TS_STREAM_TYPE_PACKETIZED_DATA = 0x06
}
export class PESReader {
@@ -26,18 +27,26 @@ export class PESReader {
constructor(public pid: number, public type: MptsElementaryStreamType) {
- if (type === MptsElementaryStreamType.TS_STREAM_TYPE_AAC) {
+ switch(type) {
+ case MptsElementaryStreamType.TS_STREAM_TYPE_AAC:
this.payloadReader = new AdtsReader();
- } else if (type === MptsElementaryStreamType.TS_STREAM_TYPE_H264) {
+ break;
+ case MptsElementaryStreamType.TS_STREAM_TYPE_H264:
this.payloadReader = new H264Reader();
- } else if (type === MptsElementaryStreamType.TS_STREAM_TYPE_ID3) {
+ break;
+ case MptsElementaryStreamType.TS_STREAM_TYPE_ID3:
this.payloadReader = new ID3Reader();
- } else if (type === MptsElementaryStreamType.TS_STREAM_TYPE_MPA || type === MptsElementaryStreamType.TS_STREAM_TYPE_MPA_LSF) {
+ break;
+ case MptsElementaryStreamType.TS_STREAM_TYPE_MPA:
+ case MptsElementaryStreamType.TS_STREAM_TYPE_MPA_LSF:
this.payloadReader = new MpegReader();
- } else if (type === MptsElementaryStreamType.TS_STREAM_TYPE_METADATA) {
- this.payloadReader = new UnknownReader();
- } else {
+ break;
+ case MptsElementaryStreamType.TS_STREAM_TYPE_METADATA:
+ case MptsElementaryStreamType.TS_STREAM_TYPE_PACKETIZED_DATA:
+ break;
+ default:
this.payloadReader = new UnknownReader();
+ break;
}
this.payloadReader.onData = this._handlePayloadReadData.bind(this);
From 331a5b127ae175ea55db24a207ab37c6e35dff63 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 18 Aug 2022 15:17:05 +0200
Subject: [PATCH 180/197] rm blanks
---
src/demuxer/ts/ts-track.ts | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/demuxer/ts/ts-track.ts b/src/demuxer/ts/ts-track.ts
index 0918049..25ac199 100644
--- a/src/demuxer/ts/ts-track.ts
+++ b/src/demuxer/ts/ts-track.ts
@@ -21,6 +21,4 @@ export class TSTrack extends Track {
popFrames(wholePayloadUnits: boolean = true): Frame[] {
return this.pes?.payloadReader?.popFrames(wholePayloadUnits) || [];
}
-
-
}
From 90104bee77ebea87a634d5f3c17db58099789dcf Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 18 Aug 2022 15:30:45 +0200
Subject: [PATCH 181/197] mpts demux: remove all concepts around update-track,
it seems the frames should be appended the right order ITFP, and re-order on
every append seems very unlazy for such an edge-case.
rm update-tracks calls around all other demuxer impls, rename enum to MpegContainerType, fix method prefixes, rename to _parseTsPackets + disable RAW AAC support in container type detection (see comment, should be support by own demux, and as this is mostly an HLS corner case)
---
src/demuxer/mp4/mp4-demuxer.ts | 16 +-------
src/demuxer/track.ts | 7 +---
src/demuxer/ts/mpegts-demuxer.ts | 66 ++++++++++++++++----------------
src/demuxer/webm/webm-demuxer.ts | 14 +------
4 files changed, 38 insertions(+), 65 deletions(-)
diff --git a/src/demuxer/mp4/mp4-demuxer.ts b/src/demuxer/mp4/mp4-demuxer.ts
index dc6eba6..7380f6f 100644
--- a/src/demuxer/mp4/mp4-demuxer.ts
+++ b/src/demuxer/mp4/mp4-demuxer.ts
@@ -50,15 +50,11 @@ export class Mp4Demuxer implements IDemuxer {
public append(data: Uint8Array): void {
this.atoms = this._parseAtoms(data);
- // "HACK" digest any last sample-table
+ // digest any last sample-table
this._digestSampleTable();
-
- this._updateTracks();
}
- public end(): void {
- this._updateTracks();
- }
+ public end(): void {}
public flush() {
this.atoms.length = 0;
@@ -73,14 +69,6 @@ export class Mp4Demuxer implements IDemuxer {
return this.atoms;
}
- private _updateTracks(): void {
- for (const trackId in this.tracks) {
- if (this.tracks.hasOwnProperty(trackId)) {
- this.tracks[trackId].update();
- }
- }
- }
-
private _parseAtoms(data: Uint8Array, offset: number = 0): Atom[] {
const atoms: Atom[] = [];
diff --git a/src/demuxer/track.ts b/src/demuxer/track.ts
index bb8dd42..803b8f7 100644
--- a/src/demuxer/track.ts
+++ b/src/demuxer/track.ts
@@ -23,6 +23,7 @@ export abstract class Track {
public static MIME_TYPE_ID3: string = 'application/id3';
public static MIME_TYPE_UNKNOWN: string = 'unknown';
+ // not used by TS-track subclass ...
protected frames: Frame[] = [];
private _timeScale: number = NaN;
@@ -35,12 +36,6 @@ export abstract class Track {
return this.type === TrackType.AUDIO || this.type === TrackType.VIDEO;
}
- public update(): void {
- this.frames = this.getFrames().sort((a: Frame, b: Frame): number => {
- return a.dts - b.dts;
- });
- }
-
public flush() {
this.frames.length = 0;
}
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index 299e517..f427e58 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -5,8 +5,8 @@ import { MptsElementaryStreamType, PESReader } from './pes-reader';
import { TSTrack } from './ts-track';
import { BitReader } from '../../utils/bit-reader';
-enum CONTAINER_TYPE {
- UNKNOWN = 1,
+enum MpegContainerType {
+ UNKNOWN,
MPEG_TS,
RAW_AAC,
RAW_MPEG_AUDIO
@@ -19,7 +19,7 @@ export class MpegTSDemuxer implements IDemuxer {
public tracks: { [id: number] : TSTrack; } = {};
- private _containerType: CONTAINER_TYPE = CONTAINER_TYPE.UNKNOWN;
+ private _containerType: MpegContainerType = MpegContainerType.UNKNOWN;
private _data: Uint8Array;
private _dataOffset: number;
@@ -54,7 +54,6 @@ export class MpegTSDemuxer implements IDemuxer {
}
this._parse();
- this._updateTracks();
if (pruneAfterParse) {
return this.prune();
@@ -85,12 +84,6 @@ export class MpegTSDemuxer implements IDemuxer {
}
public end(): void {
- for (const trackId in this.tracks) {
- if (this.tracks.hasOwnProperty(trackId)) {
- (this.tracks[trackId] as TSTrack).pes.flush();
- this.tracks[trackId].update();
- }
- }
this._data = null;
this._dataOffset = 0;
}
@@ -101,49 +94,58 @@ export class MpegTSDemuxer implements IDemuxer {
this._findContainerType();
- if (this._containerType === CONTAINER_TYPE.MPEG_TS) {
- this._readPackets();
+ if (this._containerType === MpegContainerType.MPEG_TS) {
+ this._parseTsPackets();
} else {
- const streamReader: BitReader = new BitReader(this._data);
- this.tracks[0] = new TSTrack(0,
- TrackType.AUDIO, Track.MIME_TYPE_AAC,
- new PESReader(0, MptsElementaryStreamType.TS_STREAM_TYPE_AAC));
- this.tracks[0].pes.appendPacket(false, streamReader);
+ this._parseRawEsPackets();
}
}
- private _updateTracks(): void {
- for (const trackId in this.tracks) {
- if (this.tracks.hasOwnProperty(trackId)) {
- this.tracks[trackId].update();
- }
- }
+ private _parseRawEsPackets() {
+ const streamReader: BitReader = new BitReader(this._data);
+ this.tracks[0] = new TSTrack(0,
+ TrackType.AUDIO, Track.MIME_TYPE_AAC,
+ new PESReader(0, MptsElementaryStreamType.TS_STREAM_TYPE_AAC));
+ this.tracks[0].pes.appendPacket(false, streamReader);
}
private _findContainerType(): void {
- if (this._containerType !== CONTAINER_TYPE.UNKNOWN) return;
+ if (this._containerType !== MpegContainerType.UNKNOWN) return;
while (this._dataOffset < this._data.byteLength) {
if (this._data[this._dataOffset] === MpegTSDemuxer.MPEGTS_SYNC) {
- this._containerType = CONTAINER_TYPE.MPEG_TS;
+ this._containerType = MpegContainerType.MPEG_TS;
break;
- } else if ((this._data.byteLength - this._dataOffset) >= 4) {
+ }
+
+ // Q: Makes sense supporting this here, or better in specific demux impl?
+ // Doing that in this way relates mostly to handling transparently
+ // HLS segments probably. But generally doesnt really give any gain
+ // to support plain ADTS/AAC as a container in an MPEG-TS demuxer?
+ // Format detection should be done on the payload before creating
+ // any specific demuxer for it?
+ // For sure, this current code-path may rather spawn unexpected side-effects,
+ // while being an extreme corner-case on usage side.
+ /*
+ else if ((this._data.byteLength - this._dataOffset) >= 4) {
const dataRead: number = (this._data[this._dataOffset] << 8) | (this._data[this._dataOffset + 1]);
if (dataRead === 0x4944 || (dataRead & 0xfff6) === 0xfff0) {
- this._containerType = CONTAINER_TYPE.RAW_AAC;
+ this._containerType = MpegContainerType.RAW_AAC;
break;
}
}
+ */
+
this._dataOffset++;
}
- if (this._containerType === CONTAINER_TYPE.UNKNOWN) {
- throw new Error('Format not supported');
+ if (this._containerType === MpegContainerType.UNKNOWN) {
+ throw new Error('Transport stream packets format not recognized');
}
}
- private _readPackets(): void {
+ private _parseTsPackets(): void {
// run as long as there is at least a full packet in buffer
while ((this._data.byteLength - this._dataOffset) >= MpegTSDemuxer.MPEGTS_PACKET_SIZE) {
@@ -157,11 +159,11 @@ export class MpegTSDemuxer implements IDemuxer {
const packet: Uint8Array = this._data.subarray(this._dataOffset + 1,
this._dataOffset + MpegTSDemuxer.MPEGTS_PACKET_SIZE);
this._dataOffset += MpegTSDemuxer.MPEGTS_PACKET_SIZE;
- this._processTsPacket(packet);
+ this._readTsPacket(packet);
}
}
- private _processTsPacket(packet: Uint8Array): void {
+ private _readTsPacket(packet: Uint8Array): void {
this._packetsCount++;
diff --git a/src/demuxer/webm/webm-demuxer.ts b/src/demuxer/webm/webm-demuxer.ts
index e26667d..c8cdf5d 100644
--- a/src/demuxer/webm/webm-demuxer.ts
+++ b/src/demuxer/webm/webm-demuxer.ts
@@ -43,13 +43,9 @@ export class WebMDemuxer implements IDemuxer {
this.data = this.data.subarray(this.dataOffset);
this.dataOffset = 0;
}
-
- this.updateTracks();
}
- public end(): void {
- this.updateTracks();
- }
+ public end(): void {}
private parseElements(end: number): EbmlElement[] {
const elements: EbmlElement[] = [];
@@ -204,12 +200,4 @@ export class WebMDemuxer implements IDemuxer {
}
return null;
}
-
- private updateTracks(): void {
- for (const trackId in this.tracks) {
- if (this.tracks.hasOwnProperty(trackId)) {
- this.tracks[trackId].update();
- }
- }
- }
}
From aa8fc0e56bd5e337d1aa1ac98c00a9eff01307f4 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Sat, 20 Aug 2022 16:50:05 +0200
Subject: [PATCH 182/197] track model: make frames property abstract readonly
to be implemented by getter in either subclass, to be able to debug and plain
serialize the generic object type. has us remove also remove getFrames
overload in TSTrack
---
src/demuxer/mp4/mp4-track.ts | 10 +++++++---
src/demuxer/track.ts | 5 ++---
src/demuxer/ts/ts-track.ts | 6 ++----
src/demuxer/webm/webm-track.ts | 17 ++++++++++++-----
4 files changed, 23 insertions(+), 15 deletions(-)
diff --git a/src/demuxer/mp4/mp4-track.ts b/src/demuxer/mp4/mp4-track.ts
index 5722fa1..1811ebe 100644
--- a/src/demuxer/mp4/mp4-track.ts
+++ b/src/demuxer/mp4/mp4-track.ts
@@ -18,6 +18,8 @@ export type Mp4TrackDefaults = {
export class Mp4Track extends Track {
+ private _frames: Frame[] = [];
+
private baseDataOffset: number = 0;
private baseMediaDecodeTime: number = 0;
@@ -44,6 +46,8 @@ export class Mp4Track extends Track {
}
}
+ get frames() { return this._frames; }
+
/**
* post: endDts ie duration incremented by frame duration
* @param frame
@@ -51,7 +55,7 @@ export class Mp4Track extends Track {
*/
public appendFrame(frame: Frame) {
this.endDts += frame.duration;
- this.frames.push(frame);
+ this._frames.push(frame);
}
public flush() {
@@ -66,8 +70,8 @@ export class Mp4Track extends Track {
}
public getDuration() {
- return this.frames.length ?
- this.endDts - this.frames[0].dts : 0;
+ return this._frames.length ?
+ this.endDts - this._frames[0].dts : 0;
}
public getDurationInSeconds() {
diff --git a/src/demuxer/track.ts b/src/demuxer/track.ts
index 803b8f7..799ab4d 100644
--- a/src/demuxer/track.ts
+++ b/src/demuxer/track.ts
@@ -23,15 +23,14 @@ export abstract class Track {
public static MIME_TYPE_ID3: string = 'application/id3';
public static MIME_TYPE_UNKNOWN: string = 'unknown';
- // not used by TS-track subclass ...
- protected frames: Frame[] = [];
-
private _timeScale: number = NaN;
constructor(public id: number,
public type: TrackType,
public mimeType: string) {}
+ abstract readonly frames: Frame[];
+
public isAv() {
return this.type === TrackType.AUDIO || this.type === TrackType.VIDEO;
}
diff --git a/src/demuxer/ts/ts-track.ts b/src/demuxer/ts/ts-track.ts
index 25ac199..68cb666 100644
--- a/src/demuxer/ts/ts-track.ts
+++ b/src/demuxer/ts/ts-track.ts
@@ -10,14 +10,12 @@ export class TSTrack extends Track {
super(id, type, mimeType);
}
+ get frames() { return this?.pes?.payloadReader.frames || []; }
+
getResolution(): [number, number] {
return [0, 0];
}
- getFrames(): Frame[] {
- return this?.pes?.payloadReader.frames || [];
- }
-
popFrames(wholePayloadUnits: boolean = true): Frame[] {
return this.pes?.payloadReader?.popFrames(wholePayloadUnits) || [];
}
diff --git a/src/demuxer/webm/webm-track.ts b/src/demuxer/webm/webm-track.ts
index c971435..d3cb1d1 100644
--- a/src/demuxer/webm/webm-track.ts
+++ b/src/demuxer/webm/webm-track.ts
@@ -9,6 +9,8 @@ import { Vint, EbmlElement } from './ebml/ebml-types';
export class WebMTrack extends Track {
+ private _frames: Frame[];
+
private lastPts: number;
private nsPerFrame: number;
private lastTimecodeBase: number;
@@ -21,15 +23,20 @@ export class WebMTrack extends Track {
const codec: string = info.CodecName || WebMTrack.getCodecNameFromID(info.CodecID);
super(info.TrackNumber, type, type + '/' + codec);
+ // explicit init after super-call needed
+ this._frames = [];
+ this.lastPts = 0;
+ this.lastTimecodeBase = 0;
+
this.type = type;
this.codec = codec;
this.metadata = metadata;
- this.lastPts = 0;
this.nsPerFrame = info.DefaultDuration;
- this.lastTimecodeBase = 0;
this.timecodeScale = info.TrackTimecodeScale;
}
+ get frames() { return this._frames; }
+
private static getType(type: number): TrackType {
switch (type) {
case 1:
@@ -67,7 +74,7 @@ export class WebMTrack extends Track {
}
public getFrames(): Frame[] {
- return this.frames;
+ return this._frames;
}
public getMetadata(): any { // FIXME: Seems this is the only implementation and it violates the base-class return-type
@@ -106,13 +113,13 @@ export class WebMTrack extends Track {
this.lastPts = 1000 * ((this.lastTimecodeBase + timecode) / (this.timecodeScale > 0 ? this.timecodeScale : 1));
if (element.name === 'SimpleBlock' && flags & 0x80) {
- this.frames.push(new Frame(
+ this._frames.push(new Frame(
FRAME_TYPE.I,
this.lastPts, 0, 0,
buffer.length
));
} else {
- this.frames.push(new Frame(
+ this._frames.push(new Frame(
FRAME_TYPE.P,
this.lastPts, 0, 0,
buffer.length
From 4c5cc5e0c6fc0138b9a9b93c6bb78ed7f7c54c93 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Sat, 20 Aug 2022 18:27:10 +0200
Subject: [PATCH 183/197] mpts generic payload reader: fix bug in popFrames
where instead resetting _lastPusiFramesLen = 0 it was wrongly set to the
remainder frame list len (which would make any sense but work by change in
most cases,
where this was 0 then anyhow). the bug would cause the segmentation to come out of pace of the actual payload buffer.
---
src/demuxer/ts/payload/payload-reader.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/demuxer/ts/payload/payload-reader.ts b/src/demuxer/ts/payload/payload-reader.ts
index 8823b82..a472b38 100644
--- a/src/demuxer/ts/payload/payload-reader.ts
+++ b/src/demuxer/ts/payload/payload-reader.ts
@@ -83,12 +83,13 @@ export abstract class PayloadReader {
public popFrames(wholePayloadUnits: boolean = true): Frame[] {
let numFrames = wholePayloadUnits ? this._lastPusiFramesLen : this.frames.length;
+ // shortcut fast case opti
if (numFrames === 0) return [];
// split-slice frame-list:
// returns slice to pop, mutates list to remainder (deletes sliced items)
const frames = this.frames.splice(0, numFrames);
// set current payload-unit frame-count to remainder length
- this._lastPusiFramesLen = this.frames.length;
+ this._lastPusiFramesLen = 0;
this._pusiCount = 0;
return frames;
}
From 8d7a9efdbda5de278f4d4b4542423685a6dd4d3b Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Sat, 20 Aug 2022 18:55:14 +0200
Subject: [PATCH 184/197] mpts payload reader: add better explanations around
popFrames logic and prior bugfix
---
src/demuxer/ts/payload/payload-reader.ts | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/src/demuxer/ts/payload/payload-reader.ts b/src/demuxer/ts/payload/payload-reader.ts
index a472b38..855e440 100644
--- a/src/demuxer/ts/payload/payload-reader.ts
+++ b/src/demuxer/ts/payload/payload-reader.ts
@@ -82,15 +82,25 @@ export abstract class PayloadReader {
}
public popFrames(wholePayloadUnits: boolean = true): Frame[] {
+ // determine number of frames to splice
let numFrames = wholePayloadUnits ? this._lastPusiFramesLen : this.frames.length;
- // shortcut fast case opti
+ // early return shortcut opti:
if (numFrames === 0) return [];
+
// split-slice frame-list:
// returns slice to pop, mutates list to remainder (deletes sliced items)
const frames = this.frames.splice(0, numFrames);
- // set current payload-unit frame-count to remainder length
+
+ // reset pusi related counters:
+ //
+ // note: prior bug would erraticaly set this to remainder length,
+ // which would cause popFrames to return frames not yet completed in buffer,
+ // thus bringing frame output and actual payload out of whack and
+ // therefore making the assumptions upon PES packet segmentation made on parsing input
+ // to fail in runtime assertions.
this._lastPusiFramesLen = 0;
this._pusiCount = 0;
+
return frames;
}
}
From 44c8461529bfa03a085a24ca2d77ab96f5af4dc7 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Mon, 5 Sep 2022 23:32:54 +0200
Subject: [PATCH 185/197] adts-reader: add compile-time const
ADTS_HEADER_ASSERT_MPEG_VERSION with default to false, see commit description
below ie comment in code.
// some popular encoders set this to MPEG2 still, even though sending AAC.
// performing the assertion (that expects MP4A compliant i.e unset),
// will then lead to an error and failure to parse every frame then.
// by default we skip this assertion to be more tolerant of either encoders,
// and assume payload advertisement is done in PMT either way and content
// validated/detected to be AAC downstream further anyhow.
// Therefore:
// Only enable this, if you know you need the parser to fail on input data where the bit is set wrongly.
---
src/demuxer/ts/payload/adts-reader.ts | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/src/demuxer/ts/payload/adts-reader.ts b/src/demuxer/ts/payload/adts-reader.ts
index 1ff132c..377e4d4 100644
--- a/src/demuxer/ts/payload/adts-reader.ts
+++ b/src/demuxer/ts/payload/adts-reader.ts
@@ -22,6 +22,16 @@ enum AdtsReaderState {
const ADTS_HEADER_LEN_WITH_CRC = ADTS_HEADER_LEN + ADTS_CRC_SIZE;
+// some popular encoders set this to MPEG2 still, even though sending AAC.
+// performing the assertion (that expects MP4A compliant i.e unset),
+// will then lead to an error and failure to parse every frame then.
+// by default we skip this assertion to be more tolerant of either encoders,
+// and assume payload advertisement is done in PMT either way and content
+// validated/detected to be AAC downstream further anyhow.
+// Therefore:
+// Only enable this, if you know you need the parser to fail on input data where the bit is set wrongly.
+const ADTS_HEADER_ASSERT_MPEG_VERSION = false;
+
export interface AdtsFrameInfo {
aacObjectType: number;
channels: number;
@@ -212,7 +222,7 @@ export class AdtsReader extends PayloadReader {
reader.skipBits(12);
const mpegVersion: number = reader.readBool() ? 1 : 0; // MPEG Version: 0 for MPEG-4, 1 for MPEG-2
- if (mpegVersion !== 0) {
+ if (ADTS_HEADER_ASSERT_MPEG_VERSION && mpegVersion !== 0) {
throw new Error(`Expected in header-data MPEG-version flag = 0 (only MP4-audio supported), but signals MPEG-2!`);
}
From 72b49b931fc9265e9f35c50412fee39385b2b70f Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 7 Sep 2022 16:08:05 +0200
Subject: [PATCH 186/197] pes-reader: add _timeWrapOver32BitMp4Range option.
computes mod over the constant range to comply easily with mp4 remux, as we
use audio samplerate on the ADTS frame output side. see below for more
details.
MPTS has a larger bitfield for timestamps (33 bit) than what fMP4 allows to feed into MSE (32 bit). That wrapover happens by itself as we create mp4 with mpts 90khz timerange and use the exact same numbers
however here we are using audio samplerate timescale, for the audio only for the video we still use the mpts clock.
now, that would be causing the wrapover to play out differently since for audio and video
=> Solution now we compute the wrapover properly when reading the PES and so even if the audio time gets calculated to be sample-rate precise it stays in the same plane
TODO: make the default false (should generally not do such opinionated op on timestamps), and/or also propagate the option to Demuxer handle constructor.
---
src/demuxer/ts/pes-reader.ts | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/src/demuxer/ts/pes-reader.ts b/src/demuxer/ts/pes-reader.ts
index fb54ffd..b44782d 100644
--- a/src/demuxer/ts/pes-reader.ts
+++ b/src/demuxer/ts/pes-reader.ts
@@ -18,6 +18,8 @@ export enum MptsElementaryStreamType {
TS_STREAM_TYPE_PACKETIZED_DATA = 0x06
}
+const MP4_BASE_MEDIA_DTS_32BIT_RANGE = Math.pow(2, 32) - 1;
+
export class PESReader {
public payloadReader: PayloadReader;
@@ -25,7 +27,8 @@ export class PESReader {
private currentDts: number = NaN;
private currentCto: number = NaN;
- constructor(public pid: number, public type: MptsElementaryStreamType) {
+ constructor(public pid: number, public type: MptsElementaryStreamType,
+ private _timeWrapOver32BitMp4Range: boolean = true) {
switch(type) {
case MptsElementaryStreamType.TS_STREAM_TYPE_AAC:
@@ -100,7 +103,12 @@ export class PESReader {
// parses the optional header section.
// reads the packet up to the data section in every case.
- const [dts, pts] = parsePesHeaderOptionalFields(packet);
+ let [dts, pts] = parsePesHeaderOptionalFields(packet);
+
+ if (this._timeWrapOver32BitMp4Range) {
+ dts %= MP4_BASE_MEDIA_DTS_32BIT_RANGE;
+ pts %= MP4_BASE_MEDIA_DTS_32BIT_RANGE;
+ }
this.currentDts = dts;
this.currentCto = pts - dts;
From 3d037c06da21d21b0d8f5fefef4d4a430bea8f81 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 7 Sep 2022 19:33:11 +0200
Subject: [PATCH 187/197] mpts demuxer: add public r/w prop
enableWrapOver32BitClock. current value is passed into every PESReader (for
each track created). Thus value changes only applies to tracks created after
mutation.
---
src/demuxer/ts/mpegts-demuxer.ts | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index f427e58..fc6ae2b 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -29,6 +29,11 @@ export class MpegTSDemuxer implements IDemuxer {
private _pmtId: number = NaN;
private _pmtParsed: boolean = false;
+ /**
+ * Either mutation of this property only applies to any track created *after*.
+ */
+ public enableWrapOver32BitClock: boolean = true;
+
get currentBufferSize(): number {
return this._data?.byteLength || 0;
}
@@ -234,7 +239,9 @@ export class MpegTSDemuxer implements IDemuxer {
packetParser.skipBytes(infoLength);
bytesRemaining -= infoLength + 5;
if (!this.tracks[elementaryPid]) {
- const pes: PESReader = new PESReader(elementaryPid, streamType);
+
+ const pesReader: PESReader = new PESReader(elementaryPid, streamType, this.enableWrapOver32BitClock);
+
let type: TrackType;
let mimeType: string;
if (streamType === MptsElementaryStreamType.TS_STREAM_TYPE_AAC) {
@@ -257,7 +264,7 @@ export class MpegTSDemuxer implements IDemuxer {
type = TrackType.UNKNOWN;
mimeType = Track.MIME_TYPE_UNKNOWN;
}
- this.tracks[elementaryPid] = new TSTrack(elementaryPid, type, mimeType, pes);
+ this.tracks[elementaryPid] = new TSTrack(elementaryPid, type, mimeType, pesReader);
}
}
this._pmtParsed = true;
From 88f4664c21af65510bdb5137ed2b34bbfb310894 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 8 Sep 2022 02:04:38 +0200
Subject: [PATCH 188/197] pes-reader: simplify clock wrapping to only be done
on DTS since for CTO we only need the diff that is performed in JS number
land + add default const for initial prop setting on demuxer class
---
src/demuxer/ts/mpegts-demuxer.ts | 4 +++-
src/demuxer/ts/pes-reader.ts | 9 ++-------
2 files changed, 5 insertions(+), 8 deletions(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index fc6ae2b..a0c584e 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -12,6 +12,8 @@ enum MpegContainerType {
RAW_MPEG_AUDIO
}
+const ENABLE_WRAP_OVER_CLOCK_32BIT = true;
+
export class MpegTSDemuxer implements IDemuxer {
private static MPEGTS_SYNC: number = 0x47;
@@ -32,7 +34,7 @@ export class MpegTSDemuxer implements IDemuxer {
/**
* Either mutation of this property only applies to any track created *after*.
*/
- public enableWrapOver32BitClock: boolean = true;
+ public enableWrapOver32BitClock: boolean = ENABLE_WRAP_OVER_CLOCK_32BIT;
get currentBufferSize(): number {
return this._data?.byteLength || 0;
diff --git a/src/demuxer/ts/pes-reader.ts b/src/demuxer/ts/pes-reader.ts
index b44782d..89649b6 100644
--- a/src/demuxer/ts/pes-reader.ts
+++ b/src/demuxer/ts/pes-reader.ts
@@ -28,7 +28,7 @@ export class PESReader {
private currentCto: number = NaN;
constructor(public pid: number, public type: MptsElementaryStreamType,
- private _timeWrapOver32BitMp4Range: boolean = true) {
+ private _timeWrapOver32BitMp4Range: boolean) {
switch(type) {
case MptsElementaryStreamType.TS_STREAM_TYPE_AAC:
@@ -105,12 +105,7 @@ export class PESReader {
// reads the packet up to the data section in every case.
let [dts, pts] = parsePesHeaderOptionalFields(packet);
- if (this._timeWrapOver32BitMp4Range) {
- dts %= MP4_BASE_MEDIA_DTS_32BIT_RANGE;
- pts %= MP4_BASE_MEDIA_DTS_32BIT_RANGE;
- }
-
- this.currentDts = dts;
+ this.currentDts = this._timeWrapOver32BitMp4Range ? dts % MP4_BASE_MEDIA_DTS_32BIT_RANGE : dts;
this.currentCto = pts - dts;
}
From 781b3deccad0093661583b0a9ccba5e0316597be Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 8 Sep 2022 02:05:22 +0200
Subject: [PATCH 189/197] ts-demux: removed default value for PESReader
constructor
---
src/demuxer/ts/mpegts-demuxer.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index a0c584e..3002780 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -112,7 +112,7 @@ export class MpegTSDemuxer implements IDemuxer {
const streamReader: BitReader = new BitReader(this._data);
this.tracks[0] = new TSTrack(0,
TrackType.AUDIO, Track.MIME_TYPE_AAC,
- new PESReader(0, MptsElementaryStreamType.TS_STREAM_TYPE_AAC));
+ new PESReader(0, MptsElementaryStreamType.TS_STREAM_TYPE_AAC, false));
this.tracks[0].pes.appendPacket(false, streamReader);
}
From 4e48f49ab1e1ae80c4da975e01f882588a3610a5 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 8 Sep 2022 04:05:45 +0200
Subject: [PATCH 190/197] ts-track: add toJSON overload impl for debugging
purpose (mostly to avoid printing frame data buffers when serializing)
---
src/demuxer/ts/ts-track.ts | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/src/demuxer/ts/ts-track.ts b/src/demuxer/ts/ts-track.ts
index 68cb666..a5cab9a 100644
--- a/src/demuxer/ts/ts-track.ts
+++ b/src/demuxer/ts/ts-track.ts
@@ -10,6 +10,15 @@ export class TSTrack extends Track {
super(id, type, mimeType);
}
+ public toJSON(): string {
+ const { id, type, mimeType } = this;
+ return JSON.stringify({
+ id,
+ type,
+ mimeType
+ });
+ }
+
get frames() { return this?.pes?.payloadReader.frames || []; }
getResolution(): [number, number] {
From 09593d4c5d65d62f6618aea018f82aee357a084f Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Thu, 8 Sep 2022 19:08:52 +0200
Subject: [PATCH 191/197] ts-track fix toJSON to return object not string (this
is how JSON.stringify expects it to work)
---
src/demuxer/ts/ts-track.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/demuxer/ts/ts-track.ts b/src/demuxer/ts/ts-track.ts
index a5cab9a..ef700fd 100644
--- a/src/demuxer/ts/ts-track.ts
+++ b/src/demuxer/ts/ts-track.ts
@@ -10,13 +10,13 @@ export class TSTrack extends Track {
super(id, type, mimeType);
}
- public toJSON(): string {
+ public toJSON() {
const { id, type, mimeType } = this;
- return JSON.stringify({
+ return {
id,
type,
mimeType
- });
+ };
}
get frames() { return this?.pes?.payloadReader.frames || []; }
From 36227fa9a6e53ec3dd0b4bf8da073d67991d2b36 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 13 Sep 2022 02:13:03 +0200
Subject: [PATCH 192/197] timescale: rm toSecondsFromMicros &
mpegClockTimeToMicroSecs (not needed anymore); replace with
mpegClockTimeToSecs plain computation
---
src/utils/timescale.ts | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/src/utils/timescale.ts b/src/utils/timescale.ts
index c32e096..ae0fac4 100644
--- a/src/utils/timescale.ts
+++ b/src/utils/timescale.ts
@@ -4,13 +4,9 @@ export function toMicroseconds(value, timescale) {
return MICROSECOND_TIMESCALE * value / timescale;
}
-export function toSecondsFromMicros(us) {
- return us / MICROSECOND_TIMESCALE;
-}
-
export const MPEG_CLOCK_HZ = 90000;
-export function mpegClockTimeToMicroSecs(time: number): number {
- return toMicroseconds(time, MPEG_CLOCK_HZ);
+export function mpegClockTimeToSecs(time: number): number {
+ return time / MPEG_CLOCK_HZ;
}
From ec3f648cd559414672e67a7e1841ae47b60d5f3e Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 13 Sep 2022 02:15:02 +0200
Subject: [PATCH 193/197] pes-reader: add pre-assertion on CTO computation
result < 0; throw error that eases any debugging in case needed showing
DTS/PTS in seconds as well as in clock ticks (not recomputing for actual
assignment of CTO)
---
src/demuxer/ts/pes-reader.ts | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/demuxer/ts/pes-reader.ts b/src/demuxer/ts/pes-reader.ts
index 89649b6..e39bbe8 100644
--- a/src/demuxer/ts/pes-reader.ts
+++ b/src/demuxer/ts/pes-reader.ts
@@ -105,8 +105,13 @@ export class PESReader {
// reads the packet up to the data section in every case.
let [dts, pts] = parsePesHeaderOptionalFields(packet);
+ const cto = pts - dts;
+ if (cto < 0) {
+ throw new Error(`Computed CTO < 0 with DTS = ${dts} (${mpegClockTimeToSecs(dts)} [s]) / PTS = ${pts} (${mpegClockTimeToSecs(pts)} [s])`);
+ }
+
this.currentDts = this._timeWrapOver32BitMp4Range ? dts % MP4_BASE_MEDIA_DTS_32BIT_RANGE : dts;
- this.currentCto = pts - dts;
+ this.currentCto = cto;
}
private _handlePayloadReadData(data: Uint8Array, dts: number, cto: number, naluType: number = NaN) {
From a60b7ce101b55ce48ce5b064cbea2aac1e916090 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 13 Sep 2022 02:22:06 +0200
Subject: [PATCH 194/197] pes-header PTS/DTS clock parsing: fix 2 bugs in value
expansion logic. 1) lastByte assignment has to be priorized properly within
wrapping operation 2) byte 14 instead of 13 was read whilst not using
lastByte reg.
Thus the wrong LSB was read for PTS = DTS, but also reading 1 byte more than we should, and finally causing the read offset for PTS != DTS to be 1 byte forward than expected. that extra offset was however not causing transport level packet skip issues as the remainder length was and is computed invariable to the number of read bytes (see end of func).
---
src/demuxer/ts/payload/pes-header.ts | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/demuxer/ts/payload/pes-header.ts b/src/demuxer/ts/payload/pes-header.ts
index b573b29..ad8cc3f 100644
--- a/src/demuxer/ts/payload/pes-header.ts
+++ b/src/demuxer/ts/payload/pes-header.ts
@@ -58,24 +58,25 @@ export function parsePesHeaderOptionalFields(packet: BitReader): [number, number
let pts = NaN;
let dts = NaN;
if (ptsDtsFlags & 0xC0) {
+
// the PTS and DTS are not written out directly. For information
// on how they are encoded, see
// http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
+ let lastByte;
pts = (packet.readByte() & 0x0E) << 27 |
(packet.readByte() & 0xFF) << 20 |
(packet.readByte() & 0xFE) << 12 |
(packet.readByte() & 0xFF) << 5 |
- (packet.readByte() & 0xFE) >>> 3;
+ ((lastByte = packet.readByte()) & 0xFE) >>> 3;
pts *= 4; // Left shift by 2
- pts += (packet.readByte() & 0x06) >>> 1; // OR by the two LSBs
+ pts += (lastByte & 0x06) >>> 1; // OR by the two LSBs
dts = pts;
if (ptsDtsFlags & 0x40) {
- let lastByte;
dts = (packet.readByte() & 0x0E) << 27 |
(packet.readByte() & 0xFF) << 20 |
(packet.readByte() & 0xFE) << 12 |
(packet.readByte() & 0xFF) << 5 |
- (lastByte = packet.readByte() & 0xFE) >>> 3;
+ ((lastByte = packet.readByte()) & 0xFE) >>> 3;
dts *= 4; // Left shift by 2
dts += (lastByte & 0x06) >>> 1; // OR by the two LSBs
}
From 466052f18223e9b824737d1454a8079b868bb383 Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Tue, 13 Sep 2022 02:22:20 +0200
Subject: [PATCH 195/197] frame: rm unused import toSecondsFromMicros + add
mpegClockTimeToSecs import in pes-reader
---
src/demuxer/frame.ts | 1 -
src/demuxer/ts/pes-reader.ts | 1 +
2 files changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/demuxer/frame.ts b/src/demuxer/frame.ts
index 036b05a..a67d1f9 100644
--- a/src/demuxer/frame.ts
+++ b/src/demuxer/frame.ts
@@ -1,5 +1,4 @@
import { FRAME_TYPE } from "../codecs/h264/nal-units";
-import { toSecondsFromMicros } from "../utils/timescale";
export class Frame {
diff --git a/src/demuxer/ts/pes-reader.ts b/src/demuxer/ts/pes-reader.ts
index e39bbe8..17d3a7e 100644
--- a/src/demuxer/ts/pes-reader.ts
+++ b/src/demuxer/ts/pes-reader.ts
@@ -7,6 +7,7 @@ import { H264Reader } from './payload/h264-reader';
import { ID3Reader } from './payload/id3-reader';
import { MpegReader } from './payload/mpeg-reader';
import { parsePesHeaderOptionalFields } from './payload/pes-header';
+import { mpegClockTimeToSecs } from '../../utils/timescale';
export enum MptsElementaryStreamType {
TS_STREAM_TYPE_AAC = 0x0F,
From b9ac0e2e0873f7b54551ed115e1cc41fc10be6da Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Wed, 14 Sep 2022 23:41:20 +0200
Subject: [PATCH 196/197] mpeg-ts-demuxer: make internal tracks hash properly
private, add pub getter that returns clone (via obj spread) to class users,
rename TSTrack class to MpegTSTrack, add numberOfTracks, add MpegTSTracksHash
type export
---
src/demuxer/ts/mpegts-demuxer.ts | 35 +++++++++++++++++++++++++-------
src/demuxer/ts/ts-track.ts | 2 +-
src/index.ts | 2 +-
3 files changed, 30 insertions(+), 9 deletions(-)
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index 3002780..597f6eb 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -2,7 +2,7 @@ import { IDemuxer } from '../demuxer';
import { Track, TrackType } from '../track';
import { MptsElementaryStreamType, PESReader } from './pes-reader';
-import { TSTrack } from './ts-track';
+import { MpegTSTrack } from './ts-track';
import { BitReader } from '../../utils/bit-reader';
enum MpegContainerType {
@@ -14,12 +14,14 @@ enum MpegContainerType {
const ENABLE_WRAP_OVER_CLOCK_32BIT = true;
+export type MpegTSTracksHash = { [id: number] : MpegTSTrack; }
+
export class MpegTSDemuxer implements IDemuxer {
private static MPEGTS_SYNC: number = 0x47;
private static MPEGTS_PACKET_SIZE: number = 188;
- public tracks: { [id: number] : TSTrack; } = {};
+ private _tracks: MpegTSTracksHash = {};
private _containerType: MpegContainerType = MpegContainerType.UNKNOWN;
@@ -48,6 +50,14 @@ export class MpegTSDemuxer implements IDemuxer {
return this._pmtParsed;
}
+ get tracks(): MpegTSTracksHash {
+ return { ... this._tracks };
+ }
+
+ get numberOfTracks(): number {
+ return Object.keys(this._tracks).length;
+ }
+
public append(data: Uint8Array, pruneAfterParse: boolean = false): Uint8Array | null {
if (!this._data || this._data.byteLength === 0) {
this._data = new Uint8Array(data);
@@ -110,10 +120,10 @@ export class MpegTSDemuxer implements IDemuxer {
private _parseRawEsPackets() {
const streamReader: BitReader = new BitReader(this._data);
- this.tracks[0] = new TSTrack(0,
+ this._tracks[0] = new MpegTSTrack(0,
TrackType.AUDIO, Track.MIME_TYPE_AAC,
new PESReader(0, MptsElementaryStreamType.TS_STREAM_TYPE_AAC, false));
- this.tracks[0].pes.appendPacket(false, streamReader);
+ this._tracks[0].pes.appendPacket(false, streamReader);
}
private _findContainerType(): void {
@@ -203,7 +213,7 @@ export class MpegTSDemuxer implements IDemuxer {
} else if (pid === this._pmtId) {
this._parseProgramMapTable(payloadUnitStartIndicator, packetReader);
} else {
- const track: TSTrack = this.tracks[pid];
+ const track: MpegTSTrack = this._tracks[pid];
// handle case where PID not found?
if (track && track.pes) {
track.pes.appendPacket(payloadUnitStartIndicator, packetReader);
@@ -232,6 +242,10 @@ export class MpegTSDemuxer implements IDemuxer {
packetParser.skipBytes(programInfoLength);
let bytesRemaining: number = sectionLength - 9 - programInfoLength - 4;
+ if (bytesRemaining <= 0) {
+ throw new Error(`PMT packet-parser: bytesRemaining = ${bytesRemaining} after packet header (no entries data)`);
+ }
+
while (bytesRemaining > 0) {
const streamType: number = packetParser.readBits(8);
packetParser.skipBits(3);
@@ -240,7 +254,8 @@ export class MpegTSDemuxer implements IDemuxer {
const infoLength: number = packetParser.readBits(12);
packetParser.skipBytes(infoLength);
bytesRemaining -= infoLength + 5;
- if (!this.tracks[elementaryPid]) {
+
+ if (!this._tracks[elementaryPid]) {
const pesReader: PESReader = new PESReader(elementaryPid, streamType, this.enableWrapOver32BitClock);
@@ -266,10 +281,16 @@ export class MpegTSDemuxer implements IDemuxer {
type = TrackType.UNKNOWN;
mimeType = Track.MIME_TYPE_UNKNOWN;
}
- this.tracks[elementaryPid] = new TSTrack(elementaryPid, type, mimeType, pesReader);
+ this._tracks[elementaryPid] = new MpegTSTrack(elementaryPid, type, mimeType, pesReader);
}
}
+
this._pmtParsed = true;
+
+ if(this.numberOfTracks === 0) {
+ throw new Error('Parsed new PMT but have zero tracks')
+ }
+
this.onProgramMapUpdate();
}
}
diff --git a/src/demuxer/ts/ts-track.ts b/src/demuxer/ts/ts-track.ts
index ef700fd..a8d02d2 100644
--- a/src/demuxer/ts/ts-track.ts
+++ b/src/demuxer/ts/ts-track.ts
@@ -2,7 +2,7 @@ import { Track, TrackType } from '../track';
import { Frame } from '../frame';
import { PESReader } from './pes-reader';
-export class TSTrack extends Track {
+export class MpegTSTrack extends Track {
constructor(id: number, type: TrackType, mimeType: string,
public pes: PESReader) {
diff --git a/src/index.ts b/src/index.ts
index 8bff4b8..0d916b1 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -11,7 +11,7 @@ export { IDemuxer, TracksHash } from './demuxer/demuxer';
export { Track } from './demuxer/track';
export { Frame } from './demuxer/frame';
export { Atom, ContainerAtom } from './demuxer/mp4/atoms/atom';
-export { TSTrack } from './demuxer/ts/ts-track';
+export { MpegTSTrack as TSTrack } from './demuxer/ts/ts-track';
export function createMpegTSDemuxer(): MpegTSDemuxer { // Q: these methods should return IDemuxer to maintain abstraction solid?
return new MpegTSDemuxer();
From 549dfe286a4366b339228b7516087ac8e952f18b Mon Sep 17 00:00:00 2001
From: Stephan Hesse
Date: Fri, 16 Sep 2022 21:07:08 +0200
Subject: [PATCH 197/197] mpts demux: refactor and fix issues in PSI table data
parsing: support presence of NIT entry at first position in PAT (was mistaken
for program-map PID). Centralize and breakout generic PSI syntax header
parsing to static.
simplified overall parsing calculation via the prior addition + rename private member to _programMapPid + added documentation on various header/table specs (from Wikipedia) and other helpful comments to not get lost here anymore.
---
src/demuxer/ts/mpegts-demuxer.ts | 62 +++++++++++++++++++++-----------
src/demuxer/ts/psi-header.ts | 44 +++++++++++++++++++++++
2 files changed, 86 insertions(+), 20 deletions(-)
create mode 100644 src/demuxer/ts/psi-header.ts
diff --git a/src/demuxer/ts/mpegts-demuxer.ts b/src/demuxer/ts/mpegts-demuxer.ts
index 597f6eb..6a603de 100644
--- a/src/demuxer/ts/mpegts-demuxer.ts
+++ b/src/demuxer/ts/mpegts-demuxer.ts
@@ -4,6 +4,7 @@ import { Track, TrackType } from '../track';
import { MptsElementaryStreamType, PESReader } from './pes-reader';
import { MpegTSTrack } from './ts-track';
import { BitReader } from '../../utils/bit-reader';
+import { parsePsiPacketHeader } from './psi-header';
enum MpegContainerType {
UNKNOWN,
@@ -30,7 +31,7 @@ export class MpegTSDemuxer implements IDemuxer {
private _packetsCount: number = 0;
- private _pmtId: number = NaN;
+ private _programMapPid: number = NaN;
private _pmtParsed: boolean = false;
/**
@@ -210,7 +211,7 @@ export class MpegTSDemuxer implements IDemuxer {
if (adaptationField === 1 || adaptationField === 3) {
if (pid === 0) {
this._parseProgramAllocationTable(payloadUnitStartIndicator, packetReader);
- } else if (pid === this._pmtId) {
+ } else if (pid === this._programMapPid) {
this._parseProgramMapTable(payloadUnitStartIndicator, packetReader);
} else {
const track: MpegTSTrack = this._tracks[pid];
@@ -223,37 +224,57 @@ export class MpegTSDemuxer implements IDemuxer {
}
private _parseProgramAllocationTable(payloadUnitStartIndicator: boolean, packetParser: BitReader): void {
- if (payloadUnitStartIndicator) {
- packetParser.skipBytes(packetParser.readByte());
+ const tableDataLength = parsePsiPacketHeader(packetParser, payloadUnitStartIndicator);
+
+ let bytesRemaining = tableDataLength;
+ while (bytesRemaining > 0) {
+
+ /**
+ * Program num 16 Relates to the Table ID extension in the associated PMT. A value of 0 is reserved for a NIT packet identifier.
+ Reserved bits 3 Set to 0x07 (all bits on)
+ Program map PID 13 The packet identifier that contains the associated PMT
+ */
+ const programNum = packetParser.readBits(16);
+ packetParser.readBits(3); // skip reserved bits
+ const pid = packetParser.readBits(13);
+ // other progam-numbers than 0 specifiy PMT, we expect to find an entry for this in each PAT.
+ // optional entry with 1 is reserved for NIT (network info), where other PSI table types are carried, e.g SIT.
+ if (programNum !== 0) {
+ this._programMapPid = pid;
+ }
+ bytesRemaining -= 4;
}
- packetParser.skipBits(27 + 7 * 8);
- this._pmtId = packetParser.readBits(13);
}
private _parseProgramMapTable(payloadUnitStartIndicator: boolean, packetParser: BitReader): void {
- if (payloadUnitStartIndicator) {
- packetParser.skipBytes(packetParser.readByte());
- }
+ const tableDataLength = parsePsiPacketHeader(packetParser, payloadUnitStartIndicator);
- packetParser.skipBits(12);
- const sectionLength: number = packetParser.readBits(12);
- packetParser.skipBits(4 + 7 * 8);
- const programInfoLength: number = packetParser.readBits(12);
- packetParser.skipBytes(programInfoLength);
- let bytesRemaining: number = sectionLength - 9 - programInfoLength - 4;
+ /**
+ * Reserved bits 3 Set to 0x07 (all bits on)
+ PCR PID 13 If this is unused. then it is set to 0x1FFF (all bits on).
+ Reserved bits 4 Set to 0x0F (all bits on)
+ Program info length unused bits 2 Set to 0 (all bits off)
+ */
- if (bytesRemaining <= 0) {
- throw new Error(`PMT packet-parser: bytesRemaining = ${bytesRemaining} after packet header (no entries data)`);
- }
+ packetParser.skipBits(3); // reserved bits 0x07
+ const pcrPid = packetParser.readBits(13); // PCR PID
+ packetParser.skipBits(4); // reserved bits 0x0F
+ packetParser.skipBits(2); // Program info length unused bits
+
+ const programInfoLength: number = packetParser.readBits(10);
+ packetParser.skipBytes(programInfoLength);
+ let bytesRemaining: number = tableDataLength - programInfoLength - 4; // 4 bytes from PMT header fields above
while (bytesRemaining > 0) {
+
const streamType: number = packetParser.readBits(8);
packetParser.skipBits(3);
const elementaryPid: number = packetParser.readBits(13);
packetParser.skipBits(4);
const infoLength: number = packetParser.readBits(12);
packetParser.skipBytes(infoLength);
- bytesRemaining -= infoLength + 5;
+
+ bytesRemaining -= infoLength + 5; // 5 bytes fields parsed above
if (!this._tracks[elementaryPid]) {
@@ -270,7 +291,8 @@ export class MpegTSDemuxer implements IDemuxer {
} else if (streamType === MptsElementaryStreamType.TS_STREAM_TYPE_ID3) {
type = TrackType.TEXT;
mimeType = Track.MIME_TYPE_ID3;
- } else if (streamType === MptsElementaryStreamType.TS_STREAM_TYPE_MPA || streamType === MptsElementaryStreamType.TS_STREAM_TYPE_MPA_LSF) {
+ } else if (streamType === MptsElementaryStreamType.TS_STREAM_TYPE_MPA
+ || streamType === MptsElementaryStreamType.TS_STREAM_TYPE_MPA_LSF) {
type = TrackType.AUDIO;
mimeType = Track.MIME_TYPE_MPEG;
} else if (streamType === MptsElementaryStreamType.TS_STREAM_TYPE_METADATA) {
diff --git a/src/demuxer/ts/psi-header.ts b/src/demuxer/ts/psi-header.ts
new file mode 100644
index 0000000..40b13e9
--- /dev/null
+++ b/src/demuxer/ts/psi-header.ts
@@ -0,0 +1,44 @@
+import { BitReader } from "../../utils/bit-reader";
+
+/**
+ * Parses the PSI until the table-data section start, returns the size of remaining bytes in it, without the CRC.
+ * @param packetParser
+ * @param payloadUnitStartIndicator
+ * @returns Section length minus 9 (5 bytes syntax section + 32 bits trailing CRC) = Table/Data section size
+ */
+export function parsePsiPacketHeader(packetParser: BitReader, payloadUnitStartIndicator: boolean) {
+ // PSI structure start: read pointer field and skip filler bytes
+ if (payloadUnitStartIndicator) {
+ packetParser.skipBytes(packetParser.readByte());
+ }
+ /**
+ Table ID 8
+ Section syntax indicator 1
+ Private bit 1 The PAT, PMT, and CAT all set this to 0. Other tables set this to 1.
+ Reserved bits 2 Set to 0x03 (all bits on)
+ */
+ packetParser.skipBits(12); // skip prior PSI header data (we expect table ID to comply and syntax section to be present always).
+ packetParser.skipBits(2); // Section length unused bits
+ const sectionLength: number = packetParser.readBits(10); // Section length
+
+ /*
+ Table ID extension 16 Informational only identifier. The PAT uses this for the transport stream identifier and the PMT uses this for the Program number.
+ Reserved bits 2 Set to 0x03 (all bits on)
+ Version number 5 Syntax version number. Incremented when data is changed and wrapped around on overflow for values greater than 32.
+ Current/next indicator 1 Indicates if data is current in effect or is for future use. If the bit is flagged on, then the data is to be used at the present moment.
+ Section number 8 This is an index indicating which table this is in a related sequence of tables. The first table starts from 0.
+ Last section number 8 This indicates which table is the last table in the sequence of tables.
+
+ => 5 bytes
+
+ */
+
+ packetParser.skipBytes(5)// Skip all syntax section prior table data
+
+ /*
+ Table data N*8 Data as defined by the Table Identifier.
+ CRC32 32 A checksum of the entire table excluding the pointer field, pointer filler bytes and the trailing CRC32.
+ */
+
+ return sectionLength - 9;
+}