@warren-bank/hls-proxy 3.5.3 → 3.6.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/.gitattributes +11 -0
- package/README.md +24 -0
- package/hls-proxy/expressjs_utils.js +19 -0
- package/hls-proxy/manifest_parser.js +20 -10
- package/hls-proxy/proxy.js +3 -3
- package/hls-proxy/segment_cache.js +2 -2
- package/hls-proxy/timers.js +1 -1
- package/hls-proxy/utils.js +30 -7
- package/package.json +1 -1
package/.gitattributes
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# ------------------------------------------------
|
|
1
2
|
# https://github.com/github/linguist
|
|
2
3
|
# https://github.com/github/linguist#vendored-code
|
|
3
4
|
# https://github.com/github/linguist/blob/master/lib/linguist/languages.yml
|
|
@@ -6,3 +7,13 @@
|
|
|
6
7
|
.related/* linguist-vendored=true
|
|
7
8
|
|
|
8
9
|
* linguist-language=JavaScript
|
|
10
|
+
|
|
11
|
+
# ------------------------------------------------
|
|
12
|
+
|
|
13
|
+
* text=auto
|
|
14
|
+
|
|
15
|
+
*.cmd text eol=crlf
|
|
16
|
+
*.bat text eol=crlf
|
|
17
|
+
*.sh text eol=lf
|
|
18
|
+
|
|
19
|
+
# ------------------------------------------------
|
package/README.md
CHANGED
|
@@ -57,6 +57,19 @@
|
|
|
57
57
|
const hls_proxy_url = `${proxy_url}/${ btoa(`${video_url}|${referer_url}`) }${file_extension}`
|
|
58
58
|
}
|
|
59
59
|
```
|
|
60
|
+
* [example Javascript]: construction of URL to _HLS Proxy_ for video stream w/ "Referer" and additional request headers
|
|
61
|
+
```javascript
|
|
62
|
+
{
|
|
63
|
+
const proxy_url = 'http://127.0.0.1:8080'
|
|
64
|
+
const video_url = 'https://example.com/video/master.m3u8'
|
|
65
|
+
const referer_url = 'https://example.com/videos.html'
|
|
66
|
+
const file_extension = '.m3u8'
|
|
67
|
+
const headers = {"Cookie": "foo=bar"}
|
|
68
|
+
|
|
69
|
+
const querystring = `?headers=${ encodeURIComponent( btoa( JSON.stringify(headers) ) ) }`
|
|
70
|
+
const hls_proxy_url = `${proxy_url}/${ btoa(`${video_url}|${referer_url}`) }${file_extension}${querystring}`
|
|
71
|
+
}
|
|
72
|
+
```
|
|
60
73
|
* [example Bash]: construction of URL to _HLS Proxy_ for video stream
|
|
61
74
|
```bash
|
|
62
75
|
proxy_url='http://127.0.0.1:8080'
|
|
@@ -74,6 +87,17 @@
|
|
|
74
87
|
|
|
75
88
|
hls_proxy_url="${proxy_url}/"$(echo -n "${video_url}|${referer_url}" | base64 --wrap=0)"$file_extension"
|
|
76
89
|
```
|
|
90
|
+
* [example Bash]: construction of URL to _HLS Proxy_ for video stream w/ "Referer" and additional request headers
|
|
91
|
+
```bash
|
|
92
|
+
proxy_url='http://127.0.0.1:8080'
|
|
93
|
+
video_url='https://example.com/video/master.m3u8'
|
|
94
|
+
referer_url='https://example.com/videos.html'
|
|
95
|
+
file_extension='.m3u8'
|
|
96
|
+
headers_json='{"Cookie": "foo=bar"}'
|
|
97
|
+
|
|
98
|
+
querystring='?headers='$(echo -n "$headers_json" | base64 --wrap=0)
|
|
99
|
+
hls_proxy_url="${proxy_url}/"$(echo -n "${video_url}|${referer_url}" | base64 --wrap=0)"${file_extension}${querystring}"
|
|
100
|
+
```
|
|
77
101
|
|
|
78
102
|
##### notes:
|
|
79
103
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const {URL} = require('./url')
|
|
2
|
+
|
|
1
3
|
const get_full_req_url = function(req) {
|
|
2
4
|
return req.originalUrl || req.url
|
|
3
5
|
}
|
|
@@ -6,6 +8,10 @@ const has_req_param = function(req, key) {
|
|
|
6
8
|
return (req.params && (typeof req.params === 'object') && req.params[key])
|
|
7
9
|
}
|
|
8
10
|
|
|
11
|
+
const has_req_query = function(req, key) {
|
|
12
|
+
return (req.query && (typeof req.query === 'object') && req.query[key])
|
|
13
|
+
}
|
|
14
|
+
|
|
9
15
|
const get_proxy_req_url = function(req) {
|
|
10
16
|
const key = "0"
|
|
11
17
|
return has_req_param(req, key)
|
|
@@ -13,6 +19,18 @@ const get_proxy_req_url = function(req) {
|
|
|
13
19
|
: req.url
|
|
14
20
|
}
|
|
15
21
|
|
|
22
|
+
const get_proxy_req_query = function(req, key) {
|
|
23
|
+
if (has_req_param(req, key))
|
|
24
|
+
return req.params[key]
|
|
25
|
+
|
|
26
|
+
if (has_req_query(req, key))
|
|
27
|
+
return req.query[key]
|
|
28
|
+
|
|
29
|
+
const url = new URL(req.url)
|
|
30
|
+
const qs = url.searchParams
|
|
31
|
+
return qs.get(key)
|
|
32
|
+
}
|
|
33
|
+
|
|
16
34
|
const get_base_req_url = function(req) {
|
|
17
35
|
let base_url = ''
|
|
18
36
|
const key = "0"
|
|
@@ -28,5 +46,6 @@ const get_base_req_url = function(req) {
|
|
|
28
46
|
module.exports = {
|
|
29
47
|
get_full_req_url,
|
|
30
48
|
get_proxy_req_url,
|
|
49
|
+
get_proxy_req_query,
|
|
31
50
|
get_base_req_url
|
|
32
51
|
}
|
|
@@ -112,12 +112,13 @@ const parse_HHMMSS_to_seconds = function(str) {
|
|
|
112
112
|
// prefetch_urls: [],
|
|
113
113
|
// modified_m3u8: ''
|
|
114
114
|
// }
|
|
115
|
-
const parse_manifest = function(m3u8_content, m3u8_url, referer_url, hooks, cache_segments, debug, vod_start_at_ms, redirected_base_url, should_prefetch_url, manifest_extension, segment_extension, qs_password) {
|
|
115
|
+
const parse_manifest = function(m3u8_content, m3u8_url, referer_url, querystring_req_headers, hooks, cache_segments, debug, vod_start_at_ms, redirected_base_url, should_prefetch_url, manifest_extension, segment_extension, qs_password) {
|
|
116
116
|
const m3u8_lines = m3u8_content.split(regexs.m3u8_line_separator)
|
|
117
117
|
m3u8_content = null
|
|
118
118
|
|
|
119
119
|
const meta_data = {}
|
|
120
120
|
const embedded_urls = extract_embedded_urls(m3u8_lines, m3u8_url, referer_url, (cache_segments ? meta_data : null))
|
|
121
|
+
const qs_headers = !!querystring_req_headers ? utils.base64_encode(JSON.stringify(querystring_req_headers)) : null
|
|
121
122
|
const prefetch_urls = []
|
|
122
123
|
|
|
123
124
|
if (embedded_urls && Array.isArray(embedded_urls) && embedded_urls.length) {
|
|
@@ -125,7 +126,7 @@ const parse_manifest = function(m3u8_content, m3u8_url, referer_url, hooks, cach
|
|
|
125
126
|
redirect_embedded_url(embedded_url, hooks, m3u8_url, debug)
|
|
126
127
|
if (validate_embedded_url(embedded_url)) {
|
|
127
128
|
finalize_embedded_url(embedded_url, vod_start_at_ms, debug)
|
|
128
|
-
encode_embedded_url(embedded_url, hooks, redirected_base_url, debug, manifest_extension, segment_extension, qs_password)
|
|
129
|
+
encode_embedded_url(embedded_url, hooks, redirected_base_url, debug, manifest_extension, segment_extension, qs_headers, qs_password)
|
|
129
130
|
get_prefetch_url(embedded_url, should_prefetch_url, prefetch_urls)
|
|
130
131
|
modify_m3u8_line(embedded_url, m3u8_lines)
|
|
131
132
|
}
|
|
@@ -332,7 +333,7 @@ const finalize_embedded_url = function(embedded_url, vod_start_at_ms, debug) {
|
|
|
332
333
|
}
|
|
333
334
|
}
|
|
334
335
|
|
|
335
|
-
const encode_embedded_url = function(embedded_url, hooks, redirected_base_url, debug, manifest_extension, segment_extension, qs_password) {
|
|
336
|
+
const encode_embedded_url = function(embedded_url, hooks, redirected_base_url, debug, manifest_extension, segment_extension, qs_headers, qs_password) {
|
|
336
337
|
if (embedded_url.unencoded_url) {
|
|
337
338
|
let file_extension = embedded_url.url_type
|
|
338
339
|
if (file_extension) {
|
|
@@ -344,9 +345,6 @@ const encode_embedded_url = function(embedded_url, hooks, redirected_base_url, d
|
|
|
344
345
|
|
|
345
346
|
embedded_url.encoded_url = `${redirected_base_url}/${ utils.base64_encode(embedded_url.unencoded_url) }.${file_extension || 'other'}`
|
|
346
347
|
|
|
347
|
-
if (qs_password)
|
|
348
|
-
embedded_url.encoded_url += `?password=${qs_password}`
|
|
349
|
-
|
|
350
348
|
debug(3, 'redirecting (proxied):', embedded_url.encoded_url)
|
|
351
349
|
|
|
352
350
|
if (hooks && (hooks instanceof Object) && hooks.redirect_final && (typeof hooks.redirect_final === 'function')) {
|
|
@@ -354,6 +352,18 @@ const encode_embedded_url = function(embedded_url, hooks, redirected_base_url, d
|
|
|
354
352
|
|
|
355
353
|
debug(3, 'redirecting (proxied, post-hook):', embedded_url.encoded_url)
|
|
356
354
|
}
|
|
355
|
+
|
|
356
|
+
let qs_pairs = []
|
|
357
|
+
if (qs_headers)
|
|
358
|
+
qs_pairs.push(['headers', qs_headers])
|
|
359
|
+
if (qs_password)
|
|
360
|
+
qs_pairs.push(['password', qs_password])
|
|
361
|
+
if (qs_pairs.length) {
|
|
362
|
+
qs_pairs = qs_pairs.map(pair => `${pair[0]}=${encodeURIComponent(pair[1])}`)
|
|
363
|
+
|
|
364
|
+
embedded_url.encoded_url += '?' + qs_pairs.join('&')
|
|
365
|
+
debug(3, 'redirecting (proxied, with querystring):', embedded_url.encoded_url)
|
|
366
|
+
}
|
|
357
367
|
}
|
|
358
368
|
else {
|
|
359
369
|
embedded_url.encoded_url = ''
|
|
@@ -382,7 +392,7 @@ const modify_m3u8_line = function(embedded_url, m3u8_lines) {
|
|
|
382
392
|
}
|
|
383
393
|
}
|
|
384
394
|
|
|
385
|
-
const modify_m3u8_content = function(params, segment_cache, m3u8_content, m3u8_url, referer_url, inbound_req_headers, redirected_base_url, qs_password) {
|
|
395
|
+
const modify_m3u8_content = function(params, segment_cache, m3u8_content, m3u8_url, referer_url, querystring_req_headers, inbound_req_headers, redirected_base_url, qs_password) {
|
|
386
396
|
const {hooks, cache_segments, max_segments, debug_level, manifest_extension, segment_extension} = params
|
|
387
397
|
|
|
388
398
|
const {has_cache, get_time_since_last_access, is_expired, prefetch_segment} = segment_cache
|
|
@@ -424,13 +434,13 @@ const modify_m3u8_content = function(params, segment_cache, m3u8_content, m3u8_u
|
|
|
424
434
|
const matching_url = urls[0]
|
|
425
435
|
urls[0] = undefined
|
|
426
436
|
|
|
427
|
-
promise = prefetch_segment(m3u8_url, matching_url, referer_url, inbound_req_headers, dont_touch_access)
|
|
437
|
+
promise = prefetch_segment(m3u8_url, matching_url, referer_url, querystring_req_headers, inbound_req_headers, dont_touch_access)
|
|
428
438
|
}
|
|
429
439
|
|
|
430
440
|
promise.then(() => {
|
|
431
441
|
urls.forEach((matching_url, index) => {
|
|
432
442
|
if (matching_url) {
|
|
433
|
-
prefetch_segment(m3u8_url, matching_url, referer_url, inbound_req_headers, dont_touch_access)
|
|
443
|
+
prefetch_segment(m3u8_url, matching_url, referer_url, querystring_req_headers, inbound_req_headers, dont_touch_access)
|
|
434
444
|
|
|
435
445
|
urls[index] = undefined
|
|
436
446
|
}
|
|
@@ -440,7 +450,7 @@ const modify_m3u8_content = function(params, segment_cache, m3u8_content, m3u8_u
|
|
|
440
450
|
: null
|
|
441
451
|
|
|
442
452
|
{
|
|
443
|
-
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, manifest_extension, segment_extension, qs_password)
|
|
453
|
+
const parsed_manifest = parse_manifest(m3u8_content, m3u8_url, referer_url, querystring_req_headers, hooks, cache_segments, debug, vod_start_at_ms, redirected_base_url, should_prefetch_url, manifest_extension, segment_extension, qs_password)
|
|
444
454
|
is_vod = !!parsed_manifest.meta_data.is_vod // default: false => hls live stream
|
|
445
455
|
seg_duration_ms = parsed_manifest.meta_data.seg_duration_ms || 10000 // default: 10 seconds in ms
|
|
446
456
|
prefetch_urls = parsed_manifest.prefetch_urls
|
package/hls-proxy/proxy.js
CHANGED
|
@@ -47,7 +47,7 @@ const get_middleware = function(params) {
|
|
|
47
47
|
|
|
48
48
|
utils.add_CORS_headers(res)
|
|
49
49
|
|
|
50
|
-
const {redirected_base_url, url_type, url, referer_url} = parse_req_url(req)
|
|
50
|
+
const {redirected_base_url, url_type, url, referer_url, querystring_req_headers} = parse_req_url(req)
|
|
51
51
|
|
|
52
52
|
if (!url) {
|
|
53
53
|
res.writeHead(400)
|
|
@@ -79,7 +79,7 @@ const get_middleware = function(params) {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
const options = get_request_options(url, is_m3u8, referer_url, req.headers)
|
|
82
|
+
const options = get_request_options(url, is_m3u8, referer_url, querystring_req_headers, req.headers)
|
|
83
83
|
debug(1, 'proxying:', url)
|
|
84
84
|
debug(3, 'm3u8:', (is_m3u8 ? 'true' : 'false'))
|
|
85
85
|
|
|
@@ -107,7 +107,7 @@ const get_middleware = function(params) {
|
|
|
107
107
|
: url
|
|
108
108
|
|
|
109
109
|
res.writeHead(200, { "content-type": "application/x-mpegURL" })
|
|
110
|
-
res.end( modify_m3u8_content(response.toString().trim(), m3u8_url, referer_url, req.headers, redirected_base_url, qs_password) )
|
|
110
|
+
res.end( modify_m3u8_content(response.toString().trim(), m3u8_url, referer_url, querystring_req_headers, req.headers, redirected_base_url, qs_password) )
|
|
111
111
|
}
|
|
112
112
|
})
|
|
113
113
|
.catch((e) => {
|
|
@@ -161,7 +161,7 @@ module.exports = function(params) {
|
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
const prefetch_segment = function(m3u8_url, url, referer_url, inbound_req_headers, dont_touch_access) {
|
|
164
|
+
const prefetch_segment = function(m3u8_url, url, referer_url, querystring_req_headers, inbound_req_headers, dont_touch_access) {
|
|
165
165
|
let promise = Promise.resolve()
|
|
166
166
|
|
|
167
167
|
if (cache[m3u8_url] === undefined) {
|
|
@@ -184,7 +184,7 @@ module.exports = function(params) {
|
|
|
184
184
|
index = ts.length
|
|
185
185
|
ts[index] = {key: get_privatekey_from_url(url), has: false, cb: [], type: null, state: {}}
|
|
186
186
|
|
|
187
|
-
let options = get_request_options(url, /* is_m3u8= */ false, referer_url, inbound_req_headers)
|
|
187
|
+
let options = get_request_options(url, /* is_m3u8= */ false, referer_url, querystring_req_headers, inbound_req_headers)
|
|
188
188
|
promise = request(options, '', {binary: true, stream: false, cookieJar: cookies.getCookieJar()})
|
|
189
189
|
.then(({redirects, response}) => {
|
|
190
190
|
debug(1, `prefetch (complete, ${response.length} bytes):`, debug_url)
|
package/hls-proxy/timers.js
CHANGED
|
@@ -11,7 +11,7 @@ const initialize_timers = function(params) {
|
|
|
11
11
|
const get_request_options = utils.get_request_options.bind(null, params)
|
|
12
12
|
|
|
13
13
|
const request_wrapper = function(url, POST_data, user_config) {
|
|
14
|
-
const options = get_request_options(url, /* is_m3u8= */ false, /* referer_url= */ null, /* inbound_req_headers= */ null)
|
|
14
|
+
const options = get_request_options(url, /* is_m3u8= */ false, /* referer_url= */ null, /* querystring_req_headers= */ null, /* inbound_req_headers= */ null)
|
|
15
15
|
const config = Object.assign(
|
|
16
16
|
{},
|
|
17
17
|
(user_config || {}),
|
package/hls-proxy/utils.js
CHANGED
|
@@ -19,7 +19,7 @@ const base64_decode = function(str) {
|
|
|
19
19
|
const parse_req_url = function(params, req) {
|
|
20
20
|
const {is_secure, host, manifest_extension, segment_extension, hooks} = params
|
|
21
21
|
|
|
22
|
-
const result = {redirected_base_url: '', url_type: '', url: '', referer_url: ''}
|
|
22
|
+
const result = {redirected_base_url: '', url_type: '', url: '', referer_url: '', querystring_req_headers: null}
|
|
23
23
|
|
|
24
24
|
const matches = regexs.req_url.exec( expressjs.get_proxy_req_url(req) )
|
|
25
25
|
|
|
@@ -56,6 +56,18 @@ const parse_req_url = function(params, req) {
|
|
|
56
56
|
|
|
57
57
|
result.url = url
|
|
58
58
|
}
|
|
59
|
+
|
|
60
|
+
let qs_headers = expressjs.get_proxy_req_query(req, 'headers')
|
|
61
|
+
if (qs_headers) {
|
|
62
|
+
try {
|
|
63
|
+
qs_headers = base64_decode( decodeURIComponent( qs_headers ) ).trim()
|
|
64
|
+
qs_headers = JSON.parse(qs_headers)
|
|
65
|
+
|
|
66
|
+
if (qs_headers && (qs_headers instanceof Object))
|
|
67
|
+
result.querystring_req_headers = qs_headers
|
|
68
|
+
}
|
|
69
|
+
catch(e) {}
|
|
70
|
+
}
|
|
59
71
|
}
|
|
60
72
|
|
|
61
73
|
return result
|
|
@@ -120,21 +132,31 @@ const debug = function() {
|
|
|
120
132
|
}
|
|
121
133
|
}
|
|
122
134
|
|
|
123
|
-
const normalize_req_headers = function(req_headers) {
|
|
135
|
+
const normalize_req_headers = function(req_headers, blacklist) {
|
|
124
136
|
const normalized = {}
|
|
125
137
|
|
|
138
|
+
if (blacklist && !Array.isArray(blacklist))
|
|
139
|
+
blacklist = null
|
|
140
|
+
if (blacklist)
|
|
141
|
+
blacklist = blacklist.filter(val => val && (typeof val === 'string')).map(val => val.toLowerCase())
|
|
142
|
+
if (blacklist && !blacklist.length)
|
|
143
|
+
blacklist = null
|
|
144
|
+
|
|
126
145
|
for (let name in req_headers) {
|
|
127
|
-
|
|
146
|
+
const lc_name = name.toLowerCase()
|
|
147
|
+
|
|
148
|
+
if (!blacklist || (blacklist.indexOf(lc_name) === -1))
|
|
149
|
+
normalized[lc_name] = req_headers[name]
|
|
128
150
|
}
|
|
129
151
|
|
|
130
152
|
return normalized
|
|
131
153
|
}
|
|
132
154
|
|
|
133
|
-
const get_request_options = function(params, url, is_m3u8, referer_url, inbound_req_headers) {
|
|
155
|
+
const get_request_options = function(params, url, is_m3u8, referer_url, querystring_req_headers, inbound_req_headers) {
|
|
134
156
|
const {copy_req_headers, req_headers, req_options, hooks, http_proxy} = params
|
|
135
157
|
|
|
136
158
|
const copied_req_headers = (copy_req_headers && inbound_req_headers && (inbound_req_headers instanceof Object))
|
|
137
|
-
? normalize_req_headers(inbound_req_headers)
|
|
159
|
+
? normalize_req_headers(inbound_req_headers, ['host'])
|
|
138
160
|
: null
|
|
139
161
|
|
|
140
162
|
const additional_req_options = (hooks && (hooks instanceof Object) && hooks.add_request_options && (typeof hooks.add_request_options === 'function'))
|
|
@@ -145,7 +167,7 @@ const get_request_options = function(params, url, is_m3u8, referer_url, inbound_
|
|
|
145
167
|
? hooks.add_request_headers(url, is_m3u8)
|
|
146
168
|
: null
|
|
147
169
|
|
|
148
|
-
if (!req_options && !http_proxy && !additional_req_options && !copied_req_headers && !req_headers && !additional_req_headers && !referer_url) return url
|
|
170
|
+
if (!req_options && !http_proxy && !additional_req_options && !copied_req_headers && !req_headers && !additional_req_headers && !referer_url && !querystring_req_headers) return url
|
|
149
171
|
|
|
150
172
|
const request_options = Object.assign(
|
|
151
173
|
{},
|
|
@@ -161,7 +183,8 @@ const get_request_options = function(params, url, is_m3u8, referer_url, inbound_
|
|
|
161
183
|
((additional_req_options && additional_req_options.headers) ? additional_req_options.headers : {}),
|
|
162
184
|
(req_headers || {}),
|
|
163
185
|
(additional_req_headers || {}),
|
|
164
|
-
(referer_url ? {"referer": referer_url, "origin": referer_url.replace(regexs.origin, '$1')} : {})
|
|
186
|
+
(referer_url ? {"referer": referer_url, "origin": referer_url.replace(regexs.origin, '$1')} : {}),
|
|
187
|
+
(querystring_req_headers || {})
|
|
165
188
|
)
|
|
166
189
|
|
|
167
190
|
// normalize
|
package/package.json
CHANGED