Repository: streamlink/streamlink

Issue:
showroom-live Unable to open URL
----

### 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

Trying to use streamlink to open a showroom-live stream seems to fail due to 404, possibly due to site structure changes.

### Expected / Actual behavior

Running `streamlink <room url>`, in the case that the given room is live, should probably open a stream or ask for a quality setting, but reports 404 instead.

### Reproduction steps / Explicit stream URLs to test

1. Find a live room from here: https://www.showroom-live.com/onlive
2. Enter `streamlink <room url>`

### Environment details

Operating system and version: Win7x64
Streamlink and Python version: Streamlink 0.8.1, Prepackaged Python

### Comments, logs, screenshots, etc.

`streamlink https://www.showroom-live.com/misaki0719`

`[cli][info] Found matching plugin showroom for URL https://www.showroom-live.com/misaki0719`
`error: Unable to open URL: https://www.showroom-live.com/room/get_live_data?room_id=144710`
`(404 Client Error: Not Found for url: https://www.showroom-live.com/room/get_live_data?room_id=144710)`


Discussion:
This is due to an API change. The API location for room information is https://www.showroom-live.com/api/live/live_info?room_id={room_id} now. I don't know Python and rtmp well enough to fix this problem since the variable names have also changed.
Example of new API output:
{"age_verification_status":0,"video_type":0,"enquete_gift_num":0,"is_enquete":false,"bcsvr_port":8080,"live_type":0,"is_free_gift_only":false,"bcsvr_host":"online.showroom-live.com","live_id":0,"is_enquete_result":false,"live_status":1,"room_id":115516,"bcsvr_key":"","background_image_url":null}
@wlerin Sorry to bother you. It seems you are the original author of this plugin so I wanted to let you know.
Ah, I forgot to update this. The `live_info` endpoint does not contain the necessary information, and I haven't found any other API endpoint that does. However, the html of the room page (e.g. https://www.showroom-live.com/w1017 ) does, at least when the stream is live. I can submit a patch to get it from there instead, but really we need to find the new endpoint, if one exists. (The bcsvr information there is for a websocket connection, it's possible the stream info is pushed through that.)

@Antares31415 I don't believe the variable names have changed, the ones we need just aren't there anymore.
I have updated the showroom plugin on my fork here:
https://github.com/wlerin/streamlink/blob/showroom/src/streamlink/plugins/showroom.py

Basically all functionality besides getting a single streamable url is gone. I'll remove most of the comments before submitting a PR, but I need to see if I can figure out how to restore some of the other features first.
there is an api for hls and rtmp urls @wlerin 
`https://www.showroom-live.com/api/live/streaming_url?room_id={room_id}`

https://gist.github.com/back-to/a051c3e7bb450d323e4d1f07c1b1a6d6

here is a diff file, just the `is_live` is not fixed there

```
error: Unable to validate JSON: Key 'streaming_url_list' not found in {}
```
for offline streams

and some other cleanups could be done.

feel free to use it, if you want.



Resolving patch:
diff --git a/src/streamlink/plugins/showroom.py b/src/streamlink/plugins/showroom.py
--- a/src/streamlink/plugins/showroom.py
+++ b/src/streamlink/plugins/showroom.py
@@ -3,16 +3,16 @@
 
 from streamlink.plugin import Plugin
 from streamlink.plugin.api import http, validate, useragents
-from streamlink.stream import RTMPStream
+from streamlink.stream import HLSStream, RTMPStream
 
 _url_re = re.compile(r'''^https?://
-        (?:\w*.)?
-        showroom-live.com/
-        (?:
-            (?P<room_title>[\w-]+$)
-            |
-            room/profile\?room_id=(?P<room_id>\d+)$
-        )
+    (?:\w*.)?
+    showroom-live.com/
+    (?:
+        (?P<room_title>[\w-]+$)
+        |
+        room/profile\?room_id=(?P<room_id>\d+)$
+    )
 ''', re.VERBOSE)
 
 _room_id_re = re.compile(r'"roomId":(?P<room_id>\d+),')
@@ -20,30 +20,35 @@
 _room_id_lookup_failure_log = 'Failed to find room_id for {0} using {1} regex'
 
 _api_status_url = 'https://www.showroom-live.com/room/is_live?room_id={room_id}'
-_api_data_url = 'https://www.showroom-live.com/room/get_live_data?room_id={room_id}'
+_api_stream_url = 'https://www.showroom-live.com/api/live/streaming_url?room_id={room_id}'
 
-_api_data_schema = validate.Schema(
-    {
-        "streaming_url_list_rtmp": validate.all([
+_api_stream_schema = validate.Schema(
+    validate.any({
+        "streaming_url_list": validate.all([
             {
                 "url": validate.text,
-                "stream_name": validate.text,
+                validate.optional("stream_name"): validate.text,
                 "id": int,
                 "label": validate.text,
-                "is_default": int
+                "is_default": int,
+                "type": validate.text,
+                "quality": int,
             }
-        ]),
-        "is_live": int,
-        "room": {
-            "room_url_key": validate.text
-        },
-        "telop": validate.any(None, validate.text)
-    }
+        ])
+    },
+    {}
+    )
 )
+
+# the "low latency" streams are rtmp, the others are hls
 _rtmp_quality_lookup = {
     "オリジナル画質": "high",
+    "オリジナル画質(低遅延)": "high",
+    "original spec(low latency)": "high",
     "original spec": "high",
     "低画質": "low",
+    "低画質(低遅延)": "low",
+    "low spec(low latency)": "low",
     "low spec": "low"
 }
 # changes here must also be updated in test_plugin_showroom
@@ -95,15 +100,7 @@ def __init__(self, url):
             'User-Agent': useragents.FIREFOX
         }
         self._room_id = None
-        self._info = None
-        self._title = None
-
-    @property
-    def telop(self):
-        if self._info:
-            return self._info['telop']
-        else:
-            return ""
+        self._stream_urls = None
 
     @property
     def room_id(self):
@@ -111,10 +108,6 @@ def room_id(self):
             self._room_id = self._get_room_id()
         return self._room_id
 
-    def _get_stream_info(self, room_id):
-        res = http.get(_api_data_url.format(room_id=room_id), headers=self._headers)
-        return http.json(res, schema=_api_data_schema)
-
     def _get_room_id(self):
         """
         Locates unique identifier ("room_id") for the room.
@@ -137,16 +130,9 @@ def _get_room_id(self):
                     return  # Raise exception?
             return match.group('room_id')
 
-    def _get_title(self):
-        if self._title is None:
-            if 'profile?room_id=' not in self.url:
-                self._title = self.url.rsplit('/', 1)[-1]
-            else:
-                if self._info is None:
-                    # TODO: avoid this
-                    self._info = self._get_stream_info(self.room_id)
-                self._title = self._info.get('room').get('room_url_key')
-        return self._title
+    def _get_stream_info(self, room_id):
+        res = http.get(_api_stream_url.format(room_id=room_id), headers=self._headers)
+        return http.json(res, schema=_api_stream_schema)
 
     def _get_rtmp_stream(self, stream_info):
         rtmp_url = '/'.join((stream_info['url'], stream_info['stream_name']))
@@ -156,14 +142,15 @@ def _get_rtmp_stream(self, stream_info):
         return quality, RTMPStream(self.session, params=params)
 
     def _get_streams(self):
-        self._info = self._get_stream_info(self.room_id)
-        if not self._info or not self._info['is_live']:
+        info = self._get_stream_info(self.room_id)
+        if not info:
             return
 
-        self.logger.debug("Getting streams for {0}".format(self._get_title()))
-
-        for stream_info in self._info.get("streaming_url_list_rtmp", []):
-            yield self._get_rtmp_stream(stream_info)
-
+        for stream_info in info.get("streaming_url_list", []):
+            if stream_info["type"] == "rtmp":
+                yield self._get_rtmp_stream(stream_info)
+            elif stream_info["type"] == "hls":
+                for s in HLSStream.parse_variant_playlist(self.session, stream_info["url"]).items():
+                    yield s
 
 __plugin__ = Showroom


Test patch: