Repository: streamlink/streamlink

Issue:
Plugin for Niconico Live doesn't work.
<!--
Thanks for reporting a plugin issue!
USE THE TEMPLATE. Otherwise your plugin issue may be rejected.

First, see the contribution guidelines:
https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md#contributing-to-streamlink

Also check the list of open and closed plugin issues:
https://github.com/streamlink/streamlink/issues?q=is%3Aissue+label%3A%22plugin+issue%22

Please see the text preview to avoid unnecessary formatting errors.
-->


## Plugin Issue

<!-- Replace [ ] with [x] in order to check the box -->
- [x] This is a plugin issue and I have read the contribution guidelines.


### Description

<!-- Explain the plugin issue as thoroughly as you can. -->
NicoNico Live Broadcast will not start playing and will time out.

### Reproduction steps / Explicit stream URLs to test

<!-- How can we reproduce this? Please note the exact steps below using the list format supplied. If you need more steps please add them. -->

1. install streamlink 1.4.1
2. Play any Niconico live broadcast 
    ex `$ streamlink live2.nicovideo.jp/watch/lv326495242 best`
3. Time out
　`[cli][info] Found matching plugin nicolive for URL live2.nicovideo.jp/watch/lv326495242`
    `[plugin.nicolive][error] Waiting for permit timed out.`




### Log output

<!--
TEXT LOG OUTPUT IS REQUIRED for a plugin issue!
Use the `--loglevel debug` parameter and avoid using parameters which suppress log output.
https://streamlink.github.io/cli.html#cmdoption-l

Make sure to **remove usernames and passwords**
You can copy the output to https://gist.github.com/ or paste it below.
-->

```
$ streamlink live2.nicovideo.jp/watch/lv326495242 --loglevel debug
[plugin.nicolive][debug] Restored cookies: nicolivehistory, nicosid
[cli][debug] OS:         macOS 10.15.5
[cli][debug] Python:     3.8.3
[cli][debug] Streamlink: 1.4.1
[cli][debug] Requests(2.23.0), Socks(1.7.1), Websocket(0.57.0)
[cli][info] Found matching plugin nicolive for URL live2.nicovideo.jp/watch/lv326495242
[plugin.nicolive][debug] Getting video page: http://live2.nicovideo.jp/watch/lv326495242
[plugin.nicolive][debug] Video page response code: 200
[plugin.nicolive][debug] Got wss_api_url: wss://a.live2.nicovideo.jp/wsapi/v2/watch/3645917628179?audience_token=29570850150482_3645917628179_1592250299_169bab4af6e54066e9fe092cfabf186910e48bc3
[plugin.nicolive][debug] Got broadcast_id:
[plugin.nicolive][debug] Connecting: wss://a.live2.nicovideo.jp/wsapi/v2/watch/3645917628179?audience_token=29570850150482_3645917628179_1592250299_169bab4af6e54066e9fe092cfabf186910e48bc3
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Sending: {"type": "watch", "body": {"command": "playerversion", "params": ["leo"]}}
[plugin.nicolive][debug] Sending: {"type": "watch", "body": {"command": "getpermit", "requirement": {"broadcastId": "", "route": "", "stream": {"protocol": "hls", "requireNewStream": true, "priorStreamQuality": "abr", "isLowLatency": true, "isChasePlay": false}, "room": {"isCommentable": true, "protocol": "webSocket"}}}}
[plugin.nicolive][debug] Received: {"type":"error","data":{"code":"INVALID_MESSAGE"}}
[plugin.nicolive][debug] Received: {"type":"error","data":{"code":"INVALID_MESSAGE"}}
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Received: {"type":"ping"}
[plugin.nicolive][debug] Sending: {"type": "pong", "body": {}}
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Received: {"type":"serverTime","data":{"currentMs":"2020-06-15T04:45:18.267+09:00"}}
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Received: {"type":"ping"}
[plugin.nicolive][debug] Sending: {"type": "pong", "body": {}}
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Received: {"type":"statistics","data":{"viewers":41753,"comments":16267,"adPoints":65000}}
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Received: {"type":"ping"}
[plugin.nicolive][debug] Sending: {"type": "pong", "body": {}}
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][error] Waiting for permit timed out.
error: No playable streams found on this URL: live2.nicovideo.jp/watch/lv326495242
```


### Additional comments, screenshots, etc.
There are reports that there was a change in the API specification in late May, but we don't know the details.



[Love Streamlink? Please consider supporting our collective. Thanks!](https://opencollective.com/streamlink/donate)



Discussion:
Hey guys,

I guess I resolved the new wss api format issue.

1. There is new parameter in wss_api_url, which is frontend_id. I leave the snippet here and example of the new wss_api_url.
```
# frontend_id from webpage
self.frontend_id = extract_text(resp.text, "&quot;frontendId&quot;:", ",&quot;")

# wss_api_url
wss://a.live2.nicovideo.jp/wsapi/v2/watch/lv326495242/timeshift?audience_token=lv326495242_anonymous-user-30352534264954_1592752486_dc0f7e344c038d2ce3e2c627bc095d72fb4a827c&frontend_id=12
```

2. There is new initial message I found as below:

```
{"type":"startWatching","data":{"stream":{"quality":"high","protocol":"hls","latency":"high","chasePlay":false},"room":{"protocol":"webSocket","commentable":true},"reconnect":false}}
```

3. The m3u8 URI is saved in type "stream" message as following below:
```
Return: 
RECEIVED: {"type":"stream","data":{"uri":"https://pa023c07d94.dmc.nico/hlsarchive/ht2_nicolive/nicolive-hamster-lv326495242_main_2c0612eb8f7799ee0bd6a083871f8c80fa5e4ddb94d870ffd747fb7d74800144/master.m3u8?ht2_nicolive=anonymous-user-30352534264954.gcsicpdhrl_qc8d18_1xah805rtldwa","syncUri":"https://pa023c07d94.dmc.nico/hlsarchive/ht2_nicolive/nicolive-hamster-lv326495242_main_2c0612eb8f7799ee0bd6a083871f8c80fa5e4ddb94d870ffd747fb7d74800144/stream_sync.json?ht2_nicolive=anonymous-user-30352534264954.gcsicpdhrl_qc8d18_1xah805rtldwa","quality":"high","availableQualities":["abr","super_high","high","normal","low","super_low"],"protocol":"hls"}}

```

I also found other repository got the same problem. Here is the reference link.
https://github.com/himananiito/livedl/issues/49

Send PR soon~
@wiresp33d Thank you for your work.
I tested it in my environment. It works fine.

Let's keep the issue open until the PR has been reviewed and merged in.
Furthermore, there seems to have been a change in the API.

```
$ streamlink live2.nicovideo.jp/watch/lv326699169 --loglevel debug
[plugin.nicolive][debug] Restored cookies: nicolivehistory, nicosid
[cli][debug] OS:         macOS 10.15.5
[cli][debug] Python:     3.8.3
[cli][debug] Streamlink: 1.4.1
[cli][debug] Requests(2.23.0), Socks(1.7.1), Websocket(0.57.0)
[cli][info] Found matching plugin nicolive for URL live2.nicovideo.jp/watch/lv326699169
[plugin.nicolive][debug] Getting video page: http://live2.nicovideo.jp/watch/lv326699169
[plugin.nicolive][debug] Video page response code: 200
[plugin.nicolive][debug] Got wss_api_url: wss://a.live2.nicovideo.jp/wsapi/v2/watch/4925817882387?audience_token=30541512903109_4925817882387_1593489232_4904ee3bb54aecd7efce343e19f62dfa92fc2487
[plugin.nicolive][debug] Got broadcast_id:
[plugin.nicolive][debug] Connecting: wss://a.live2.nicovideo.jp/wsapi/v2/watch/4925817882387?audience_token=30541512903109_4925817882387_1593489232_4904ee3bb54aecd7efce343e19f62dfa92fc2487
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Sending: {"type": "watch", "body": {"command": "playerversion", "params": ["leo"]}}
[plugin.nicolive][debug] Sending: {"type": "watch", "body": {"command": "getpermit", "requirement": {"broadcastId": "", "route": "", "stream": {"protocol": "hls", "requireNewStream": true, "priorStreamQuality": "abr", "isLowLatency": true, "isChasePlay": false}, "room": {"isCommentable": true, "protocol": "webSocket"}}}}
[plugin.nicolive][debug] Received: {"type":"error","data":{"code":"INVALID_MESSAGE"}}
[plugin.nicolive][debug] Received: {"type":"error","data":{"code":"INVALID_MESSAGE"}}
[plugin.nicolive][debug] Waiting for permit...
.
.
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Received: {"type":"ping"}
[plugin.nicolive][debug] Sending: {"type": "pong", "body": {}}
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][debug] Received: {"type":"serverTime","data":{"currentMs":"2020-06-29T12:54:32.944+09:00"}}
[plugin.nicolive][debug] Waiting for permit...
.
.
[plugin.nicolive][debug] Waiting for permit...
[plugin.nicolive][error] Waiting for permit timed out.
error: No playable streams found on this URL: live2.nicovideo.jp/watch/lv326699169
```

Supposedly, "broadcast_id" has been removed.


Resolving patch:
diff --git a/src/streamlink/plugins/nicolive.py b/src/streamlink/plugins/nicolive.py
--- a/src/streamlink/plugins/nicolive.py
+++ b/src/streamlink/plugins/nicolive.py
@@ -108,18 +108,13 @@ def get_wss_api_url(self):
         try:
             self.wss_api_url = extract_text(
                 resp.text, "&quot;webSocketUrl&quot;:&quot;", "&quot;")
+            if not self.wss_api_url:
+                return False
         except Exception as e:
             _log.debug(e)
             _log.debug("Failed to extract wss api url")
             return False
 
-        try:
-            self.broadcast_id = extract_text(
-                resp.text, "&quot;broadcastId&quot;:&quot;", "&quot;")
-        except Exception as e:
-            _log.debug(e)
-            _log.warning("Failed to extract broadcast id")
-
         try:
             self.frontend_id = extract_text(
                 resp.text, "&quot;frontendId&quot;:", ",&quot;")
@@ -132,7 +127,6 @@ def get_wss_api_url(self):
         _log.debug("Video page response code: {0}".format(resp.status_code))
         _log.trace(u"Video page response body: {0}".format(resp.text))
         _log.debug("Got wss_api_url: {0}".format(self.wss_api_url))
-        _log.debug("Got broadcast_id: {0}".format(self.broadcast_id))
         _log.debug("Got frontend_id: {0}".format(self.frontend_id))
 
         return self.wss_api_url.startswith("wss://")


Test patch: