Repository: streamlink/streamlink

Issue:
ViuTV live
## Plugin Request

<!-- Replace [ ] with [x] in order to check the box -->
- [x] This is a plugin request and I have read the contribution guidelines and plugin request requirements.


### Description

<!-- Explain the plugin and site as clearly as you can. What is the site about? Who runs it? What content does it provide? What value does it bring to Streamlink? Etc. -->
One of a local television station from Hong Kong which currently accessible via Internet streaming. ViuTV is one of a few disinterested news channel which provide fare perspective reports compared to major aired television in Hong Kong.

### Example stream URLs

<!-- Example URLs for streams are required. Plugin requests which do not have example URLs will be closed. -->

1. https://viu.tv/ch/99


### Additional comments, screenshots, etc.
![chrome_2019-09-07_22-29-31](https://user-images.githubusercontent.com/771955/64476235-071fe400-d1bf-11e9-964e-1807da742eed.png)

ViuTV is also run by Now, so the method for fetching playback should be the same as NowTV
https://github.com/streamlink/streamlink/issues/2592



[Love Streamlink? Please consider supporting our collective. Thanks!](https://opencollective.com/streamlink/donate)



Discussion:
Appears to be geo-locked, I cannot access it :(
> Appears to be geo-locked, I cannot access it :(

Check you email please

Resolving patch:
diff --git a/src/streamlink/plugins/viutv.py b/src/streamlink/plugins/viutv.py
new file mode 100644
--- /dev/null
+++ b/src/streamlink/plugins/viutv.py
@@ -0,0 +1,46 @@
+import datetime
+import logging
+import random
+import re
+import json
+
+from streamlink.plugin import Plugin
+from streamlink.stream import HLSStream
+
+log = logging.getLogger(__name__)
+
+
+class ViuTV(Plugin):
+    _url_re = re.compile(r"https?://viu\.tv/ch/(\d+)")
+    api_url = "https://api.viu.now.com/p8/2/getLiveURL"
+
+    @classmethod
+    def can_handle_url(cls, url):
+        return cls._url_re.match(url) is not None
+
+    @property
+    def device_id(self):
+        return "".join(random.choice("abcdef0123456789") for _ in range(18))
+
+    @property
+    def channel_id(self):
+        return self._url_re.match(self.url).group(1)
+
+    def _get_streams(self):
+        api_res = self.session.http.post(self.api_url,
+                                         headers={"Content-Type": 'application/json'},
+                                         data=json.dumps({"callerReferenceNo": datetime.datetime.now().strftime("%Y%m%d%H%M%S"),
+                                                          "channelno": self.channel_id.zfill(3),
+                                                          "mode": "prod",
+                                                          "deviceId": self.device_id,
+                                                          "deviceType": "5",
+                                                          "format": "HLS"}))
+        data = self.session.http.json(api_res)
+        if data['responseCode'] == 'SUCCESS':
+            for stream_url in data.get("asset", {}).get("hls", {}).get("adaptive", []):
+                return HLSStream.parse_variant_playlist(self.session, stream_url)
+        else:
+            log.error("Failed to get stream URL: {0}".format(data['responseCode']))
+
+
+__plugin__ = ViuTV


Test patch:
diff --git a/tests/plugins/test_viutv.py b/tests/plugins/test_viutv.py
new file mode 100644
--- /dev/null
+++ b/tests/plugins/test_viutv.py
@@ -0,0 +1,23 @@
+import unittest
+
+import pytest
+
+from streamlink.plugins.viutv import ViuTV
+
+
+class TestPluginViuTV:
+    valid_urls = [
+        ("https://viu.tv/ch/99",),
+    ]
+    invalid_urls = [
+        ("https://viu.tv/encore/king-maker-ii/king-maker-iie4fuk-hei-baan-hui-ji-on-ji-sun-dang-cheung",),
+        ("https://www.youtube.com",)
+    ]
+
+    @pytest.mark.parametrize(["url"], valid_urls)
+    def test_can_handle_url(self, url):
+        assert ViuTV.can_handle_url(url), "url should be handled"
+
+    @pytest.mark.parametrize(["url"], invalid_urls)
+    def test_can_handle_url_negative(self, url):
+        assert not ViuTV.can_handle_url(url), "url should not be handled"