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 | new Date(node.attributes.availabilitystarttime).getTime();
|
40 | isStatic = node.attributes.type !== 'dynamic';
|
41 | break;
|
42 | case 'period':
|
43 |
|
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 | }
|
173 | exports.default = DashMPDParser;
|
174 | ;
|
175 |
|
\ | No newline at end of file |