Skip to content

plugins.twitcasting: switch to llfmp4 and tc-hls #6540

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

bastimeyer
Copy link
Member

@bastimeyer bastimeyer commented May 24, 2025

Ref #6539

@qwer-lives have a look at this PR please.
https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md#pull-request-feedback

This switches to the llfmp4 and tc-hls streams, which also simplifies the plugin. I have no idea about the availability of these streams compared to the previous implementation though, so I'd like to get some feedback on this from users of this site. As can be seen in the PR's diff, previously, llfmp4 was avoided because of alleged video/audio desync issues.

I also have no idea about password protected streams. Similar to before, this is only implemented for the websocket streams.

HLS have been assigned a lower priority because of the delay. --stream-priority=websocket / --stream-priority=hls allows for selecting only a specific type of stream...

$ streamlink https://twitcasting.tv/mille_tomo
[cli][info] Found matching plugin twitcasting for URL https://twitcasting.tv/mille_tomo
Available streams: low (worst), medium, high, base, mobilesource, main (best)

websocket

$ streamlink https://twitcasting.tv/mille_tomo best -l debug --stream-priority websocket -o /dev/null
[cli][debug] OS:         Linux-6.14.4-2-git-x86_64-with-glibc2.41
[cli][debug] Python:     3.13.3
[cli][debug] OpenSSL:    OpenSSL 3.5.0 8 Apr 2025
[cli][debug] Streamlink: 7.3.0+17.g6565774f
[cli][debug] Dependencies:
[cli][debug]  certifi: 2024.12.14
[cli][debug]  isodate: 0.7.2
[cli][debug]  lxml: 5.3.0
[cli][debug]  pycountry: 24.6.1
[cli][debug]  pycryptodome: 3.21.0
[cli][debug]  PySocks: 1.7.1
[cli][debug]  requests: 2.32.3
[cli][debug]  trio: 0.27.0
[cli][debug]  trio-websocket: 0.11.1
[cli][debug]  urllib3: 2.3.0
[cli][debug]  websocket-client: 1.8.0
[cli][debug] Arguments:
[cli][debug]  url=https://twitcasting.tv/mille_tomo
[cli][debug]  stream=['best']
[cli][debug]  --loglevel=debug
[cli][debug]  --player=/usr/bin/mpv
[cli][debug]  --output=/dev/null
[cli][debug]  --stream-types=['websocket']
[cli][debug]  --webbrowser-headless=True
[cli][info] Found matching plugin twitcasting for URL https://twitcasting.tv/mille_tomo
[cli][info] Available streams: base (worst), mobilesource, main (best)
[cli][info] Opening stream: main (websocket)
[cli][info] Writing output to
/dev/null
[cli][debug] Checking file output
[plugin.api.websocket][debug] Connecting to: wss://202-218-171-200.twitcasting.tv/tc.edge/v1/streams/816863752.8193.96/fmp4/solid
[cli][debug] Pre-buffering 8192 bytes
[plugin.api.websocket][debug] Connected: wss://202-218-171-200.twitcasting.tv/tc.edge/v1/streams/816863752.8193.96/fmp4/solid
[cli][debug] Writing stream to output
[plugin.api.websocket][debug] Closed: wss://202-218-171-200.twitcasting.tv/tc.edge/v1/streams/816863752.8193.96/fmp4/solid
[cli][info] Stream ended
Interrupted! Exiting...
[cli][info] Closing currently open stream...
[download] Written 5.88 MiB to /dev/null (20s @ 308.21 KiB/s)

HLS (~15 seconds of delay)

$ streamlink https://twitcasting.tv/mille_tomo best -l debug --stream-priority hls -o /dev/null
[cli][debug] OS:         Linux-6.14.4-2-git-x86_64-with-glibc2.41
[cli][debug] Python:     3.13.3
[cli][debug] OpenSSL:    OpenSSL 3.5.0 8 Apr 2025
[cli][debug] Streamlink: 7.3.0+17.g6565774f
[cli][debug] Dependencies:
[cli][debug]  certifi: 2024.12.14
[cli][debug]  isodate: 0.7.2
[cli][debug]  lxml: 5.3.0
[cli][debug]  pycountry: 24.6.1
[cli][debug]  pycryptodome: 3.21.0
[cli][debug]  PySocks: 1.7.1
[cli][debug]  requests: 2.32.3
[cli][debug]  trio: 0.27.0
[cli][debug]  trio-websocket: 0.11.1
[cli][debug]  urllib3: 2.3.0
[cli][debug]  websocket-client: 1.8.0
[cli][debug] Arguments:
[cli][debug]  url=https://twitcasting.tv/mille_tomo
[cli][debug]  stream=['best']
[cli][debug]  --loglevel=debug
[cli][debug]  --player=/usr/bin/mpv
[cli][debug]  --output=/dev/null
[cli][debug]  --stream-types=['hls']
[cli][debug]  --webbrowser-headless=True
[cli][info] Found matching plugin twitcasting for URL https://twitcasting.tv/mille_tomo
[cli][info] Available streams: low (worst), medium, high (best)
[cli][info] Opening stream: high (hls)
[cli][info] Writing output to
/dev/null
[cli][debug] Checking file output
[stream.hls][debug] Reloading playlist
[cli][debug] Pre-buffering 8192 bytes
[stream.hls][debug] First Sequence: 3776; Last Sequence: 3779
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 3777; End Sequence: None
[stream.hls][debug] Adding segment 3777 to queue
[stream.hls][debug] Adding segment 3778 to queue
[stream.hls][debug] Adding segment 3779 to queue
[stream.hls][debug] Writing segment 3777 to output
[stream.hls][debug] Segment initialization 3777 complete
[cli][debug] Writing stream to output
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 3780 to queue
[stream.hls][debug] Writing segment 3777 to output
[stream.hls][debug] Segment 3777 complete
[stream.hls][debug] Writing segment 3778 to output
[stream.hls][debug] Segment 3778 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 3781 to queue
[stream.hls][debug] Writing segment 3779 to output
[stream.hls][debug] Segment 3779 complete
[stream.hls][debug] Writing segment 3780 to output
[stream.hls][debug] Segment 3780 complete
[stream.hls][debug] Writing segment 3781 to output
[stream.hls][debug] Segment 3781 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 3782 to queue
[stream.hls][debug] Writing segment 3782 to output
[stream.hls][debug] Segment 3782 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 3783 to queue
[stream.hls][debug] Writing segment 3783 to output
[stream.hls][debug] Segment 3783 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 3784 to queue
[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing writer thread
[cli][info] Stream ended
Interrupted! Exiting...
[cli][info] Closing currently open stream...
[download] Written 6.61 MiB to /dev/null (20s @ 344.33 KiB/s)

@bastimeyer bastimeyer added the plugin issue A Plugin does not work correctly label May 24, 2025
@qwer-lives
Copy link

I've tried it out and I observe the same: both streams work, HLS has some extra delay.

I've tried around ~20 different channels and all of them had both llfmp4 and tc-hls streams so it looks like they are generally available but to be fair I can't confirm it 100%. I also checked for streams that need login credentials and they also have both types available. I don't know anything about password-protected streams either, unfortunately.

My normal use case is to output to a .ts file so I'm not sure if the desync issues will manifest in the same way - I'll still record a bunch of streams tonight and check how everything looks tomorrow.

@qwer-lives
Copy link

qwer-lives commented May 25, 2025

Of the 8 streams that I left recording overnight (using the llfmp4 streams), they're all fine except one where the audio has a constant shift from the very start of the video (it doesn't change throughout the video). I tried a bunch more streams now to see if I can reproduce it but no luck, so maybe the delay was part of the original stream.

@bastimeyer
Copy link
Member Author

they're all fine except one where the audio has a constant shift from the very start of the video

Thanks for checking... I'm not going to merge this one then.

This plugin simply downloads a progressive stream from a websocket, meaning there's no remuxing of separate video and audio streams being done. When there's desync, then this is caused by the stream not having proper presentation timestamps of the video and audio streams set, so the player is unable to synchronize the data.

According to a quick test, both video and audio streams from the llfmp4 source always have a PTS of 0.

$ streamlink --quiet --stdout https://twitcasting.tv/g:118232163499600440174 best | ffprobe -of json -v error -show_streams pipe:0 | jq '.streams[] | .start_pts'
0
0

$ streamlink --quiet --stdout https://twitcasting.tv/haisinyouaccoun best | ffprobe -of json -v error -show_streams pipe:0 | jq '.streams[] | .start_pts'
0
0

$ streamlink --quiet --stdout https://twitcasting.tv/c:megaradio best | ffprobe -of json -v error -show_streams pipe:0 | jq '.streams[] | .start_pts'
0
0

This is not the case on the master branch with the old websocket addresses

$ streamlink --quiet --stdout https://twitcasting.tv/g:118232163499600440174 best | ffprobe -of json -v error -show_streams pipe:0 | jq '.streams[] | .start_pts'
0
96

$ streamlink --quiet --stdout https://twitcasting.tv/haisinyouaccoun best | ffprobe -of json -v error -show_streams pipe:0 | jq '.streams[] | .start_pts'
0
480

$ streamlink --quiet --stdout https://twitcasting.tv/c:megaradio best | ffprobe -of json -v error -show_streams pipe:0 | jq '.streams[] | .start_pts'
0
396

HLS streams also don't have this issue, but those are avoided due to latency reasons...

@bastimeyer bastimeyer closed this May 25, 2025
@bastimeyer bastimeyer deleted the plugins/twitcasting/llfmp4-and-tc-hls branch May 25, 2025 11:55
@Hakkin
Copy link
Contributor

Hakkin commented Jun 1, 2025

See comment #6539 (comment) for some testing, also just tested this patch on a password protected stream: Websocket works, but HLS doesn't. The "word" query param needs to be added to the playlist URL just like is done for the websocket connection. The playlist request then sets a cookie that is needed for accessing the media segments, I assume the HTTP session would handle this automatically though (manually passing in the properly formatted playlist URL to streamlink plays it correctly).

> GET /tc.livehls/v1/streams/{id}/hls/132.68/media.m3u8?word={hash} HTTP/1.1
< HTTP/1.1 200 OK
< Set-Cookie: tc_lvhls_ssid_{id}=<random>; Path=/tc.livehls/v1/streams/{id}/hls/; Max-Age=600; HttpOnly

@bastimeyer bastimeyer restored the plugins/twitcasting/llfmp4-and-tc-hls branch June 1, 2025 18:32
@bastimeyer bastimeyer reopened this Jun 1, 2025
@bastimeyer bastimeyer force-pushed the plugins/twitcasting/llfmp4-and-tc-hls branch from 6565774 to 5d2f1a0 Compare June 1, 2025 18:35
@bastimeyer
Copy link
Member Author

@Hakkin thanks for checking...

I tested some 30 streams and none of them seemed egregiously out of sync

See my comment above. The websocket streams don't align the audio and video streams at all, which means it's completely unpredictable whether a stream will have desync or not and how much the desync will be, a couple of frames or multiple seconds. The old websocket streams on master had relative timestamps set (now also offline for me) whereas the HLS streams have absolute timestamps set, which both work fine.

From this PR branch, first stream from their popular live streams page:

$ streamlink --quiet --stdout --stream-types websocket https://twitcasting.tv/c:abzou_sub best | ffprobe -of json -v error -show_streams pipe:0 | jq '.streams[] | .start_pts'
0
0

$ streamlink --quiet --stdout --stream-types hls https://twitcasting.tv/c:abzou_sub best | ffprobe -of json -v error -show_streams pipe:0 | jq '.streams[] | .start_pts'
11320680
11320706

Since their website also defaults to the websocket streams, there's not much we can do here. If there's desync, then so be it. I don't think selecting HLS streams as the default makes much sense considering the ridiculous latency. Users can always choose the HLS streams for recording. I've updated the stream names accordingly, to avoid any potential confusion on which stream is using which protocol (another case of the urgently needed full rewrite of the stream selection #4902).

I've rebased the PR and also added the word URL parameter to HLS streams, so password protected streams should work there as well. Since I don't know any password protected streams on this website, I didn't test this.

Going to merge this in a bit later. You can give this a try if you want though...

@Hakkin
Copy link
Contributor

Hakkin commented Jun 1, 2025

I've rebased the PR and also added the word URL parameter to HLS streams, so password protected streams should work there as well. Since I don't know any password protected streams on this website, I didn't test this.

Tested, HLS works fine now, thanks.

The websocket streams don't align the audio and video streams at all, which means it's completely unpredictable whether a stream will have desync or not and how much the desync will be, a couple of frames or multiple seconds.

The PTS starts at 0 and seems to increase properly, which should keep sync as long as they implemented it correctly, but obviously something on their end is messed up and the audio and video PTS starts get offset somehow. Agree that there's not really anything that can be done about it.

@bastimeyer bastimeyer merged commit ad5189b into streamlink:master Jun 1, 2025
24 checks passed
@bastimeyer bastimeyer deleted the plugins/twitcasting/llfmp4-and-tc-hls branch June 1, 2025 19:54
@bastimeyer bastimeyer linked an issue Jun 1, 2025 that may be closed by this pull request
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
plugin issue A Plugin does not work correctly
Projects
None yet
Development

Successfully merging this pull request may close these issues.

plugins.twitcasting: Websocket address seems to have changed
3 participants