Skip to content

plugins.twitch: implement disable-ads parameter #2372

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

Merged
merged 3 commits into from
Mar 29, 2019

Conversation

bastimeyer
Copy link
Member

@bastimeyer bastimeyer commented Mar 23, 2019

Resolves #2368, #2357 (deleted, for whatever reason, which is really sad - I've opened a support ticket to get it hopefully restored)

Please see #2369 and my comment here first:
#2369 (comment)

Adds a custom TwitchHLSStream implementation and the --twitch-disable-ads CLI parameter, which will skip HLS segments between #EXT-X-SCTE35-OUT and #EXT-X-SCTE35-IN tags.

If this PR gets merged, please don't squash the commits.

- Move tag specific logic and segment/playlist creation logic into
  individual methods so that the parser can be extended more easily.
- Allow load method to set additional keyword arguments on the parser.
- Enable setting custom arguments when reloading the playlist.
- Instantiate own class in HLSStream.parse_variant_playlist.
@bastimeyer bastimeyer added the plugin enhancement A new feature for a working Plugin label Mar 23, 2019
@codecov
Copy link

codecov bot commented Mar 23, 2019

Codecov Report

Merging #2372 into master will increase coverage by 0.18%.
The diff coverage is 85.1%.

@@            Coverage Diff             @@
##           master    #2372      +/-   ##
==========================================
+ Coverage   52.59%   52.77%   +0.18%     
==========================================
  Files         237      237              
  Lines       14802    14860      +58     
==========================================
+ Hits         7785     7843      +58     
  Misses       7017     7017

@codecov
Copy link

codecov bot commented Mar 23, 2019

Codecov Report

Merging #2372 into master will increase coverage by 0.19%.
The diff coverage is 85.91%.

@@            Coverage Diff             @@
##           master    #2372      +/-   ##
==========================================
+ Coverage   52.51%   52.71%   +0.19%     
==========================================
  Files         238      238              
  Lines       14863    14923      +60     
==========================================
+ Hits         7806     7867      +61     
+ Misses       7057     7056       -1

@itsotter
Copy link

itsotter commented Mar 24, 2019

Getting this after applying the changes:
disableads

Never seen it before with PotPlayer and it's allowed through firewall. Does it make sense for this change to add player-specific problems? Should I try MPV?

EDIT: Back up and running with MPV; no ads so far. Good stuff.

@Zero3K
Copy link

Zero3K commented Mar 24, 2019

Yes, please do so.

@bastimeyer
Copy link
Member Author

I'd be more interested in the -l debug output and which segments got skipped ([plugin.twitch][debug] Skipping ad segment X).

Since ads can be skipped at the beginning, there's no data for the player to read, which is the reason why you're seeing this. This still needs to be changed. I haven't touched streamlink_cli's code yet. You're also using the HTTP transport method and PotPlayer. Please use a player which is reading from stdin and which doesn't complain about an initial lack of data.

Copy link
Collaborator

@back-to back-to left a comment

Choose a reason for hiding this comment

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

actually very difficult to test this, I get ads very rarely.

@bastimeyer bastimeyer force-pushed the plugins/twitch/disable-ads branch from 4051790 to 56e9e82 Compare March 24, 2019 15:31
@Zero3K
Copy link

Zero3K commented Mar 24, 2019

Can someone please apply it to the latest source, compile it, upload it to u.teknik.io and post the link here?

@bastimeyer
Copy link
Member Author

No, this is not how this works. You'll have to wait for this to be merged and then published in the next (nightly) release or you can build it on your own by cloning my Streamlink fork. We will not upload an official Streamlink build to an external site, please stop asking and spamming this PR with questions unrelated to the code changes, thank you.

@bastimeyer bastimeyer force-pushed the plugins/twitch/disable-ads branch from 56e9e82 to ebb3f77 Compare March 25, 2019 11:44
@bastimeyer
Copy link
Member Author

I've made some further changes and removed the worker reference from the parser.

Also added support for the EXT-SCTE35-OUT-CONT tag, although I'm not sure if it gets used by Twitch. It's meant as an analogy to the non-standardized EXT-X-CUE-OUT-CONT tag.

@bastimeyer bastimeyer force-pushed the plugins/twitch/disable-ads branch 2 times, most recently from 43a6016 to 66b100c Compare March 25, 2019 16:46
@alexzorin
Copy link

PR seems to work as advertised (with --twitch-disable-ads and --twitch-oauth-token=...), thanks for this!

I've observed that skipping the ads using this flag causes the preroll segments to always be present in the playlist.

My guess is that not actually downloading the stream segments containing the adverts, results in Twitch not acknowledging the ad as having been watched.

So the PR may be inadvertently be shooting itself in the foot by not downloading those segments.

Do you think that always downloading the ad segments in the background would perhaps be a better way to go?

Repro 1: Using --twitch-disable-ads

Executed 4 times sequentially:

$ streamlink https://twitch.tv/ccncdota2 720p --player mpv -l debug \
--twitch-disable-ads --twitch-oauth-token=...

Resulting in:

Invocation 1: Preroll ad in m3u8, have to wait for ~8 ad segments
Invocation 2: Preroll ad in m3u8, have to wait for ~8 ad segments
Invocation 3: Preroll ad in m3u8, have to wait for ~8 ad segments
Invocation 4: Preroll ad in m3u8, have to wait for ~8 ad segments

Result is that the m3u8 contains preroll adds on all 4 invocations.

Repro 2: Allowing the ad to play

Execute 4 times sequentially:

$ streamlink https://twitch.tv/ccncdota2 720p --player mpv -l debug \
--twitch-oauth-token=...

Invocation 1: Preroll ad in m3u8, fetched and played fully
Invocation 2: No ad in m3u8, real stream immediately
Invocation 3: No ad in m3u8, real stream immediately
Invocation 4: No ad in m3u8, real stream immediately

@bastimeyer
Copy link
Member Author

@alexzorin thanks for testing this. As I've said, I haven't been getting ads myself the past couple of days, so I can't test this on real stream data, which makes it a bit difficult.

All segments, including ads, will now be downloaded, but then filtered out in the TwitchHLSStreamWriter's write method.

I've also improved the logging a bit and it will now log to the info channel when it will begin or stop skipping segments (instead of logging each skipped segment individually in the debug channel).

@alexzorin
Copy link

alexzorin commented Mar 26, 2019

Thanks, with 71301da , my previous repro no longer happens - now ads are gone after the first viewing (edit: non-viewing I guess would be more accurate).

Hard to work in such a fuzzy environment! Twitch could change how they maintain ad viewing state at any time (at the moment I think it's bound to the oauth_token) ... but hopefully downloading (but not playing) the ads is a reasonable thing to do for other users of streamlink ...

First invocation (single ad in playlist)
$ streamlink https://twitch.tv/ogamingsc2 480p --player mpv -l debug --twitch-oauth-token=... --twitch-disable-ads
[cli][debug] OS:         Linux-4.18.0-16-generic-x86_64-with-Ubuntu-18.10-cosmic
[cli][debug] Python:     3.6.7
[cli][debug] Streamlink: 1.0.0
[cli][debug] Requests(2.21.0), Socks(1.6.7), Websocket(0.56.0)
[cli][info] Found matching plugin twitch for URL https://twitch.tv/ogamingsc2
[cli][debug] Plugin specific arguments:
[cli][debug]  --twitch-oauth-token=******** (oauth_token)
[cli][debug]  --twitch-disable-ads=True (disable_ads)
[plugin.twitch][debug] Getting live HLS streams for ogamingsc2
[plugin.twitch][info] Attempting to authenticate using OAuth token
[plugin.twitch][info] Successfully logged in as ...
[utils.l10n][debug] Language code: en_AU
[cli][info] Available streams: audio_only, 160p (worst), 360p, 480p, 720p, 720p60, 1080p60 (best)
[cli][info] Opening stream: 480p (hls-twitch)
[plugin.twitch][info] Will skip ad segments
[stream.hls][debug] Reloading playlist
[stream.hls][debug] First Sequence: 0; Last Sequence: 1
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 0; End Sequence: None
[stream.hls][debug] Adding segment 0 to queue
[cli][debug] Pre-buffering 8192 bytes
[stream.hls][debug] Adding segment 1 to queue
[plugin.twitch][info] Will skip ads beginning with segment 0
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 2 to queue
[stream.hls][debug] Adding segment 3 to queue
[stream.hls][debug] Adding segment 4 to queue
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 5 to queue
[stream.hls][debug] Adding segment 6 to queue
[stream.hls][debug] Adding segment 7 to queue
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 8 to queue
[stream.hls][debug] Adding segment 9 to queue
[stream.hls][debug] Adding segment 10 to queue
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 11 to queue
[stream.hls][debug] Adding segment 12 to queue
[stream.hls][debug] Adding segment 13 to queue
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 14 to queue
[stream.hls][debug] Adding segment 15 to queue
[stream.hls][debug] Adding segment 16 to queue
[plugin.twitch][info] Will stop skipping ads beginning with segment 16
[stream.hls][debug] Download of segment 16 complete
[cli][info] Starting player: mpv
[cli.output][debug] Opening subprocess: mpv --title https://twitch.tv/ogamingsc2 -
[cli][debug] Writing stream to output
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 17 to queue
[stream.hls][debug] Adding segment 18 to queue
[stream.hls][debug] Download of segment 17 complete
[stream.hls][debug] Download of segment 18 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 19 to queue
[stream.hls][debug] Adding segment 20 to queue
[stream.hls][debug] Adding segment 21 to queue
[stream.hls][debug] Adding segment 22 to queue
[stream.hls][debug] Download of segment 19 complete
[stream.hls][debug] Download of segment 20 complete
[stream.hls][debug] Download of segment 21 complete
[stream.hls][debug] Download of segment 22 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 23 to queue
[stream.hls][debug] Adding segment 24 to queue
[stream.hls][debug] Adding segment 25 to queue
[stream.hls][debug] Download of segment 23 complete
[stream.hls][debug] Download of segment 24 complete
[stream.hls][debug] Download of segment 25 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 26 to queue
[stream.hls][debug] Adding segment 27 to queue
[stream.hls][debug] Adding segment 28 to queue
[stream.hls][debug] Download of segment 26 complete
[stream.hls][debug] Download of segment 27 complete
^C[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing writer thread
[cli][info] Stream ended
[stream.hls][debug] Download of segment 28 complete
Interrupted! Exiting...
[cli][info] Closing currently open stream...
Subsequent invocation (no ads in playlist)
$ streamlink https://twitch.tv/ogamingsc2 480p --player mpv -l debug --twitch-oauth-token=... --twitch-disable-ads
[cli][debug] OS:         Linux-4.18.0-16-generic-x86_64-with-Ubuntu-18.10-cosmic
[cli][debug] Python:     3.6.7
[cli][debug] Streamlink: 1.0.0
[cli][debug] Requests(2.21.0), Socks(1.6.7), Websocket(0.56.0)
[cli][info] Found matching plugin twitch for URL https://twitch.tv/ogamingsc2
[cli][debug] Plugin specific arguments:
[cli][debug]  --twitch-oauth-token=******** (oauth_token)
[cli][debug]  --twitch-disable-ads=True (disable_ads)
[plugin.twitch][debug] Getting live HLS streams for ogamingsc2
[plugin.twitch][info] Attempting to authenticate using OAuth token
[plugin.twitch][info] Successfully logged in as ...
[utils.l10n][debug] Language code: en_AU
[cli][info] Available streams: audio_only, 160p (worst), 360p, 480p, 720p, 720p60, 1080p60 (best)
[cli][info] Opening stream: 480p (hls-twitch)
[plugin.twitch][info] Will skip ad segments
[stream.hls][debug] Reloading playlist
[stream.hls][debug] First Sequence: 17559; Last Sequence: 17572
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 17570; End Sequence: None
[stream.hls][debug] Adding segment 17570 to queue
[cli][debug] Pre-buffering 8192 bytes
[stream.hls][debug] Adding segment 17571 to queue
[stream.hls][debug] Adding segment 17572 to queue
[stream.hls][debug] Download of segment 17570 complete
[cli][info] Starting player: mpv
[cli.output][debug] Opening subprocess: mpv --title https://twitch.tv/ogamingsc2 -
[stream.hls][debug] Download of segment 17571 complete
[cli][debug] Writing stream to output
[stream.hls][debug] Download of segment 17572 complete
^C[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing writer thread
[cli][info] Stream ended
Interrupted! Exiting...
[cli][info] Closing currently open stream...

@bastimeyer bastimeyer force-pushed the plugins/twitch/disable-ads branch from 71301da to 70c03e1 Compare March 26, 2019 07:08
@bastimeyer bastimeyer requested review from back-to and beardypig March 26, 2019 08:35
@PoorChameleon
Copy link

PoorChameleon commented Mar 26, 2019

Hello, I've tested the changes and it seems to result in (sort of) unseekable streams. It would be usable in live situations, but for watching vods it would be detrimental not being able to seek to any part of the whole video.

To elaborate a bit, when using hls-passthrough you get no seeking, when using --player-continuous-http you get seeking on a livestream, however with a local http server you won't get the full vod length when opening a vod. You will only be able to seek the amount you've buffered the stream for.

If the changes are made as is and I'm not just missing something obvious, people will lose the ability to seek full vod ranges when this is merged. Please feel free to correct me if it's an easy setting fix.

@bastimeyer
Copy link
Member Author

Streamlink doesn't support any kind of seeking. When using Streamlink, the player will consume a progressive stream from stdin, a named pipe (--player-fifo) or a local HTTP server (--player{,-continuous,-external}-http) and only lets you seek in its buffered stream data read from Streamlink. This PR doesn't change anything in regards to that.
https://streamlink.github.io/cli.html#cmdoption-n
https://streamlink.github.io/cli.html#cmdoption-player-http
https://streamlink.github.io/cli.html#cmdoption-player-passthrough

Seeking in the player is only possible if you're using the --player-passthrough option which lets the player bypass Streamlink's streaming logic completely and uses its own implementation for fetching and reading the stream data. Streamlink will only be used for resolving the actual stream URL in this case.

This further means that when using the passthrough option, you won't be affected by these changes at all and you will continue seeing ads unless the player can filter them out on its own.

@PoorChameleon
Copy link

PoorChameleon commented Mar 26, 2019

I must be missing something then. When using a build without these changes streamlink https://www.twitch.tv/videos/399340658 --player-passthrough hls results in a seekable vod, but when using one with the only difference being these changes (cloned folder with the changes applied) it is no longer seekable with the same command. Both also have an identical streamlinkrc config file (only change from default being mpv path change and quality defaults).

I don't want to make this a troubleshooting thread if it really cannot be the changes made, so I guess I'll just use a portable build without the changes for vods in that case.

@bastimeyer
Copy link
Member Author

No, you're right, something broke and it's not using the passthrough (see with -l debug). Probably because of the HLSStream inheritance. I will fix this later. Sorry about that... Good catch! :)

- Implement TwitchHLSStream{,Worker,Writer,Reader} and TwitchM3U8Parser
- Skip HLS segments between SCTE35-OUT{,-CONT} and SCTE35-IN tags
- Add --twitch-disable-ads parameter (plugin specific)
- Add tests
@bastimeyer bastimeyer force-pushed the plugins/twitch/disable-ads branch from 70c03e1 to 94edeb0 Compare March 26, 2019 12:17
@beardypig
Copy link
Member

Re-running failed AppVeyor task - failed due to not being able to connect to codecov ...

@bastimeyer
Copy link
Member Author

Yep, worst CI service of all time...

Btw, there's still the problem of players refusing to work when the initial stream data is missing when it gets filtered out. This would require some changes how the output methods are called in streamlink_cli.main:
https://github.com/streamlink/streamlink/blob/1.0.0/src/streamlink_cli/main.py#L429-L449
I don't know what the best approach would be here in order to delay these calls and if it's even a good idea... What do you think, @beardypig?

@theinfinityproject
Copy link

theinfinityproject commented Mar 26, 2019

Im testing this pr at the moment. No software expert here but one thing i noticed is, that since im on the last commit i have higher cpu usage then im used to by streamlink. Running streamlink on manjaro 18.04 and using vlc. cpu is a i7-7500u 2.7Ghzx4 and 16gb ram.Normally streamlink takes about 5 to 10% cpu power when i watch a stream on best, right now its around 15-25% cpu power.

@beardypig
Copy link
Member

@bastimeyer it seems like create_output shouldn't be called until open_stream returns something in prebuffer. Is that not the case?

@theinfinityproject which options are you using?

@bastimeyer
Copy link
Member Author

@beardypig Both output_stream and output_stream_http need to be changed, so that open_stream delays the player launch. I don't think though that this should be done in this PR.

I'm done so far with my changes here (I think), so if I could get a proper review so that it can be merged and we can continue with the changes for the 1.1.0 release which I've been waiting for, I'd be more than happy.

Copy link
Member

@beardypig beardypig left a comment

Choose a reason for hiding this comment

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

I don't see anything obviously wrong in here :) That is to say, LGTM.

@theinfinityproject
Copy link

theinfinityproject commented Mar 27, 2019

@theinfinityproject which options are you using?

@beardypig pretty much the default ones, for example yesterday: streamlink --twitch-disable-ads twitch.tv/wagamamatv best

@beardypig
Copy link
Member

@theinfinityproject and you don't have anything set in the streamlinkrc file either? Strange that it would use more CPU... 😕

@bastimeyer
Copy link
Member Author

There are no changes here which would lead to an increase like this. That sounds more of an aggregation error of the process's CPU usage or something else unrelated.

Here's a comparison of Streamlink built from 42105d8 (the commit on the master branch which this PR is based on) and 94edeb0 (the HEAD of this PR) on my ancient system and running it for 300 seconds.

$ grep 'model name' /proc/cpuinfo | uniq | cut -d':' -f2
 Intel(R) Core(TM) i7 CPU         930  @ 2.80GHz

$ streamlink --version && \
  timeout -s SIGINT 300 /usr/bin/time -v streamlink -l none -o /dev/null twitch.tv/monstercat best
streamlink 1.0.0+46.g42105d8
[download][null] Written 96.4 MB (4m54s @ 327.8 KB/s)
Interrupted! Exiting...
Command exited with non-zero status 130
  Command being timed: "streamlink -l none -o /dev/null twitch.tv/monstercat best"
  User time (seconds): 6.12
  System time (seconds): 0.64
  Percent of CPU this job got: 2%
  Elapsed (wall clock) time (h:mm:ss or m:ss): 5:00.07
  Average shared text size (kbytes): 0
  Average unshared data size (kbytes): 0
  Average stack size (kbytes): 0
  Average total size (kbytes): 0
  Maximum resident set size (kbytes): 54520
  Average resident set size (kbytes): 0
  Major (requiring I/O) page faults: 0
  Minor (reclaiming a frame) page faults: 42842
  Voluntary context switches: 14330
  Involuntary context switches: 116
  Swaps: 0
  File system inputs: 0
  File system outputs: 0
  Socket messages sent: 0
  Socket messages received: 0
  Signals delivered: 0
  Page size (bytes): 4096
  Exit status: 130

$ streamlink --version && \
  timeout -s SIGINT 300 /usr/bin/time -v streamlink -l none -o /dev/null --twitch-disable-ads twitch.tv/monstercat best
streamlink 1.0.0+49.g94edeb0
[download][null] Written 96.4 MB (4m54s @ 329.2 KB/s)
Interrupted! Exiting...
Command exited with non-zero status 130
  Command being timed: "streamlink -l none -o /dev/null --twitch-disable-ads twitch.tv/monstercat best"
  User time (seconds): 6.13
  System time (seconds): 0.76
  Percent of CPU this job got: 2%
  Elapsed (wall clock) time (h:mm:ss or m:ss): 5:00.07
  Average shared text size (kbytes): 0
  Average unshared data size (kbytes): 0
  Average stack size (kbytes): 0
  Average total size (kbytes): 0
  Maximum resident set size (kbytes): 54236
  Average resident set size (kbytes): 0
  Major (requiring I/O) page faults: 0
  Minor (reclaiming a frame) page faults: 42031
  Voluntary context switches: 15566
  Involuntary context switches: 175
  Swaps: 0
  File system inputs: 0
  File system outputs: 0
  Socket messages sent: 0
  Socket messages received: 0
  Signals delivered: 0
  Page size (bytes): 4096
  Exit status: 130

@theinfinityproject
Copy link

theinfinityproject commented Mar 27, 2019

@beardypig no, i didnt alter streamlinkrc.
@bastimeyer my results differ but idk how to compare see PS

streamlink --version &&   timeout -s SIGINT 300 /usr/bin/time -v streamlink -l none -o /dev/null --twitch-disable-ads twitch.tv/esl_dota2 best
streamlink 1.0.0+49.g94edeb0
[download][null] Written 225.4 MB (4m54s @ 659.2 KB/s)                                                                                         Interrupted! Exiting...
Command exited with non-zero status 130
	Command being timed: "streamlink -l none -o /dev/null --twitch-disable-ads twitch.tv/esl_dota2 best"
	User time (seconds): 13.10
	System time (seconds): 5.98
	Percent of CPU this job got: 6%
	Elapsed (wall clock) time (h:mm:ss or m:ss): 5:00.13
	Average shared text size (kbytes): 0
	Average unshared data size (kbytes): 0
	Average stack size (kbytes): 0
	Average total size (kbytes): 0
	Maximum resident set size (kbytes): 59928
	Average resident set size (kbytes): 0
	Major (requiring I/O) page faults: 0
	Minor (reclaiming a frame) page faults: 50098
	Voluntary context switches: 111514
	Involuntary context switches: 905
	Swaps: 0
	File system inputs: 0
	File system outputs: 0
	Socket messages sent: 0
	Socket messages received: 0
	Signals delivered: 0
	Page size (bytes): 4096
	Exit status: 130

PS: Im not familiar with git since i have a background in humanities and only basic linux knowledge but if u tell me how to revert the commit with git then i could compare it like u did

@bastimeyer
Copy link
Member Author

bastimeyer commented Mar 28, 2019

If there are no further comments or concerns, please somebody go ahead and merge (don't squash). For the moment I don't care if there are players which are having troubles with missing data when it gets filtered out. The twitch-disable-ads parameter is optional and if it's causing problems, people can simply not use it or switch to a different player. As I've said earlier, a fix for that should be submitted in a new pull request.


@theinfinityproject I've shown you in my comment earlier that your issue is not related to this pull request and caused by something else. Your own GNU time output also shows a usage of 6% over 300s (with a high system-level load, though) while running the python process, so it's definitely not 25%.

@gravyboat
Copy link
Member

gravyboat commented Mar 29, 2019

Looks good to me @bastimeyer so I will merge, no squash as requested. Thanks for adding this!

@gravyboat gravyboat merged commit bf8580f into streamlink:master Mar 29, 2019
@laurencee laurencee mentioned this pull request Mar 29, 2019
mkbloke pushed a commit to mkbloke/streamlink that referenced this pull request Aug 18, 2020
…able-ads

 plugins.twitch: implement disable-ads parameter
@bastimeyer bastimeyer deleted the plugins/twitch/disable-ads branch January 19, 2021 23:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
plugin enhancement A new feature for a working Plugin
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants