We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
plugins.bilibili: Playable streams can't be found on stream pages with custom layout
### Checklist

- [X] This is a [plugin issue](https://streamlink.github.io/plugins.html) and not [a different kind of issue](https://github.com/streamlink/streamlink/issues/new/choose)
- [X] [I have read the contribution guidelines](https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md#contributing-to-streamlink)
- [X] [I have checked the list of open and recently closed plugin issues](https://github.com/streamlink/streamlink/issues?q=is%3Aissue+label%3A%22plugin+issue%22)
- [X] [I have checked the commit log of the master branch](https://github.com/streamlink/streamlink/commits/master)

### Streamlink version

streamlink 6.5.0

### Description

It appears that for Bilibili streams with custom pages, the plugin will simply say there's no playable stream found, even if a stream is actually live. An example of [a stream with a custom page at the time of posting](https://live.bilibili.com/27888667) is given in the debug log (screenshot shown here).
![image](https://github.com/streamlink/streamlink/assets/37262651/6275fb0c-e7b8-4d86-9fc7-f817536f96d1)


### Debug log

```text
PS C:\Users\Admin> streamlink --loglevel=debug https://live.bilibili.com/27888667 best
[cli][debug] OS:         Windows 10
[cli][debug] Python:     3.11.7
[cli][debug] OpenSSL:    OpenSSL 3.0.11 19 Sep 2023
[cli][debug] Streamlink: 6.5.0
[cli][debug] Dependencies:
[cli][debug]  certifi: 2023.11.17
[cli][debug]  isodate: 0.6.1
[cli][debug]  lxml: 4.9.3
[cli][debug]  pycountry: 23.12.11
[cli][debug]  pycryptodome: 3.19.0
[cli][debug]  PySocks: 1.7.1
[cli][debug]  requests: 2.31.0
[cli][debug]  trio: 0.23.2
[cli][debug]  trio-websocket: 0.11.1
[cli][debug]  typing-extensions: 4.9.0
[cli][debug]  urllib3: 2.1.0
[cli][debug]  websocket-client: 1.7.0
[cli][debug] Arguments:
[cli][debug]  url=https://live.bilibili.com/27888667
[cli][debug]  stream=['best']
[cli][debug]  --loglevel=debug
[cli][debug]  --ffmpeg-ffmpeg=C:\Program Files\Streamlink\ffmpeg\ffmpeg.exe
[cli][info] Found matching plugin bilibili for URL https://live.bilibili.com/27888667
error: No playable streams found on this URL: https://live.bilibili.com/27888667
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/streamlink/plugins/bilibili.py`
Content:
```
1 """
2 $description Chinese video sharing website based in Shanghai, themed around animation, comics, and games (ACG).
3 $url live.bilibili.com
4 $type live
5 """
6 
7 import re
8 
9 from streamlink.plugin import Plugin, pluginmatcher
10 from streamlink.plugin.api import validate
11 from streamlink.stream.hls import HLSStream
12 
13 
14 @pluginmatcher(re.compile(
15     r"https?://live\.bilibili\.com/(?P<channel>[^/]+)",
16 ))
17 class Bilibili(Plugin):
18     SHOW_STATUS_OFFLINE = 0
19     SHOW_STATUS_ONLINE = 1
20     SHOW_STATUS_ROUND = 2
21 
22     def _get_streams(self):
23         schema_stream = validate.all(
24             [{
25                 "protocol_name": str,
26                 "format": validate.all(
27                     [{
28                         "format_name": str,
29                         "codec": validate.all(
30                             [{
31                                 "codec_name": str,
32                                 "base_url": str,
33                                 "url_info": [{
34                                     "host": validate.url(),
35                                     "extra": str,
36                                 }],
37                             }],
38                             validate.filter(lambda item: item["codec_name"] == "avc"),
39                         ),
40                     }],
41                     validate.filter(lambda item: item["format_name"] == "fmp4"),
42                 ),
43             }],
44             validate.filter(lambda item: item["protocol_name"] == "http_hls"),
45         )
46 
47         data = self.session.http.get(
48             self.url,
49             schema=validate.Schema(
50                 validate.parse_html(),
51                 validate.xml_xpath_string(".//script[contains(text(),'window.__NEPTUNE_IS_MY_WAIFU__={')][1]/text()"),
52                 validate.none_or_all(
53                     validate.transform(str.replace, "window.__NEPTUNE_IS_MY_WAIFU__=", ""),
54                     validate.parse_json(),
55                     {
56                         "roomInitRes": {
57                             "data": {
58                                 "live_status": int,
59                                 "playurl_info": {
60                                     "playurl": {
61                                         "stream": schema_stream,
62                                     },
63                                 },
64                             },
65                         },
66                         "roomInfoRes": {
67                             "data": {
68                                 "room_info": {
69                                     "live_id": int,
70                                     "title": str,
71                                     "area_name": str,
72                                 },
73                                 "anchor_info": {
74                                     "base_info": {
75                                         "uname": str,
76                                     },
77                                 },
78                             },
79                         },
80                     },
81                     validate.union_get(
82                         ("roomInfoRes", "data", "room_info", "live_id"),
83                         ("roomInfoRes", "data", "anchor_info", "base_info", "uname"),
84                         ("roomInfoRes", "data", "room_info", "area_name"),
85                         ("roomInfoRes", "data", "room_info", "title"),
86                         ("roomInitRes", "data", "live_status"),
87                         ("roomInitRes", "data", "playurl_info", "playurl", "stream"),
88                     ),
89                 ),
90             ),
91         )
92         if not data:
93             return
94 
95         self.id, self.author, self.category, self.title, live_status, streams = data
96         if live_status != self.SHOW_STATUS_ONLINE:
97             return
98 
99         for stream in streams:
100             for stream_format in stream["format"]:
101                 for codec in stream_format["codec"]:
102                     for url_info in codec["url_info"]:
103                         url = f"{url_info['host']}{codec['base_url']}{url_info['extra']}"
104                         yield "live", HLSStream(self.session, url)
105 
106 
107 __plugin__ = Bilibili
108
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/streamlink/plugins/bilibili.py b/src/streamlink/plugins/bilibili.py
--- a/src/streamlink/plugins/bilibili.py
+++ b/src/streamlink/plugins/bilibili.py
@@ -4,23 +4,31 @@
 $type live
 """
 
+import logging
 import re
 
+from streamlink.exceptions import NoStreamsError
 from streamlink.plugin import Plugin, pluginmatcher
 from streamlink.plugin.api import validate
 from streamlink.stream.hls import HLSStream
 
 
+log = logging.getLogger(__name__)
+
+
 @pluginmatcher(re.compile(
     r"https?://live\.bilibili\.com/(?P<channel>[^/]+)",
 ))
 class Bilibili(Plugin):
+    _URL_API_PLAYINFO = "https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo"
+
     SHOW_STATUS_OFFLINE = 0
     SHOW_STATUS_ONLINE = 1
     SHOW_STATUS_ROUND = 2
 
-    def _get_streams(self):
-        schema_stream = validate.all(
+    @staticmethod
+    def _schema_streams():
+        return validate.all(
             [{
                 "protocol_name": str,
                 "format": validate.all(
@@ -44,6 +52,38 @@
             validate.filter(lambda item: item["protocol_name"] == "http_hls"),
         )
 
+    def _get_api_playinfo(self, room_id):
+        return self.session.http.get(
+            self._URL_API_PLAYINFO,
+            params={
+                "room_id": room_id,
+                "no_playurl": 0,
+                "mask": 1,
+                "qn": 0,
+                "platform": "web",
+                "protocol": "0,1",
+                "format": "0,1,2",
+                "codec": "0,1,2",
+                "dolby": 5,
+                "panorama": 1,
+            },
+            schema=validate.Schema(
+                validate.parse_json(),
+                {
+                    "code": 0,
+                    "data": {
+                        "playurl_info": {
+                            "playurl": {
+                                "stream": self._schema_streams(),
+                            },
+                        },
+                    },
+                },
+                validate.get(("data", "playurl_info", "playurl", "stream")),
+            ),
+        )
+
+    def _get_page_playinfo(self):
         data = self.session.http.get(
             self.url,
             schema=validate.Schema(
@@ -58,7 +98,7 @@
                                 "live_status": int,
                                 "playurl_info": {
                                     "playurl": {
-                                        "stream": schema_stream,
+                                        "stream": self._schema_streams(),
                                     },
                                 },
                             },
@@ -94,9 +134,18 @@
 
         self.id, self.author, self.category, self.title, live_status, streams = data
         if live_status != self.SHOW_STATUS_ONLINE:
-            return
+            log.info("Channel is offline")
+            raise NoStreamsError
+
+        return streams
+
+    def _get_streams(self):
+        streams = self._get_page_playinfo()
+        if not streams:
+            log.debug("Falling back to _get_api_playinfo()")
+            streams = self._get_api_playinfo(self.match["channel"])
 
-        for stream in streams:
+        for stream in streams or []:
             for stream_format in stream["format"]:
                 for codec in stream_format["codec"]:
                     for url_info in codec["url_info"]:

{"golden_diff": "diff --git a/src/streamlink/plugins/bilibili.py b/src/streamlink/plugins/bilibili.py\n--- a/src/streamlink/plugins/bilibili.py\n+++ b/src/streamlink/plugins/bilibili.py\n@@ -4,23 +4,31 @@\n $type live\n \"\"\"\n \n+import logging\n import re\n \n+from streamlink.exceptions import NoStreamsError\n from streamlink.plugin import Plugin, pluginmatcher\n from streamlink.plugin.api import validate\n from streamlink.stream.hls import HLSStream\n \n \n+log = logging.getLogger(__name__)\n+\n+\n @pluginmatcher(re.compile(\n     r\"https?://live\\.bilibili\\.com/(?P<channel>[^/]+)\",\n ))\n class Bilibili(Plugin):\n+    _URL_API_PLAYINFO = \"https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo\"\n+\n     SHOW_STATUS_OFFLINE = 0\n     SHOW_STATUS_ONLINE = 1\n     SHOW_STATUS_ROUND = 2\n \n-    def _get_streams(self):\n-        schema_stream = validate.all(\n+    @staticmethod\n+    def _schema_streams():\n+        return validate.all(\n             [{\n                 \"protocol_name\": str,\n                 \"format\": validate.all(\n@@ -44,6 +52,38 @@\n             validate.filter(lambda item: item[\"protocol_name\"] == \"http_hls\"),\n         )\n \n+    def _get_api_playinfo(self, room_id):\n+        return self.session.http.get(\n+            self._URL_API_PLAYINFO,\n+            params={\n+                \"room_id\": room_id,\n+                \"no_playurl\": 0,\n+                \"mask\": 1,\n+                \"qn\": 0,\n+                \"platform\": \"web\",\n+                \"protocol\": \"0,1\",\n+                \"format\": \"0,1,2\",\n+                \"codec\": \"0,1,2\",\n+                \"dolby\": 5,\n+                \"panorama\": 1,\n+            },\n+            schema=validate.Schema(\n+                validate.parse_json(),\n+                {\n+                    \"code\": 0,\n+                    \"data\": {\n+                        \"playurl_info\": {\n+                            \"playurl\": {\n+                                \"stream\": self._schema_streams(),\n+                            },\n+                        },\n+                    },\n+                },\n+                validate.get((\"data\", \"playurl_info\", \"playurl\", \"stream\")),\n+            ),\n+        )\n+\n+    def _get_page_playinfo(self):\n         data = self.session.http.get(\n             self.url,\n             schema=validate.Schema(\n@@ -58,7 +98,7 @@\n                                 \"live_status\": int,\n                                 \"playurl_info\": {\n                                     \"playurl\": {\n-                                        \"stream\": schema_stream,\n+                                        \"stream\": self._schema_streams(),\n                                     },\n                                 },\n                             },\n@@ -94,9 +134,18 @@\n \n         self.id, self.author, self.category, self.title, live_status, streams = data\n         if live_status != self.SHOW_STATUS_ONLINE:\n-            return\n+            log.info(\"Channel is offline\")\n+            raise NoStreamsError\n+\n+        return streams\n+\n+    def _get_streams(self):\n+        streams = self._get_page_playinfo()\n+        if not streams:\n+            log.debug(\"Falling back to _get_api_playinfo()\")\n+            streams = self._get_api_playinfo(self.match[\"channel\"])\n \n-        for stream in streams:\n+        for stream in streams or []:\n             for stream_format in stream[\"format\"]:\n                 for codec in stream_format[\"codec\"]:\n                     for url_info in codec[\"url_info\"]:\n", "issue": "plugins.bilibili: Playable streams can't be found on stream pages with custom layout\n### Checklist\r\n\r\n- [X] This is a [plugin issue](https://streamlink.github.io/plugins.html) and not [a different kind of issue](https://github.com/streamlink/streamlink/issues/new/choose)\r\n- [X] [I have read the contribution guidelines](https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md#contributing-to-streamlink)\r\n- [X] [I have checked the list of open and recently closed plugin issues](https://github.com/streamlink/streamlink/issues?q=is%3Aissue+label%3A%22plugin+issue%22)\r\n- [X] [I have checked the commit log of the master branch](https://github.com/streamlink/streamlink/commits/master)\r\n\r\n### Streamlink version\r\n\r\nstreamlink 6.5.0\r\n\r\n### Description\r\n\r\nIt appears that for Bilibili streams with custom pages, the plugin will simply say there's no playable stream found, even if a stream is actually live. An example of [a stream with a custom page at the time of posting](https://live.bilibili.com/27888667) is given in the debug log (screenshot shown here).\r\n![image](https://github.com/streamlink/streamlink/assets/37262651/6275fb0c-e7b8-4d86-9fc7-f817536f96d1)\r\n\r\n\r\n### Debug log\r\n\r\n```text\r\nPS C:\\Users\\Admin> streamlink --loglevel=debug https://live.bilibili.com/27888667 best\r\n[cli][debug] OS:         Windows 10\r\n[cli][debug] Python:     3.11.7\r\n[cli][debug] OpenSSL:    OpenSSL 3.0.11 19 Sep 2023\r\n[cli][debug] Streamlink: 6.5.0\r\n[cli][debug] Dependencies:\r\n[cli][debug]  certifi: 2023.11.17\r\n[cli][debug]  isodate: 0.6.1\r\n[cli][debug]  lxml: 4.9.3\r\n[cli][debug]  pycountry: 23.12.11\r\n[cli][debug]  pycryptodome: 3.19.0\r\n[cli][debug]  PySocks: 1.7.1\r\n[cli][debug]  requests: 2.31.0\r\n[cli][debug]  trio: 0.23.2\r\n[cli][debug]  trio-websocket: 0.11.1\r\n[cli][debug]  typing-extensions: 4.9.0\r\n[cli][debug]  urllib3: 2.1.0\r\n[cli][debug]  websocket-client: 1.7.0\r\n[cli][debug] Arguments:\r\n[cli][debug]  url=https://live.bilibili.com/27888667\r\n[cli][debug]  stream=['best']\r\n[cli][debug]  --loglevel=debug\r\n[cli][debug]  --ffmpeg-ffmpeg=C:\\Program Files\\Streamlink\\ffmpeg\\ffmpeg.exe\r\n[cli][info] Found matching plugin bilibili for URL https://live.bilibili.com/27888667\r\nerror: No playable streams found on this URL: https://live.bilibili.com/27888667\r\n```\r\n\n", "before_files": [{"content": "\"\"\"\n$description Chinese video sharing website based in Shanghai, themed around animation, comics, and games (ACG).\n$url live.bilibili.com\n$type live\n\"\"\"\n\nimport re\n\nfrom streamlink.plugin import Plugin, pluginmatcher\nfrom streamlink.plugin.api import validate\nfrom streamlink.stream.hls import HLSStream\n\n\n@pluginmatcher(re.compile(\n    r\"https?://live\\.bilibili\\.com/(?P<channel>[^/]+)\",\n))\nclass Bilibili(Plugin):\n    SHOW_STATUS_OFFLINE = 0\n    SHOW_STATUS_ONLINE = 1\n    SHOW_STATUS_ROUND = 2\n\n    def _get_streams(self):\n        schema_stream = validate.all(\n            [{\n                \"protocol_name\": str,\n                \"format\": validate.all(\n                    [{\n                        \"format_name\": str,\n                        \"codec\": validate.all(\n                            [{\n                                \"codec_name\": str,\n                                \"base_url\": str,\n                                \"url_info\": [{\n                                    \"host\": validate.url(),\n                                    \"extra\": str,\n                                }],\n                            }],\n                            validate.filter(lambda item: item[\"codec_name\"] == \"avc\"),\n                        ),\n                    }],\n                    validate.filter(lambda item: item[\"format_name\"] == \"fmp4\"),\n                ),\n            }],\n            validate.filter(lambda item: item[\"protocol_name\"] == \"http_hls\"),\n        )\n\n        data = self.session.http.get(\n            self.url,\n            schema=validate.Schema(\n                validate.parse_html(),\n                validate.xml_xpath_string(\".//script[contains(text(),'window.__NEPTUNE_IS_MY_WAIFU__={')][1]/text()\"),\n                validate.none_or_all(\n                    validate.transform(str.replace, \"window.__NEPTUNE_IS_MY_WAIFU__=\", \"\"),\n                    validate.parse_json(),\n                    {\n                        \"roomInitRes\": {\n                            \"data\": {\n                                \"live_status\": int,\n                                \"playurl_info\": {\n                                    \"playurl\": {\n                                        \"stream\": schema_stream,\n                                    },\n                                },\n                            },\n                        },\n                        \"roomInfoRes\": {\n                            \"data\": {\n                                \"room_info\": {\n                                    \"live_id\": int,\n                                    \"title\": str,\n                                    \"area_name\": str,\n                                },\n                                \"anchor_info\": {\n                                    \"base_info\": {\n                                        \"uname\": str,\n                                    },\n                                },\n                            },\n                        },\n                    },\n                    validate.union_get(\n                        (\"roomInfoRes\", \"data\", \"room_info\", \"live_id\"),\n                        (\"roomInfoRes\", \"data\", \"anchor_info\", \"base_info\", \"uname\"),\n                        (\"roomInfoRes\", \"data\", \"room_info\", \"area_name\"),\n                        (\"roomInfoRes\", \"data\", \"room_info\", \"title\"),\n                        (\"roomInitRes\", \"data\", \"live_status\"),\n                        (\"roomInitRes\", \"data\", \"playurl_info\", \"playurl\", \"stream\"),\n                    ),\n                ),\n            ),\n        )\n        if not data:\n            return\n\n        self.id, self.author, self.category, self.title, live_status, streams = data\n        if live_status != self.SHOW_STATUS_ONLINE:\n            return\n\n        for stream in streams:\n            for stream_format in stream[\"format\"]:\n                for codec in stream_format[\"codec\"]:\n                    for url_info in codec[\"url_info\"]:\n                        url = f\"{url_info['host']}{codec['base_url']}{url_info['extra']}\"\n                        yield \"live\", HLSStream(self.session, url)\n\n\n__plugin__ = Bilibili\n", "path": "src/streamlink/plugins/bilibili.py"}], "after_files": [{"content": "\"\"\"\n$description Chinese video sharing website based in Shanghai, themed around animation, comics, and games (ACG).\n$url live.bilibili.com\n$type live\n\"\"\"\n\nimport logging\nimport re\n\nfrom streamlink.exceptions import NoStreamsError\nfrom streamlink.plugin import Plugin, pluginmatcher\nfrom streamlink.plugin.api import validate\nfrom streamlink.stream.hls import HLSStream\n\n\nlog = logging.getLogger(__name__)\n\n\n@pluginmatcher(re.compile(\n    r\"https?://live\\.bilibili\\.com/(?P<channel>[^/]+)\",\n))\nclass Bilibili(Plugin):\n    _URL_API_PLAYINFO = \"https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo\"\n\n    SHOW_STATUS_OFFLINE = 0\n    SHOW_STATUS_ONLINE = 1\n    SHOW_STATUS_ROUND = 2\n\n    @staticmethod\n    def _schema_streams():\n        return validate.all(\n            [{\n                \"protocol_name\": str,\n                \"format\": validate.all(\n                    [{\n                        \"format_name\": str,\n                        \"codec\": validate.all(\n                            [{\n                                \"codec_name\": str,\n                                \"base_url\": str,\n                                \"url_info\": [{\n                                    \"host\": validate.url(),\n                                    \"extra\": str,\n                                }],\n                            }],\n                            validate.filter(lambda item: item[\"codec_name\"] == \"avc\"),\n                        ),\n                    }],\n                    validate.filter(lambda item: item[\"format_name\"] == \"fmp4\"),\n                ),\n            }],\n            validate.filter(lambda item: item[\"protocol_name\"] == \"http_hls\"),\n        )\n\n    def _get_api_playinfo(self, room_id):\n        return self.session.http.get(\n            self._URL_API_PLAYINFO,\n            params={\n                \"room_id\": room_id,\n                \"no_playurl\": 0,\n                \"mask\": 1,\n                \"qn\": 0,\n                \"platform\": \"web\",\n                \"protocol\": \"0,1\",\n                \"format\": \"0,1,2\",\n                \"codec\": \"0,1,2\",\n                \"dolby\": 5,\n                \"panorama\": 1,\n            },\n            schema=validate.Schema(\n                validate.parse_json(),\n                {\n                    \"code\": 0,\n                    \"data\": {\n                        \"playurl_info\": {\n                            \"playurl\": {\n                                \"stream\": self._schema_streams(),\n                            },\n                        },\n                    },\n                },\n                validate.get((\"data\", \"playurl_info\", \"playurl\", \"stream\")),\n            ),\n        )\n\n    def _get_page_playinfo(self):\n        data = self.session.http.get(\n            self.url,\n            schema=validate.Schema(\n                validate.parse_html(),\n                validate.xml_xpath_string(\".//script[contains(text(),'window.__NEPTUNE_IS_MY_WAIFU__={')][1]/text()\"),\n                validate.none_or_all(\n                    validate.transform(str.replace, \"window.__NEPTUNE_IS_MY_WAIFU__=\", \"\"),\n                    validate.parse_json(),\n                    {\n                        \"roomInitRes\": {\n                            \"data\": {\n                                \"live_status\": int,\n                                \"playurl_info\": {\n                                    \"playurl\": {\n                                        \"stream\": self._schema_streams(),\n                                    },\n                                },\n                            },\n                        },\n                        \"roomInfoRes\": {\n                            \"data\": {\n                                \"room_info\": {\n                                    \"live_id\": int,\n                                    \"title\": str,\n                                    \"area_name\": str,\n                                },\n                                \"anchor_info\": {\n                                    \"base_info\": {\n                                        \"uname\": str,\n                                    },\n                                },\n                            },\n                        },\n                    },\n                    validate.union_get(\n                        (\"roomInfoRes\", \"data\", \"room_info\", \"live_id\"),\n                        (\"roomInfoRes\", \"data\", \"anchor_info\", \"base_info\", \"uname\"),\n                        (\"roomInfoRes\", \"data\", \"room_info\", \"area_name\"),\n                        (\"roomInfoRes\", \"data\", \"room_info\", \"title\"),\n                        (\"roomInitRes\", \"data\", \"live_status\"),\n                        (\"roomInitRes\", \"data\", \"playurl_info\", \"playurl\", \"stream\"),\n                    ),\n                ),\n            ),\n        )\n        if not data:\n            return\n\n        self.id, self.author, self.category, self.title, live_status, streams = data\n        if live_status != self.SHOW_STATUS_ONLINE:\n            log.info(\"Channel is offline\")\n            raise NoStreamsError\n\n        return streams\n\n    def _get_streams(self):\n        streams = self._get_page_playinfo()\n        if not streams:\n            log.debug(\"Falling back to _get_api_playinfo()\")\n            streams = self._get_api_playinfo(self.match[\"channel\"])\n\n        for stream in streams or []:\n            for stream_format in stream[\"format\"]:\n                for codec in stream_format[\"codec\"]:\n                    for url_info in codec[\"url_info\"]:\n                        url = f\"{url_info['host']}{codec['base_url']}{url_info['extra']}\"\n                        yield \"live\", HLSStream(self.session, url)\n\n\n__plugin__ = Bilibili\n", "path": "src/streamlink/plugins/bilibili.py"}]}