Repository: streamlink/streamlink

Issue:
Vaughn Live returns invalid links
### Checklist

- [x] This is a bug report.
- [ ] This is a feature request.
- [ ] This is a plugin (improvement) request.
- [X] I have read the contribution guidelines.

### Description

...

### Expected / Actual behavior

...

### Reproduction steps / Explicit stream URLs to test

1. streamlink https://vaughnlive.tv/everything80s --stream-url --best >>my.m3u
2. streamlink https://vaughnlive.tv/franky_eyes2 --stream-url --best >>my.m3u


the above result in unplayable streams:

output from file:

rtmp://192.240.105.171:1935/live? live=1 pageUrl=https://vaughnlive.tv/everything80s playpath=live_everything80s

rtmp://192.240.105.171:1935/live? live=1 pageUrl=https://vaughnlive.tv/franky_eyes2 playpath=live_franky_eyes2

### Logs
sorry no log , and no idea where to get it.


### Comments, screenshots, etc.

...


[Love Streamlink? Please consider supporting our collective. Thanks!](https://opencollective.com/streamlink/donate)



Discussion:
Vaughnlive changed the port number for their rtmp servers the other week, I think this was fixed in 0.13.0. Make sure you are using the latest version of streamlink. 
I've been having problems with it too, even when using port 2935.

```
[console][error] Try 1/1: Could not open stream <RTMPStream({'pageUrl': 'https://vaughnlive.tv/wiriamu', 'flv': '-', 'live': True, 'playpath': 'live_wiriamu', 'rtmp': 'rtmp://192.240.105.171:2935/live?'}, redirect=False> (Failed to read data from stream: Read timeout)
error: Could not open stream <RTMPStream({'pageUrl': 'https://vaughnlive.tv/wiriamu', 'flv': '-', 'live': True, 'playpath': 'live_wiriamu', 'rtmp': 'rtmp://192.240.105.171:2935/live?'}, redirect=False>, tried 1 times, exiting
[console][info] Closing currently open stream...
```


Resolving patch:
diff --git a/src/streamlink/plugins/vaughnlive.py b/src/streamlink/plugins/vaughnlive.py
--- a/src/streamlink/plugins/vaughnlive.py
+++ b/src/streamlink/plugins/vaughnlive.py
@@ -1,14 +1,9 @@
-import itertools
-import logging
-import random
 import re
-import ssl
-
-import websocket
 
+from streamlink import StreamError
 from streamlink.plugin import Plugin
 from streamlink.plugin.api import useragents
-from streamlink.stream import RTMPStream
+from streamlink.stream import HTTPStream, StreamIOIterWrapper, StreamIOThreadWrapper
 
 _url_re = re.compile(r"""
     http(s)?://(\w+\.)?
@@ -18,92 +13,62 @@
 """, re.VERBOSE)
 
 
-class VLWebSocket(websocket.WebSocket):
-    def __init__(self, **_):
-        self.session = _.pop("session")
-        self.logger = logging.getLogger("streamlink.plugins.vaughnlive.websocket")
-        sslopt = _.pop("sslopt", {})
-        sslopt["cert_reqs"] = ssl.CERT_NONE
-        super(VLWebSocket, self).__init__(sslopt=sslopt, **_)
+class VaughnStream(HTTPStream):
+    def open(self):
+        method = self.args.get("method", "GET")
+        timeout = self.session.options.get("http-timeout")
+        res = self.session.http.request(method=method,
+                                        stream=True,
+                                        exception=StreamError,
+                                        timeout=timeout,
+                                        **self.args)
+
+        def fix_stream():
+            """
+            Replace the first 3 bytes of the stream with b'FLV'
+            :return: stream iterator
+            """
+            content_iter = res.iter_content(8192)
+            data = next(content_iter)
+            yield b'FLV' + data[3:]
+            for chunk in content_iter:
+                yield chunk
 
-    def send(self, payload, opcode=websocket.ABNF.OPCODE_TEXT):
-        self.logger.debug("Sending message: {0}", payload)
-        return super(VLWebSocket, self).send(payload + "\n\x00", opcode)
+        fd = StreamIOIterWrapper(fix_stream())
+        if self.buffered:
+            fd = StreamIOThreadWrapper(self.session, fd, timeout=timeout)
 
-    def recv(self):
-        d = super(VLWebSocket, self).recv().replace("\n", "").replace("\x00", "")
-        return d.split(" ", 1)
+        return fd
 
 
 class VaughnLive(Plugin):
-    servers = ["wss://sapi-ws-{0}x{1:02}.vaughnlive.tv".format(x, y) for x, y in itertools.product(range(1, 3),
-                                                                                                   range(1, 6))]
-    origin = "https://vaughnlive.tv"
-    rtmp_server_map = {
-        "594140c69edad": "192.240.105.171:2935",
-        "585c4cab1bef1": "192.240.105.171:2935",
-        "5940d648b3929": "192.240.105.171:2935",
-        "5941854b39bc4": "192.240.105.171:2935"
-    }
-    name_remap = {"#vl": "live", "#btv": "btv", "#pt": "pt", "#igb": "instagib", "#vtv": "vtv"}
-    domain_map = {"vaughnlive": "#vl", "breakers": "#btv", "instagib": "#igb", "vapers": "#vtv", "pearltime": "#pt"}
+    domain_map = {"vaughnlive": "live", "breakers": "btv", "instagib": "igb", "vapers": "vtv", "pearltime": "pt"}
+    stream_url = "https://mp4.vaughnsoft.net/live"
 
     @classmethod
     def can_handle_url(cls, url):
         return _url_re.match(url)
 
-    def api_url(self):
-        return random.choice(self.servers)
-
-    def parse_ack(self, action, message):
-        if action.endswith("3"):
-            channel, _, viewers, token, server, choked, is_live, chls, trns, ingest = message.split(";")
-            is_live = is_live == "1"
-            viewers = int(viewers)
-            self.logger.debug("Viewers: {0}, isLive={1}", viewers, is_live)
-            domain, channel = channel.split("-", 1)
-            return is_live, server, domain, channel, token, ingest
-        else:
-            self.logger.error("Unhandled action format: {0}", action)
-
-    def _get_info(self, stream_name):
-        server = self.api_url()
-        self.logger.debug("Connecting to API: {0}", server)
-        ws = websocket.create_connection(server,
-                                         header=["User-Agent: {0}".format(useragents.CHROME)],
-                                         origin=self.origin,
-                                         class_=VLWebSocket,
-                                         session=self.session)
-        ws.send("MVN LOAD3 {0}".format(stream_name))
-        action, message = ws.recv()
-        return self.parse_ack(action, message)
-
-    def _get_rtmp_streams(self, server, domain, channel, token):
-        rtmp_server = self.rtmp_server_map.get(server, server)
-
-        url = "rtmp://{0}/live?{1}".format(rtmp_server, token)
-
-        yield "live", RTMPStream(self.session, params={
-            "rtmp": url,
-            "pageUrl": self.url,
-            "playpath": "{0}_{1}".format(self.name_remap.get(domain, "live"), channel),
-            "live": True
-        })
-
     def _get_streams(self):
+        self.session.http.headers = {
+            "Origin": "https://vaughnlive.tv",
+            "Referer": self.url,
+            "User-Agent": useragents.FIREFOX
+        }
         m = _url_re.match(self.url)
         if m:
-            stream_name = "{0}-{1}".format(self.domain_map[(m.group("domain").lower())],
+            stream_name = "{0}_{1}".format(self.domain_map[(m.group("domain").lower())],
                                            m.group("channel"))
-
-            is_live, server, domain, channel, token, ingest = self._get_info(stream_name)
-
-            if not is_live:
-                self.logger.info("Stream is currently off air")
-            else:
-                self.logger.info("Stream powered by VaughnSoft - remember to support them.")
-                for s in self._get_rtmp_streams(server, domain, channel, token):
-                    yield s
+            self.logger.info("Stream powered by VaughnSoft - remember to support them.")
+            stream = VaughnStream(self.session,
+                                  self.stream_url,
+                                  params=dict(app="live",
+                                              stream=stream_name,
+                                              port=2935))
+            stream_url = stream.to_url()
+            res = self.session.http.head(stream_url, raise_for_status=False)
+            print(res.status_code)
+            yield "live", stream
 
 
 __plugin__ = VaughnLive


Test patch: