1 | "use strict";
|
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
4 | };
|
5 | Object.defineProperty(exports, "__esModule", { value: true });
|
6 | const stream_1 = require("stream");
|
7 | const sax_1 = __importDefault(require("sax"));
|
8 | const parse_time_1 = require("./parse-time");
|
9 |
|
10 |
|
11 |
|
12 | class 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 |
|
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 | }
|
174 | exports.default = DashMPDParser;
|
175 |
|
\ | No newline at end of file |