Repository: streamlink/streamlink
Pull request #3372 (2020-11-28 12:58:15)

plugins.youtube: quickfix for "/live" URL
before

```
$ streamlink https://www.youtube.com/user/deutschewelleenglish/live -l info
[cli][info] Found matching plugin youtube for URL https://www.youtube.com/user/deutschewelleenglish/live
error: Could not find a video on this page
```

after

```
$ streamlink https://www.youtube.com/user/deutschewelleenglish/live worst -l info
[cli][info] Found matching plugin youtube for URL https://www.youtube.com/user/deutschewelleenglish/live
[cli][info] Available streams: 144p (worst), 240p, 360p, 480p, 720p (best)
[cli][info] Opening stream: 144p (hls)
```


Patch:
diff --git a/src/streamlink/plugins/twitch.py b/src/streamlink/plugins/twitch.py
index dfb624db..47895568 100644
--- a/src/streamlink/plugins/twitch.py
+++ b/src/streamlink/plugins/twitch.py
@@ -232,24 +232,6 @@ class TwitchAPI:
             validate.transform(int)
         ))
 
-    def stream_rerun(self, channel_id):
-        return self.call("/kraken/streams/{0}".format(channel_id), schema=validate.Schema(
-            {
-                "stream": validate.any(None, validate.all({
-                    "stream_type": validate.text,
-                    "broadcast_platform": validate.text,
-                    "channel": validate.all({
-                        "broadcaster_software": validate.text
-                    }, validate.get("broadcaster_software"))
-                }, validate.union((
-                    validate.get("stream_type"),
-                    validate.get("broadcast_platform"),
-                    validate.get("channel")
-                ))))
-            },
-            validate.get("stream")
-        ))
-
     def metadata_video(self, video_id):
         return self.call("/kraken/videos/{0}".format(video_id), schema=validate.Schema(validate.any(
             validate.all(
@@ -321,7 +303,7 @@ class TwitchAPI:
                 "login": channel_or_vod if is_live else "",
                 "isVod": not is_live,
                 "vodID": channel_or_vod if not is_live else "",
-                "playerType": "site"
+                "playerType": "embed"
             }
         }
         subschema = {
@@ -407,6 +389,35 @@ class TwitchAPI:
             validate.get("clip")
         ))
 
+    def stream_metadata(self, channel):
+        request = {
+            "operationName": "StreamMetadata",
+            "extensions": {
+                "persistedQuery": {
+                    "version": 1,
+                    "sha256Hash": "1c719a40e481453e5c48d9bb585d971b8b372f8ebb105b17076722264dfa5b3e"
+                }
+            },
+            "variables": {
+                "channelLogin": channel
+            }
+        }
+
+        return self.call_gql(request, schema=validate.Schema(
+            {
+                "data": {
+                    "user": {
+                        "stream": {
+                            "type": str,
+                        }
+                    }
+                }
+            },
+            validate.get("data"),
+            validate.get("user"),
+            validate.get("stream")
+        ))
+
 
 class Twitch(Plugin):
     arguments = PluginArguments(
@@ -595,8 +606,8 @@ class Twitch(Plugin):
             return False
 
         try:
-            stream_type, broadcast_platform, broadcaster_software = self.api.stream_rerun(self.channel_id)
-            if stream_type != "live" or broadcast_platform == "rerun" or broadcaster_software == "watch_party_rerun":
+            stream = self.api.stream_metadata(self.channel)
+            if stream["type"] != "live":
                 log.info("Reruns were disabled by command line option")
                 return True
         except (PluginError, TypeError):
@@ -612,6 +623,10 @@ class Twitch(Plugin):
 
         # only get the token once the channel has been resolved
         log.debug("Getting live HLS streams for {0}".format(self.channel))
+        self.session.http.headers.update({
+            "referer": "https://player.twitch.tv",
+            "origin": "https://player.twitch.tv",
+        })
         sig, token, restricted_bitrates = self._access_token(True, self.channel)
         url = self.usher.channel(self.channel, sig=sig, token=token, fast_bread=True)
 
diff --git a/src/streamlink/plugins/youtube.py b/src/streamlink/plugins/youtube.py
index f7e2eaa3..58dae326 100644
--- a/src/streamlink/plugins/youtube.py
+++ b/src/streamlink/plugins/youtube.py
@@ -249,7 +249,7 @@ class YouTube(Plugin):
                             log.debug("Video ID from videoRenderer (live)")
                             return x["videoId"]
 
-        if "/embed/live_stream" in url:
+        if urlparse(url).path.endswith(("/embed/live_stream", "/live")):
             for link in itertags(res.text, "link"):
                 if link.attributes.get("rel") == "canonical":
                     canon_link = link.attributes.get("href")


Test patch:
diff --git a/tests/plugins/test_twitch.py b/tests/plugins/test_twitch.py
index fe7989f2..eb199178 100644
--- a/tests/plugins/test_twitch.py
+++ b/tests/plugins/test_twitch.py
@@ -470,22 +470,8 @@ class TestTwitchReruns(unittest.TestCase):
     log_call = call("Reruns were disabled by command line option")
 
     def subject(self, **params):
-        with requests_mock.Mocker() as mock:
-            mock.get(
-                "https://api.twitch.tv/kraken/users?login=foo",
-                json={"users": [{"_id": 1234}]}
-            )
-            mock.get(
-                "https://api.twitch.tv/kraken/streams/1234",
-                json={"stream": None} if params.pop("offline", False) else {"stream": {
-                    "stream_type": params.pop("stream_type", "live"),
-                    "broadcast_platform": params.pop("broadcast_platform", "live"),
-                    "channel": {
-                        "broadcaster_software": params.pop("broadcaster_software", "")
-                    }
-                }}
-            )
-
+        with patch("streamlink.plugins.twitch.TwitchAPI.stream_metadata") as mock:
+            mock.return_value = None if params.pop("offline", False) else {"type": params.pop("stream_type", "live")}
             session = Streamlink()
             Twitch.bind(session, "tests.plugins.test_twitch")
             plugin = Twitch("https://www.twitch.tv/foo")
@@ -501,18 +487,6 @@ class TestTwitchReruns(unittest.TestCase):
         self.assertTrue(self.subject(stream_type="rerun"))
         self.assertIn(self.log_call, mock_log.info.call_args_list)
 
-    def test_disable_reruns_mixed(self, mock_log):
-        self.assertTrue(self.subject(stream_type="rerun", broadcast_platform="live"))
-        self.assertIn(self.log_call, mock_log.info.call_args_list)
-
-    def test_disable_reruns_mixed2(self, mock_log):
-        self.assertTrue(self.subject(stream_type="live", broadcast_platform="rerun"))
-        self.assertIn(self.log_call, mock_log.info.call_args_list)
-
-    def test_disable_reruns_broadcaster_software(self, mock_log):
-        self.assertTrue(self.subject(broadcaster_software="watch_party_rerun"))
-        self.assertIn(self.log_call, mock_log.info.call_args_list)
-
     def test_disable_reruns_offline(self, mock_log):
         self.assertFalse(self.subject(offline=True))
         self.assertNotIn(self.log_call, mock_log.info.call_args_list)