Repository: streamlink/streamlink

Issue:
Openrec.tv: cannot get stream from subscription live stream
### Checklist

- [X] This is a plugin issue and not a different kind of issue
- [X] [I have read the contribution guidelines](https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md#contributing-to-streamlink)
- [X] [I have checked the list of open and recently closed plugin issues](https://github.com/streamlink/streamlink/issues?q=is%3Aissue+label%3A%22plugin+issue%22)
- [X] [I have checked the commit log of the master branch](https://github.com/streamlink/streamlink/commits/master)

### Streamlink version

Latest stable release

### Description

Cannot access to subscription-only live stream.

I tried the channel that I'm subscribed to (premium), or the ones that currently is on free-trial (so everyone can watch), neither worked.

### Debug log

```text
D:\>streamlink --openrectv-email="****" --openrectv-password="****" --loglevel debug --url https:/
/www.openrec.tv/live/lv81d9lm789
[cli][debug] OS:         Windows 7
[cli][debug] Python:     3.8.7
[cli][debug] Streamlink: 2.4.0
[cli][debug] Requests(2.26.0), Socks(1.7.1), Websocket(0.58.0)
[cli][debug] Arguments:
[cli][debug]  url=https://www.openrec.tv/live/lv81d9lm789
[cli][debug]  --loglevel=debug
[cli][debug]  --player="D:\Program Files\SMPlayer\mpv\mpv.exe" --cache=yes --demuxer-max-bytes=10000KiB
[cli][debug]  --url=https://www.openrec.tv/live/lv81d9lm789
[cli][debug]  --rtmpdump=C:\Program Files (x86)\Streamlink\rtmpdump\rtmpdump.exe
[cli][debug]  --ffmpeg-ffmpeg=D:\Program Files\!cli\ffmpeg.exe
[cli][debug]  --openrectv-email=****
[cli][debug]  --openrectv-password=********
[cli][info] Found matching plugin openrectv for URL https://www.openrec.tv/live/lv81d9lm789
[plugins.openrectv][debug] Logged in as ****
[plugins.openrectv][debug] Got valid detail response
[plugins.openrectv][debug] Found video: きどまだSeason2　＃16 (lv81d9lm789)
error: No playable streams found on this URL: https://www.openrec.tv/live/lv81d9lm789
```



Discussion:
Hello @fireattack 
Probably I can solve your issue, but I don't subscribe any paid channels, so I can't do the experiment.
So, I would like to know the response of `https://apiv5.openrec.tv/api/v5/movies/lv81d9lm789/detail` when you access `https://www.openrec.tv/live/lv81d9lm789` using DevTools.
In particular, I would like to know `data.items[0].media`.
Please let me know **only whether each value is null or not** because it contains sensitive information.
```json
{
    "status": 0,
    "data": {
        "type": "movie_detail",
        "items": [
            {
                "media": {
                    "url": "XXX/playlist.m3u8",
                    "url_source": null,
                    "url_public": "XXX/public.m3u8",
                    "url_low_latency": null,
                    "url_ull": null,
                    "url_dvr": null,
                    "url_trailer": null,
                    "url_audio": "XXX/aac.m3u8",
                    "url_dvr_audio": null
                },
                "subs_trial_media": {
                    "url": null,
                    "url_low_latency": null,
                    "url_ull": null,
                    "url_dvr": null,
                    "url_audio": null,
                    "url_dvr_audio": null
                },
```

Keep in mind this live is already finished so it's a VOD now. Can't say if it's API return value structure stays the same while live, but it looks like so. 

From the look of it, we probably should also try `subs_trial_media` part if the first one fails, so you can still watch trial (which is available for all the users during the live). 

Thanks for your time!

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
@@ -15,7 +15,8 @@ class OPENRECtv(Plugin):
     _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://public.openrec.tv/external/api/v5/movies/{id}"
+    movie_info_url = "https://public.openrec.tv/external/api/v5/movies/{id}"
+    subscription_info_url = "https://apiv5.openrec.tv/api/v5/movies/{id}/detail"
     login_url = "https://www.openrec.tv/viewapp/v4/mobile/user/login"
 
     _info_schema = validate.Schema({
@@ -23,6 +24,7 @@ class OPENRECtv(Plugin):
         validate.optional("title"): validate.text,
         validate.optional("movie_type"): validate.text,
         validate.optional("onair_status"): validate.any(None, int),
+        validate.optional("public_type"): validate.text,
         validate.optional("media"): {
             "url": validate.any(None, validate.url()),
             "url_public": validate.any(None, validate.url()),
@@ -30,6 +32,17 @@ class OPENRECtv(Plugin):
         }
     })
 
+    _subscription_schema = validate.Schema({
+        validate.optional("status"): int,
+        validate.optional("data"): {
+            "items": [{
+                "media": {
+                    "url": validate.any(None, validate.url())
+                }
+            }]
+        }
+    })
+
     _login_schema = validate.Schema({
         validate.optional("error_message"): validate.text,
         "status": int,
@@ -68,7 +81,7 @@ def login(self, email, password):
         return data["status"] == 0
 
     def _get_movie_data(self):
-        url = self.api_url.format(id=self.video_id)
+        url = self.movie_info_url.format(id=self.video_id)
         res = self.session.http.get(url, headers={
             "access-token": self.session.http.cookies.get("access_token"),
             "uuid": self.session.http.cookies.get("uuid")
@@ -81,6 +94,20 @@ def _get_movie_data(self):
         else:
             log.error("Failed to get video stream: {0}".format(data["message"]))
 
+    def _get_subscription_movie_data(self):
+        url = self.subscription_info_url.format(id=self.video_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._subscription_schema)
+
+        if data["status"] == 0:
+            log.debug("Got valid subscription info")
+            return data
+        else:
+            log.error("Failed to get video stream: {0}".format(data["message"]))
+
     def get_author(self):
         mdata = self._get_movie_data()
         if mdata:
@@ -100,8 +127,12 @@ def _get_streams(self):
         if mdata:
             log.debug("Found video: {0} ({1})".format(mdata["title"], mdata["id"]))
             m3u8_file = None
+            # subscription
+            if mdata["public_type"] == "member":
+                subs_data = self._get_subscription_movie_data()
+                m3u8_file = subs_data["data"]["items"][0]["media"]["url"]
             # streaming
-            if mdata["onair_status"] == 1:
+            elif mdata["onair_status"] == 1:
                 m3u8_file = mdata["media"]["url_ull"]
             # archive
             elif mdata["onair_status"] == 2 and mdata["media"]["url_public"] is not None:


Test patch: