有支持“同时设置多个A-B区间并循环播放”的播放器吗?

找了一圈常用播放器(vlc, potplayer, mpv等),传统的 A-B Repeat 永远只能设一组 \[A, B\]。如果一段视频里有三四处想反复精读的地方,每次都要重新设点,打断思绪。 想要一个能像播放列表一样管理“多个片段区间”,点播放后只在这些区间里循环跳跃播放的本地播放器。求大家指路!

这个需求确实很有个性,所以也许不太容易找到符合你需求的现成app,可以拿AI花半小时搓一个。

确实,现在的 AI 写代码是快。你这么一说我倒真想试试用 mpv 的 lua 脚本糊一个。

不过好奇问下,如果是你来做,UI 这块打算怎么处理?单条进度条上怎么优雅地展示并操作多段选区?如果只是快捷键打点盲跳感觉有点容易乱,真想听听大家的交互思路。

mpv写一个脚本可以的, 因为有个edl格式支持截取时间, 我用我旧的标记选段脚本找ai升级了一下内联edl播放成功了, 不过由于脚本标记模式的信息有点多, 把画面都挡完了不适合观看,我只是测试了一下可行度

-- ============================================================
-- video_cut_mini.lua - 极简版:F2 标记模式 / N 标记 / Ctrl+p EDL 播放
-- ============================================================
-- script-opts/video_cut_mini.conf 可配置:
--   keybind_toggle=f2         (进入/退出标记模式)
--   keybind_mark=n            (标记 A/B 点)
--   keybind_play_marked=Ctrl+p(EDL 播放/退出)
-- ============================================================

local options = require 'mp.options'

local o = {
    keybind_toggle      = "f2",
    keybind_mark        = "n",
    keybind_play_marked = "Ctrl+p",
}
options.read_options(o, 'video_cut_mini')

-- ====================== 状态 ======================
local marking_mode = false
local segments = {}
local current_segment_start = nil

local edl_active = false
local edl_saved_path = nil
local edl_saved_pos = 0

-- ====================== 提示(2 秒自动消失) ======================
local function notify(msg, duration)
    mp.osd_message(msg, duration or 2)
end

-- ====================== 路径 → file:// URL(用于 EDL 片段) ======================
local function path_to_file_url(p)
    p = p:gsub("\\", "/")
    if p:match("^%a:") then p = "/" .. p end
    if not p:match("^/") then p = "/" .. p end
    -- 百分号编码:保留 unreserved 与 / :,其余转义(兼容中文路径、空格、, ; =)
    p = p:gsub("[^%w%-%._~:/]", function(c)
        return string.format("%%%02X", string.byte(c))
    end)
    return "file://" .. p
end

-- ====================== 有效片段 ======================
local function get_valid_segments()
    local valid = {}
    for _, s in ipairs(segments) do
        if s.start and s.end_ and s.start < s.end_ then
            table.insert(valid, s)
        end
    end
    return valid
end

-- ====================== 标记 A/B ======================
local function mark_current_time()
    if not marking_mode then return end
    local pos = mp.get_property_number("time-pos")
    if not pos then
        notify("无法获取当前位置", 2)
        return
    end
    if not current_segment_start then
        current_segment_start = pos
        table.insert(segments, { start = pos })
        notify(string.format("✓ 开始点: %.3f", pos), 2)
    else
        local n = #segments
        segments[n].end_ = pos
        current_segment_start = nil
        notify(string.format("✓ 结束点: %.3f (片段 %d)", pos, n), 2)
    end
end

-- ====================== EDL 播放 / 退出 ======================
local function stop_edl()
    if not edl_active then return end
    if edl_saved_path then
        mp.commandv("loadfile", edl_saved_path, "replace")
        if edl_saved_pos and edl_saved_pos > 0 then
            mp.commandv("seek", string.format("%.3f", edl_saved_pos), "absolute")
        end
    end
    edl_active = false
    edl_saved_path = nil
    edl_saved_pos = 0
end

local function play_marked_segments()
    -- 已在 EDL:退出并回原文件
    if edl_active then
        stop_edl()
        notify("已退出 EDL 播放模式", 2)
        return
    end

    if not marking_mode then
        notify("请先按 " .. o.keybind_toggle .. " 进入标记模式", 2)
        return
    end

    local valid = get_valid_segments()
    if #valid == 0 then
        notify("没有有效片段,无法播放", 2)
        return
    end

    local path = mp.get_property("path")
    if not path or path == "" then
        notify("无法获取文件路径", 2)
        return
    end

    edl_saved_path = path
    edl_saved_pos  = mp.get_property_number("time-pos") or 0

    -- 正确的 EDL 片段格式:<file_url>,<start>,<length>;...
    local file_url = path_to_file_url(path)
    local parts = {}
    for _, seg in ipairs(valid) do
        local length = seg.end_ - seg.start
        table.insert(parts, string.format("%s,%.6f,%.6f", file_url, seg.start, length))
    end
    local edl = "edl://" .. table.concat(parts, ";")

    mp.msg.info("EDL URL: " .. edl)

    local ok = pcall(function()
        mp.command_native({
            name    = "loadfile",
            url     = edl,
            flags   = "replace",
            options = "external-file=" .. path
        })
    end)
    if not ok then
        mp.set_property("external-file", path)
        mp.commandv("loadfile", edl, "replace")
    end

    edl_active = true
    notify(string.format("▶ EDL 播放:%d 个片段(%s 退出)", #valid, o.keybind_play_marked), 2)
end

-- ====================== 模式切换 ======================
local function toggle_marking_mode()
    if not marking_mode then
        marking_mode = true
        segments = {}
        current_segment_start = nil

        mp.add_forced_key_binding(o.keybind_mark, "mini_mark", mark_current_time)
        mp.add_forced_key_binding(o.keybind_play_marked, "mini_play", play_marked_segments)
        mp.add_forced_key_binding("Esc", "mini_exit", function()
            if marking_mode then toggle_marking_mode() end
        end)

        notify("已进入标记模式(" .. o.keybind_mark .. " 标记 A/B," ..
               o.keybind_play_marked .. " EDL 播放,Esc 退出)", 2)
    else
        -- 退出:先结束 EDL(如有)
        if edl_active then stop_edl() end

        marking_mode = false
        segments = {}
        current_segment_start = nil

        mp.remove_key_binding("mini_mark")
        mp.remove_key_binding("mini_play")
        mp.remove_key_binding("mini_exit")

        notify("已退出标记模式", 2)
    end
end

-- ====================== 全局绑定 ======================
mp.add_key_binding(o.keybind_toggle, "mini_toggle", toggle_marking_mode)

使用流程

  1. F2 → 提示“已进入标记模式”,2 秒后消失
  2. 播放到 A 点按 n → “✓ 开始点”
  3. 播放到 B 点按 n → “✓ 结束点”
  4. 重复 2-3 标记多个片段
  5. Ctrl+p → 顺序播放所有片段(再按一次回原文件)
  6. Esc 或 F2 → 退出模式

这个是我那个脚本的精简版, 把其他主体功能都砍完了, 只保留了进模式 标记 和播放, 可以拿去给ai看看思路

时间轴上面加一个段落轨道,通过鼠标或快捷键快速导航。

用 AI 辅助不难实现

另外这个需求也可以用视频剪辑软件(反复预览是视频剪辑的常规操作)