Repository: streamlink/streamlink

Issue:
Bigo TV cannot successfully get the videoSrc URL
<!--
Thanks for reporting a plugin issue!
USE THE TEMPLATE. Otherwise your plugin issue may be rejected.

First, see the contribution guidelines:
https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md#contributing-to-streamlink

Also check the list of open and closed plugin issues:
https://github.com/streamlink/streamlink/issues?q=is%3Aissue+label%3A%22plugin+issue%22

Please see the text preview to avoid unnecessary formatting errors.
-->


## Plugin Issue

<!-- Replace [ ] with [x] in order to check the box -->
- [x] This is a plugin issue and I have read the contribution guidelines.


### Description

<!-- Explain the plugin issue as thoroughly as you can. -->
I try to use streamlink to watch the bigo live tv, but the command line pops up the error message as "[plugin.bigo][error] No playlist found."

### Reproduction steps / Explicit stream URLs to test

<!-- How can we reproduce this? Please note the exact steps below using the list format supplied. If you need more steps please add them. -->

1. streamlink  http://www.bigo.tv/11819287 best &


### Log output

<!--
TEXT LOG OUTPUT IS REQUIRED for a plugin issue!
Use the `--loglevel debug` parameter and avoid using parameters which suppress log output.
https://streamlink.github.io/cli.html#cmdoption-l

Make sure to **remove usernames and passwords**
You can copy the output to https://gist.github.com/ or paste it below.
-->

```
[cli][debug] OS:         Linux-5.4.0-33-generic-x86_64-with-glibc2.29
[cli][debug] Python:     3.8.2
[cli][debug] Streamlink: 1.4.1
[cli][debug] Requests(2.22.0), Socks(1.6.7), Websocket(0.53.0)
[cli][info] Found matching plugin bigo for URL http://www.bigo.tv/11819287
[plugin.bigo][error] No playlist found.
error: No playable streams found on this URL: http://www.bigo.tv/11819287
```


### Additional comments, screenshots, etc.

After reviewing the bigo.py in streamlink/plugin, I found that the original method attempts to get the videoSrc in the webpage. However, recently the web page excluded the parameter in the page. 

So, here is the alternative
1. Add new API URL. 
```
api_url = "http://www.bigo.tv/OInterface/getVideoParam?bigoId="
```
2. Extract bigo_id from self.url. 
```
extract_id = self.url.split('/')[-1]
```
3. Use composed url to get the videoSrc. 
```
page = self.session.http.get(api_url + extract_id, ...)
```
4. Change the videomatch & videourl. 
```
videomatch = page.json()['data']['videoSrc']; videourl = videomatch
```



Discussion:
If you've got a working solution for a plugin issue, then please submit a pull request with the changes, so it can be reviewed by others.

Resolving patch:
diff --git a/src/streamlink/plugins/bigo.py b/src/streamlink/plugins/bigo.py
--- a/src/streamlink/plugins/bigo.py
+++ b/src/streamlink/plugins/bigo.py
@@ -1,37 +1,37 @@
-import logging
 import re
 
 from streamlink.plugin import Plugin
-from streamlink.plugin.api import useragents
+from streamlink.plugin.api import useragents, validate
 from streamlink.stream import HLSStream
 
-log = logging.getLogger(__name__)
-
 
 class Bigo(Plugin):
-    _url_re = re.compile(r"^https?://(?:www\.)?bigo\.tv/[^/]+$")
-    _video_re = re.compile(
-        r"""videoSrc:\s?["'](?P<url>[^"']+)["']""",
-        re.M)
+    _url_re = re.compile(r"https?://(?:www\.)?bigo\.tv/([^/]+)$")
+    _api_url = "https://www.bigo.tv/OInterface/getVideoParam?bigoId={0}"
+
+    _video_info_schema = validate.Schema({
+        "code": 0,
+        "msg": "success",
+        "data": {
+            "videoSrc": validate.any(None, "", validate.url())
+        }
+    })
 
     @classmethod
     def can_handle_url(cls, url):
         return cls._url_re.match(url) is not None
 
     def _get_streams(self):
-        page = self.session.http.get(
-            self.url,
+        match = self._url_re.match(self.url)
+        res = self.session.http.get(
+            self._api_url.format(match.group(1)),
             allow_redirects=True,
             headers={"User-Agent": useragents.IPHONE_6}
         )
-        videomatch = self._video_re.search(page.text)
-        if not videomatch:
-            log.error("No playlist found.")
-            return
-
-        videourl = videomatch.group(1)
-        log.debug("URL={0}".format(videourl))
-        yield "live", HLSStream(self.session, videourl)
+        data = self.session.http.json(res, schema=self._video_info_schema)
+        videourl = data["data"]["videoSrc"]
+        if videourl:
+            yield "live", HLSStream(self.session, videourl)
 
 
 __plugin__ = Bigo


Test patch:
diff --git a/tests/plugins/test_bigo.py b/tests/plugins/test_bigo.py
--- a/tests/plugins/test_bigo.py
+++ b/tests/plugins/test_bigo.py
@@ -5,26 +5,33 @@
 
 class TestPluginBigo(unittest.TestCase):
     def test_can_handle_url(self):
-        # Correct urls
-        self.assertTrue(Bigo.can_handle_url("http://bigo.tv/00000000"))
-        self.assertTrue(Bigo.can_handle_url("https://bigo.tv/00000000"))
-        self.assertTrue(Bigo.can_handle_url("https://www.bigo.tv/00000000"))
-        self.assertTrue(Bigo.can_handle_url("http://www.bigo.tv/00000000"))
-        self.assertTrue(Bigo.can_handle_url("http://www.bigo.tv/fancy1234"))
-        self.assertTrue(Bigo.can_handle_url("http://www.bigo.tv/abc.123"))
-        self.assertTrue(Bigo.can_handle_url("http://www.bigo.tv/000000.00"))
+        should_match = [
+            "http://bigo.tv/00000000",
+            "https://bigo.tv/00000000",
+            "https://www.bigo.tv/00000000",
+            "http://www.bigo.tv/00000000",
+            "http://www.bigo.tv/fancy1234",
+            "http://www.bigo.tv/abc.123",
+            "http://www.bigo.tv/000000.00"
+        ]
+        for url in should_match:
+            self.assertTrue(Bigo.can_handle_url(url), url)
 
-        # Old URLs don't work anymore
-        self.assertFalse(Bigo.can_handle_url("http://live.bigo.tv/00000000"))
-        self.assertFalse(Bigo.can_handle_url("https://live.bigo.tv/00000000"))
-        self.assertFalse(Bigo.can_handle_url("http://www.bigoweb.co/show/00000000"))
-        self.assertFalse(Bigo.can_handle_url("https://www.bigoweb.co/show/00000000"))
-        self.assertFalse(Bigo.can_handle_url("http://bigoweb.co/show/00000000"))
-        self.assertFalse(Bigo.can_handle_url("https://bigoweb.co/show/00000000"))
+    def test_can_handle_url_negative(self):
+        should_not_match = [
+            # Old URLs don't work anymore
+            "http://live.bigo.tv/00000000",
+            "https://live.bigo.tv/00000000",
+            "http://www.bigoweb.co/show/00000000",
+            "https://www.bigoweb.co/show/00000000",
+            "http://bigoweb.co/show/00000000",
+            "https://bigoweb.co/show/00000000"
 
-        # Wrong URL structure
-        self.assertFalse(Bigo.can_handle_url("ftp://www.bigo.tv/00000000"))
-        self.assertFalse(Bigo.can_handle_url("https://www.bigo.tv/show/00000000"))
-        self.assertFalse(Bigo.can_handle_url("http://www.bigo.tv/show/00000000"))
-        self.assertFalse(Bigo.can_handle_url("http://bigo.tv/show/00000000"))
-        self.assertFalse(Bigo.can_handle_url("https://bigo.tv/show/00000000"))
+            # Wrong URL structure
+            "https://www.bigo.tv/show/00000000",
+            "http://www.bigo.tv/show/00000000",
+            "http://bigo.tv/show/00000000",
+            "https://bigo.tv/show/00000000"
+        ]
+        for url in should_not_match:
+            self.assertFalse(Bigo.can_handle_url(url), url)