Repository: streamlink/streamlink

Issue:
PlutoTV stops streaming when switching to a commercial
<!--
Thanks for reporting a bug!
USE THE TEMPLATE. Otherwise your bug report 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 bug reports:
https://github.com/streamlink/streamlink/issues?q=is%3Aissue+label%3A%22bug%22

Please see the text preview to avoid unnecessary formatting errors.
-->


## Bug Report

<!-- Replace the space character between the square brackets with an x in order to check the boxes -->
- [x ] This is a bug report and I have read the contribution guidelines.
- [ ] I am using the latest development version from the master branch.


### Description

<!-- Explain the bug as thoroughly as you can. Don't leave out information which is necessary for us to reproduce and debug this issue. -->
Streaming stops whenever plutoTV switches to a local commercial.  I have seen this on various pluto news channels such as NEWSY and Newsmax.  After a news segment, the channel will switch to local commercial and a message from pluto states that they will be right back - but the stream hangs at that point, never to return unless, the stream is restarted.

### Expected / Actual behavior

<!-- What do you expect to happen, and what is actually happening? -->
I expect that if the channel switches segments that the stream will continue to play as normal, but it does not do this, the stream stops

### 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. ...Tune to plutotv- Newsy and watch for 5-10 minutes until the segment changes
2. ...for me at the change, streaming stops
3. ...


### Log output

<!--
TEXT LOG OUTPUT IS REQUIRED for a bug report!
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.

Don't post screenshots of the log output and instead copy the text from your terminal application.
-->

```
REPLACE THIS TEXT WITH THE LOG OUTPUT
```


### Additional comments, etc.



[Love Streamlink? Please consider supporting our collective. Thanks!](https://opencollective.com/streamlink/donate)



Discussion:
What video player are you using?  Some will not handle the stream delay as well as others.  Have you tried mpv?
Hi,

 

I am using both the web-based (Chrome) Nextpvr client and Kodi as a client to nextpvr.

 

I am using Nextpvr v5 (latest rev) as the program that is interfacing with Streamlink.

 

 

Darryl Markowitz

 

 

 

 

From: Ian Cameron [mailto:notifications@github.com] 
Sent: Monday, December 28, 2020 9:36 AM
To: streamlink/streamlink
Cc: TranzPhat; Author
Subject: Re: [streamlink/streamlink] PlutoTV stops streaming when switching to a commercial (#3462)

 

What video player are you using? Some will not handle the stream delay as well as others. Have you tried mpv?

—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub <https://github.com/streamlink/streamlink/issues/3462#issuecomment-751732561> , or unsubscribe <https://github.com/notifications/unsubscribe-auth/ASI3BNIA5NFIIEFRQHXFS2DSXCJWLANCNFSM4VMCWDVA> .  <https://github.com/notifications/beacon/ASI3BNM63TZXFCCEA5H2UR3SXCJWLA5CNFSM4VMCWDVKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOFTHIOUI.gif> 



-- 
This email has been checked for viruses by Avast antivirus software.
https://www.avast.com/antivirus

I don't think your issue is with the PlutoTV plugin.  I've got it streaming right now direct to mpv and it seems to be working fine.

I don't know anything about the other software you're using, so you're probably going to have to seek help elsewhere I'm afraid.
Ian,

 

I am kind of new at this, so I apologize in advance.

 

I am using ffmpeg to start the stream, this is the call:

 

C:\Program Files\NextPVR\Other\ffmpeg.exe -y -analyzeduration 10M  -i http://127.0.0.1:8866/live?channeloid=8366&transcoder=b048e544-2dae-4769-9455-b96dec75c990&client=f605f5a0-6d55-4a10-a25c-39c15d86bf26 -map_metadata -1 -threads 0 -ignore_unknown -map 0:v:0? -map 0:a:0 -map -0:s -vcodec copy -acodec copy -hls_time 3 -start_number 0 -hls_list_size 403 -y "C:\Users\Public\NPVR-data\web\temp\Top Stories by Newsy 2-b048e5442dae47699455b96dec75c990.m3u8"

 

Not sure if this helps any.  

 

From: Ian Cameron [mailto:notifications@github.com] 
Sent: Monday, December 28, 2020 10:05 AM
To: streamlink/streamlink
Cc: TranzPhat; Author
Subject: Re: [streamlink/streamlink] PlutoTV stops streaming when switching to a commercial (#3462)

 

I don't think your issue is with the PlutoTV plugin. I've got it streaming right now direct to mpv and it seems to be working fine.

I don't know anything about the other software you're using, so you're probably going to have to seek help elsewhere I'm afraid.

—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub <https://github.com/streamlink/streamlink/issues/3462#issuecomment-751741755> , or unsubscribe <https://github.com/notifications/unsubscribe-auth/ASI3BNIQVQUYLEBWOOVYLBTSXCNDJANCNFSM4VMCWDVA> .  <https://github.com/notifications/beacon/ASI3BNI4ZH4VWIHAPSMMYQLSXCNDJA5CNFSM4VMCWDVKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOFTHKWOY.gif> 



-- 
This email has been checked for viruses by Avast antivirus software.
https://www.avast.com/antivirus

For me It varies by channel music channels are the worst.

streamlink https://pluto.tv/live-tv/mtv-spankin-new best
streamlink  https://pluto.tv/live-tv/vevo-pop best

For me it is when this pluto image comes 

![image](https://user-images.githubusercontent.com/2148031/103225621-57610f00-48f8-11eb-9679-77d6c5cc3d98.png)

followed by a pluto commercial not a channel commercial.

These fail with mpv, ffplay, vlc etc.

I can't seem to duplicate it with Newsy but the Pluto commercials aren't as frequent.
Post the **debug log** output (`--loglevel debug`), as mentioned in the issue template.

If your player crashes during an ad transition then it might be because of a stream discontinuity, which is not supported by Streamlink.
I just got an `ffmpeg pipe copy` failure.  Running with `--ffmpeg-verbose` also.
I think it gets stuck at this point:
```
$ streamlink -l debug --ffmpeg-verbose https://pluto.tv/live-tv/mtv-spankin-new 240p
[cli][debug] OS:         Linux-5.4.0-58-generic-x86_64-with-glibc2.29
[cli][debug] Python:     3.8.5
[cli][debug] Streamlink: 2.0.0+1.g374130a
[cli][debug] Requests(2.25.1), Socks(1.7.1), Websocket(0.57.0)
[cli][info] Found matching plugin pluto for URL https://pluto.tv/live-tv/mtv-spankin-new
[utils.l10n][debug] Language code: en_GB
[cli][info] Available streams: 240p (worst), 480p, 720p_alt, 720p (best)
[cli][info] Opening stream: 240p (muxed-stream)
[stream.ffmpegmux][debug] Opening hls substream
[stream.hls][debug] Reloading playlist
[stream.hls][debug] First Sequence: 267960612; Last Sequence: 267960617
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 267960615; End Sequence: None
[stream.hls][debug] Adding segment 267960615 to queue
[stream.hls][debug] Adding segment 267960616 to queue
[stream.hls][debug] Adding segment 267960617 to queue
[stream.ffmpegmux][debug] ffmpeg command: ffmpeg -nostats -y -i /tmp/ffmpeg-290343-476 -c:v copy -c:a copy -map 0 -f mpegts pipe:1
[stream.ffmpegmux][debug] Starting copy to pipe: /tmp/ffmpeg-290343-476
[cli][debug] Pre-buffering 8192 bytes
ffmpeg version 4.2.4-1ubuntu0.1 Copyright (c) 2000-2020 the FFmpeg developers
  built with gcc 9 (Ubuntu 9.3.0-10ubuntu2)
  configuration: --prefix=/usr --extra-version=1ubuntu0.1 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --arch=amd64 --enable-gpl --disable-stripping --enable-avresample --disable-filter=resample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librsvg --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-nvenc --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared
  WARNING: library configuration mismatch
  avcodec     configuration: --prefix=/usr --extra-version=1ubuntu0.1 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --arch=amd64 --enable-gpl --disable-stripping --enable-avresample --disable-filter=resample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librsvg --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-nvenc --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared --enable-version3 --disable-doc --disable-programs --enable-libaribb24 --enable-liblensfun --enable-libopencore_amrnb --enable-libopencore_amrwb --enable-libtesseract --enable-libvo_amrwbenc
  libavutil      56. 31.100 / 56. 31.100
  libavcodec     58. 54.100 / 58. 54.100
  libavformat    58. 29.100 / 58. 29.100
  libavdevice    58.  8.100 / 58.  8.100
  libavfilter     7. 57.100 /  7. 57.100
  libavresample   4.  0.  0 /  4.  0.  0
  libswscale      5.  5.100 /  5.  5.100
  libswresample   3.  5.100 /  3.  5.100
  libpostproc    55.  5.100 / 55.  5.100
[stream.hls][debug] Download of segment 267960615 complete
Input #0, mpegts, from '/tmp/ffmpeg-290343-476':
  Duration: N/A, start: 38068.935844, bitrate: N/A
  Program 1 
    Stream #0:0[0x7d0](eng): Audio: aac (LC) ([15][0][0][0] / 0x000F), 48000 Hz, stereo, fltp, 123 kb/s
    Stream #0:1[0x3e8]: Video: h264 (Main) ([27][0][0][0] / 0x001B), yuv420p(tv, progressive), 426x240 [SAR 1:1 DAR 71:40], Closed Captions, 29.97 fps, 29.97 tbr, 90k tbn, 59.94 tbc
Output #0, mpegts, to 'pipe:1':
  Metadata:
    encoder         : Lavf58.29.100
    Stream #0:0(eng): Audio: aac (LC) ([15][0][0][0] / 0x000F), 48000 Hz, stereo, fltp, 123 kb/s
    Stream #0:1: Video: h264 (Main) ([27][0][0][0] / 0x001B), yuv420p(tv, progressive), 426x240 [SAR 1:1 DAR 71:40], q=2-31, 29.97 fps, 29.97 tbr, 90k tbn, 90k tbc
Stream mapping:
  Stream #0:0 -> #0:0 (copy)
  Stream #0:1 -> #0:1 (copy)
Press [q] to stop, [?] for help
[cli][info] Starting player: mpv
[cli.output][debug] Opening subprocess: mpv --force-media-title=https://pluto.tv/live-tv/mtv-spankin-new -
[cli][debug] Writing stream to output
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Download of segment 267960616 complete
[stream.hls][debug] Adding segment 267960618 to queue
[stream.hls][debug] Adding segment 267960619 to queue
[stream.hls][debug] Download of segment 267960617 complete
...
stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 267960672 to queue
[stream.hls][debug] Adding segment 267960673 to queue
[stream.hls][debug] Download of segment 267960655 complete
[mpegts @ 0x55c85b7ee740] New video stream 0:2 at pos:16900636 and DTS:1.42133s
[mpegts @ 0x55c85b7ee740] New audio stream 0:3 at pos:16907968 and DTS:1.4s
...
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 267960679 to queue
[stream.hls][debug] Download of segment 267960658 complete
[stream.hls][debug] Adding segment 267960680 to queue
[mpegts @ 0x55c85b7ee740] DTS 129840 < 1474320 out of order
[stream.hls][debug] Download of segment 267960659 complete
[stream.hls][debug] Adding segment 267960681 to queue
[stream.ffmpegmux][error] Pipe copy aborted: /tmp/ffmpeg-290343-476
[stream.hls][debug] Download of segment 267960660 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 267960694 to queue
[stream.hls][debug] Download of segment 267960661 complete
[stream.hls][debug] Adding segment 267960695 to queue
```
Streamlink is carrying on with the segment downloads after.

Just to note: I've edited the post above to include a bit more info in the log output.  The new stream mappings are probably the likely cause I imagine.
they just use different AD types

**redirector** ADs which currently won't work _(used for Livechannels, but can be replaced with a valid segment)_
`uri=https://...-.akamaized.net/../redirector/...?url=https%3A%2F%2Fplutotv-.akamaized.net...` `title='AD'`

**creative** ADs which seems to work
`uri=https://....pluto.tv/..._ad/creative/..._ad/720p/.../hls/hls_...-00000.ts` `title=None`


@back-to Following up on your last comment, I wonder if you can explain a little further:

> they just use different AD types

What is an AD type, please?

> redirector ADs which currently won't work

Is there a plan to get them to work?

For my own use case, I'm trying to save the output of a Pluto TV stream to a file. Would it be possible to get `streamlink` to save its output to a different file each time the stream source changes? If it is, would that help because, in theory, the adverts would get saved to a different file and I'd "just" need to stitch together the programme parts?

I've tried using `ffmpeg -c copy` to repair a file copy of a stream from Pluto but playback breaks a few minutes into the adverts. I've also tried `-c:v libx264 -crf 22 -preset slow` to re-mux the file but that doesn't work either :(

I'm also getting slightly different output when running `streamlink` and `ffmpeg` in debug mode compared with @mkbloke:

```
streamlink.exe -l debug --ffmpeg-verbose https://pluto.tv/en/live-tv/baywatch-gb best
[cli][debug] OS:         Windows 10
[cli][debug] Python:     3.9.8
[cli][debug] Streamlink: 3.0.2
[cli][debug] Requests(2.26.0), Socks(1.7.1), Websocket(1.2.1)
[cli][debug] Arguments:
[cli][debug]  url=https://pluto.tv/en/live-tv/baywatch-gb
[cli][debug]  stream=['best']
[cli][debug]  --loglevel=debug
[cli][debug]  --ffmpeg-ffmpeg=C:\Program Files (x86)\Streamlink\ffmpeg\ffmpeg.exe
[cli][debug]  --ffmpeg-verbose=True
[cli][info] Found matching plugin pluto for URL https://pluto.tv/en/live-tv/baywatch-gb
[utils.l10n][debug] Language code: en_GB
[cli][info] Available streams: 570k (worst), 1000k, 1500k, 2100k, 3100k (best)
[cli][info] Opening stream: 3100k (muxed-stream)
[stream.ffmpegmux][debug] Opening hls substream
[stream.hls][debug] Reloading playlist
[utils.named_pipe][info] Creating pipe streamlinkpipe-5904-1-3924
[stream.ffmpegmux][debug] ffmpeg command: C:\Program Files (x86)\Streamlink\ffmpeg\ffmpeg.exe -nostats -y -i \\.\pipe\streamlinkpipe-5904-1-3924 -c:v copy -c:a copy -map 0 -f mpegts pipe:1
[stream.ffmpegmux][debug] Starting copy to pipe: \\.\pipe\streamlinkpipe-5904-1-3924
[cli][debug] Pre-buffering 8192 bytes
ffmpeg version n4.4.1-20211030 Copyright (c) 2000-2021 the FFmpeg developers
  built with gcc 10-win32 (GCC) 20210610
  configuration: --prefix=/ffbuild/prefix --pkg-config-flags=--static --pkg-config=pkg-config --cross-prefix=i686-w64-mingw32- --arch=i686 --target-os=mingw32 --enable-gpl --enable-version3 --disable-debug --disable-w32threads --enable-pthreads --enable-iconv --enable-libxml2 --enable-zlib --enable-libfreetype --enable-libfribidi --enable-gmp --enable-lzma --enable-fontconfig --enable-libvorbis --enable-opencl --enable-libvmaf --enable-vulkan --disable-libxcb --disable-xlib --enable-amf --enable-libaom --enable-avisynth --enable-libdav1d --disable-libdavs2 --disable-libfdk-aac --enable-ffnvcodec --enable-cuda-llvm --disable-frei0r --enable-libglslang --enable-libgme --enable-libass --enable-libbluray --enable-libmp3lame --enable-libopus --enable-libtheora --enable-libvpx --enable-libwebp --enable-lv2 --enable-libmfx --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --disable-librav1e --enable-librubberband --enable-schannel --enable-sdl2 --enable-libsoxr --enable-libsrt --disable-libsvtav1 --enable-libtwolame --disable-libuavs3d --disable-libdrm --disable-vaapi --enable-libvidstab --enable-libx264 --enable-libx265 --disable-libxavs2 --enable-libxvid --enable-libzimg --enable-libzvbi --extra-cflags=-DLIBTWOLAME_STATIC --extra-cxxflags= --extra-ldflags=-pthread --extra-ldexeflags= --extra-libs=-lgomp --extra-version=20211030
  libavutil      56. 70.100 / 56. 70.100
  libavcodec     58.134.100 / 58.134.100
  libavformat    58. 76.100 / 58. 76.100
  libavdevice    58. 13.100 / 58. 13.100
  libavfilter     7.110.100 /  7.110.100
  libswscale      5.  9.100 /  5.  9.100
  libswresample   3.  9.100 /  3.  9.100
  libpostproc    55.  9.100 / 55.  9.100
[stream.hls][debug] Segments in this playlist are encrypted
[stream.hls][debug] First Sequence: 2; Last Sequence: 6
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 4; End Sequence: None
[stream.hls][debug] Adding segment 4 to queue
[stream.hls][debug] Adding segment 5 to queue
[stream.hls][debug] Adding segment 6 to queue
[stream.hls][debug] Segment 4 complete
[stream.hls][debug] Segment 5 complete
Input #0, mpegts, from '\\.\pipe\streamlinkpipe-5904-1-3924':
  Duration: N/A, start: 16.466667, bitrate: N/A
  Program 1
    Metadata:
      service_name    : Service
      service_provider: Hybrik
  Stream #0:0[0x100]: Video: h264 (High) ([27][0][0][0] / 0x001B), yuv420p(progressive), 1280x720 [SAR 1:1 DAR 16:9], 30 fps, 30 tbr, 90k tbn, 60 tbc
  Stream #0:1[0x101]: Audio: aac (LC) ([15][0][0][0] / 0x000F), 48000 Hz, stereo, fltp, 96 kb/s
Output #0, mpegts, to 'pipe:1':
  Metadata:
    encoder         : Lavf58.76.100
  Stream #0:0: Video: h264 (High) ([27][0][0][0] / 0x001B), yuv420p(progressive), 1280x720 [SAR 1:1 DAR 16:9], q=2-31, 30 fps, 30 tbr, 90k tbn, 90k tbc
  Stream #0:1: Audio: aac (LC) ([15][0][0][0] / 0x000F), 48000 Hz, stereo, fltp, 96 kb/s
Stream mapping:
  Stream #0:0 -> #0:0 (copy)
  Stream #0:1 -> #0:1 (copy)
Press [q] to stop, [?] for help
[cli][info] Starting player: "C:\Program Files\VideoLAN\VLC\vlc.exe"
[cli.output][debug] Opening subprocess: "C:\Program Files\VideoLAN\VLC\vlc.exe" --input-title-format https://pluto.tv/en/live-tv/baywatch-gb -
[stream.hls][debug] Segment 6 complete
[cli][debug] Writing stream to output
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Segments in this playlist are encrypted
[stream.hls][debug] Adding segment 7 to queue
[stream.hls][debug] Segment 7 complete
[mpegts @ 05a28980] New video stream 0:2 at pos:4954176 and DTS:0.111111s
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Segments in this playlist are encrypted
[stream.hls][debug] Adding segment 8 to queue
[stream.hls][debug] Segment 8 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Segments in this playlist are encrypted
[stream.hls][debug] Adding segment 9 to queue
[stream.hls][debug] Segment 9 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Segments in this playlist are encrypted
[stream.hls][debug] Adding segment 10 to queue
[stream.hls][debug] Segment 10 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Segments in this playlist are encrypted
[stream.hls][debug] Adding segment 11 to queue
[stream.hls][debug] Segment 11 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Segments in this playlist are encrypted
[stream.hls][debug] Adding segment 12 to queue
[stream.hls][debug] Segment 12 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Segments in this playlist are encrypted
[stream.hls][debug] Adding segment 13 to queue
[stream.hls][debug] Adding segment 14 to queue
[stream.hls][debug] Segment 13 complete
[stream.hls][debug] Segment 14 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Segments in this playlist are encrypted
[stream.hls][debug] Adding segment 15 to queue
[stream.hls][debug] Segment 15 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Segments in this playlist are encrypted
[stream.hls][debug] Adding segment 16 to queue
[stream.hls][debug] Segment 16 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Segments in this playlist are encrypted
[stream.hls][debug] Adding segment 17 to queue
[stream.hls][debug] Segment 17 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Segments in this playlist are encrypted
[stream.hls][debug] Adding segment 18 to queue
[stream.hls][debug] Segment 18 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Segments in this playlist are encrypted
[stream.hls][debug] Adding segment 19 to queue
[stream.hls][debug] Segment 19 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Segments in this playlist are encrypted
```
If `streamlink` is started during a programme, VLC seems to be OK showing the video and audio until the end of the first advert. After that, it is audio only.

Once the main programme restarts, stopping and restarting `streamlink` resumes playback of video and audio, although the output still reports "Segments in this playlist are encrypted" so I'm not sure how relevant that is.


Resolving patch:
diff --git a/src/streamlink/plugins/pluto.py b/src/streamlink/plugins/pluto.py
--- a/src/streamlink/plugins/pluto.py
+++ b/src/streamlink/plugins/pluto.py
@@ -1,99 +1,171 @@
 import logging
 import re
+from urllib.parse import parse_qs, urljoin
 from uuid import uuid4
 
 from streamlink.plugin import Plugin, pluginmatcher
 from streamlink.plugin.api import validate
-from streamlink.stream.ffmpegmux import MuxedStream
-from streamlink.stream.hls import HLSStream
+from streamlink.stream.hls import HLSStream, HLSStreamReader, HLSStreamWriter
 from streamlink.utils.url import update_qsd
 
 log = logging.getLogger(__name__)
 
 
-@pluginmatcher(re.compile(r'''
+class PlutoHLSStreamWriter(HLSStreamWriter):
+    ad_re = re.compile(r"_ad/creative/|dai\.google\.com")
+
+    def should_filter_sequence(self, sequence):
+        return self.ad_re.search(sequence.segment.uri) is not None or super().should_filter_sequence(sequence)
+
+
+class PlutoHLSStreamReader(HLSStreamReader):
+    __writer__ = PlutoHLSStreamWriter
+
+
+class PlutoHLSStream(HLSStream):
+    __shortname__ = "hls-pluto"
+    __reader__ = PlutoHLSStreamReader
+
+
+@pluginmatcher(re.compile(r"""
     https?://(?:www\.)?pluto\.tv/(?:\w{2}/)?(?:
-        live-tv/(?P<slug_live>[^/?]+)/?$
+        live-tv/(?P<slug_live>[^/]+)
         |
-        on-demand/series/(?P<slug_series>[^/]+)/season/\d+/episode/(?P<slug_episode>[^/]+)$
+        on-demand/series/(?P<slug_series>[^/]+)(?:/season/\d+)?/episode/(?P<slug_episode>[^/]+)
         |
-        on-demand/movies/(?P<slug_movies>[^/]+)$
-    )
-''', re.VERBOSE))
+        on-demand/movies/(?P<slug_movies>[^/]+)
+    )/?$
+""", re.VERBOSE))
 class Pluto(Plugin):
-    def _schema_media(self, slug):
-        return validate.Schema(
-            [{
-                'name': str,
-                'slug': str,
-                validate.optional('stitched'): {
-                    'urls': [
-                        {
-                            'type': str,
-                            'url': validate.url(),
-                        }
-                    ]
-                }
-            }],
-            validate.filter(lambda k: k['slug'].lower() == slug.lower()),
-            validate.get(0),
+    def _get_api_data(self, type, slug, filter=None):
+        log.debug(f"slug={slug}")
+        app_version = self.session.http.get(self.url, schema=validate.Schema(
+            validate.parse_html(),
+            validate.xml_xpath_string(".//head/meta[@name='appVersion']/@content"),
+            validate.any(None, str),
+        ))
+        if not app_version:
+            return
+
+        log.debug(f"app_version={app_version}")
+
+        return self.session.http.get(
+            "https://boot.pluto.tv/v4/start",
+            params={
+                "appName": "web",
+                "appVersion": app_version,
+                "deviceVersion": "94.0.0",
+                "deviceModel": "web",
+                "deviceMake": "firefox",
+                "deviceType": "web",
+                "clientID": str(uuid4()),
+                "clientModelNumber": "1.0",
+                type: slug,
+            },
+            schema=validate.Schema(
+                validate.parse_json(), {
+                    "servers": {
+                        "stitcher": validate.url(),
+                    },
+                    validate.optional("EPG"): [{
+                        "name": str,
+                        "id": str,
+                        "slug": str,
+                        "stitched": {
+                            "path": str,
+                        },
+                    }],
+                    validate.optional("VOD"): [{
+                        "name": str,
+                        "id": str,
+                        "slug": str,
+                        "genre": str,
+                        "stitched": {
+                            "path": str,
+                        },
+                        validate.optional("seasons"): [{
+                            "episodes": validate.all([{
+                                "name": str,
+                                "_id": str,
+                                "slug": str,
+                                "stitched": {
+                                    "path": str,
+                                },
+                            }], validate.filter(lambda k: filter and k["slug"] == filter)),
+                        }],
+                    }],
+                    "sessionToken": str,
+                    "stitcherParams": str,
+                },
+            ),
         )
 
-    def _get_streams(self):
-        data = None
+    def _get_playlist(self, host, path, params, token):
+        qs = parse_qs(params)
+        qs["jwt"] = token
+        yield from PlutoHLSStream.parse_variant_playlist(self.session, update_qsd(urljoin(host, path), qs)).items()
 
+    @staticmethod
+    def _get_media_data(data, key, slug):
+        media = data.get(key)
+        if media and media[0]["slug"] == slug:
+            return media[0]
+
+    def _get_streams(self):
         m = self.match.groupdict()
-        if m['slug_live']:
-            res = self.session.http.get('https://api.pluto.tv/v2/channels')
-            data = self.session.http.json(res,
-                                          schema=self._schema_media(m['slug_live']))
-        elif m['slug_series'] and m['slug_episode']:
-            res = self.session.http.get(f'http://api.pluto.tv/v3/vod/slugs/{m["slug_series"]}')
-            data = self.session.http.json(
-                res,
-                schema=validate.Schema(
-                    {'seasons': validate.all(
-                        [{'episodes': self._schema_media(m['slug_episode'])}],
-                        validate.filter(lambda k: k['episodes'] is not None))},
-                    validate.get('seasons'),
-                    validate.get(0),
-                    validate.any(None, validate.get('episodes'))
-                ),
-            )
-        elif m['slug_movies']:
-            res = self.session.http.get('https://api.pluto.tv/v3/vod/categories',
-                                        params={'includeItems': 'true', 'deviceType': 'web'})
-            data = self.session.http.json(
-                res,
-                schema=validate.Schema(
-                    {'categories': validate.all(
-                        [{'items': self._schema_media(m['slug_movies'])}],
-                        validate.filter(lambda k: k['items'] is not None))},
-                    validate.get('categories'),
-                    validate.get(0),
-                    validate.any(None, validate.get('items')),
-                ),
-            )
-
-        log.trace(f'{data!r}')
-        if data is None or not data.get('stitched'):
+        if m["slug_live"]:
+            data = self._get_api_data("channelSlug", m["slug_live"])
+            media = self._get_media_data(data, "EPG", m["slug_live"])
+            if not media:
+                return
+
+            self.id = media["id"]
+            self.title = media["name"]
+            path = media["stitched"]["path"]
+
+        elif m["slug_series"] and m["slug_episode"]:
+            data = self._get_api_data("episodeSlugs", m["slug_series"], filter=m["slug_episode"])
+            media = self._get_media_data(data, "VOD", m["slug_series"])
+            if not media or "seasons" not in media:
+                return
+
+            for season in media["seasons"]:
+                if season["episodes"]:
+                    episode = season["episodes"][0]
+                    if episode["slug"] == m["slug_episode"]:
+                        break
+            else:
+                return
+
+            self.author = media["name"]
+            self.category = media["genre"]
+            self.id = episode["_id"]
+            self.title = episode["name"]
+            path = episode["stitched"]["path"]
+
+        elif m["slug_movies"]:
+            data = self._get_api_data("episodeSlugs", m["slug_movies"])
+            media = self._get_media_data(data, "VOD", m["slug_movies"])
+            if not media:
+                return
+
+            self.category = media["genre"]
+            self.id = media["id"]
+            self.title = media["name"]
+            path = media["stitched"]["path"]
+
+        else:
             return
 
-        self.title = data['name']
-        stream_url_no_sid = data['stitched']['urls'][0]['url']
-        device_id = str(uuid4())
-        stream_url = update_qsd(stream_url_no_sid, {
-            'deviceId': device_id,
-            'sid': device_id,
-            'deviceType': 'web',
-            'deviceMake': 'Firefox',
-            'deviceModel': 'Firefox',
-            'appName': 'web',
-        })
-
-        self.session.set_option('ffmpeg-fout', 'mpegts')
-        for q, s in HLSStream.parse_variant_playlist(self.session, stream_url).items():
-            yield q, MuxedStream(self.session, s)
+        log.trace(f"data={data!r}")
+        log.debug(f"path={path}")
+
+        return self._get_playlist(
+            data["servers"]["stitcher"],
+            path,
+            data["stitcherParams"],
+            data["sessionToken"],
+        )
 
 
 __plugin__ = Pluto


Test patch:
diff --git a/tests/plugins/test_pluto.py b/tests/plugins/test_pluto.py
--- a/tests/plugins/test_pluto.py
+++ b/tests/plugins/test_pluto.py
@@ -23,7 +23,13 @@ class TestPluginCanHandleUrlPluto(PluginCanHandleUrl):
         'http://www.pluto.tv/lc/on-demand/series/leverage/season/1/episode/the-nigerian-job-2009-1-1',
         'http://pluto.tv/lc/on-demand/series/fear-factor-usa-(lf)/season/5/episode/underwater-safe-bob-car-ramp-2004-5-3',
         'https://www.pluto.tv/lc/on-demand/movies/dr.-no-1963-1-1',
+        'https://www.pluto.tv/lc/on-demand/movies/dr.-no-1963-1-1/',
         'http://pluto.tv/lc/on-demand/movies/the-last-dragon-(1985)-1-1',
+        'http://pluto.tv/lc/on-demand/movies/the-last-dragon-(1985)-1-1/',
+        'https://pluto.tv/en/on-demand/series/great-british-menu-ptv1/episode/north-west-fish-2009-5-7-ptv1',
+        'https://pluto.tv/en/on-demand/series/great-british-menu-ptv1/episode/north-west-fish-2009-5-7-ptv1/',
+        'https://www.pluto.tv/en/on-demand/series/great-british-menu-ptv1/episode/north-west-fish-2009-5-7-ptv1',
+        'https://www.pluto.tv/en/on-demand/series/great-british-menu-ptv1/episode/north-west-fish-2009-5-7-ptv1/',
     ]
 
     should_not_match = [
@@ -47,4 +53,8 @@ class TestPluginCanHandleUrlPluto(PluginCanHandleUrl):
         'http://pluto.tv/lc/on-demand/series/dr.-no-1963-1-1',
         'http://pluto.tv/lc/on-demand/movies/leverage/season/1/episode/the-nigerian-job-2009-1-1',
         'http://pluto.tv/lc/on-demand/fear-factor-usa-(lf)/season/5/episode/underwater-safe-bob-car-ramp-2004-5-3',
+        'https://pluto.tv/en/on-demand/series/great-british-menu-ptv1/episode/north-west-fish-2009-5-7-ptv1/extra',
+        'https://pluto.tv/en/on-demand/series/great-british-menu-ptv1/season/5/episode/north-west-fish-2009-5-7-ptv1/extra',
+        'https://www.pluto.tv/en/on-demand/series/great-british-menu-ptv1/episode/north-west-fish-2009-5-7-ptv1/extra',
+        'https://www.pluto.tv/en/on-demand/series/great-british-menu-ptv1/season/5/episode/north-west-fish-2009-5-7-ptv1/extra',
     ]