Repository: streamlink/streamlink

Issue:
plugins.ustreamtv: Plug-in does not handle combined-embed video streams
### 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

Many videos on IBM's video streaming service (ustream.tv) are now recorded as chunked .m4v, which is not detected by the plug-in.  The source only seems to handle .mp4 streams.

Example URL: https://www.ustream.tv/combined-embed/11748282/video/132038074

### Debug log

```text
C:\Users\Chad\Desktop>streamlink -v https://www.ustream.tv/combined-embed/11748282/video/132038074
[cli][info] Found matching plugin ustreamtv for URL https://www.ustream.tv/combined-embed/11748282/video/132038074
error: No playable streams found on this URL: https://www.ustream.tv/combined-embed/11748282/video/132038074

Sample header:
https://uhsakamai-a.akamaihd.net/sjc/omega/vod/us1-37ed7ef2-e250-4085-b0e2-9f5bbf7d69e8/10584000/53926414920000/plain/rfc/4/chunk_0_a0e49b82a5.m4vh

Sample chunks:
https://uhsakamai-a.akamaihd.net/sjc/omega/vod/us1-37ed7ef2-e250-4085-b0e2-9f5bbf7d69e8/10584000/53926414920000/plain/rfc/4/chunk_229_d777c5fb22.m4v
https://uhsakamai-a.akamaihd.net/sjc/omega/vod/us1-37ed7ef2-e250-4085-b0e2-9f5bbf7d69e8/10584000/53926414920000/plain/rfc/2/chunk_230_1c8466259a.m4v
```



Discussion:
> are now recorded as chunked .m4v, which is not detected by the plug-in. The source only seems to handle .mp4 streams.

That's not true. The plugin does support initialization segments, as you can see in the `UStreamTVStreamWriter` class:
https://github.com/streamlink/streamlink/blob/2c0925335ebeb8dd5dda95dee79d6b39f36375c4/src/streamlink/plugins/ustreamtv.py#L340-L383

> error: No playable streams found on this URL: https://www.ustream.tv/combined-embed/11748282/video/132038074

The issue is the URL format which the plugin does not support. If you change it to the one it expects while keeping the channel ID and video ID, the stream works fine:

```
$ streamlink -l debug 'ustream.tv/channel/id/11748282/recorded/132038074' best
[cli][debug] OS:         Linux-5.19.2-1-git-x86_64-with-glibc2.36
[cli][debug] Python:     3.10.6
[cli][debug] Streamlink: 4.3.0+7.gb6166ad7
[cli][debug] Dependencies:
[cli][debug]  isodate: 0.6.1
[cli][debug]  lxml: 4.9.0
[cli][debug]  pycountry: 22.3.5
[cli][debug]  pycryptodome: 3.14.1
[cli][debug]  PySocks: 1.7.1
[cli][debug]  requests: 2.28.1
[cli][debug]  websocket-client: 1.3.2
[cli][debug] Arguments:
[cli][debug]  url=ustream.tv/channel/id/11748282/recorded/132038074
[cli][debug]  stream=['best']
[cli][debug]  --loglevel=debug
[cli][debug]  --player=mpv
[cli][info] Found matching plugin ustreamtv for URL ustream.tv/channel/id/11748282/recorded/132038074
[plugins.ustreamtv][debug] Connecting to UStream API: media_id=132038074, application=recorded, referrer=https://ustream.tv/channel/id/11748282/recorded/132038074, cluster=live
[plugin.api.websocket][debug] Connecting to: wss://r3563127-1-132038074-recorded-ws-live.ums.services.video.ibm.com/1/ustream
[plugins.ustreamtv][debug] Waiting for stream data (for at most 15 seconds)...
[plugins.ustreamtv][debug] Processing 'moduleInfo' - 'cdnConfig'
[plugins.ustreamtv][debug] Processing 'moduleInfo' - 'stream'
[cli][info] Available streams: 252p+a83k (worst), 252p+a188k, 360p+a83k, 360p+a188k, 486p+a83k, 486p+a188k, 720p+a83k, 720p+a188k, 1072p+a83k, 1072p+a188k (best)
[cli][info] Opening stream: 1072p+a188k (muxed-stream)
[cli][info] Starting player: mpv
[stream.ffmpegmux][debug] Opening ustreamtv substream
[plugins.ustreamtv][debug] Stream opened, keeping websocket connection alive
[plugins.ustreamtv][debug] Adding video segment 0 to queue
[stream.ffmpegmux][debug] Opening ustreamtv substream
[plugins.ustreamtv][debug] Adding video segment 1 to queue
[plugins.ustreamtv][debug] Adding video segment 2 to queue
[plugins.ustreamtv][debug] Adding video segment 3 to queue
[plugins.ustreamtv][debug] Adding video segment 4 to queue
[plugins.ustreamtv][debug] Adding video segment 5 to queue
[plugins.ustreamtv][debug] Adding video segment 6 to queue
[plugins.ustreamtv][debug] Adding video segment 7 to queue
[plugins.ustreamtv][debug] Adding video segment 8 to queue
[plugins.ustreamtv][debug] Adding video segment 9 to queue
[plugins.ustreamtv][debug] Adding video segment 10 to queue
[plugins.ustreamtv][debug] Adding video segment 11 to queue
[plugins.ustreamtv][debug] Adding video segment 12 to queue
[plugins.ustreamtv][debug] Adding video segment 13 to queue
[plugins.ustreamtv][debug] Adding video segment 14 to queue
[plugins.ustreamtv][debug] Adding video segment 15 to queue
[plugins.ustreamtv][debug] Adding video segment 16 to queue
[plugins.ustreamtv][debug] Adding video segment 17 to queue
[plugins.ustreamtv][debug] Adding video segment 18 to queue
[plugins.ustreamtv][debug] Adding video segment 19 to queue
[plugins.ustreamtv][debug] Adding video segment 20 to queue
[plugins.ustreamtv][debug] Adding audio segment 0 to queue
[utils.named_pipe][info] Creating pipe streamlinkpipe-87806-1-2459
[plugins.ustreamtv][debug] Adding audio segment 1 to queue
[plugins.ustreamtv][debug] Adding audio segment 2 to queue
[plugins.ustreamtv][debug] Adding audio segment 3 to queue
[plugins.ustreamtv][debug] Adding audio segment 4 to queue
[plugins.ustreamtv][debug] Adding audio segment 5 to queue
[plugins.ustreamtv][debug] Adding audio segment 6 to queue
[plugins.ustreamtv][debug] Adding audio segment 7 to queue
[plugins.ustreamtv][debug] Adding audio segment 8 to queue
[plugins.ustreamtv][debug] Adding audio segment 9 to queue
[plugins.ustreamtv][debug] Adding audio segment 10 to queue
[plugins.ustreamtv][debug] Adding audio segment 11 to queue
[plugins.ustreamtv][debug] Adding audio segment 12 to queue
[plugins.ustreamtv][debug] Adding audio segment 13 to queue
[plugins.ustreamtv][debug] Adding audio segment 14 to queue
[plugins.ustreamtv][debug] Adding audio segment 15 to queue
[plugins.ustreamtv][debug] Adding audio segment 16 to queue
[plugins.ustreamtv][debug] Adding audio segment 17 to queue
[plugins.ustreamtv][debug] Adding audio segment 18 to queue
[plugins.ustreamtv][debug] Adding audio segment 19 to queue
[plugins.ustreamtv][debug] Adding audio segment 20 to queue
[utils.named_pipe][info] Creating pipe streamlinkpipe-87806-2-3406
[stream.ffmpegmux][debug] ffmpeg command: /usr/bin/ffmpeg -nostats -y -i /tmp/streamlinkpipe-87806-1-2459 -i /tmp/streamlinkpipe-87806-2-3406 -c:v copy -c:a copy -map 0 -map 1 -f matroska pipe:1
[stream.ffmpegmux][debug] Starting copy to pipe: /tmp/streamlinkpipe-87806-1-2459
[stream.ffmpegmux][debug] Starting copy to pipe: /tmp/streamlinkpipe-87806-2-3406
[cli][debug] Pre-buffering 8192 bytes
```

This means that the plugin's URL matcher needs to be updated for these particular URL formats:
`combined-embed/{channel_id}/video/{video_id}`
https://github.com/streamlink/streamlink/blob/2c0925335ebeb8dd5dda95dee79d6b39f36375c4/src/streamlink/plugins/ustreamtv.py#L464-L472
Sorry about the misdiagnosis, but it was 4:00am and I got sloppy reviewing the source.  Fortunately, this fix seems easier and there's also a simple workaround now that we know how to map the URLs.

The title has been updated.

Resolving patch:
diff --git a/src/streamlink/plugins/ustreamtv.py b/src/streamlink/plugins/ustreamtv.py
--- a/src/streamlink/plugins/ustreamtv.py
+++ b/src/streamlink/plugins/ustreamtv.py
@@ -462,13 +462,15 @@ def open(self):
 
 
 @pluginmatcher(re.compile(r"""
-    https?://(?:(www\.)?ustream\.tv|video\.ibm\.com)
-        (?:
-            (/embed/|/channel/id/)(?P<channel_id>\d+)
-        )?
-        (?:
-            (/embed)?/recorded/(?P<video_id>\d+)
-        )?
+    https?://(?:(?:www\.)?ustream\.tv|video\.ibm\.com)
+    (?:
+        /combined-embed
+        /(?P<combined_channel_id>\d+)
+        (?:/video/(?P<combined_video_id>\d+))?
+        |
+        (?:(?:/embed/|/channel/(?:id/)?)(?P<channel_id>\d+))?
+        (?:(?:/embed)?/recorded/(?P<video_id>\d+))?
+    )
 """, re.VERBOSE))
 @pluginargument(
     "password",
@@ -481,11 +483,11 @@ class UStreamTV(Plugin):
     STREAM_READY_TIMEOUT = 15
 
     def _get_media_app(self):
-        video_id = self.match.group("video_id")
+        video_id = self.match.group("video_id") or self.match.group("combined_video_id")
         if video_id:
             return video_id, "recorded"
 
-        channel_id = self.match.group("channel_id")
+        channel_id = self.match.group("channel_id") or self.match.group("combined_channel_id")
         if not channel_id:
             channel_id = self.session.http.get(
                 self.url,


Test patch:
diff --git a/tests/plugins/test_ustreamtv.py b/tests/plugins/test_ustreamtv.py
--- a/tests/plugins/test_ustreamtv.py
+++ b/tests/plugins/test_ustreamtv.py
@@ -9,14 +9,47 @@
 class TestPluginCanHandleUrlUStreamTV(PluginCanHandleUrl):
     __plugin__ = UStreamTV
 
-    should_match = [
-        "http://www.ustream.tv/streamlink",
-        "http://www.ustream.tv/channel/id/1234",
-        "http://www.ustream.tv/embed/1234",
-        "http://www.ustream.tv/recorded/6543",
-        "http://www.ustream.tv/embed/recorded/6543",
-        "https://video.ibm.com/channel/H5rQLwmTGrW",
-        "https://video.ibm.com/recorded/124680279",
+    should_match_groups = [
+        (
+            "https://www.ustream.tv/nasahdtv",
+            {},
+        ),
+        (
+            "https://www.ustream.tv/channel/6540154",
+            {"channel_id": "6540154"},
+        ),
+        (
+            "https://www.ustream.tv/channel/id/6540154",
+            {"channel_id": "6540154"},
+        ),
+        (
+            "https://www.ustream.tv/embed/6540154",
+            {"channel_id": "6540154"},
+        ),
+        (
+            "https://www.ustream.tv/recorded/132041157",
+            {"video_id": "132041157"},
+        ),
+        (
+            "https://www.ustream.tv/embed/recorded/132041157",
+            {"video_id": "132041157"},
+        ),
+        (
+            "https://www.ustream.tv/combined-embed/6540154",
+            {"combined_channel_id": "6540154"},
+        ),
+        (
+            "https://www.ustream.tv/combined-embed/6540154/video/132041157",
+            {"combined_channel_id": "6540154", "combined_video_id": "132041157"},
+        ),
+        (
+            "https://video.ibm.com/nasahdtv",
+            {},
+        ),
+        (
+            "https://video.ibm.com/recorded/132041157",
+            {"video_id": "132041157"},
+        ),
     ]