Repository: streamlink/streamlink
Pull request #1666 (2018-05-18 15:59:12)

Add plugin for www.kingkong.com.tw
This pull request will add a plugin for www.kingkong.com.tw, a game streaming website in Taiwan.

This plugin is mostly modified from the one of Douyutv.

Example of stream URLs can be found in the Kingkong front page.
A VOD URL example is provided FYI:
https://www.kingkong.com.tw/video/2152350G38400MJTU

codecov[bot]: # [Codecov](https://codecov.io/gh/streamlink/streamlink/pull/1666?src=pr&el=h1) Report
> Merging [#1666](https://codecov.io/gh/streamlink/streamlink/pull/1666?src=pr&el=desc) into [master](https://codecov.io/gh/streamlink/streamlink/commit/f2f9c756c384aed308f97fa930f8310302bce612?src=pr&el=desc) will **decrease** coverage by `0.11%`.
> The diff coverage is `0%`.


```diff
@@            Coverage Diff             @@
##           master    #1666      +/-   ##
==========================================
- Coverage   33.16%   33.05%   -0.12%     
==========================================
  Files         229      230       +1     
  Lines       12904    12947      +43     
==========================================
  Hits         4279     4279              
- Misses       8625     8668      +43
```

gravyboat: @littlebtc This looks pretty good! Do you think you could add some tests for this plugin?

Patch:
diff --git a/docs/plugin_matrix.rst b/docs/plugin_matrix.rst
index b13dd541..6a472d95 100644
--- a/docs/plugin_matrix.rst
+++ b/docs/plugin_matrix.rst
@@ -111,6 +111,7 @@ idf1                idf1.fr              Yes   Yes
 ine                 ine.com              ---   Yes
 itvplayer           itv.com/itvplayer    Yes   Yes   Streams may be geo-restricted to Great Britain.
 kanal7              kanal7.com           Yes   No
+kingkong            www.kingkong.com.tw  Yes   Yes
 liveedu             - liveedu.tv         Yes   --    Some streams require a login.
                     - livecoding.tv
 liveme              liveme.com           Yes   --
diff --git a/src/streamlink/plugins/gardenersworld.py b/src/streamlink/plugins/gardenersworld.py
index e80ecbe5..9f42a5a1 100644
--- a/src/streamlink/plugins/gardenersworld.py
+++ b/src/streamlink/plugins/gardenersworld.py
@@ -2,15 +2,15 @@ from __future__ import print_function
 
 import re
 
+from streamlink import NoPluginError
 from streamlink.plugin import Plugin
 from streamlink.plugin.api import http
-from streamlink.plugins.brightcove import BrightcovePlayer
+from streamlink.utils import update_scheme
 
 
 class GardenersWorld(Plugin):
     url_re = re.compile(r"https?://(?:www\.)?gardenersworld\.com/")
-    object_re = re.compile('''<object.*?id="brightcove-pod-object".*?>(.*?)</object>''', re.DOTALL)
-    param_re = re.compile('''<param.*?name="(.*?)".*?value="(.*?)".*?/>''')
+    iframe_re = re.compile('''iframe src=(?P<quote>["'])(?P<url>.*?)(?P=quote)''')
 
     @classmethod
     def can_handle_url(cls, url):
@@ -18,18 +18,14 @@ class GardenersWorld(Plugin):
 
     def _get_streams(self):
         page = http.get(self.url)
-        object_m = self.object_re.search(page.text)
-        if object_m:
-            object_t = object_m.group(1)
-            params = {}
-            for param_m in self.param_re.finditer(object_t):
-                params[param_m.group(1)] = param_m.group(2)
-
-            return BrightcovePlayer.from_player_key(self.session,
-                                                    params.get("playerID"),
-                                                    params.get("playerKey"),
-                                                    params.get("videoID"),
-                                                    url=self.url)
+        iframe = self.iframe_re.search(page.text)
+        if iframe:
+            self.logger.debug("Handing off of {0}".format(iframe.group("url")))
+            try:
+                return self.session.streams(update_scheme(self.url, iframe.group("url")))
+            except NoPluginError:
+                self.logger.error("Handing off of {0} failed".format(iframe.group("url")))
+                return None
 
 
 __plugin__ = GardenersWorld
diff --git a/src/streamlink/plugins/kingkong.py b/src/streamlink/plugins/kingkong.py
new file mode 100644
index 00000000..e50b8144
--- /dev/null
+++ b/src/streamlink/plugins/kingkong.py
@@ -0,0 +1,98 @@
+import re
+
+from streamlink.plugin import Plugin
+from streamlink.plugin.api import http, validate
+from streamlink.stream import HTTPStream, HLSStream
+
+API_URL = "https://g-api.langlive.com/webapi/v1/room/info?room_id={0}"
+VOD_API_URL = (
+    "https://g-api.langlive.com/webapi/v1/replayer/detail?live_id={0}")
+STATUS_ONLINE = 1
+STATUS_OFFLINE = 0
+STREAM_WEIGHTS = {
+    "360P": 360,
+    "480P": 480,
+    "720P": 720,
+    "source": 1080
+}
+
+_url_re = re.compile(r"""
+    https://www\.kingkong\.com\.tw/
+    (?:
+        video/(?P<vid>[0-9]+G[0-9A-Za-z]+)|
+        (?P<channel>[0-9]+)
+    )
+""", re.VERBOSE)
+
+_room_schema = validate.Schema(
+    {
+        "data": {
+            "live_info": {
+                "live_status": int,
+                "stream_items": [{
+                    "title": validate.text,
+                    "video": validate.any('', validate.url(
+                        scheme="https",
+                        path=validate.endswith(".flv")
+                    ))
+                }]
+            }
+        }
+    },
+    validate.get("data")
+)
+
+_vod_schema = validate.Schema(
+    {
+        "data": {
+            "live_info": {
+                "video": validate.text
+            }
+        }
+    },
+    validate.get("data")
+)
+
+
+class Kingkong(Plugin):
+    @classmethod
+    def can_handle_url(cls, url):
+        return _url_re.match(url)
+
+    @classmethod
+    def stream_weight(cls, stream):
+        if stream in STREAM_WEIGHTS:
+            return STREAM_WEIGHTS[stream], "kingkong"
+        return Plugin.stream_weight(stream)
+
+    def _get_streams(self):
+        match = _url_re.match(self.url)
+        vid = match.group("vid")
+
+        if vid:
+            res = http.get(VOD_API_URL.format(vid))
+            data = http.json(res, schema=_vod_schema)
+            yield "source", HLSStream(
+                self.session, data["live_info"]["video"])
+            return
+
+        channel = match.group("channel")
+        res = http.get(API_URL.format(channel))
+        room = http.json(res, schema=_room_schema)
+        if not room:
+            self.logger.info("Not a valid room url.")
+            return
+
+        live_info = room["live_info"]
+        if live_info["live_status"] != STATUS_ONLINE:
+            self.logger.info("Stream currently unavailable.")
+            return
+
+        for item in live_info["stream_items"]:
+            quality = item["title"]
+            if quality == u"\u6700\u4f73":  # "Best" in Chinese
+                quality = "source"
+            yield quality, HTTPStream(self.session, item["video"])
+
+
+__plugin__ = Kingkong


Test patch:
diff --git a/tests/test_plugin_kingkong.py b/tests/test_plugin_kingkong.py
new file mode 100644
index 00000000..311041bb
--- /dev/null
+++ b/tests/test_plugin_kingkong.py
@@ -0,0 +1,17 @@
+import unittest
+from streamlink.plugins.kingkong import Kingkong
+
+
+class TestPluginKingkong(unittest.TestCase):
+    def test_can_handle_url(self):
+        # Valid stream and VOD URLS
+        self.assertTrue(Kingkong.can_handle_url(
+            "https://www.kingkong.com.tw/600000"))
+        self.assertTrue(Kingkong.can_handle_url(
+            "https://www.kingkong.com.tw/video/2152350G38400MJTU"))
+
+        # Others
+        self.assertFalse(Kingkong.can_handle_url(
+            "https://www.kingkong.com.tw/category/lol"))
+        self.assertFalse(Kingkong.can_handle_url(
+            "https://www.kingkong.com.tw/videos/2152350"))