Repository: streamlink/streamlink

Issue:
OPENREC plugin broken
The following command no longer works:

streamlink openrec.tv/live/ddsEf14ETTVFl  <-- random url


Even though the stream is live it will say "no playable streams found".
It seems like openrec has changed something. Could someone please take a look? 

Thank you


Discussion:
@jstrat231 This does not conform to the Plugin issue template

> Logs are always required for a plugin issue, use `-l debug` [(help)](https://streamlink.github.io/cli.html#cmdoption-l) and avoid using `--stream-url` as that will suppress log output.
What's that in scrub terms :D? I have no idea how to code any of this stuff
I'm not quite sure what these -1 debug options mean.
If I was to modify the link mention in the OP how would I do it with your recommendation?
`-l debug` will not give you any useful information. Openrec recently updated their site and that must've broken the plugin. There are no recent commits or pull requests addressing this issue.
@happapa `-l debug` will give useful information to us, that is why we ask for it. For example version of streamlink, operating system, various library versions, and perhaps some information about the plugin error. 

@jstrat231 run streamlink again but add `-l debug` to the command: eg. `streamlink -l debug http://... best`.
@beardypig  Seem to be getting the following error:
https://imgur.com/a/dgF7cX2

That streamer is currently live but no whatever what I type, it keeps saying "no playable streams found"/

OpenRec recently made big changes to their website and player so I wouldn't be surprised if it somehow broke the previous plugin. Everything was working fine before the website changes.


I have the latest version of streamlink installed. Windows 7 Ultimate 64 bit.
@jstrat231 
It's not `-1` (one), it's `-l` (lowercase L), the shorthand parameter for `--loglevel`
https://streamlink.github.io/cli.html#cmdoption-l
And don't use images to show log output.
My bad.

Just tried this livestream of PUBG with the -l and got the same no playable streams found:
https://www.openrec.tv/live/Ved7VSqUVVC

Doesn't seem to provide any additional info. Just says "searching the page for live stream URLS" followed by no playable streams found.

@jstrat231 it may not look like it provides any useful information, but it does for us. So please include the logs :)
```
[root@localhost ~]$ streamlink -l debug  https://www.openrec.tv/live/4jbzAvK5Or8 best
[cli][info] Found matching plugin openrectv for URL https://www.openrec.tv/live/4jbzAvK5Or8
[plugin.openrectv][debug] Searching the page for live stream URLs
error: No playable streams found on this URL: https://www.openrec.tv/live/4jbzAvK5Or8
```
I think they have just updated the website such that you need to login before you can watch the videos. There is no hlsvariant or m3u8 url in the page source.
@ydixon you’re using a pretty old version of streamlink, you should update it. However, the last change was February 2017 so I imagine it’s stillness broken. Always best to use the latest version though.
```
[root@localhost ~]$ streamlink -l debug https://www.openrec.tv/live/4jbzAvK5Or8 best
[cli][debug] OS:         Linux-4.14.18-300.fc27.x86_64-x86_64-with-fedora-27-Twenty_Seven
[cli][debug] Python:     2.7.14
[cli][debug] Streamlink: 0.14.2
[cli][debug] Requests(2.19.1), Socks(1.6.7), Websocket(0.48.0)
[cli][info] Found matching plugin openrectv for URL https://www.openrec.tv/live/4jbzAvK5Or8
[plugin.openrectv][debug] Searching the page for live stream URLs
error: No playable streams found on this URL: https://www.openrec.tv/live/4jbzAvK5Or8
```
My apologies, I was using my own script for this work before it failed. When it failed, i stumbled upon this repo, looking for solution. Anyways, I just updated streamlink, this is the log.
@ydixon thanks. I’ll take a look tomorrow and let you know. 

Resolving patch:
diff --git a/src/streamlink/plugins/openrectv.py b/src/streamlink/plugins/openrectv.py
--- a/src/streamlink/plugins/openrectv.py
+++ b/src/streamlink/plugins/openrectv.py
@@ -1,61 +1,154 @@
+import logging
 import re
 
-from streamlink.plugin import Plugin
+from streamlink.plugin import Plugin, PluginArgument, PluginArguments
 from streamlink.plugin.api import validate
 from streamlink.stream import HLSStream
 from streamlink.utils import parse_json
 
-_url_re = re.compile(r"https?://(?:www\.)?openrec.tv/(live|movie)/")
-_playlist_url_re = re.compile(r"data-(source)?file=\"(?P<url>[^\"]+)\"")
-_movie_data_re = re.compile(r'''<script type="application/ld\+json">(.*?)</script>''', re.DOTALL | re.M)
-_live_schema = validate.Schema(
-    validate.transform(_playlist_url_re.findall),
-    [
-        validate.union({
-            "isSource": validate.all(validate.get(0), validate.transform(lambda s: s == "source")),
-            "url": validate.all(validate.get(1), validate.url(scheme="http",
-                                                              path=validate.endswith(".m3u8")))
-        })
-    ]
-)
-_movie_schema = validate.Schema(
-    validate.transform(_movie_data_re.search),
-    validate.any(
-        None,
-        validate.all(
-            validate.get(1),
-            validate.transform(parse_json),
-            validate.get("contentUrl")
-        )
-    )
-)
+log = logging.getLogger(__name__)
 
 
 class OPENRECtv(Plugin):
+    _url_re = re.compile(r"https?://(?:www\.)?openrec.tv/(?:live|movie)/(?P<id>[^/]+)")
+    _stores_re = re.compile(r"window.stores\s*=\s*({.*?});", re.DOTALL | re.MULTILINE)
+    _config_re = re.compile(r"window.sharedConfig\s*=\s*({.*?});", re.DOTALL | re.MULTILINE)
+
+    api_url = "https://apiv5.openrec.tv/api/v5/movies/{id}/detail"
+    login_url = "https://www.openrec.tv/viewapp/v4/mobile/user/login"
+
+    _config_schema = validate.Schema({
+        "urls": {
+            "apiv5Authorized": validate.url()
+        }
+    })
+    _stores_schema = validate.Schema({
+        "moviePageStore": {
+            "movieStore": {
+                "id": validate.text,
+                "title": validate.text,
+                "media": {
+                    "url": validate.any(None, '', validate.url())
+                }
+            }
+        }
+    }, validate.get("moviePageStore"), validate.get("movieStore"))
+
+    _detail_schema = validate.Schema({
+        validate.optional("error_message"): validate.text,
+        "status": int,
+        validate.optional("data"): {
+            "type": validate.text,
+            "items": [{
+                "media": {
+                    "url": validate.any(None, validate.url()),
+                    "url_dvr": validate.any(None, validate.url())
+                }
+            }]
+        }
+    })
+
+    _login_schema = validate.Schema({
+        validate.optional("error_message"): validate.text,
+        "status": int,
+        validate.optional("data"): object
+    })
+
+    arguments = PluginArguments(
+        PluginArgument(
+            "email",
+            requires=["password"],
+            metavar="EMAIL",
+            help="""
+            The email associated with your openrectv account,
+            required to access any openrectv stream.
+            """),
+        PluginArgument(
+            "password",
+            sensitive=True,
+            metavar="PASSWORD",
+            help="""
+            An openrectv account password to use with --openrectv-email.
+            """)
+    )
+
+    def __init__(self, url):
+        super(OPENRECtv, self).__init__(url)
+        self._pdata = None
+        self._pres = None
+        self._pconfig = None
+
     @classmethod
     def can_handle_url(cls, url):
-        return _url_re.match(url) is not None
+        return cls._url_re.match(url) is not None
 
-    @classmethod
-    def stream_weight(cls, stream):
-        if stream == "source":
-            return 1080 + 1, "openrectv"
-        return Plugin.stream_weight(stream)
+    def login(self, email, password):
+        res = self.session.http.post(self.login_url, data={"mail": email, "password": password})
+        data = self.session.http.json(res, self._login_schema)
+        if data["status"] == 0:
+            log.debug("Logged in as {0}".format(data["data"]["user_name"]))
+        else:
+            log.error("Failed to login: {0}".format(data["error_message"]))
+        return data["status"] == 0
+
+    def _get_page(self):
+        if not self._pres:
+            self._pres = self.session.http.get(self.url)
+        return self._pres
+
+    def _get_movie_data(self):
+        pres = self._get_page()
+        match = self._stores_re.search(pres.text)
+        if match:
+            self._pdata = parse_json(match.group(1), schema=self._stores_schema)
+
+        return self._pdata
+
+    def _get_page_config(self):
+        pres = self._get_page()
+        match = self._config_re.search(pres.text)
+        if match:
+            self._pconfig = parse_json(match.group(1))
+
+        return self._pconfig
+
+    def _get_details(self, id):
+        config = self._get_page_config()
+        api_url = config["urls"]["apiv5Authorized"]
+        url = "{base}/movies/{id}/detail".format(base=api_url, id=id)
+        res = self.session.http.get(url, headers={
+            "access-token": self.session.http.cookies.get("access_token"),
+            "uuid": self.session.http.cookies.get("uuid")
+        })
+        data = self.session.http.json(res, schema=self._detail_schema)
+
+        if data["status"] == 0:
+            log.debug("Got valid detail response")
+            return data["data"]
+        else:
+            log.error("Failed to get video stream: {0}".format(data["error_message"]))
+
+    def get_title(self):
+        mdata = self._get_movie_data()
+        if mdata:
+            return mdata["title"]
 
     def _get_streams(self):
-        stype = _url_re.match(self.url).group(1)
-        if stype.lower() == "live":
-            self.logger.debug("Searching the page for live stream URLs")
-            playlists = self.session.http.get(self.url, schema=_live_schema)
-            for playlist in playlists:
-                for q, s in HLSStream.parse_variant_playlist(self.session, playlist["url"]).items():
-                    yield "source" if playlist["isSource"] else q, s
-        elif stype.lower() == "movie":
-            self.logger.debug("Searching the page for VOD stream URLs")
-            playlist = self.session.http.get(self.url, schema=_movie_schema)
-            if playlist:
-                for s in HLSStream.parse_variant_playlist(self.session, playlist).items():
+        mdata = self._get_movie_data()
+        if mdata:
+            log.debug("Found video: {0} ({1})".format(mdata["title"], mdata["id"]))
+            if mdata["media"]["url"]:
+                for s in HLSStream.parse_variant_playlist(self.session, mdata["media"]["url"]).items():
                     yield s
+            elif self.get_option("email") and self.get_option("password"):
+                if self.login(self.get_option("email"), self.get_option("password")):
+                    details = self._get_details(mdata["id"])
+                    if details:
+                        for item in details["items"]:
+                            for s in HLSStream.parse_variant_playlist(self.session, item["media"]["url"]).items():
+                                yield s
+            else:
+                log.error("You must login to access this stream")
 
 
 __plugin__ = OPENRECtv


Test patch: