mpv icon indicating copy to clipboard operation
mpv copied to clipboard

Speeding up YouTube video loading and seeking?

Open nyanpasu64 opened this issue 3 years ago • 10 comments

Important Information

Provide following Information:

  • mpv version: v0.34.1
  • Linux Distribution and Version: Void Linux
  • Source of the mpv binary: distro package, xbps-src rebuilt
  • Window Manager and version: xfwm4 4.16.1
  • GPU driver and version: i915, Linux 5.18.19, 3rd Gen Core processor Graphics Controller

Reproduction steps

  • Install yt-dlp (eg. using pipx).

Regular settings:

  • Optionally add hwdec to mpv.conf (slightly slower startup, insignificant unless my testing is inaccurate).
  • Load a YouTube URL in mpv.
  • Click the seek bar ahead of the currently buffered video, to seek into the future.

save-position-on-quit:

  • Add save-position-on-quit to mpv.conf.
  • Load a long YouTube video, seek multiple minutes into the future, and close mpv.
  • Reopen mpv with the same video.

Expected behavior

Loading videos in mpv should not be much slower than invoking yt-dlp to get the URL, and seeking should be as fast as seeking in the YouTube web UI (1 second).

Actual behavior

On my slow Ivy Bridge laptop with or without hardware decoding, loading URLs takes around 4.5 seconds for a window to appear and the play point to appear in the terminal. Around 2.6 seconds is spent waiting for yt-dlp, around 700ms before it trying and failing to play the HTML file, a second or two to buffer the URL, plus overhead.

save-position-on-quit adds around 2.5 seconds (probably variable) to the startup time, spent seeking. Manually seeking after playback begins takes a variable amount of time depending on where you seek to, I've observed 3 and 6 seconds.

Using https://www.youtube.com/watch?v=9ghnfSWKOgY as a seek time benchmark:

  • mpv (h.264) takes 3.0 seconds to seek to 19:04 bookmark (Page Up). I'm guessing it's not using range-based URLs.
  • firefox (supplied with the VP9 non-range-based URL obtained from yt-dlp -j 'https://www.youtube.com/watch?v=9ghnfSWKOgY'|gron|less) takes ~2.1 seconds to seek to 19:04 (eyeballed).
    • mpv on the same URL takes 2 seconds to seek to 19:02, and 3 seconds to seek to 19:05. It could probably be improved, but not by much.
  • youtube web takes 1 second to seek to 19:04 bookmark (Ctrl+Right), with cache cleared!!!
    • firefox dev tools's network tab shows youtube web is using range-based URLs (URLs ending with &range=333999644-335298975&rn=5&rbuf=0 or &range=335298976-336678963&rn=7&rbuf=1813 etc.), eliminating the need to parse video metadata to seek quickly.

Ideas

  • mpv [open_demux_thread] takes 700ms failing to play a html page before invoking yt-dlp. Hopefully this can be fixed; you could send certain urls straight to yt-dlp instead of trying to download them, or race direct playback and yt-dlp for URLs that don't end in a recognized media file extension, or when you see a text/html file you cancel demuxing it and invoke yt-dlp instead. I've applied this patch locally and it seems to help, but I don't know if it breaks compatibility with text/html pages which succeed in demuxing (I don't know if they exist). (Returning an error from stream_create_instance prints a loud red error message to terminal, which I felt was inappropriate in normal operation.)
diff --git a/demux/demux.c b/demux/demux.c
index fdec805d15..3934302fcf 100644
--- a/demux/demux.c
+++ b/demux/demux.c
@@ -3401,6 +3401,19 @@ static struct demuxer *demux_open(struct stream *stream,
         }
     }
 
+    // `text/html; charset=utf-8` is never a playable audio or video format.
+    // Erroring early allows us to call yt-dlp and begin video playback faster.
+    {
+        const char *mime_type = stream->mime_type;
+        if (mime_type
+            && strncmp(mime_type, "text/html", 9) == 0
+            && (mime_type[9] == '\0' || mime_type[9] == ';'))
+        {
+            MP_VERBOSE(stream, "Refusing to play HTML file, falling back to ytdl\n");
+            goto done;
+        }
+    }
+
     struct parent_stream_info sinfo = {
         .seekable = stream->seekable,
         .is_network = stream->is_network,
  • yt-dlp isn't fast. i haven't looked into making it fast. it's less slow (1 second) on my Ryzen with wired Ethernet, than on my 10 year old laptop over Wi-Fi (2.6 seconds).
  • mpv hardware decoding is not much slower to start than software decoding. i wouldn't spend time here, but I may be wrong.

The biggest improvement in video resume and seek time would require either modifying yt-dlp or mpv to use range-based URLs to seek YouTube streams efficiently. I don't know if this is a standard practice in the metadata files specifying the URLs to download (what's it called, ~~m3u8? HLS?~~ maybe DASH? see https://www.google.com/search?q=range+rn+rbuf), or specific to YouTube. Is implementing URL-based fast seeking practical?

Log file

mpv-no-seek-no-hwdec.cast.txt mpv-no-seek.cast.txt mpv-seek.cast.txt

Sample files

https://www.youtube.com/watch?v=9ghnfSWKOgY

nyanpasu64 avatar Sep 02 '22 14:09 nyanpasu64

You can use yt-dlp on the first try with --script-opts-append=ytdl_hook-try_ytdl_first=yes. To increase the download speed see https://github.com/mpv-player/mpv/issues/8655#issuecomment-1199733728

guidocella avatar Sep 02 '22 14:09 guidocella

I think I'm going to try out try_ytdl_first=yes. I still believe my text/html change (or a similar change), or racing yt-dlp and video playback, is a useful alternative to ensure fast video and extractor playback (if it could be made to definitely not break video playback).

#4793 is, unfortunate. Unfortunately Google is effectively a malicious or hostile actor, trying to fingerprint clients and harm the experience of alternative ones. Nonetheless, is downloading a different URL every few seconds (mirroring the YouTube web client, which I suspect changes video quality on file boundaries), rather than sending range HTTP headers, viable to implement (perhaps https://satadalsengupta.github.io/docs/papers/2017_nossdav_youtubedash.pdf)?

nyanpasu64 avatar Sep 02 '22 14:09 nyanpasu64

That seems harder as you would have to instruct ffmpeg on which URLs to download. mpv already gets the 10M http-chunk-size from yt-dlp -J, and ffmpeg already implements -offset and -end_offset (if you try something like ffmpeg -offset 10M -end_offset 20M -i 'https://rr1---sn-uxaxpu5ap5-u5ge.googlevideo.com/videoplayback... the download isn't throttled), someone just needs to implement continously updating the offset while playing a single URL. I tried but I couldn't figure it out.

guidocella avatar Sep 02 '22 15:09 guidocella

Also you can use https://github.com/mpv-player/mpv/issues/9042#issuecomment-886113926 to ensure fast yt-dlp playback without breaking media URLs.

guidocella avatar Sep 02 '22 15:09 guidocella

Well, yt-dlp does not even support vp9 live streams!

How much is try_ytdl_first=yes affecting the speed for you?

download isn't throttled

Does it throttle if you download from the start? I suppose, for yt-dlp no?

ZaquL avatar Sep 03 '22 04:09 ZaquL

Well, yt-dlp does not even support vp9 live streams!

I used yt-dlp to get the video (not livestream) information, and it returned a VP9 URL. But YouTube Web uses range-based URLs even for non-livestream videos.

How much is try_ytdl_first=yes affecting the speed for you?

If my calculations are accurate it'll reduce startup time by 700ms or so. I haven't measured, but I'm happy with that part of the startup time.

Ideally something like script-opts=ytdl_hook-try_ytdl_first=yes,ytdl_hook-exclude="%.webm$|%.ts$|%.mp3$|%.m3u8$|%.m3u$|%.mkv$|%.mp4$|%.VOB$" would be added to the default mpv configuration (easy but picking the right command is difficult and bikesheddy), and range-based seeking would be supported (difficult, I don't want to try implementing now).

nyanpasu64 avatar Sep 03 '22 05:09 nyanpasu64

video (not livestream) inform

The keyword is livestream. Everything is supported for not livestreams.

ZaquL avatar Sep 03 '22 05:09 ZaquL

Does it throttle if you download from the start?

It doesn't get throttled as long as -end_offset is <= 10M.

guidocella avatar Sep 03 '22 06:09 guidocella

I find I have seeking issue when ytdl_hook uses EDL to mix multiple streams from youtube so I try to select a format with a single stream when using ytdl.

stumpedatwork avatar Sep 04 '22 01:09 stumpedatwork

It is a common problem for me on VP9 4k60 youtube videos, yeah. Especially animation. Seeking is completly broken on such streams.

ZaquL avatar Sep 06 '22 09:09 ZaquL

I suspect fast seeking in YouTube videos is related to DASH and #7033. It appears nontrivial to implement though.

nyanpasu64 avatar Oct 03 '22 01:10 nyanpasu64

(if you try something like ffmpeg -offset 10M -end_offset 20M -i 'https://rr1---sn-uxaxpu5ap5-u5ge.googlevideo.com/videoplayback... the download isn't throttled)

Is it possible to pass these options to mpv's libavformat demuxer using --demuxer-lavf-o=?

sun95n avatar Dec 30 '22 22:12 sun95n

Is it possible to pass these options to mpv's libavformat demuxer using --demuxer-lavf-o=?

Yes, but playing a single chunk isn't useful.

guidocella avatar Dec 31 '22 05:12 guidocella