Repository: streamlink/streamlink (python)

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)


Pull request:
plugins.viutv: support for the viu.tv live stream
Fixes #2620 :)

Patch:
diff --git a/README.md b/README.md
index f595ac45..5d6a67d8 100644
--- a/README.md
+++ b/README.md
@@ -16,12 +16,27 @@ services when they are available for the content you consume and enjoy.
 
 # [Installation][streamlink-installation]
 
-Please refer to our documentation for different ways to install Streamlink:
+#### Installation via Python pip
+
+```bash
+sudo pip install streamlink
+```
+
+#### Manual installation via Python
+
+```bash
+git clone https://github.com/streamlink/streamlink
+cd streamlink
+sudo python setup.py install
+```
+
+#### Windows, MacOS, Linux and BSD specific installation methods
 
 - [Windows][streamlink-installation-windows]
-- [macOS][streamlink-installation-macos]
-- [Linux and BSD][streamlink-installation-linux-and-bsd]
-- [Source code][streamlink-installation-source]
+- [Windows portable version][streamlink-installation-windows-portable]
+- [MacOS][streamlink-installation-others]
+- [Linux and BSD][streamlink-installation-linux]
+
 
 # Features
 
@@ -86,10 +101,10 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
   [streamlink-plugins]: https://streamlink.github.io/plugin_matrix.html
   [streamlink-documentation]: https://streamlink.github.io/cli.html
   [streamlink-installation]: https://streamlink.github.io/install.html
-  [streamlink-installation-windows]: https://streamlink.github.io/install.html#windows
-  [streamlink-installation-macos]: https://streamlink.github.io/install.html#macos
-  [streamlink-installation-linux-and-bsd]: https://streamlink.github.io/install.html#linux-and-bsd
-  [streamlink-installation-source]: https://streamlink.github.io/install.html#source-code
+  [streamlink-installation-windows]: https://streamlink.github.io/install.html#windows-binaries
+  [streamlink-installation-windows-portable]: https://streamlink.github.io/install.html#windows-portable-version
+  [streamlink-installation-linux]: https://streamlink.github.io/install.html#linux-and-bsd-packages
+  [streamlink-installation-others]: https://streamlink.github.io/install.html#other-platforms
   [livestreamer]: https://github.com/chrippa/livestreamer
   [contributing]: https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md
   [changelog]: https://github.com/streamlink/streamlink/blob/master/CHANGELOG.rst
diff --git a/docs/plugin_matrix.rst b/docs/plugin_matrix.rst
index 7ffd8bd5..2363fc1e 100644
--- a/docs/plugin_matrix.rst
+++ b/docs/plugin_matrix.rst
@@ -261,6 +261,7 @@ viasat                  - juicyplay.dk       Yes   Yes   Streams may be geo-rest
 vidio                   vidio.com            Yes   Yes
 vimeo                   vimeo.com            Yes   Yes   Password-protected videos are not supported.
 vinhlongtv              thvli.vn             Yes   No    Streams are geo-restricted to Vietnam
+viutv                   viu.tv               Yes   No    Streams are geo-restricted to Hong Kong
 vk                      vk.com               Yes   Yes
 vrtbe                   vrt.be/vrtnu         Yes   Yes
 vtvgo                   vtvgo.vn             Yes   No
diff --git a/src/streamlink/plugins/viutv.py b/src/streamlink/plugins/viutv.py
new file mode 100644
index 00000000..24b6cbb9
--- /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
index 00000000..7b978808
--- /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"