UNPKG

7 kBJavaScriptView Raw
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6const stream_1 = require("stream");
7const sax_1 = __importDefault(require("sax"));
8const parse_time_1 = require("./parse-time");
9/**
10 * A wrapper around sax that emits segments.
11 */
12class DashMPDParser extends stream_1.Writable {
13 constructor(targetID) {
14 super();
15 this._parser = sax_1.default.createStream(false, { lowercase: true });
16 this._parser.on('error', this.emit.bind(this, 'error'));
17 let lastTag;
18 let currtime = 0;
19 let seq = 0;
20 let segmentTemplate;
21 let timescale, offset, duration, baseURL;
22 let timeline = [];
23 let getSegments = false;
24 let isStatic;
25 let treeLevel;
26 let periodStart;
27 const tmpl = (str) => {
28 const context = {
29 RepresentationID: targetID,
30 Number: seq,
31 Time: currtime,
32 };
33 return str.replace(/\$(\w+)\$/g, (m, p1) => context[p1] + '');
34 };
35 this._parser.on('opentag', (node) => {
36 switch (node.name) {
37 case 'mpd':
38 currtime =
39 new Date(node.attributes.availabilitystarttime).getTime();
40 isStatic = node.attributes.type !== 'dynamic';
41 break;
42 case 'period':
43 // Reset everything on <Period> tag.
44 seq = 0;
45 timescale = 1000;
46 duration = 0;
47 offset = 0;
48 baseURL = [];
49 treeLevel = 0;
50 periodStart = parse_time_1.durationStr(node.attributes.start) || 0;
51 break;
52 case 'segmentlist':
53 seq = parseInt(node.attributes.startnumber) || seq;
54 timescale = parseInt(node.attributes.timescale) || timescale;
55 duration = parseInt(node.attributes.duration) || duration;
56 offset = parseInt(node.attributes.presentationtimeoffset) || offset;
57 break;
58 case 'segmenttemplate':
59 segmentTemplate = node.attributes;
60 seq = parseInt(node.attributes.startnumber) || seq;
61 timescale = parseInt(node.attributes.timescale) || timescale;
62 break;
63 case 'segmenttimeline':
64 case 'baseurl':
65 lastTag = node.name;
66 break;
67 case 's':
68 timeline.push([
69 parseInt(node.attributes.d),
70 parseInt(node.attributes.r)
71 ]);
72 break;
73 case 'adaptationset':
74 case 'representation':
75 treeLevel++;
76 if (targetID == null) {
77 targetID = node.attributes.id;
78 }
79 getSegments = node.attributes.id === targetID + '';
80 if (getSegments) {
81 if (periodStart) {
82 currtime += periodStart;
83 }
84 if (offset) {
85 currtime -= offset / timescale * 1000;
86 }
87 this.emit('starttime', currtime);
88 }
89 if (getSegments && segmentTemplate && timeline.length) {
90 if (segmentTemplate.initialization) {
91 this.emit('item', {
92 url: baseURL.filter(s => !!s).join('') +
93 tmpl(segmentTemplate.initialization),
94 seq: seq - 1,
95 duration: 0,
96 });
97 }
98 for (let [duration, repeat] of timeline) {
99 duration = duration / timescale * 1000;
100 repeat = repeat || 1;
101 for (let i = 0; i < repeat; i++) {
102 this.emit('item', {
103 url: baseURL.filter(s => !!s).join('') +
104 tmpl(segmentTemplate.media),
105 seq: seq++,
106 duration,
107 });
108 currtime += duration;
109 }
110 }
111 }
112 break;
113 case 'initialization':
114 if (getSegments) {
115 this.emit('item', {
116 url: baseURL.filter(s => !!s).join('') + node.attributes.sourceurl,
117 seq: seq++,
118 duration: 0,
119 });
120 }
121 break;
122 case 'segmenturl':
123 if (getSegments) {
124 let tl = timeline.shift();
125 let segmentDuration = (tl && tl[0] || duration) / timescale * 1000;
126 this.emit('item', {
127 url: baseURL.filter(s => !!s).join('') + node.attributes.media,
128 seq: seq++,
129 duration: segmentDuration,
130 });
131 currtime += segmentDuration;
132 }
133 break;
134 }
135 });
136 const onEnd = () => {
137 if (isStatic) {
138 this.emit('endlist');
139 }
140 if (!getSegments) {
141 this.emit('error', Error(`Representation '${targetID}' not found`));
142 }
143 this.emit('end');
144 };
145 this._parser.on('closetag', (tagName) => {
146 switch (tagName) {
147 case 'adaptationset':
148 case 'representation':
149 treeLevel--;
150 break;
151 case 'segmentlist':
152 if (getSegments) {
153 this.emit('endearly');
154 onEnd();
155 this._parser.removeAllListeners();
156 }
157 break;
158 }
159 });
160 this._parser.on('text', (text) => {
161 if (lastTag === 'baseurl') {
162 baseURL[treeLevel] = text;
163 lastTag = null;
164 }
165 });
166 this.on('finish', onEnd);
167 }
168 _write(chunk, encoding, callback) {
169 this._parser.write(chunk, encoding);
170 callback();
171 }
172}
173exports.default = DashMPDParser;
174;
175//# sourceMappingURL=dash-mpd-parser.js.map
\No newline at end of file