Open siberkolosis opened 4 months ago
it isn't elegant.. but you could quickly and easily write something yourself to shorten your playlists using a hook function.
ex: hlsd --hooks /path/to/hooks.js
// hooks.js
module.exports = {
"modify_m3u8_content": function(m3u8_content, m3u8_url) {
// if (!some_conditional_test(m3u8_url)) return m3u8_content
const max_segments = 6
const token = "\nhttp"
const all_segments = m3u8_content.split(token)
return all_segments.shift() + token + all_segments.slice(-1 * max_segments).join(token)
}
}
note: i haven't tested this code.. just a quick scribble.. but it looks about right
actually.. on 2nd thought.. that previous example assumes that the manifest uses absolute URLs. a better example might be:
// hooks.js
module.exports = {
"modify_m3u8_content": function(m3u8_content, m3u8_url) {
// if (!some_conditional_test(m3u8_url)) return m3u8_content
const max_segments = 6
const token = "\n#EXTINF:"
const all_segments = m3u8_content.split(token)
return all_segments.shift() + token + all_segments.slice(-1 * max_segments).join(token)
}
}
unit test:
{
const m3u8_url = 'https://docs.aws.amazon.com/mediatailor/latest/ug/manifest-hls-example.html'
const m3u8_content = `#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:7
#EXT-X-MEDIA-SEQUENCE:8779957
#EXTINF:6.006,
index_1_8779957.ts?m=1566416212
#EXTINF:6.006,
index_1_8779958.ts?m=1566416212
#EXTINF:5.372,
index_1_8779959.ts?m=1566416212
#EXT-OATCLS-SCTE35:/DAlAAAAAsvhAP/wFAXwAAAGf+/+AdLfiP4AG3dAAAEBAQAAXytxmQ==
#EXT-X-CUE-OUT:20.020
#EXTINF:0.634,
index_1_8779960.ts?m=1566416212
#EXT-X-CUE-OUT-CONT:ElapsedTime=0.634,Duration=21,SCTE35=/DAlAAAAAsvhAP/wFAXwAAAGf+/+AdLfiP4AG3dAAAEBAQAAXytxmQ==
#EXTINF:6.006,
index_1_8779961.ts?m=1566416212
#EXT-X-CUE-OUT-CONT:ElapsedTime=6.640,Duration=21,SCTE35=/DAlAAAAAsvhAP/wFAXwAAAGf+/+AdLfiP4AG3dAAAEBAQAAXytxmQ==
#EXTINF:6.006,
index_1_8779962.ts?m=1566416212
#EXT-X-CUE-OUT-CONT:ElapsedTime=12.646,Duration=21,SCTE35=/DAlAAAAAsvhAP/wFAXwAAAGf+/+AdLfiP4AG3dAAAEBAQAAXytxmQ==
#EXTINF:6.006,
index_1_8779963.ts?m=1566416212
#EXT-X-CUE-OUT-CONT:ElapsedTime=18.652,Duration=21,SCTE35=/DAlAAAAAsvhAP/wFAXwAAAGf+/+AdLfiP4AG3dAAAEBAQAAXytxmQ==
#EXTINF:1.368,
index_1_8779964.ts?m=1566416212
#EXT-X-CUE-IN
#EXTINF:4.638,
index_1_8779965.ts?m=1566416212
#EXTINF:6.006,
index_1_8779966.ts?m=1566416212
#EXTINF:6.006,
index_1_8779967.ts?m=1566416212
#EXTINF:6.006,
index_1_8779968.ts?m=1566416212`
const modify_m3u8_content = function(m3u8_content, m3u8_url) {
// if (!some_conditional_test(m3u8_url)) return m3u8_content
const max_segments = 6
const token = "\n#EXTINF:"
const all_segments = m3u8_content.split(token)
return all_segments.shift() + token + all_segments.slice(-1 * max_segments).join(token)
}
console.log(
modify_m3u8_content(m3u8_content, m3u8_url)
)
}
outputs:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:7
#EXT-X-MEDIA-SEQUENCE:8779957
#EXTINF:6.006,
index_1_8779963.ts?m=1566416212
#EXT-X-CUE-OUT-CONT:ElapsedTime=18.652,Duration=21,SCTE35=/DAlAAAAAsvhAP/wFAXwAAAGf+/+AdLfiP4AG3dAAAEBAQAAXytxmQ==
#EXTINF:1.368,
index_1_8779964.ts?m=1566416212
#EXT-X-CUE-IN
#EXTINF:4.638,
index_1_8779965.ts?m=1566416212
#EXTINF:6.006,
index_1_8779966.ts?m=1566416212
#EXTINF:6.006,
index_1_8779967.ts?m=1566416212
#EXTINF:6.006,
index_1_8779968.ts?m=1566416212
success.
thanks...
I don't know if those "#EXT-X-CUE-*" tags could be problematic. Since they (or possibly other tags) may be, I tweaked the example.. this one shows you how you could filter unwanted tags and only keep the ones that you're interested in:
// hooks.js
const filter_partial_playlist = function(pp) {
const token = '#EXTINF:'
const all_lines = pp.split(/[\r\n]+/g)
return all_lines
.filter(function(line) {
return (!line || (line[0] !== '#') || line.startsWith(token))
})
.join("\n")
}
const modify_m3u8_content = function(m3u8_content, m3u8_url) {
// if (!some_conditional_test(m3u8_url)) return m3u8_content
const max_segments = 6
const token = "\n#EXTINF:"
const all_segments = m3u8_content.split(token)
return all_segments.shift() + token + filter_partial_playlist( all_segments.slice(-1 * max_segments).join(token) )
}
module.exports = {
modify_m3u8_content
}
unit test:
{
const m3u8_url = 'https://docs.aws.amazon.com/mediatailor/latest/ug/manifest-hls-example.html'
const m3u8_content = `#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:7
#EXT-X-MEDIA-SEQUENCE:8779957
#EXTINF:6.006,
index_1_8779957.ts?m=1566416212
#EXTINF:6.006,
index_1_8779958.ts?m=1566416212
#EXTINF:5.372,
index_1_8779959.ts?m=1566416212
#EXT-OATCLS-SCTE35:/DAlAAAAAsvhAP/wFAXwAAAGf+/+AdLfiP4AG3dAAAEBAQAAXytxmQ==
#EXT-X-CUE-OUT:20.020
#EXTINF:0.634,
index_1_8779960.ts?m=1566416212
#EXT-X-CUE-OUT-CONT:ElapsedTime=0.634,Duration=21,SCTE35=/DAlAAAAAsvhAP/wFAXwAAAGf+/+AdLfiP4AG3dAAAEBAQAAXytxmQ==
#EXTINF:6.006,
index_1_8779961.ts?m=1566416212
#EXT-X-CUE-OUT-CONT:ElapsedTime=6.640,Duration=21,SCTE35=/DAlAAAAAsvhAP/wFAXwAAAGf+/+AdLfiP4AG3dAAAEBAQAAXytxmQ==
#EXTINF:6.006,
index_1_8779962.ts?m=1566416212
#EXT-X-CUE-OUT-CONT:ElapsedTime=12.646,Duration=21,SCTE35=/DAlAAAAAsvhAP/wFAXwAAAGf+/+AdLfiP4AG3dAAAEBAQAAXytxmQ==
#EXTINF:6.006,
index_1_8779963.ts?m=1566416212
#EXT-X-CUE-OUT-CONT:ElapsedTime=18.652,Duration=21,SCTE35=/DAlAAAAAsvhAP/wFAXwAAAGf+/+AdLfiP4AG3dAAAEBAQAAXytxmQ==
#EXTINF:1.368,
index_1_8779964.ts?m=1566416212
#EXT-X-CUE-IN
#EXTINF:4.638,
index_1_8779965.ts?m=1566416212
#EXTINF:6.006,
index_1_8779966.ts?m=1566416212
#EXTINF:6.006,
index_1_8779967.ts?m=1566416212
#EXTINF:6.006,
index_1_8779968.ts?m=1566416212`
const filter_partial_playlist = function(pp) {
const token = '#EXTINF:'
const all_lines = pp.split(/[\r\n]+/g)
return all_lines
.filter(function(line) {
return (!line || (line[0] !== '#') || line.startsWith(token))
})
.join("\n")
}
const modify_m3u8_content = function(m3u8_content, m3u8_url) {
// if (!some_conditional_test(m3u8_url)) return m3u8_content
const max_segments = 6
const token = "\n#EXTINF:"
const all_segments = m3u8_content.split(token)
return all_segments.shift() + token + filter_partial_playlist( all_segments.slice(-1 * max_segments).join(token) )
}
console.log(
modify_m3u8_content(m3u8_content, m3u8_url)
)
}
outputs:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:7
#EXT-X-MEDIA-SEQUENCE:8779957
#EXTINF:6.006,
index_1_8779963.ts?m=1566416212
#EXTINF:1.368,
index_1_8779964.ts?m=1566416212
#EXTINF:4.638,
index_1_8779965.ts?m=1566416212
#EXTINF:6.006,
index_1_8779966.ts?m=1566416212
#EXTINF:6.006,
index_1_8779967.ts?m=1566416212
#EXTINF:6.006,
index_1_8779968.ts?m=1566416212
success.
working perfect on my case. problem is on master playlist multibitrate, no "EXTINF:", but adding EXTINF: in last line.
how to combine with my hook.js
module.exports = { "redirect_final": (url) =>
https://images-blogger-opensocial.googleusercontent.com/gadgets/proxy?refresh=4&container=focus&gadget=a&url=${encodeURIComponent(url)} }
you wouldn't want to modify your master playlist.. since it only contains a few links.. to child manifests having alternate bitrates.
you could either:
some_conditional_test(m3u8_url)
// hooks.js
const filter_partial_playlist = function(pp) {
const token = '#EXTINF:'
const all_lines = pp.split(/[\r\n]+/g)
return all_lines
.filter(function(line) {
return (!line || (line[0] !== '#') || line.startsWith(token))
})
.join("\n")
}
const modify_m3u8_content = function(m3u8_content, m3u8_url) {
// if (!some_conditional_test(m3u8_url)) return m3u8_content
const max_segments = 6
const token = "\n#EXTINF:"
const all_segments = m3u8_content.split(token)
if (all_segments.length === 1) return m3u8_content
return all_segments.shift() + token + filter_partial_playlist( all_segments.slice(-1 * max_segments).join(token) )
}
module.exports = {
modify_m3u8_content
}
..that should do the trick
I should probably mention that this example hook does not support manifests using rotating encryption keys.
It would be easy enough to whitelist the #EXT-X-KEY:
tag in filter_partial_playlist
.
The difficulty is that the last instance of this tag in the part of the manifest that is being removed..
should be cherry-picked and re-inserted:
return all_segments.shift() + "\n" + get_last_key(all_segments) + token + filter_partial_playlist( all_segments.slice(-1 * max_segments).join(token) )
here is an implementation that does support changing encryption keys:
// hooks.js
const modify_m3u8_content = function(m3u8_content, m3u8_url) {
// if (!some_conditional_test(m3u8_url)) return m3u8_content
const max_segments = 6
const token = "\n#EXTINF:"
const all_segments = m3u8_content.split(token)
if (all_segments.length === 1) return m3u8_content
return all_segments.shift() + get_last_encryption_key(all_segments, max_segments) + token + filter_partial_playlist( all_segments.slice(-1 * max_segments).join(token) )
}
const get_last_encryption_key = function(segments, count_exclude) {
if (count_exclude >= segments.length) return ''
const token = /\n(#EXT-X-KEY:.*?)(?:[\r\n]|$)/i
for (let i = (segments.length - count_exclude - 1); i >= 0; i--) {
const match = token.exec(segments[i])
if (match) return ("\n" + match[1])
}
return ''
}
const filter_partial_playlist = function(pp) {
const token_whitelist = /^#(?:EXTINF|EXT-X-KEY):/i
const all_lines = pp.split(/[\r\n]+/g)
return all_lines
.filter(function(line) {
return (!line || (line[0] !== '#') || token_whitelist.test(line))
})
.join("\n")
}
module.exports = {
modify_m3u8_content
}
unit test:
{
const m3u8_url = 'https://github.com/google/ExoPlayer/issues/3725'
const m3u8_content = `#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:5
#EXT-X-PROGRAM-DATE-TIME:2018-01-18T20:42:59+00:00
#EXT-X-KEY:METHOD=AES-128,URI="https://foo.com/321630/keys/id/2348028",IV=0x66AA04B5A372A3D24D91A44C194BD050
#EXT-X-MEDIA-SEQUENCE:259371
#EXTINF:5.005,
018/20/42/59_405.ts
#EXT-X-KEY:METHOD=AES-128,URI="https://foo.com/321630/keys/id/2348028",IV=0xAD9A75768DADF6684C4A55AF649484F2
#EXTINF:5.005,
018/20/43/04_410.ts
#EXTINF:5.005,
018/20/43/09_415.ts
#EXTINF:5.005,
018/20/43/14_420.ts
#EXTINF:5.005,
018/20/43/19_425.ts
#EXTINF:5.005,
018/20/43/24_430.ts
#EXTINF:5.005,
018/20/43/29_435.ts
#EXT-X-KEY:METHOD=AES-128,URI="https://foo.com/321630/keys/id/2348028",IV=0xC4467EF6216F8E93A2D241FCAD2FC486
#EXTINF:5.005,
018/20/43/34_440.ts
#EXTINF:5.005,
018/20/43/39_445.ts
#EXTINF:5.005,
018/20/43/44_450.ts`
const modify_m3u8_content = function(m3u8_content, m3u8_url) {
// if (!some_conditional_test(m3u8_url)) return m3u8_content
const max_segments = 6
const token = "\n#EXTINF:"
const all_segments = m3u8_content.split(token)
if (all_segments.length === 1) return m3u8_content
return all_segments.shift() + get_last_encryption_key(all_segments, max_segments) + token + filter_partial_playlist( all_segments.slice(-1 * max_segments).join(token) )
}
const get_last_encryption_key = function(segments, count_exclude) {
if (count_exclude >= segments.length) return ''
const token = /\n(#EXT-X-KEY:.*?)(?:[\r\n]|$)/i
for (let i = (segments.length - count_exclude - 1); i >= 0; i--) {
const match = token.exec(segments[i])
if (match) return ("\n" + match[1])
}
return ''
}
const filter_partial_playlist = function(pp) {
const token_whitelist = /^#(?:EXTINF|EXT-X-KEY):/i
const all_lines = pp.split(/[\r\n]+/g)
return all_lines
.filter(function(line) {
return (!line || (line[0] !== '#') || token_whitelist.test(line))
})
.join("\n")
}
console.log(
modify_m3u8_content(m3u8_content, m3u8_url)
)
}
outputs:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:5
#EXT-X-PROGRAM-DATE-TIME:2018-01-18T20:42:59+00:00
#EXT-X-KEY:METHOD=AES-128,URI="https://foo.com/321630/keys/id/2348028",IV=0x66AA04B5A372A3D24D91A44C194BD050
#EXT-X-MEDIA-SEQUENCE:259371
#EXT-X-KEY:METHOD=AES-128,URI="https://foo.com/321630/keys/id/2348028",IV=0xAD9A75768DADF6684C4A55AF649484F2
#EXTINF:5.005,
018/20/43/19_425.ts
#EXTINF:5.005,
018/20/43/24_430.ts
#EXTINF:5.005,
018/20/43/29_435.ts
#EXT-X-KEY:METHOD=AES-128,URI="https://foo.com/321630/keys/id/2348028",IV=0xC4467EF6216F8E93A2D241FCAD2FC486
#EXTINF:5.005,
018/20/43/34_440.ts
#EXTINF:5.005,
018/20/43/39_445.ts
#EXTINF:5.005,
018/20/43/44_450.ts
thank you. work perfect. i can easely share hls or iptv with hls-proxy. with vpn split i can bypass geo blocking. someday don't want to create a dash proxy version? Many paid videos now use dash. with Widevine license server. Is it possible that the Widevine license server can be proxied too?
I wrote a little proxy server that can convert DASH manifests to HLS manifests on-the-fly. I haven't looked at that project in a very long time, but it did work.. and passed whatever testing that I performed at the time. You might be able to daisy-chain it together with this HLS proxy.. I don't see why that wouldn't work, but I haven't played around with it at all. Generally speaking.. I try to avoid DASH; I just don't like working with it.. too verbose.
sometimes live video streaming has 2k segments in the playlist. The advantage is that the video can be rewinded. however the m3u gets very large at around 1MB. I want it to only have 5 or 6 segments, is that possible?