Merge pull request #34913 from iptv-org/patch-2026.03.6

Patch 2026.03.6
This commit is contained in:
Pecaquito
2026-03-26 18:46:39 -04:00
committed by GitHub
22 changed files with 379 additions and 23 deletions

113
package-lock.json generated
View File

@@ -36,15 +36,18 @@
"commander": "^14.0.0",
"console-table-printer": "^2.14.6",
"cross-env": "^10.0.0",
"es-toolkit": "^1.45.1",
"eslint": "^9.32.0",
"glob": "^11.0.3",
"globals": "^16.3.0",
"hls-parser": "^0.16.0",
"iptv-playlist-parser": "^0.15.1",
"jest": "^30.0.5",
"jest-expect-message": "^1.1.3",
"lodash.uniqueid": "^4.0.1",
"m3u-linter": "^0.4.2",
"mediainfo.js": "^0.3.6",
"mpd-parser": "^1.3.1",
"node-cleanup": "^2.1.2",
"normalize-url": "^8.1.0",
"socks-proxy-agent": "^8.0.5",
@@ -497,6 +500,15 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/runtime": {
"version": "7.29.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
"version": "7.27.2",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
@@ -3453,6 +3465,30 @@
"win32"
]
},
"node_modules/@videojs/vhs-utils": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-4.0.0.tgz",
"integrity": "sha512-xJp7Yd4jMLwje2vHCUmi8MOUU76nxiwII3z4Eg3Ucb+6rrkFVGosrXlMgGnaLjq724j3wzNElRZ71D/CKrTtxg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.12.5",
"global": "^4.4.0",
"url-toolkit": "^2.2.1"
},
"engines": {
"node": ">=8",
"npm": ">=5"
}
},
"node_modules/@xmldom/xmldom": {
"version": "0.8.11",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz",
"integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
@@ -4107,6 +4143,11 @@
"node": ">=8"
}
},
"node_modules/dom-walk": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -4204,6 +4245,16 @@
"node": ">= 0.4"
}
},
"node_modules/es-toolkit": {
"version": "1.45.1",
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.45.1.tgz",
"integrity": "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==",
"license": "MIT",
"workspaces": [
"docs",
"benchmarks"
]
},
"node_modules/esbuild": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz",
@@ -4635,9 +4686,10 @@
}
},
"node_modules/flatted": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
"license": "ISC"
},
"node_modules/follow-redirects": {
"version": "1.15.11",
@@ -4897,6 +4949,16 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/global": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
"integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==",
"license": "MIT",
"dependencies": {
"min-document": "^2.19.0",
"process": "^0.11.10"
}
},
"node_modules/globals": {
"version": "16.3.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz",
@@ -4979,6 +5041,12 @@
"node": ">= 0.4"
}
},
"node_modules/hls-parser": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/hls-parser/-/hls-parser-0.16.0.tgz",
"integrity": "sha512-jb8tX36Nn49MtvSBTWcQjD4ktwJtzrfOlnI8zpo4zQjTq0ZGmG0RsoCQXwvKQGvt4CBFrs8Q3znq0HoNCpt5BA==",
"license": "MIT"
},
"node_modules/html-escaper": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
@@ -6617,6 +6685,15 @@
"node": ">=6"
}
},
"node_modules/min-document": {
"version": "2.19.2",
"resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.2.tgz",
"integrity": "sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==",
"license": "MIT",
"dependencies": {
"dom-walk": "^0.1.0"
}
},
"node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
@@ -6637,6 +6714,21 @@
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/mpd-parser": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-1.3.1.tgz",
"integrity": "sha512-1FuyEWI5k2HcmhS1HkKnUAQV7yFPfXPht2DnRRGtoiiAAW+ESTbtEXIDpRkwdU+XyrQuwrIym7UkoPKsZ0SyFw==",
"license": "Apache-2.0",
"dependencies": {
"@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "^4.0.0",
"@xmldom/xmldom": "^0.8.3",
"global": "^4.4.0"
},
"bin": {
"mpd-to-m3u8-json": "bin/parse.js"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -7024,6 +7116,15 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
"license": "MIT",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -7719,6 +7820,12 @@
"punycode": "^2.1.0"
}
},
"node_modules/url-toolkit": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.5.tgz",
"integrity": "sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg==",
"license": "Apache-2.0"
},
"node_modules/v8-to-istanbul": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",

View File

@@ -60,15 +60,18 @@
"commander": "^14.0.0",
"console-table-printer": "^2.14.6",
"cross-env": "^10.0.0",
"es-toolkit": "^1.45.1",
"eslint": "^9.32.0",
"glob": "^11.0.3",
"globals": "^16.3.0",
"hls-parser": "^0.16.0",
"iptv-playlist-parser": "^0.15.1",
"jest": "^30.0.5",
"jest-expect-message": "^1.1.3",
"lodash.uniqueid": "^4.0.1",
"m3u-linter": "^0.4.2",
"mediainfo.js": "^0.3.6",
"mpd-parser": "^1.3.1",
"node-cleanup": "^2.1.2",
"normalize-url": "^8.1.0",
"socks-proxy-agent": "^8.0.5",

View File

@@ -1,13 +1,34 @@
import { Collection, Logger } from '@freearhey/core'
import { OptionValues, program } from 'commander'
import { Stream, Playlist } from '../../models'
import { Storage } from '@freearhey/storage-js'
import { STREAMS_DIR } from '../../constants'
import { PlaylistParser } from '../../core'
import { getStreamInfo } from '../../utils'
import cliProgress from 'cli-progress'
import { loadData } from '../../api'
import { program } from 'commander'
import { eachLimit } from 'async'
import path from 'node:path'
import os from 'node:os'
program.argument('[filepath...]', 'Path to file to format').parse(process.argv)
program
.argument('[filepath...]', 'Path to file to format')
.option(
'-p, --parallel <number>',
'Batch size of streams to test concurrently',
(value: string) => parseInt(value),
os.cpus().length
)
.option('-x, --proxy <url>', 'Use the specified proxy')
.option(
'-t, --timeout <number>',
'The number of milliseconds before the request will be aborted',
(value: string) => parseInt(value),
1000
)
.parse(process.argv)
const options: OptionValues = program.opts()
async function main() {
const logger = new Logger()
@@ -58,6 +79,33 @@ async function main() {
return stream
})
logger.info('adding the missing quality...')
const progressBar = new cliProgress.SingleBar({
clearOnComplete: true,
format: `[{bar}] {percentage}% | {value}/{total}`
})
progressBar.start(streams.count(), 0)
await eachLimit(streams.all(), options.parallel, async (stream: Stream) => {
progressBar.increment()
if (stream.quality) return
const streamInfo = await getStreamInfo(stream.url, {
httpUserAgent: stream.user_agent,
httpReferrer: stream.referrer,
timeout: options.timeout,
proxy: options.proxy
})
if (streamInfo) {
const height = streamInfo?.resolution?.height
if (height) {
stream.quality = `${height}p`
}
}
})
progressBar.stop()
logger.info('sorting links...')
streams = streams.sortBy(
[

View File

@@ -2,10 +2,10 @@ import { IssueLoader, PlaylistParser } from '../../core'
import { Playlist, Issue, Stream } from '../../models'
import { loadData, data as apiData } from '../../api'
import { Logger, Collection } from '@freearhey/core'
import { isURI, getStreamInfo } from '../../utils'
import { Storage } from '@freearhey/storage-js'
import { STREAMS_DIR } from '../../constants'
import * as sdk from '@iptv-org/sdk'
import { isURI } from '../../utils'
const processedIssues = new Collection()
@@ -136,24 +136,38 @@ async function addStreams({
const requests = issues.filter(
issue => issue.labels.includes('streams:add') && issue.labels.includes('approved')
)
requests.forEach((issue: Issue) => {
for (const issue of requests.all()) {
const data = issue.data
if (data.missing('stream_id') || data.missing('stream_url')) return
if (streams.includes((_stream: Stream) => _stream.url === data.getString('stream_url'))) return
if (data.missing('stream_id') || data.missing('stream_url')) continue
if (streams.includes((_stream: Stream) => _stream.url === data.getString('stream_url')))
continue
const streamUrl = data.getString('stream_url') || ''
if (!isURI(streamUrl)) return
if (!isURI(streamUrl)) continue
const streamId = data.getString('stream_id') || ''
const [channelId, feedId] = streamId.split('@')
const channel: sdk.Models.Channel | undefined = apiData.channelsKeyById.get(channelId)
if (!channel) return
if (!channel) continue
const label = data.getString('label') || ''
const quality = data.getString('quality') || null
const httpUserAgent = data.getString('http_user_agent') || null
const httpReferrer = data.getString('http_referrer') || null
let quality = data.getString('quality') || null
if (!quality) {
const streamInfo = await getStreamInfo(streamUrl, { httpUserAgent, httpReferrer })
if (streamInfo) {
const height = streamInfo?.resolution?.height
if (height) {
quality = `${height}p`
}
}
}
const stream = new Stream({
channel: channelId,
feed: feedId,
@@ -169,5 +183,5 @@ async function addStreams({
streams.add(stream)
processedIssues.add(issue.number)
})
}
}

View File

@@ -1,4 +1,13 @@
import axios, { AxiosProxyConfig, AxiosRequestConfig } from 'axios'
import { parse as parsePlaylist, setOptions } from 'hls-parser'
import { parse as parseManifest } from 'mpd-parser'
import { SocksProxyAgent } from 'socks-proxy-agent'
import { ProxyParser } from './core/proxyParser.js'
import { TESTING } from './constants.js'
import normalizeUrl from 'normalize-url'
import { orderBy } from 'es-toolkit'
import path from 'node:path'
import fs from 'node:fs'
export function isURI(string: string): boolean {
try {
@@ -21,3 +30,111 @@ export function truncate(string: string, limit: number = 100) {
return string.slice(0, limit - 3) + '...'
}
type StreamInfo = {
resolution: { width: number; height: number }
bandwidth: number
frameRate: number
codecs: string
}
export async function getStreamInfo(
url: string,
options: {
httpUserAgent?: string | null
httpReferrer?: string | null
timeout?: number
proxy?: string
}
): Promise<StreamInfo | undefined> {
let data: string | undefined
if (TESTING) {
if (url.includes('.m3u8')) {
data = fs.readFileSync(
path.resolve(__dirname, '../tests/__data__/input/playlist_update/playlist.m3u8'),
'utf8'
)
} else if (url.includes('.mpd')) {
data = fs.readFileSync(
path.resolve(__dirname, '../tests/__data__/input/playlist_update/manifest.mpd'),
'utf8'
)
}
} else {
try {
const timeout = options.timeout || 1000
let request: AxiosRequestConfig = {
signal: AbortSignal.timeout(timeout),
responseType: 'text',
headers: {
'User-Agent': options.httpUserAgent || 'Mozilla/5.0',
Referer: options.httpReferrer
}
}
if (options.proxy !== undefined) {
const proxyParser = new ProxyParser()
const proxy = proxyParser.parse(options.proxy) as AxiosProxyConfig
if (
proxy.protocol &&
['socks', 'socks5', 'socks5h', 'socks4', 'socks4a'].includes(String(proxy.protocol))
) {
const socksProxyAgent = new SocksProxyAgent(options.proxy)
request = { ...request, ...{ httpAgent: socksProxyAgent, httpsAgent: socksProxyAgent } }
} else {
request = { ...request, ...{ proxy } }
}
}
const response = await axios(url, request)
data = response.data
} catch {}
}
if (!data) return undefined
let info: StreamInfo | undefined
if (url.includes('.m3u8')) {
setOptions({ silent: true })
try {
const playlist = parsePlaylist(data)
if (playlist && playlist.isMasterPlaylist && playlist.variants.length) {
const v = orderBy(playlist.variants, ['bandwidth'], ['desc'])[0]
if (v && v.resolution && v.frameRate && v.codecs) {
info = {
resolution: { width: v.resolution.width, height: v.resolution.height },
bandwidth: v.bandwidth,
frameRate: v.frameRate,
codecs: v.codecs
}
}
}
} catch {}
} else if (url.includes('.mpd')) {
const manifest = parseManifest(data, {
manifestUri: url,
eventHandler: ({ type, message }) => console.log(`${type}: ${message}`)
})
const playlist = orderBy(manifest.playlists, [p => p.attributes.BANDWIDTH], ['desc'])[0]
if (playlist) {
const attr = playlist.attributes
info = {
resolution: { width: attr.RESOLUTION.width, height: attr.RESOLUTION.height },
bandwidth: attr.BANDWIDTH,
frameRate: attr['FRAME-RATE'],
codecs: attr.CODECS
}
}
}
return info
}

View File

@@ -0,0 +1,5 @@
#EXTM3U
#EXTINF:-1 tvg-id="",Hitradio O3 (1080p)
https://studiocam-oe3.mdn.ors.at/orf/studiocam_oe3/q6a/master.m3u8
#EXTINF:-1 tvg-id="",Hitradio O3 (720p)
https://studiocam-oe3.mdn.ors.at/orf/studiocam_oe3/q6a/manifest.mpd

View File

@@ -1,5 +1,5 @@
#EXTM3U
#EXTINF:-1 tvg-id="",Manorama News -2 [U3] (480p) [Geo-blocked] [Not 24/7]
#EXTINF:-1 tvg-id="",Manorama News -2 (1080p) [U3] (Alt) [Geo-blocked] [Not 24/7]
#EXTVLCOPT:http-referrer=http://test.com
#EXTVLCOPT:http-user-agent=Mozilla/5.0
https://ythls.onrender.com/channel/UCP0uG-mcMImgKnJz-VjJZmQ.m3u8

View File

@@ -1,11 +1,11 @@
#EXTM3U
#EXTINF:-1 tvg-id="NPO1.nl@SD",NPO 1 (342p) [Geo-blocked]
http://resolver.streaming.api.nos.nl/livestream?url=/live/npo/tvlive/npo1/npo1.isml/.m3u8
#EXTINF:-1 tvg-id="NPO2.nl@SD",NPO 2 (1080p) [Geo-blocked]
http://resolver.streaming.api.nos.nl/livestream?url=/live/npo/tvlive/npo2/npo22.isml/.m3u8
#EXTINF:-1 tvg-id="NPO2.nl@SD",NPO 2 (342p)
http://resolver.streaming.api.nos.nl/livestream?url=/live/npo/tvlive/npo2/npo2.isml/.m3u8
#EXTINF:-1 tvg-id="NPO2.nl@SD",NPO 2 (302p) [Geo-blocked]
#EXTVLCOPT:http-referrer=http://imn.iq
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
http://stream.tvtap.net:8081/live/nl-npo2.stream/playlist.m3u8?|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0+(iPhone;+CPU+iPhone+OS+17_7+like+Mac+OS+X)+AppleWebKit/605.1.15+(KHTML,+like+Gecko)+Version/18.0+Mobile/15E148+Safari/604.1"|Origin="https://origin.xyz"
#EXTINF:-1 tvg-id="NPO2.nl@SD",NPO 2 [Geo-blocked]
http://resolver.streaming.api.nos.nl/livestream?url=/live/npo/tvlive/npo2/npo22.isml/.m3u8

View File

@@ -1,5 +1,5 @@
#EXTM3U
#EXTINF:-1 tvg-id="TFX.fr@SD",TFX
#EXTINF:-1 tvg-id="TFX.fr@SD",TFX (720p)
#EXTVLCOPT:http-referrer=https://pkpakiplay.xyz/
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1
https://stitcher-ipv4.pluto.tv/v1/stitch/embed/hls/channel/64c109a4798def0008a6e03e/master.m3u8?advertisingId={PSID}&appVersion=unknown&deviceDNT={TARGETOPT}&deviceId={PSID}&deviceLat=0&deviceLon=0&deviceMake=samsung&deviceModel=samsung&deviceType=samsung-tvplus&deviceVersion=unknown&embedPartner=samsung-tvplus&profileFloor=&profileLimit=&samsung_app_domain={APP_DOMAIN}&samsung_app_name={APP_NAME}&us_privacy=1YNY
https://stitcher-ipv4.pluto.tv/v1/stitch/embed/hls/channel/64c109a4798def0008a6e03e/master.mpd?advertisingId={PSID}&appVersion=unknown&deviceDNT={TARGETOPT}&deviceId={PSID}&deviceLat=0&deviceLon=0&deviceMake=samsung&deviceModel=samsung&deviceType=samsung-tvplus&deviceVersion=unknown&embedPartner=samsung-tvplus&profileFloor=&profileLimit=&samsung_app_domain={APP_DOMAIN}&samsung_app_name={APP_NAME}&us_privacy=1YNY

View File

@@ -3,5 +3,5 @@
http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index.m3u8
#EXTINF:-1 tvg-id="BBCNews.uk",BBC News HD (480p) [Geo-blocked]
http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/playlist.m3u8
#EXTINF:-1 tvg-id="BeanoTV.uk@SD",Beano TV
#EXTINF:-1 tvg-id="BeanoTV.uk@SD",Beano TV (1080p)
https://a5b4bacecd47433dad06d3189fc7422e.mediatailor.us-east-1.amazonaws.com/v1/manifest/04fd913bb278d8775298c26fdca9d9841f37601f/RakutenTV-eu_BeanoTV/b1f233d5-847c-437d-aa4f-f73e67a85323/2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz"

View File

@@ -1,3 +1,3 @@
#EXTM3U
#EXTINF:-1 tvg-id="FastTV.us@SD",Fast TV
#EXTINF:-1 tvg-id="FastTV.us@SD",Fast TV (1080p)
https://3fa797d5.wurl.com/manifest/f36d25e7e52f1ba8d7e56eb859c636563214f541/T05PX01vdG9yVHJlbmRGYXN0VFZfSExT/b5e5e0e2-12b3-4312-93c9-c0a7c50b41ca/4.m3u8

View File

@@ -538,7 +538,7 @@ module.exports = [
closed_at: null,
author_association: 'COLLABORATOR',
active_lock_reason: null,
body: '### Stream ID\n\nTFX.fr\n\n### Stream URL\n\nhttps://stitcher-ipv4.pluto.tv/v1/stitch/embed/hls/channel/64c109a4798def0008a6e03e/master.m3u8?advertisingId={PSID}&appVersion=unknown&deviceDNT={TARGETOPT}&deviceId={PSID}&deviceLat=0&deviceLon=0&deviceMake=samsung&deviceModel=samsung&deviceType=samsung-tvplus&deviceVersion=unknown&embedPartner=samsung-tvplus&profileFloor=&profileLimit=&samsung_app_domain={APP_DOMAIN}&samsung_app_name={APP_NAME}&us_privacy=1YNY\n\n### Label\n\nNone\n\n### HTTP User Agent\n\nMozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1\n\n### HTTP Referrer\n\nhttps://pkpakiplay.xyz/\n\n### Notes (optional)\n\nSource: https://github.com/iptv-org/iptv-org.github.io/issues/1381\n\n### Contributing Guide\n\n- [X] I have read [Contributing Guide](https://github.com/iptv-org/iptv/blob/master/CONTRIBUTING.md)',
body: '### Stream ID\n\nTFX.fr\n\n### Stream URL\n\nhttps://stitcher-ipv4.pluto.tv/v1/stitch/embed/hls/channel/64c109a4798def0008a6e03e/master.mpd?advertisingId={PSID}&appVersion=unknown&deviceDNT={TARGETOPT}&deviceId={PSID}&deviceLat=0&deviceLon=0&deviceMake=samsung&deviceModel=samsung&deviceType=samsung-tvplus&deviceVersion=unknown&embedPartner=samsung-tvplus&profileFloor=&profileLimit=&samsung_app_domain={APP_DOMAIN}&samsung_app_name={APP_NAME}&us_privacy=1YNY\n\n### Label\n\nNone\n\n### HTTP User Agent\n\nMozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1\n\n### HTTP Referrer\n\nhttps://pkpakiplay.xyz/\n\n### Notes (optional)\n\nSource: https://github.com/iptv-org/iptv-org.github.io/issues/1381\n\n### Contributing Guide\n\n- [X] I have read [Contributing Guide](https://github.com/iptv-org/iptv/blob/master/CONTRIBUTING.md)',
reactions: {
url: 'https://api.github.com/repos/iptv-org/iptv/issues/14175/reactions',
total_count: 0,

View File

@@ -0,0 +1,5 @@
#EXTM3U
#EXTINF:-1 tvg-id="HitradioO3.at@SD",Hitradio O3
https://studiocam-oe3.mdn.ors.at/orf/studiocam_oe3/q6a/manifest.mpd
#EXTINF:-1 tvg-id="HitradioO3.at@SD",Hitradio O3
https://studiocam-oe3.mdn.ors.at/orf/studiocam_oe3/q6a/master.m3u8

View File

@@ -1,3 +1,3 @@
#EXTM3U
#EXTINF:-1 tvg-id="mn.in" http-referrer="http://test.com" http-user-agent="Mozilla/5.0",Manorama News -2 [U3] (480p) [Geo-blocked] [Not 24/7]
#EXTINF:-1 tvg-id="mn.in" http-referrer="http://test.com" http-user-agent="Mozilla/5.0",Manorama News -2 [U3] (Alt) [Geo-blocked] [Not 24/7]
https://ythls.onrender.com/channel/UCP0uG-mcMImgKnJz-VjJZmQ.m3u8

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- -->
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:mpeg:dash:schema:mpd:2011" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd" type="dynamic" availabilityStartTime="1970-01-01T00:00:00Z" publishTime="2026-03-24T17:45:06.924442Z" minimumUpdatePeriod="PT4S" timeShiftBufferDepth="PT31.840S" maxSegmentDuration="PT5S" minBufferTime="PT2S" profiles="urn:hbbtv:dash:profile:isoff-live:2012,urn:hbbtv:dash:profile:isoff-live:2012">
<Period id="1" start="PT0S">
<BaseURL>dash/</BaseURL>
<AdaptationSet id="1" group="1" contentType="audio" segmentAlignment="true" audioSamplingRate="48000" mimeType="audio/mp4" codecs="mp4a.40.2" startWithSAP="1">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"/>
<SegmentTemplate timescale="48000" initialization="studiocam_oe3-$RepresentationID$.dash" media="studiocam_oe3-$RepresentationID$-$Time$.dash">
<!-- 2026-03-24T17:44:28.465875Z / 1774374268 - 2026-03-24T17:45:00.316541Z -->
<SegmentTimeline>
<S t="85169964886362" d="230400"/>
<S d="207872"/>
<S d="160768"/>
<S d="219136"/>
<S d="189440"/>
<S d="179200"/>
<S d="160768"/>
<S d="181248"/>
</SegmentTimeline>
</SegmentTemplate>
<Representation id="audio_192257_und=192000" bandwidth="192000">
</Representation>
</AdaptationSet>
<AdaptationSet id="2" group="2" contentType="video" par="16:9" segmentAlignment="true" width="1280" height="720" sar="1:1" frameRate="50" mimeType="video/mp4" codecs="avc1.4D4028" startWithSAP="1">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"/>
<SegmentTemplate timescale="600" initialization="studiocam_oe3-$RepresentationID$.dash" media="studiocam_oe3-$RepresentationID$-$Time$.dash">
<!-- 2026-03-24T17:44:28.456666Z / 1774374268 - 2026-03-24T17:45:00.296666Z -->
<SegmentTimeline>
<S t="1064624561074" d="2880"/>
<S d="2592"/>
<S d="2016"/>
<S d="2736"/>
<S d="2376"/>
<S d="2232"/>
<S d="2016"/>
<S d="2256"/>
</SegmentTimeline>
</SegmentTemplate>
<Representation id="video=2000000" bandwidth="2000000" scanType="progressive">
</Representation>
</AdaptationSet>
</Period>
<UTCTiming schemeIdUri="urn:mpeg:dash:utc:http-iso:2014" value="https://time.akamai.com/?iso"/>
</MPD>

View File

@@ -0,0 +1,12 @@
#EXTM3U
## Generated with https://github.com/shaka-project/shaka-packager version v3.4.2-c819dea-release
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-MEDIA:TYPE=AUDIO,URI="stream_4.m3u8",GROUP-ID="default-audio-group",LANGUAGE="pl",NAME="stream_4",DEFAULT=NO,AUTOSELECT=YES,CHANNELS="2"
#EXT-X-STREAM-INF:BANDWIDTH=3688787,AVERAGE-BANDWIDTH=8948756,CODECS="avc1.424028,mp4a.40.2",RESOLUTION=1920x1080,FRAME-RATE=25.000,AUDIO="default-audio-group",CLOSED-CAPTIONS=NONE
stream_0.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1988787,AVERAGE-BANDWIDTH=3688345,CODECS="avc1.42401f,mp4a.40.2",RESOLUTION=1280x720,FRAME-RATE=25.000,AUDIO="default-audio-group",CLOSED-CAPTIONS=NONE
stream_1.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1188787,AVERAGE-BANDWIDTH=1774276,CODECS="avc1.42401e,mp4a.40.2",RESOLUTION=853x480,FRAME-RATE=25.000,AUDIO="default-audio-group",CLOSED-CAPTIONS=NONE
stream_2.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=688787,AVERAGE-BANDWIDTH=766090,CODECS="avc1.42401e,mp4a.40.2",RESOLUTION=640x360,FRAME-RATE=25.000,AUDIO="default-audio-group",CLOSED-CAPTIONS=NONE
stream_3.m3u8

View File

@@ -8,7 +8,7 @@ const ENV_VAR =
beforeEach(() => {
fs.emptyDirSync('tests/__data__/output')
fs.copySync('tests/__data__/input/playlist_update', 'tests/__data__/output/streams')
fs.copySync('tests/__data__/input/playlist_update/streams', 'tests/__data__/output/streams')
})
describe('playlist:update', () => {