UNPKG

7.07 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 node.attributes.availabilitystarttime ?
40 new Date(node.attributes.availabilitystarttime).getTime() : 0;
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 periodStart = parse_time_1.durationStr(node.attributes.start) || 0;
52 break;
53 case 'segmentlist':
54 seq = parseInt(node.attributes.startnumber) || seq;
55 timescale = parseInt(node.attributes.timescale) || timescale;
56 duration = parseInt(node.attributes.duration) || duration;
57 offset = parseInt(node.attributes.presentationtimeoffset) || offset;
58 break;
59 case 'segmenttemplate':
60 segmentTemplate = node.attributes;
61 seq = parseInt(node.attributes.startnumber) || seq;
62 timescale = parseInt(node.attributes.timescale) || timescale;
63 break;
64 case 'segmenttimeline':
65 case 'baseurl':
66 lastTag = node.name;
67 break;
68 case 's':
69 timeline.push([
70 parseInt(node.attributes.d),
71 parseInt(node.attributes.r)
72 ]);
73 break;
74 case 'adaptationset':
75 case 'representation':
76 treeLevel++;
77 if (targetID == null) {
78 targetID = node.attributes.id;
79 }
80 getSegments = node.attributes.id === targetID + '';
81 if (getSegments) {
82 if (periodStart) {
83 currtime += periodStart;
84 }
85 if (offset) {
86 currtime -= offset / timescale * 1000;
87 }
88 this.emit('starttime', currtime);
89 }
90 if (getSegments && segmentTemplate && timeline.length) {
91 if (segmentTemplate.initialization) {
92 this.emit('item', {
93 url: baseURL.filter(s => !!s).join('') +
94 tmpl(segmentTemplate.initialization),
95 seq: seq - 1,
96 duration: 0,
97 });
98 }
99 for (let [duration, repeat] of timeline) {
100 duration = duration / timescale * 1000;
101 repeat = repeat || 1;
102 for (let i = 0; i < repeat; i++) {
103 this.emit('item', {
104 url: baseURL.filter(s => !!s).join('') +
105 tmpl(segmentTemplate.media),
106 seq: seq++,
107 duration,
108 });
109 currtime += duration;
110 }
111 }
112 }
113 break;
114 case 'initialization':
115 if (getSegments) {
116 this.emit('item', {
117 url: baseURL.filter(s => !!s).join('') + node.attributes.sourceurl,
118 seq: seq++,
119 duration: 0,
120 });
121 }
122 break;
123 case 'segmenturl':
124 if (getSegments) {
125 let tl = timeline.shift();
126 let segmentDuration = (tl && tl[0] || duration) / timescale * 1000;
127 this.emit('item', {
128 url: baseURL.filter(s => !!s).join('') + node.attributes.media,
129 seq: seq++,
130 duration: segmentDuration,
131 });
132 currtime += segmentDuration;
133 }
134 break;
135 }
136 });
137 const onEnd = () => {
138 if (isStatic) {
139 this.emit('endlist');
140 }
141 if (!getSegments) {
142 this.emit('error', Error(`Representation '${targetID}' not found`));
143 }
144 this.emit('end');
145 };
146 this._parser.on('closetag', (tagName) => {
147 switch (tagName) {
148 case 'adaptationset':
149 case 'representation':
150 treeLevel--;
151 break;
152 case 'segmentlist':
153 if (getSegments) {
154 this.emit('endearly');
155 onEnd();
156 this._parser.removeAllListeners();
157 }
158 break;
159 }
160 });
161 this._parser.on('text', (text) => {
162 if (lastTag === 'baseurl') {
163 baseURL[treeLevel] = text;
164 lastTag = null;
165 }
166 });
167 this.on('finish', onEnd);
168 }
169 _write(chunk, encoding, callback) {
170 this._parser.write(chunk, encoding);
171 callback();
172 }
173}
174exports.default = DashMPDParser;
175//# sourceMappingURL=dash-mpd-parser.js.map
\No newline at end of file