Repository: streamlink/streamlink

Issue:
plugins.ltv_lsm_lv: No playable streams found on this URL
### Checklist

- [X] This is a [plugin issue](https://streamlink.github.io/plugins.html) and not [a different kind of issue](https://github.com/streamlink/streamlink/issues/new/choose)
- [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

streamlink 6.6.2

### Description

Since approx. the beginning of 2024 this plugin does not extract the stream url via `--stream-url` anymore.

Following command before was working correctly and now it does not anymore.

```
streamlink --stream-url https://ltv.lsm.lv/lv/tiesraide/ltv1 best
```



### Debug log

```text
error: No playable streams found on this URL: https://ltv.lsm.lv/lv/tiesraide/ltv1
```


Discussion:
bastimeyer: Force-pushed with an update of the plugin matcher (replay support), and also removed the custom HLSStream subclass.

As said, live streams are geo-blocked for me and show a warning message stream instead, so it's possible that the actual live streams still require the custom HLSStream implementation.

```
$ ./script/test-plugin-urls.py ltv_lsm_lv
:: Finding streams for URL: https://ltv.lsm.lv/lv/tiesraide/lr1
:: Found streams: 360p, 720p, 1080p, worst, best
:: Finding streams for URL: https://ltv.lsm.lv/lv/tiesraide/lr2
:: Found streams: 360p, 720p, 1080p, worst, best
:: Finding streams for URL: https://ltv.lsm.lv/lv/tiesraide/lr3
:: Found streams: 360p, 720p, 1080p, worst, best
:: Finding streams for URL: https://ltv.lsm.lv/lv/tiesraide/lr4
:: Found streams: 360p, 720p, 1080p, worst, best
:: Finding streams for URL: https://ltv.lsm.lv/lv/tiesraide/lr5
:: Found streams: 360p, 720p, 1080p, worst, best
:: Finding streams for URL: https://ltv.lsm.lv/lv/tiesraide/lr6
:: Found streams: 360p, 720p, 1080p, worst, best
:: Finding streams for URL: https://ltv.lsm.lv/lv/tiesraide/ltv1
:: Found streams: 360p, 720p, 1080p, worst, best
:: Finding streams for URL: https://ltv.lsm.lv/lv/tiesraide/ltv7
:: Found streams: 360p, 720p, 1080p, worst, best
:: Finding streams for URL: https://ltv.lsm.lv/lv/tiesraide/visiem
:: Found streams: 360p, 720p, 1080p, worst, best
:: Finding streams for URL: https://replay.lsm.lv/lv/tiesraide/ltv7/sporta-studija-aizkulises
:: Found streams: 360p, 720p, 1080p, worst, best
:: Finding streams for URL: https://replay.lsm.lv/ru/efir/ltv7/sporta-studija-aizkulises
:: Found streams: 360p, 720p, 1080p, worst, best
```
karlis-vagalis: Thanks for the super quick fix. This did indeed solve the issue.

Log (using proxy for geo block):
```
$ streamlink -l debug --http-proxy 'http://192.168.2.50:23001' 'https://ltv.lsm.lv/lv/tiesraide/ltv1' best
[session][debug] Loading plugin: ltv_lsm_lv
[cli][debug] OS:         Linux-6.7.5-arch1-1-x86_64-with-glibc2.39
[cli][debug] Python:     3.11.7
[cli][debug] OpenSSL:    OpenSSL 3.2.1 30 Jan 2024
[cli][debug] Streamlink: 6.6.1+8.g1483e44a
[cli][debug] Dependencies:
[cli][debug]  certifi: 2024.2.2
[cli][debug]  isodate: 0.6.1
[cli][debug]  lxml: 5.1.0
[cli][debug]  pycountry: 23.12.11
[cli][debug]  pycryptodome: 3.20.0
[cli][debug]  PySocks: 1.7.1
[cli][debug]  requests: 2.31.0
[cli][debug]  trio: 0.24.0
[cli][debug]  trio-websocket: 0.11.1
[cli][debug]  typing-extensions: 4.9.0
[cli][debug]  urllib3: 2.2.1
[cli][debug]  websocket-client: 1.7.0
[cli][debug] Arguments:
[cli][debug]  url=https://ltv.lsm.lv/lv/tiesraide/ltv1
[cli][debug]  stream=['best']
[cli][debug]  --loglevel=debug
[cli][debug]  --http-proxy=http://192.168.2.50:23001
[cli][info] Found matching plugin ltv_lsm_lv for URL https://ltv.lsm.lv/lv/tiesraide/ltv1
[plugins.ltv_lsm_lv][debug] Found channel ID: 9xz_ltv1-lv-srt
[utils.l10n][debug] Language code: en_US
[stream.ffmpegmux][debug] ffmpeg version n6.1.1 Copyright (c) 2000-2023 the FFmpeg developers
[stream.ffmpegmux][debug]  built with gcc 13.2.1 (GCC) 20230801
[stream.ffmpegmux][debug]  configuration: --prefix=/usr --disable-debug --disable-static --disable-stripping --enable-amf --enable-avisynth --enable-cuda-llvm --enable-lto --enable-fontconfig --enable-frei0r --enable-gmp --enable-gnutls --enable-gpl --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libdav1d --enable-libdrm --enable-libfreetype --enable-libfribidi --enable-libgsm --enable-libharfbuzz --enable-libiec61883 --enable-libjack --enable-libjxl --enable-libmodplug --enable-libmp3lame --enable-libopencore_amrnb --enable-libopencore_amrwb --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libplacebo --enable-libpulse --enable-librav1e --enable-librsvg --enable-librubberband --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libssh --enable-libsvtav1 --enable-libtheora --enable-libv4l2 --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpl --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxcb --enable-libxml2 --enable-libxvid --enable-libzimg --enable-nvdec --enable-nvenc --enable-opencl --enable-opengl --enable-shared --enable-vapoursynth --enable-version3 --enable-vulkan
[stream.ffmpegmux][debug]  libavutil      58. 29.100 / 58. 29.100
[stream.ffmpegmux][debug]  libavcodec     60. 31.102 / 60. 31.102
[stream.ffmpegmux][debug]  libavformat    60. 16.100 / 60. 16.100
[stream.ffmpegmux][debug]  libavdevice    60.  3.100 / 60.  3.100
[stream.ffmpegmux][debug]  libavfilter     9. 12.100 /  9. 12.100
[stream.ffmpegmux][debug]  libswscale      7.  5.100 /  7.  5.100
[stream.ffmpegmux][debug]  libswresample   4. 12.100 /  4. 12.100
[stream.ffmpegmux][debug]  libpostproc    57.  3.100 / 57.  3.100
[stream.hls][debug] Using external audio tracks for stream 1080p (language=lav, name=Latviešu)
[stream.hls][debug] Using external audio tracks for stream 720p (language=lav, name=Latviešu)
[stream.hls][debug] Using external audio tracks for stream 360p (language=lav, name=Latviešu)
[cli][info] Available streams: 360p (worst), 720p, 1080p (best)
[cli][info] Opening stream: 1080p (hls-multi)
[cli][info] Starting player: /usr/bin/vlc
[stream.ffmpegmux][debug] Opening hls substream
[stream.hls][debug] Reloading playlist
[stream.ffmpegmux][debug] Opening hls substream
[stream.hls][debug] Reloading playlist
[utils.named_pipe][info] Creating pipe streamlinkpipe-16073-1-9264
[utils.named_pipe][info] Creating pipe streamlinkpipe-16073-2-7207
[stream.ffmpegmux][debug] ffmpeg command: /usr/bin/ffmpeg -nostats -y -i /tmp/streamlinkpipe-16073-1-9264 -i /tmp/streamlinkpipe-16073-2-7207 -c:v copy -c:a copy -map 0:v? -map 0:a? -map 1:a -f mpegts pipe:1
[stream.ffmpegmux][debug] Starting copy to pipe: /tmp/streamlinkpipe-16073-1-9264
[stream.ffmpegmux][debug] Starting copy to pipe: /tmp/streamlinkpipe-16073-2-7207
[cli][debug] Pre-buffering 8192 bytes
[stream.hls][debug] First Sequence: 194892; Last Sequence: 194896
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 194894; End Sequence: None
[stream.hls][debug] Adding segment 194894 to queue
[stream.hls][debug] Adding segment 194895 to queue
[stream.hls][debug] Adding segment 194896 to queue
[stream.hls][debug] First Sequence: 194892; Last Sequence: 194896
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 194894; End Sequence: None
[stream.hls][debug] Adding segment 194894 to queue
[stream.hls][debug] Adding segment 194895 to queue
[stream.hls][debug] Adding segment 194896 to queue
[stream.hls][debug] Writing segment 194894 to output
[stream.hls][debug] Segment 194894 complete
[stream.hls][debug] Writing segment 194895 to output
[stream.hls][debug] Segment 194895 complete
[stream.hls][debug] Writing segment 194896 to output
[stream.hls][debug] Segment 194896 complete
[stream.hls][debug] Writing segment 194894 to output
[stream.hls][debug] Segment 194894 complete
[stream.hls][debug] Writing segment 194895 to output
[stream.hls][debug] Segment 194895 complete
[cli.output][debug] Opening subprocess: ['/usr/bin/vlc', '--input-title-format', 'https://ltv.lsm.lv/lv/tiesraide/ltv1', '-']
[cli][debug] Writing stream to output
[stream.hls][debug] Writing segment 194896 to output
[stream.hls][debug] Segment 194896 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 194897 to queue
[stream.hls][debug] Adding segment 194897 to queue
[stream.hls][debug] Writing segment 194897 to output
[stream.hls][debug] Segment 194897 complete
[stream.hls][debug] Writing segment 194897 to output
[stream.hls][debug] Segment 194897 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 194898 to queue
[stream.hls][debug] Adding segment 194898 to queue
[stream.hls][debug] Writing segment 194898 to output
[stream.hls][debug] Segment 194898 complete
[stream.hls][debug] Writing segment 194898 to output
[stream.hls][debug] Segment 194898 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 194899 to queue
[stream.hls][debug] Adding segment 194899 to queue
[stream.hls][debug] Writing segment 194899 to output
[stream.hls][debug] Segment 194899 complete
[stream.hls][debug] Writing segment 194899 to output
[stream.hls][debug] Segment 194899 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 194900 to queue
[stream.hls][debug] Adding segment 194900 to queue
[stream.hls][debug] Writing segment 194900 to output
[stream.hls][debug] Segment 194900 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 194901 to queue
[stream.hls][debug] Adding segment 194901 to queue
[stream.hls][debug] Writing segment 194901 to output
[stream.hls][debug] Segment 194901 complete
[stream.hls][debug] Writing segment 194900 to output
[stream.hls][debug] Segment 194900 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 194902 to queue
[stream.hls][debug] Adding segment 194902 to queue
[stream.hls][debug] Writing segment 194902 to output
[stream.hls][debug] Segment 194902 complete
[stream.hls][debug] Writing segment 194901 to output
[stream.hls][debug] Segment 194901 complete
^C[stream.ffmpegmux][debug] Closing ffmpeg thread
[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing writer thread
[stream.ffmpegmux][debug] Pipe copy complete: /tmp/streamlinkpipe-16073-1-9264
[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing writer thread
[stream.ffmpegmux][debug] Pipe copy complete: /tmp/streamlinkpipe-16073-2-7207
[stream.ffmpegmux][debug] Closed all the substreams
[cli][info] Stream ended
Interrupted! Exiting...
[cli][info] Closing currently open stream...
```

Patch:
diff --git a/src/streamlink/plugins/ltv_lsm_lv.py b/src/streamlink/plugins/ltv_lsm_lv.py
index 0c37e47e..95f39d9a 100644
--- a/src/streamlink/plugins/ltv_lsm_lv.py
+++ b/src/streamlink/plugins/ltv_lsm_lv.py
@@ -1,73 +1,42 @@
 """
 $description Live TV channels from LTV, a Latvian public, state-owned broadcaster.
 $url ltv.lsm.lv
-$type live
+$url replay.lsm.lv
+$type live, vod
 $region Latvia
 """
 
 import logging
 import re
-from urllib.parse import urlsplit, urlunsplit
 
 from streamlink.plugin import Plugin, pluginmatcher
 from streamlink.plugin.api import validate
-from streamlink.stream.hls import HLSStream, HLSStreamReader, HLSStreamWorker
+from streamlink.stream.hls import HLSStream
 
 
 log = logging.getLogger(__name__)
 
 
-def copy_query_url(to, from_):
-    """
-    Replace the query string in one URL with the query string from another URL
-    """
-    return urlunsplit(urlsplit(to)._replace(query=urlsplit(from_).query))
-
-
-class LTVHLSStreamWorker(HLSStreamWorker):
-    def process_segments(self, playlist, segments):
-        super().process_segments(playlist, segments)
-        # update the segment URLs with the query string from the playlist URL
-        for sequence in segments:
-            sequence.uri = copy_query_url(sequence.uri, self.stream.url)
-
-
-class LTVHLSStreamReader(HLSStreamReader):
-    __worker__ = LTVHLSStreamWorker
-
-
-class LTVHLSStream(HLSStream):
-    __reader__ = LTVHLSStreamReader
-
-    @classmethod
-    def parse_variant_playlist(cls, *args, **kwargs):
-        streams = super().parse_variant_playlist(*args, **kwargs)
-
-        for stream in streams.values():
-            stream.args["url"] = copy_query_url(stream.args["url"], stream.multivariant.uri)
-
-        return streams
-
-
 @pluginmatcher(re.compile(
-    r"https://ltv\.lsm\.lv/lv/tiesraide",
+    r"https://(?:ltv|replay)\.lsm\.lv/(?:lv/tiesraide|ru/efir)/",
 ))
 class LtvLsmLv(Plugin):
-    URL_IFRAME = "https://ltv.lsm.lv/embed/live?c={embed_id}"
     URL_API = "https://player.cloudycdn.services/player/ltvlive/channel/{channel_id}/"
 
     def _get_streams(self):
         self.session.http.headers.update({"Referer": self.url})
 
-        embed_id = self.session.http.get(self.url, schema=validate.Schema(
-            re.compile(r"""embed_id\s*:\s*(?P<q>")(?P<embed_id>\w+)(?P=q)"""),
-            validate.any(None, validate.get("embed_id")),
+        iframe_url = self.session.http.get(self.url, schema=validate.Schema(
+            re.compile(r"""(?P<q>")https:\\u002F\\u002Fltv\.lsm\.lv\\u002Fembed\\u002Flive\?\S+?(?P=q)"""),
+            validate.none_or_all(
+                validate.get(0),
+                validate.parse_json(),
+            ),
         ))
-        if not embed_id:
+        if not iframe_url:
+            log.error("Could not find video player iframe")
             return
-        log.debug(f"Found embed ID: {embed_id}")
 
-        iframe_url = self.URL_IFRAME.format(embed_id=embed_id)
         starts_at, channel_id = self.session.http.get(iframe_url, schema=validate.Schema(
             validate.parse_html(),
             validate.xml_xpath_string(".//live[1]/@*[name()=':embed-data']"),
@@ -112,7 +81,7 @@ class LtvLsmLv(Plugin):
             ),
         )
         for surl in stream_sources:
-            yield from LTVHLSStream.parse_variant_playlist(self.session, surl).items()
+            yield from HLSStream.parse_variant_playlist(self.session, surl).items()
 
 
 __plugin__ = LtvLsmLv


Test patch:
diff --git a/tests/plugins/test_ltv_lsm_lv.py b/tests/plugins/test_ltv_lsm_lv.py
index d8be7d80..7a228154 100644
--- a/tests/plugins/test_ltv_lsm_lv.py
+++ b/tests/plugins/test_ltv_lsm_lv.py
@@ -6,20 +6,15 @@ class TestPluginCanHandleUrlLtvLsmLv(PluginCanHandleUrl):
     __plugin__ = LtvLsmLv
 
     should_match = [
-        "https://ltv.lsm.lv/lv/tiesraide/example/",
-        "https://ltv.lsm.lv/lv/tiesraide/example/",
-        "https://ltv.lsm.lv/lv/tiesraide/example/live.123/",
-        "https://ltv.lsm.lv/lv/tiesraide/example/live.123/",
-    ]
-
-    should_not_match = [
-        "https://ltv.lsm.lv",
-        "http://ltv.lsm.lv",
-        "https://ltv.lsm.lv/lv",
-        "http://ltv.lsm.lv/lv",
-        "https://ltv.lsm.lv/other-site/",
-        "http://ltv.lsm.lv/other-site/",
-        "https://ltv.lsm.lv/lv/other-site/",
-        "http://ltv.lsm.lv/lv/other-site/",
-        "https://ltv.lsm.lv/lv/tieshraide/example/",
+        "https://ltv.lsm.lv/lv/tiesraide/ltv1",
+        "https://ltv.lsm.lv/lv/tiesraide/ltv7",
+        "https://ltv.lsm.lv/lv/tiesraide/visiem",
+        "https://ltv.lsm.lv/lv/tiesraide/lr1",
+        "https://ltv.lsm.lv/lv/tiesraide/lr2",
+        "https://ltv.lsm.lv/lv/tiesraide/lr3",
+        "https://ltv.lsm.lv/lv/tiesraide/lr4",
+        "https://ltv.lsm.lv/lv/tiesraide/lr5",
+        "https://ltv.lsm.lv/lv/tiesraide/lr6",
+        "https://replay.lsm.lv/lv/tiesraide/ltv7/sporta-studija-aizkulises",
+        "https://replay.lsm.lv/ru/efir/ltv7/sporta-studija-aizkulises",
     ]