Replies: 1 comment 3 replies
-
|
Other than This ends up with fully seekable VOD duration at current live segment position. Another issue is that seeking on Twitch VOD can be quite finicky in general: #14840. As mentioned there, you can grab Then there's the issue with the source: your yt-dlp --live-from-start -J https://www.twitch.tv/CHANNEL | jq -r .urlreturns the same yt-dlp -J https://www.twitch.tv/videos/1234567890 | jq -r .urlThe benefit of using VOD URL directly is that it fully populates If you ever move away from the most live playback position, If you spend too much time away from live playback and then want to jump to the newest chunk, your best bet is to again seek toward the current Here's what i've used at some point: autoprofile[twitchvod]
profile-cond=get("path", ""):find("^https://www.twitch.tv/(.*)videos?") ~= nil or get("path", ""):find("https://usher.ttvnw.net/vod/(.*).m3u8") ~= nil
script-opts-add=ytdl_hook-use_manifests=yes
profile-restore=copy
cache-secs=120
cache=yes
title=Twitch VoD — ${media-title}
ytdl-format=best+best
demuxer-lavf-o=live_start_index=0lua scriptlocal utils = require "mp.utils"
local function is_twitch_vod()
local title = mp.get_property_native("title")
return title and title:find("Twitch VoD")
end
-- With 'use_manifests=yes', yt-dlp fails to choose the best available Twitch VoD quality, defaulting to 720p.
mp.add_hook("on_preloaded", 50, function()
if is_twitch_vod() then
if mp.get_property("vid") ~= "1" then mp.set_property("vid", "1") end
if mp.get_property("aid") ~= "1" then mp.set_property("aid", "1") end
if live_vod_start then
mp.msg.info("Setting start to: " .. live_vod_start)
mp.set_property("start", tostring(live_vod_start))
live_vod_start = nil
end
end
end)
-- Use curl to read #EXT-X-TWITCH-TOTAL-SECS from the .m3u8
-- ytdl_result.duration is somtimes too large and can cause infinite load on launch
local function get_live_pos_from_playlist(ytdl_result)
if ytdl_result and ytdl_result.url then
local curl_r = mp.command_native({
name = "subprocess",
playback_only = true,
capture_stdout = true,
args = {"curl", "-s", ytdl_result.url},
})
if curl_r and curl_r.stdout then
local total_secs = curl_r.stdout:match("#EXT%-X%-TWITCH%-TOTAL%-SECS:(%d+)")
if total_secs then
return tonumber(total_secs)
end
end
end
return nil
end
mp.observe_property("user-data/mpv/ytdl/json-subprocess-result", "native", function(_, value)
if is_twitch_vod() and value and not live_vod_start then
local ytdl_result = utils.parse_json(value.stdout)
if ytdl_result and ytdl_result.is_live then
local live_pos = get_live_pos_from_playlist(ytdl_result)
local offset = live_pos and 15 or 30
if not live_pos then
mp.msg.warn("used 'ytdl_result.duration' fallback.")
live_pos = ytdl_result.duration
end
if live_pos and live_pos > offset then
live_vod_start = live_pos - offset
end
end
end
end)
-- Seek to current live segment
mp.add_key_binding(nil, "seek-to-live", function()
if not is_twitch_vod() then return end
local url = mp.get_property("path")
if not url then return end
local r = mp.get_property_native("user-data/mpv/ytdl/json-subprocess-result")
local ytdl_result = r and utils.parse_json(r.stdout)
local live_pos = get_live_pos_from_playlist(ytdl_result)
local offset = live_pos and 15 or 30
if not live_pos then
local vod_id = url:match("https://usher%.ttvnw%.net/vod/(%d+)%.m3u8")
if vod_id then
url = "https://www.twitch.tv/videos/" .. vod_id
end
local r = mp.command_native({
name = "subprocess",
playback_only = true,
capture_stdout = true,
capture_stderr = true,
args = {"yt-dlp", "--dump-json", "--no-playlist", "--", url},
})
local ytdl_result = r and utils.parse_json(r.stdout)
if ytdl_result and ytdl_result.duration then
mp.msg.warn("used 'ytdl_result.duration' fallback.")
live_pos = ytdl_result.duration
end
end
if live_pos then
local pos = math.max(0, live_pos - offset)
mp.msg.info("Seeking to live pos: " .. pos)
mp.commandv("seek", tostring(pos), "absolute")
end
end)With this, all you need to do is launch mpv with the VOD URL Keep in mind that this was only ever used on Windows, so at worst it's more of an example how to automate the whole thing. This: yt-dlp/yt-dlp#13181 (comment) is also worth reading to get an idea of issues with seeking Twitch VODs in |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
I had VOD playlist of still live livestream, for example:
https://d3fi1amfgojobc.cloudfront.net/03c477bcf70417592a6b_forsen_315951068263_1769612381/chunked/index-dvr.m3u8It's no longer live, but I was testing all below when it was live. It looked like this:
And was being updated with new
EXTINFand.tslines.I can't get MPV to play such playlist in a way that:
Looking at this M3U8 file it feels like something very much possible to do. But I'm not sure if I can get MPV to do that.
For reproducing my attempts you need a live Twitch
CHANNELname with VODs enabled.Of course, simply
mpv https://www.twitch.tv/CHANNELsucceeds at 1 and 2 but not at 4. Also it gets number 3 most of the time, unless you start with ad/get an ad midstream.Adding
--ytdl-raw-options-add=live-from-start=almost gets me where I want, but still no number 4. The--force-seekabledoes not help.You can get that M3U8 URL with:
yt-dlp --live-from-start -J https://www.twitch.tv/CHANNEL | jq -r .urlThings tried:
a) With
mpv https://m3u8urlI get 1 and 2 but not 3 and 4.b) With
mpv slice://0@https://m3u8urlI get it to start from beginning which is okaaay, it opens as playlist instead of continuous stream ([1/1680]) so it number 4 kinda works, but when I go to last item it fails number 2 - never gets new items.c) With
mpv --force-seekable --demuxer-lavf-o=live_start_index=0 https://m3u8urlit starts from beginning again which is fine, but it doesn't calculate total length, you can kinda seek far forward by spammingENDbut I didn't manage to get to end as after some time it just hangs and can't confirm number 2. But until it hangs number 4 works fine.d) And
mpv --force-seekable --demuxer-lavf-o=live_start_index=0,live_seekable=1 https://m3u8urldoesn't change much, still hung weridly after trying to seek to end after 3rd hour or so.e) Using
mpv --force-seekable --ytdl-raw-options-add=live-from-start= https://www.twitch.tv/forsenalmost is good, it's 1 and 2 and also 3 too! But you can't seek to the beginning or middle... Even with--force-seekable.And with that, even without
--force-seekableyou can glitch out to the start of the stream, getting[ffmpeg/demuxer] hls: The m3u8 list sequence may have been wrapped.warning and that messes up whole playback also adding like 24 hours to the current playback time.f) With
mpv --force-seekable --demuxer-lavf-o=live_start_index=0 --ytdl-raw-options-add=live-from-start= https://www.twitch.tv/forsenI get similar behaviour to c) and d), couldn't seek to the end (currently 5+ hours) without it hanging and not proceeding any further.g) With
mpv --demuxer-lavf-o=allowed_extensions=ALL --demuxer-lavf-o=live_start_index=0,live_seekable=1 https://m3u8urlnow I had luck and was able to seek to the current live position without hanging, but still had to spamEND, MPV did not calculate total length from start.h) Also tried messing around with
edl://thatmpv -v --ytdl-raw-options-add=live-from-start= https://www.twitch.tv/forsenshows:But to no avail.
Now that stream ended and said M3U8 ends with
#EXT-X-ENDLISTline, simplympv https://m3u8urlmakes numer 3 and 4 work correctly, while 1 and 2 of course no longer work, nor make sense.Beta Was this translation helpful? Give feedback.
All reactions