Skip to content

Conversation

@Bnyro
Copy link
Contributor

@Bnyro Bnyro commented Jan 5, 2026

I noticed that a lot of extractors are based on the following pattern:

  1. Do some magic to obtain a script file containing JWPlayer config
  2. Extract stream URLs from this JWPlayer config

Previously, all extractors had their own logic for extracting the JWPlayer config, even though the JavaScript data parsed always looks the same. So there's been a lot of duplicated code that behaved inconsistent (i.e. some only supported m3u8 links and no other stream types like mp4 and others didn't parse the subtitles).

This logic is now handled by JWPlayerHelper.kt.

I've also noticed that some of the files could be merged, e.g.

  • Filemoon
  • Filesim
  • Streamwish

contain almost the exact same logic and

  • Gamovideo
  • Hxfile
  • Supervideo
  • Vtbe
  • Vidhide

also do the exact same thing. I suspect that all these providers are based on the exact same code (only some UI changes), but I haven't moved their extractors into the same class yet because I'd like to hear some other opinions first before doing that.

The deleted files are usages of the JWPlayer API on sites that no longer exist, so I removed them.

Comment on lines 16 to 73
/**
* Get stream links the "sources" attribute inside a JWPlayer script, e.g.
*
* ```js
* <script>
* jwplayer("vplayer").setup({
* sources: [{file:"https://example.com/master.m3u8"}],
* tracks: [{file: "https://example.com/subtitles.vtt", kind: "captions", label: "en"}],
* }
* ```
*
* @param script The content of a HTML <script> tag containing the jwplayer code.
* @return whether any extractor or subtitle link was found
*/
suspend fun getStreamLinks(
script: String,
sourceName: String,
referer: String?,
callback: (ExtractorLink) -> Unit,
subtitleCallback: (SubtitleFile) -> Unit,
headers: Map<String, String> = mapOf()
): Boolean {
val sourceMatches = sourceRegex.findAll(script).flatMap { sourceMatch ->
val match = sourceMatch.groupValues[1]
.addMarks("file")
.addMarks("label")
.addMarks("type")
tryParseJson<List<Source>>(match).orEmpty()
}.toList()

val extractedLinks = sourceMatches.flatMap { link ->
if (link.file.contains(".m3u8")) {
M3u8Helper.generateM3u8(
source = sourceName,
streamUrl = link.file,
referer = referer.orEmpty(),
headers = headers
)
} else {
listOf(
newExtractorLink(
source = sourceName,
name = sourceName,
url = link.file,
) {
this.referer = url
this.headers = headers
}
)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not bad, but leaves a lot of cases unhandled.
There are several counterexamples (I just can't remember where to find them all 🙃 ) as in:

var links = {
  "hls3": "https://mmmmmmmmmm.qqqqqqqqqqqq.space/#########/hls3/01/00000/ggggggggg_l/master.txt",
  "hls4": "/stream/zzzzzzzzzzzzzzz/hhhhhhhhhhh/123456789/123456/master.m3u8",
  "hls2": "https://mmmmmmmmmm.qqqqqqqqqqqq.com/hls2/01/00000/ggggggggg_l/master.m3u8?t=##################&s=123456"
};
jwplayer("vplayer").setup({
  sources: [{
    file: links.hls4 || links.hls3 || links.hls2,
    type: "hls"
  }],
  image: "https://pppppppp.ppp/p.jpg",

file: may have a variable or function that gathers links from other parts of the script.
The links may be incomplete, so add the mainUrl prefix from the extractor or use fixUrl().

To avoid dealing with the various exceptions, I usually do it like this:

Regex("""[:=]\s*\"([^\"\s]+(\.m3u8|master\.txt)[^\"\s]*)""").findAll(unpackedScript).forEach { match ->
    val link = match.groupValues[1]
    callback.invoke(
        newExtractorLink(
            source = this.name,
            name   = this.name,
            url    = fixUrl(link)
        ) {
            this.referer = referer ?: mainUrl
        }
    )
}

That is, after : or = get all that's delimited by " and has .m3u8 or master.txt inside.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good catch, thank you for the review @diogob003 !

One example would be https://github.com/recloudstream/cloudstream/blob/master/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VidHidePro.kt#L83, I probably should have remembered that edge case because I wrote the patch over there myself :)

We're now trying to extract stream links by first

  • trying to parse the JWPlayer config object and searching for the file:<url> pattern
  • if that doesn't find anything, we're searching for hls streams as you suggested

The problem why we can't drop the first case is that some providers do provide other formats than HLS, e.g. I've already seen some .mp4 streams. So in this case, using only the second method wouldn't work.

@Bnyro Bnyro force-pushed the jwplayer-simplify branch from a45b9d4 to 5a4d16b Compare January 8, 2026 11:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants