@warren-bank/hls-proxy 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/hls-proxy/manifest_parser.js +100 -77
- package/package.json +1 -1
|
@@ -2,13 +2,10 @@ const {URL} = require('@warren-bank/url')
|
|
|
2
2
|
const utils = require('./utils')
|
|
3
3
|
|
|
4
4
|
const regexs = {
|
|
5
|
+
vod_start_at: /#vod_start(?:_prefetch_at)?=((?:\d+:)?(?:\d+:)?\d+)$/i,
|
|
5
6
|
m3u8_line_separator: /\s*[\r\n]+\s*/,
|
|
6
|
-
m3u8_line_landmark: /^(#[^:]
|
|
7
|
-
m3u8_line_url: /URI=["']([^"']+)["']/id
|
|
8
|
-
|
|
9
|
-
vod_start_at: new RegExp('#vod_start(?:_prefetch_at)?=((?:\\d+:)?(?:\\d+:)?\\d+)$', 'i'),
|
|
10
|
-
vod: new RegExp('^(?:#EXT-X-PLAYLIST-TYPE:VOD|#EXT-X-ENDLIST)$', 'im'),
|
|
11
|
-
ts_duration: new RegExp('^#EXT-X-TARGETDURATION:(\\d+)(?:\\.\\d+)?$', 'im')
|
|
7
|
+
m3u8_line_landmark: /^(#[^:]+[:]?)/,
|
|
8
|
+
m3u8_line_url: /URI=["']([^"']+)["']/id
|
|
12
9
|
}
|
|
13
10
|
|
|
14
11
|
const url_location_landmarks = {
|
|
@@ -51,16 +48,82 @@ const url_location_landmarks = {
|
|
|
51
48
|
}
|
|
52
49
|
}
|
|
53
50
|
|
|
51
|
+
const meta_data_location_landmarks = {
|
|
52
|
+
is_vod: {
|
|
53
|
+
same_line: [
|
|
54
|
+
'#EXT-X-PLAYLIST-TYPE:',
|
|
55
|
+
'#EXT-X-ENDLIST'
|
|
56
|
+
],
|
|
57
|
+
resolve_value: {
|
|
58
|
+
'#EXT-X-PLAYLIST-TYPE:': (m3u8_line, landmark) => {
|
|
59
|
+
const value = m3u8_line.substring(landmark.length, landmark.length + 3)
|
|
60
|
+
return (value.toUpperCase() === 'VOD')
|
|
61
|
+
},
|
|
62
|
+
'#EXT-X-ENDLIST': () => true
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
seg_duration_ms: {
|
|
66
|
+
same_line: [
|
|
67
|
+
'#EXT-X-TARGETDURATION:'
|
|
68
|
+
],
|
|
69
|
+
resolve_value: {
|
|
70
|
+
'#EXT-X-TARGETDURATION:': (m3u8_line, landmark) => {
|
|
71
|
+
m3u8_line = m3u8_line.substring(landmark.length)
|
|
72
|
+
const value = parseInt(m3u8_line, 10)
|
|
73
|
+
return isNaN(value)
|
|
74
|
+
? null
|
|
75
|
+
: (value * 1000) // convert seconds to ms
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const get_vod_start_at_ms = function(m3u8_url) {
|
|
82
|
+
try {
|
|
83
|
+
const matches = regexs.vod_start_at.exec(m3u8_url)
|
|
84
|
+
|
|
85
|
+
if ((matches == null) || !Array.isArray(matches) || (matches.length < 2))
|
|
86
|
+
throw ''
|
|
87
|
+
|
|
88
|
+
let offset
|
|
89
|
+
offset = matches[1]
|
|
90
|
+
offset = parse_HHMMSS_to_seconds(offset)
|
|
91
|
+
offset = offset * 1000 // convert seconds to ms
|
|
92
|
+
|
|
93
|
+
return offset
|
|
94
|
+
}
|
|
95
|
+
catch(e) {
|
|
96
|
+
const def_offset = null
|
|
97
|
+
|
|
98
|
+
return def_offset
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const parse_HHMMSS_to_seconds = function(str) {
|
|
103
|
+
const parts = str.split(':')
|
|
104
|
+
let seconds = 0
|
|
105
|
+
let multiplier = 1
|
|
106
|
+
|
|
107
|
+
while (parts.length > 0) {
|
|
108
|
+
seconds += multiplier * parseInt(parts.pop(), 10)
|
|
109
|
+
multiplier *= 60
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return seconds
|
|
113
|
+
}
|
|
114
|
+
|
|
54
115
|
// returns: {
|
|
116
|
+
// meta_data: {is_vod, seg_duration_ms},
|
|
55
117
|
// embedded_urls: [{line_index, url_indices, url_type, original_match_url, resolved_match_url, redirected_url, unencoded_url, encoded_url, referer_url}],
|
|
56
118
|
// prefetch_urls: [],
|
|
57
119
|
// modified_m3u8: ''
|
|
58
120
|
// }
|
|
59
|
-
const parse_manifest = function(m3u8_content, m3u8_url, referer_url, hooks, debug, vod_start_at_ms, redirected_base_url, should_prefetch_url) {
|
|
121
|
+
const parse_manifest = function(m3u8_content, m3u8_url, referer_url, hooks, cache_segments, debug, vod_start_at_ms, redirected_base_url, should_prefetch_url) {
|
|
60
122
|
const m3u8_lines = m3u8_content.split(regexs.m3u8_line_separator)
|
|
61
123
|
m3u8_content = null
|
|
62
124
|
|
|
63
|
-
const
|
|
125
|
+
const meta_data = {}
|
|
126
|
+
const embedded_urls = extract_embedded_urls(m3u8_lines, m3u8_url, referer_url, (cache_segments ? meta_data : null))
|
|
64
127
|
const prefetch_urls = []
|
|
65
128
|
|
|
66
129
|
if (embedded_urls && Array.isArray(embedded_urls) && embedded_urls.length) {
|
|
@@ -74,13 +137,14 @@ const parse_manifest = function(m3u8_content, m3u8_url, referer_url, hooks, debu
|
|
|
74
137
|
}
|
|
75
138
|
|
|
76
139
|
return {
|
|
140
|
+
meta_data,
|
|
77
141
|
embedded_urls,
|
|
78
142
|
prefetch_urls,
|
|
79
143
|
modified_m3u8: m3u8_lines.filter(line => !!line).join("\n")
|
|
80
144
|
}
|
|
81
145
|
}
|
|
82
146
|
|
|
83
|
-
const extract_embedded_urls = function(m3u8_lines, m3u8_url, referer_url) {
|
|
147
|
+
const extract_embedded_urls = function(m3u8_lines, m3u8_url, referer_url, meta_data) {
|
|
84
148
|
const embedded_urls = []
|
|
85
149
|
|
|
86
150
|
let m3u8_line, has_next_m3u8_line, next_m3u8_line, matches, matching_landmark, matching_url
|
|
@@ -98,6 +162,9 @@ const extract_embedded_urls = function(m3u8_lines, m3u8_url, referer_url) {
|
|
|
98
162
|
? matches[1]
|
|
99
163
|
: null
|
|
100
164
|
|
|
165
|
+
if (meta_data !== null)
|
|
166
|
+
extract_meta_data(meta_data, m3u8_line, matching_landmark)
|
|
167
|
+
|
|
101
168
|
for (let url_type in url_location_landmarks) {
|
|
102
169
|
if (matching_url && (url_location_landmarks[url_type]['same_line'].indexOf(matching_landmark) >= 0)) {
|
|
103
170
|
embedded_urls.push({
|
|
@@ -137,6 +204,20 @@ const extract_embedded_urls = function(m3u8_lines, m3u8_url, referer_url) {
|
|
|
137
204
|
return embedded_urls
|
|
138
205
|
}
|
|
139
206
|
|
|
207
|
+
const extract_meta_data = function(meta_data, m3u8_line, matching_landmark) {
|
|
208
|
+
for (let meta_data_key in meta_data_location_landmarks) {
|
|
209
|
+
if (meta_data_location_landmarks[meta_data_key]['same_line'].indexOf(matching_landmark) >= 0) {
|
|
210
|
+
const func = meta_data_location_landmarks[meta_data_key]['resolve_value'][matching_landmark]
|
|
211
|
+
if (typeof func === 'function') {
|
|
212
|
+
const meta_data_value = func(m3u8_line, matching_landmark)
|
|
213
|
+
if ((meta_data_value !== undefined) && (meta_data_value !== null)) {
|
|
214
|
+
meta_data[meta_data_key] = meta_data_value
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
140
221
|
const redirect_embedded_url = function(embedded_url, hooks, m3u8_url, debug) {
|
|
141
222
|
if (hooks && (hooks instanceof Object) && hooks.redirect && (typeof hooks.redirect === 'function')) {
|
|
142
223
|
let url, url_type, referer_url, result
|
|
@@ -252,61 +333,6 @@ const modify_m3u8_line = function(embedded_url, m3u8_lines) {
|
|
|
252
333
|
}
|
|
253
334
|
}
|
|
254
335
|
|
|
255
|
-
const get_seg_duration_ms = function(m3u8_content) {
|
|
256
|
-
try {
|
|
257
|
-
const matches = regexs.ts_duration.exec(m3u8_content)
|
|
258
|
-
|
|
259
|
-
if ((matches == null) || !Array.isArray(matches) || (matches.length < 2))
|
|
260
|
-
throw ''
|
|
261
|
-
|
|
262
|
-
let duration
|
|
263
|
-
duration = matches[1]
|
|
264
|
-
duration = parseInt(duration, 10)
|
|
265
|
-
duration = duration * 1000 // convert seconds to ms
|
|
266
|
-
|
|
267
|
-
return duration
|
|
268
|
-
}
|
|
269
|
-
catch(e) {
|
|
270
|
-
const def_duration = 10000 // 10 seconds in ms
|
|
271
|
-
|
|
272
|
-
return def_duration
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const parse_HHMMSS_to_seconds = function(str) {
|
|
277
|
-
const parts = str.split(':')
|
|
278
|
-
let seconds = 0
|
|
279
|
-
let multiplier = 1
|
|
280
|
-
|
|
281
|
-
while (parts.length > 0) {
|
|
282
|
-
seconds += multiplier * parseInt(parts.pop(), 10)
|
|
283
|
-
multiplier *= 60
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return seconds
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const get_vod_start_at_ms = function(m3u8_url) {
|
|
290
|
-
try {
|
|
291
|
-
const matches = regexs.vod_start_at.exec(m3u8_url)
|
|
292
|
-
|
|
293
|
-
if ((matches == null) || !Array.isArray(matches) || (matches.length < 2))
|
|
294
|
-
throw ''
|
|
295
|
-
|
|
296
|
-
let offset
|
|
297
|
-
offset = matches[1]
|
|
298
|
-
offset = parse_HHMMSS_to_seconds(offset)
|
|
299
|
-
offset = offset * 1000 // convert seconds to ms
|
|
300
|
-
|
|
301
|
-
return offset
|
|
302
|
-
}
|
|
303
|
-
catch(e) {
|
|
304
|
-
const def_offset = null
|
|
305
|
-
|
|
306
|
-
return def_offset
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
336
|
const modify_m3u8_content = function(params, segment_cache, m3u8_content, m3u8_url, referer_url, redirected_base_url) {
|
|
311
337
|
const {hooks, cache_segments, max_segments, debug_level} = params
|
|
312
338
|
|
|
@@ -327,21 +353,13 @@ const modify_m3u8_content = function(params, segment_cache, m3u8_content, m3u8_u
|
|
|
327
353
|
debug(4, 'proxied response (original m3u8):', `\n${debug_divider}\n${m3u8_content}\n${debug_divider}`)
|
|
328
354
|
}
|
|
329
355
|
|
|
330
|
-
|
|
331
|
-
const seg_duration_ms = (cache_segments)
|
|
332
|
-
? get_seg_duration_ms(m3u8_content)
|
|
333
|
-
: null
|
|
356
|
+
let is_vod, seg_duration_ms, prefetch_urls
|
|
334
357
|
|
|
335
358
|
// only used with prefetch
|
|
336
359
|
const vod_start_at_ms = (cache_segments)
|
|
337
360
|
? get_vod_start_at_ms(m3u8_url)
|
|
338
361
|
: null
|
|
339
362
|
|
|
340
|
-
// only used with prefetch
|
|
341
|
-
const is_vod = (cache_segments)
|
|
342
|
-
? ((typeof vod_start_at_ms === 'number') || (!has_cache(m3u8_url) && regexs.vod.test(m3u8_content)))
|
|
343
|
-
: null
|
|
344
|
-
|
|
345
363
|
// only used with prefetch
|
|
346
364
|
const perform_prefetch = (cache_segments)
|
|
347
365
|
? (urls, dont_touch_access) => {
|
|
@@ -372,11 +390,16 @@ const modify_m3u8_content = function(params, segment_cache, m3u8_content, m3u8_u
|
|
|
372
390
|
}
|
|
373
391
|
: null
|
|
374
392
|
|
|
375
|
-
let prefetch_urls
|
|
376
393
|
{
|
|
377
|
-
const parsed_manifest = parse_manifest(m3u8_content, m3u8_url, referer_url, hooks, debug, vod_start_at_ms, redirected_base_url, should_prefetch_url)
|
|
378
|
-
|
|
379
|
-
|
|
394
|
+
const parsed_manifest = parse_manifest(m3u8_content, m3u8_url, referer_url, hooks, cache_segments, debug, vod_start_at_ms, redirected_base_url, should_prefetch_url)
|
|
395
|
+
is_vod = !!parsed_manifest.meta_data.is_vod // default: false => hls live stream
|
|
396
|
+
seg_duration_ms = parsed_manifest.meta_data.seg_duration_ms || 10000 // default: 10 seconds in ms
|
|
397
|
+
prefetch_urls = parsed_manifest.prefetch_urls
|
|
398
|
+
m3u8_content = parsed_manifest.modified_m3u8
|
|
399
|
+
|
|
400
|
+
if (debug_level >= 4) {
|
|
401
|
+
debug(4, 'parsed manifest:', `\n${debug_divider}\n${JSON.stringify(parsed_manifest, null, 2)}\n${debug_divider}`)
|
|
402
|
+
}
|
|
380
403
|
}
|
|
381
404
|
|
|
382
405
|
if (prefetch_urls.length) {
|
package/package.json
CHANGED