Repository: streamlink/streamlink

Issue:
[Plugin Request] Vlive.tv
<!--
Thanks for requesting a plugin!
USE THE TEMPLATE. Otherwise your plugin request may be rejected.

First, see the contribution guidelines and plugin request requirements:
https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md#contributing-to-streamlink

Plugin requests which fall into the categories we will not implement will be closed immediately.

Also check the list of open and closed plugin requests:
https://github.com/streamlink/streamlink/issues?q=is%3Aissue+label%3A%22plugin+request%22

Please see the text preview to avoid unnecessary formatting errors.
-->


## 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
This is a live streaming site.
This site was originally mainly for idols who want to interact with fans, but now it has grown more, many people have registered and broadcast a number of more diverse programs.
<!-- 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. -->


### Example stream URLs

<!-- Example URLs for streams are required. Plugin requests which do not have example URLs will be closed. -->

1. https://www.vlive.tv/ (Home page)
2. https://www.vlive.tv/upcoming (Upcoming broadcasts will be show here)


### Additional comments, screenshots, etc.
This site has no live streaming 24/7 so you can visit the homepage and look down, sometimes seeing some people broadcasting there (IMG: https://i.imgur.com/rHBMmli.jpg).

Or visit this page to see upcoming shows: https://www.vlive.tv/upcoming

Hope you can support this page, thanks so much.

[Love Streamlink? Please consider supporting our collective. Thanks!](https://opencollective.com/streamlink/donate)



Discussion:

Resolving patch:
diff --git a/src/streamlink/plugins/vlive.py b/src/streamlink/plugins/vlive.py
new file mode 100644
--- /dev/null
+++ b/src/streamlink/plugins/vlive.py
@@ -0,0 +1,55 @@
+import re
+import json
+
+from streamlink.plugin import Plugin, PluginError
+from streamlink.stream import HLSStream
+
+
+class Vlive(Plugin):
+    _url_re = re.compile(r"https?://(?:www.)vlive\.tv/video/(\d+)")
+    _video_status = re.compile(r'oVideoStatus = (.+)<', re.DOTALL)
+    _video_init_url = "https://www.vlive.tv/video/init/view"
+
+    @classmethod
+    def can_handle_url(cls, url):
+        return cls._url_re.match(url) is not None
+
+    @property
+    def video_id(self):
+        return self._url_re.match(self.url).group(1)
+
+    def _get_streams(self):
+        vinit_req = self.session.http.get(self._video_init_url,
+                                          params=dict(videoSeq=self.video_id),
+                                          headers=dict(referer=self.url))
+        if vinit_req.status_code != 200:
+            raise PluginError('Could not get video init page (HTTP Status {})'
+                              .format(vinit_req.status_code))
+
+        video_status_js = self._video_status.search(vinit_req.text)
+        if not video_status_js:
+            raise PluginError('Could not find video status information!')
+
+        video_status = json.loads(video_status_js.group(1))
+
+        if video_status['viewType'] == 'vod':
+            raise PluginError('VODs are not supported')
+
+        if 'liveStreamInfo' not in video_status:
+            raise PluginError('Stream is offline')
+
+        stream_info = json.loads(video_status['liveStreamInfo'])
+
+        streams = dict()
+        # All "resolutions" have a variant playlist with only one entry, so just combine them
+        for i in stream_info['resolutions']:
+            res_streams = HLSStream.parse_variant_playlist(self.session, i['cdnUrl'])
+            if len(res_streams.values()) > 1:
+                self.logger.warning('More than one stream in variant playlist, using first entry!')
+
+            streams[i['name']] = res_streams.popitem()[1]
+
+        return streams
+
+
+__plugin__ = Vlive


Test patch:
diff --git a/tests/plugins/test_vlive.py b/tests/plugins/test_vlive.py
new file mode 100644
--- /dev/null
+++ b/tests/plugins/test_vlive.py
@@ -0,0 +1,23 @@
+import unittest
+
+import pytest
+
+from streamlink.plugins.vlive import Vlive
+
+
+class TestPluginVlive:
+    valid_urls = [
+        ("https://www.vlive.tv/video/156824",),
+    ]
+    invalid_urls = [
+        ("https://www.vlive.tv/events/2019vheartbeat?lang=en",),
+        ("https://twitch.tv/",)
+    ]
+
+    @pytest.mark.parametrize(["url"], valid_urls)
+    def test_can_handle_url(self, url):
+        assert Vlive.can_handle_url(url), "url should be handled"
+
+    @pytest.mark.parametrize(["url"], invalid_urls)
+    def test_can_handle_url_negative(self, url):
+        assert not Vlive.can_handle_url(url), "url should not be handled"