UNPKG

5.07 kBJavaScriptView Raw
1const Writable = require('stream').Writable;
2const sax = require('sax');
3
4
5/**
6 * A wrapper around sax that emits segments.
7 *
8 * @extends WRitableStream
9 * @constructor
10 */
11module.exports = class DashMPDParser extends Writable {
12 constructor(targetID) {
13 super();
14 this._parser = sax.createStream(false, { lowercasetags: true });
15 this._parser.on('error', this.emit.bind(this, 'error'));
16
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, startEmitted = false;
24 let isStatic;
25 let treeLevel;
26
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
36 this._parser.on('opentag', (node) => {
37 switch (node.name) {
38 case 'mpd':
39 currtime =
40 new Date(node.attributes.availabilitystarttime).getTime();
41 isStatic = node.attributes.type !== 'dynamic';
42 break;
43 case 'period':
44 // Reset everything on <Period> tag.
45 seq = 0;
46 timescale = 1000;
47 duration = 0;
48 offset = 0;
49 baseURL = [];
50 treeLevel = 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 if (!startEmitted) {
58 startEmitted = true;
59 if (offset) {
60 currtime += offset;
61 }
62 this.emit('starttime', currtime);
63 }
64 break;
65 case 'segmenttemplate':
66 segmentTemplate = node.attributes;
67 seq = parseInt(node.attributes.startnumber) || seq;
68 timescale = parseInt(node.attributes.timescale) || timescale;
69 break;
70 case 'segmenttimeline':
71 case 'baseurl':
72 lastTag = node.name;
73 break;
74 case 's':
75 timeline.push([
76 parseInt(node.attributes.d),
77 parseInt(node.attributes.r)
78 ]);
79 break;
80 case 'adaptationset':
81 case 'representation':
82 treeLevel++;
83 if (targetID == null) {
84 targetID = node.attributes.id;
85 }
86 getSegments = node.attributes.id === targetID + '';
87 if (getSegments && segmentTemplate && timeline.length) {
88 if (segmentTemplate.initialization) {
89 this.emit('item', {
90 url: baseURL.filter(s => !!s).join('') +
91 tmpl(segmentTemplate.initialization),
92 seq: seq - 1,
93 duration: 0,
94 });
95 }
96 for (let [duration, repeat] of timeline) {
97 duration = duration / timescale * 1000;
98 repeat = repeat || 1;
99 for (let i = 0; i < repeat; i++) {
100 this.emit('item', {
101 url: baseURL.filter(s => !!s).join('') +
102 tmpl(segmentTemplate.media),
103 seq: seq++,
104 duration,
105 });
106 currtime += duration;
107 }
108 }
109 }
110 break;
111 case 'initialization':
112 if (getSegments) {
113 this.emit('item', {
114 url: baseURL.filter(s => !!s).join('') + node.attributes.sourceurl,
115 seq: seq++,
116 duration: 0,
117 });
118 }
119 break;
120 case 'segmenturl':
121 if (getSegments) {
122 let tl = timeline.shift();
123 let segmentDuration = (tl && tl[0] || duration) / timescale * 1000;
124 this.emit('item', {
125 url: baseURL.filter(s => !!s).join('') + node.attributes.media,
126 seq: seq++,
127 duration: segmentDuration,
128 });
129 currtime += segmentDuration;
130 }
131 break;
132 }
133 });
134
135 const onEnd = () => {
136 if (isStatic) { this.emit('endlist'); }
137 if (!getSegments) {
138 this.emit('error', Error(`Representation '${targetID}' not found`));
139 }
140 this.emit('end');
141 };
142
143 this._parser.on('closetag', (tagName) => {
144 switch (tagName) {
145 case 'adaptationset':
146 case 'representation':
147 treeLevel--;
148 break;
149 case 'segmentlist':
150 if (getSegments) {
151 this.emit('endearly');
152 onEnd();
153 this._parser.removeAllListeners();
154 }
155 break;
156 }
157 });
158
159 this._parser.on('text', (text) => {
160 if (lastTag === 'baseurl') {
161 baseURL[treeLevel] = text;
162 lastTag = null;
163 }
164 });
165
166 this.on('finish', onEnd);
167 }
168
169 _write(chunk, encoding, callback) {
170 this._parser.write(chunk, encoding);
171 callback();
172 }
173};