prompt: 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()
  
  ```
  
golden_diff: 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"]:
  
verification_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"}]}