Skip to content

Commit

Permalink
plugins.twitch: add --twitch-supported-codecs
Browse files Browse the repository at this point in the history
Add the temporary `--twitch-supported-codecs` plugin argument
for being able to set the client's video codec preference.

Set the default value to "h264" to ensure that no compatibility
issues arise on clients without AV1/HEVC decode capabilities.
  • Loading branch information
bastimeyer committed Jan 15, 2024
1 parent 9cf393e commit 62c3d06
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 3 deletions.
26 changes: 24 additions & 2 deletions src/streamlink/plugins/twitch.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,18 +204,26 @@ def __init__(self, *args, disable_ads: bool = False, low_latency: bool = False,


class UsherService:
def __init__(self, session):
_SUPPORTED_CODECS = "h264", "h265", "av1"

def __init__(self, session: Streamlink, supported_codecs: Optional[str] = None):
self.session = session
self.supported_codecs = ",".join(sorted({
codec for codec in (supported_codecs or "").split(",") if codec in self._SUPPORTED_CODECS
})) or self._SUPPORTED_CODECS[0]

def _create_url(self, endpoint, **extra_params):
url = f"https://usher.ttvnw.net{endpoint}"
params = {
"platform": "web",
"player": "twitchweb",
"p": int(random() * 999999),
"type": "any",
"allow_source": "true",
"allow_audio_only": "true",
"allow_spectre": "false",
"playlist_include_framerate": "true",
"supported_codecs": self.supported_codecs,
}
params.update(extra_params)

Expand Down Expand Up @@ -652,6 +660,17 @@ def decode_client_integrity_token(data: str, schema: Optional[validate.Schema] =
Regular streams can cause buffering issues with this option enabled due to the reduced --hls-live-edge value.
""",
)
@pluginargument(
"supported-codecs",
help="""
A comma-separated list of codec names which signals Twitch the client's stream codec preference.
Which streams and which codecs are available depends on the specific channel and broadcast.
Default is "h264".
Supported codecs are "h264", "h265", "av1". Set to "h264,h265,av1" to enable all codecs.
""",
)
@pluginargument(
"api-header",
metavar="KEY=VALUE",
Expand Down Expand Up @@ -719,7 +738,10 @@ def __init__(self, *args, **kwargs):
api_header=self.get_option("api-header"),
access_token_param=self.get_option("access-token-param"),
)
self.usher = UsherService(session=self.session)
self.usher = UsherService(
session=self.session,
supported_codecs=self.get_option("supported-codecs"),
)

def method_factory(parent_method):
def inner():
Expand Down
24 changes: 23 additions & 1 deletion tests/plugins/test_twitch.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@
from streamlink import Streamlink
from streamlink.exceptions import NoStreamsError, PluginError
from streamlink.options import Options
from streamlink.plugins.twitch import Twitch, TwitchAPI, TwitchHLSStream, TwitchHLSStreamReader, TwitchHLSStreamWriter
from streamlink.plugins.twitch import (
Twitch,
TwitchAPI,
TwitchHLSStream,
TwitchHLSStreamReader,
TwitchHLSStreamWriter,
UsherService,
)
from tests.mixins.stream_hls import EventedHLSStreamWriter, Playlist, Segment as _Segment, Tag, TestMixinStreamHLS
from tests.plugins import PluginCanHandleUrl
from tests.resources import text
Expand Down Expand Up @@ -397,6 +404,21 @@ def test_hls_low_latency_no_ads_reload_time(self):
assert self.thread.reader.worker.playlist_reload_time == pytest.approx(23 / 3)


@pytest.mark.parametrize(("supported_codecs", "expected"), [
pytest.param(None, "h264", id="Default"),
pytest.param("foo", "h264", id="Invalid"),
pytest.param("h264", "h264", id="h264"),
pytest.param("h265", "h265", id="h265"),
pytest.param("av1", "av1", id="av1"),
pytest.param("h264,av1", "av1,h264", id="h264,av1"),
pytest.param("h264,h265,av1", "av1,h264,h265", id="h264,h265,av1"),
pytest.param("h264,h264,h265,h265,av1,av1,foo", "av1,h264,h265", id="duplicates+invalid"),
])
def test_supported_codecs(session: Streamlink, supported_codecs: str, expected: str):
usher_service = UsherService(session=session, supported_codecs=supported_codecs)
assert usher_service.supported_codecs == expected


class TestTwitchAPIAccessToken:
@pytest.fixture(autouse=True)
def _client_integrity_token(self, monkeypatch: pytest.MonkeyPatch):
Expand Down

0 comments on commit 62c3d06

Please sign in to comment.