Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions DOCS/interface-changes/osc-margins.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
add `osc-dynamic_margins` option
add `osc-sub-margin` option
add `osc-osd-margin` option
44 changes: 35 additions & 9 deletions DOCS/man/osc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -384,25 +384,51 @@ Configurable Options
within the areas not covered by the osc (``yes``). If this option is set,
the osc may overwrite the ``--video-margin-ratio-*`` options, even if the
user has set them. (It will not overwrite them if all of them are set to
default values.) Additionally, ``visibility`` must be set to ``always``.
Otherwise, this option does nothing.
default values.) By default, ``visibility`` must be set to ``always``.
Use ``dynamic_margins`` to allow margins to update with OSC visibility
instead.

Currently, this is supported for the ``bottombar``, ``slimbottombar``,
``topbar`` and ``slimtopbar`` layouts only. The other layouts do not change
if this option is set. Separately, if window controls are present (see
below), they will be affected regardless of which osc layout is in use.

The border is static and appears even if the OSC is configured to appear
only on mouse interaction. If the OSC is invisible, the border is simply
filled with the background color (black by default).

This currently still makes the OSC overlap with subtitles (if the
``--sub-use-margins`` option is set to ``yes``, the default). This may be
fixed later.
Subtitles may still overlap with the OSC when ``--sub-use-margins`` is set
to ``yes`` (the default), as they are allowed to extend into the margin
area. Setting ``--sub-use-margins=no`` confines subtitles to the video
area.

This does not work correctly with video outputs like ``--vo=xv``, which
render OSD into the unscaled video.

``dynamic_margins``
Default: no

When set to ``yes``, margins follow the actual OSC visibility: they are
applied when the OSC appears and removed when it hides. Without this
option, margins are only applied when ``visibility`` is set to ``always``.

``sub_margins``
Default: yes

Whether to adjust ``--sub-margin-y`` so that subtitles do not overlap
with the OSC. The offset is derived from the bottom OSC margin and added
on top of the current ``--sub-margin-y`` value. Requires
``dynamic_margins`` or ``visibility=always`` to take effect.
Comment on lines +411 to +417
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the default should be no. This will mess with watch-later and any kind of "persistent config" scripts, and I think mpv should not do that out of box.

The boxvideo option does similar thing, but it is disabled by default and points out the caveats in the documentation:

If this option is set, the osc may overwrite the --video-margin-ratio-* options, even if the user has set them.

Copy link
Contributor

@Samillion Samillion Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could disable them by default, and document it, to add in mpv.conf:

watch-later-options-remove=sub-pos
# osd-margin-y is not saved, but just in case
watch-later-options-remove=osd-margin-y 

At least that way no one is bothered by defaults since they're disabled, and those that enable it are aware of what could happen and how to circumvent it.

Copy link
Member Author

@kasper93 kasper93 Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the default should be no. This will mess with watch-later and any kind of "persistent config" scripts, and I think mpv should not do that out of box.

The impact is minimal, user need to change option while the OSC is visible and before the original value is restored. Also note that on shutdown the margins are restored to original state always. Though now that I think about it, it is probably too late in some cases.

This will mess with watch-later

osd/sub margins are not in default watch later options. So only if user explicitly opt-in to saving those, it could be a problem. And since those properties are relating to player state, it is probably very uncommon to save the on per-file watch later config.

Of course we will monitor the user feedback and revert if needed.

Copy link
Contributor

@na-na-hi na-na-hi Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The impact is minimal, user need to change option while the OSC is visible and before the original value is restored. Also note that on shutdown the margins are restored to original state always. Though now that I think about it, it is probably too late in some cases.

Watch later files are written before any scripts are aware of shutdown. If a script saves these option values while running, osc.lua has no way to know about that. And if a script saves these option values on quit, the values saved can depend on the order the scripts quit.

osd/sub margins are not in default watch later options. So only if user explicitly opt-in to saving those, it could be a problem. >And since those properties are relating to player state, it is probably very uncommon to save the on per-file watch later config.

The users have no way to know about this if they do not read the documentation of osc.lua, which is an unnatural place to watch when they are looking at watch later options, and they should have no reason to worry about when choosing which option are changed by mpv.

osc.lua has never done something like this before by default, so this breaks the general expectation that built-in player scripts do not modify these options.

Copy link
Member Author

@kasper93 kasper93 Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usability improvement for vast majority of users outweighs theoretical case of 1 user who wants to save osd margin in his watch later config or sub margin, same thing.

If you think we should prioritize theoretical usecases for single user, we can revert.

EDIT: We can add private internal property dedicated for osc to use if this issue is critical.

Copy link
Contributor

@na-na-hi na-na-hi Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just found out a critical issue caused by this: this affects subtitle position on screenshots, which should not happen because osc/osd are not rendered on screenshots by default, so on the screenshots the margin will be too large.

If you think we should prioritize theoretical usecases for single user, we can revert.

The use case is not theoretical. This config persists sub-margin-y and has 32 stars.

Copy link
Member Author

@kasper93 kasper93 Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you saying that both should not be enabled by default or osd is ok? I initially had subtitles disabled, because I was aware it would cause issues like that, but guido said it should be fine, so not sure.

I just found out a critical issue caused by this: this affects subtitle position on screenshots

It will render at the same position as currently displayed, no? Seems expected behavior. And remember for text subtitles, there is no "correct" position really.

EDIT: Also this is only limited to dynamic_margins=yes or visibility=always, both of which are not default. Nothing changes by default.

Copy link
Contributor

@na-na-hi na-na-hi Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you saying that both should not be enabled by default or osd is ok? I initially had subtitles disabled, because I was aware it would cause issues like that, but guido said it should be fine, so not sure.

I think there are many practical issues with sub-margin that it should be reverted. For osc-margin, I still think it should not be enabled by default (e.g. scripts like this that set and reset osd-margin-y dynamically), although uosc does set osd-margin-y (but not sub-margin-y) in a similar fashion, so it may be less problematic.

It will render at the same position as currently displayed, no? Seems expected behavior. And remember for text subtitles, there is no "correct" position really.

In the context of screenshot, it results in too large margin, which is only there in the context of osc occupying a part of window. And it does nothing for image and ASS subtitles, so it also causes inconsistent behavior.

EDIT: Also this is only limited to dynamic_margins=yes or visibility=always, both of which are not default.

visibility is bound by a key by default, and a user can switch the option at runtime without an intention to change the default config. It is like saying mpv is free to do something strange when volume is not 100 because it is "not default".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

osc can just disable these options if --watch-later-options contains --osd/sub-margin-y (which is an edge case anyway).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@na-na-hi @guidocella: Does #17555 resolve this topic?


With ``boxvideo`` enabled and ``--sub-use-margins=no``, subtitles are
already confined to the video area and this option has no additional
effect.

``osd_margins``
Default: yes

Whether to adjust ``--osd-margin-y`` so that OSD text does not overlap
with the OSC. The offset is derived from the top OSC margin (including
window controls when present) and added on top of the current
``--osd-margin-y`` value. Requires ``dynamic_margins`` or
``visibility=always`` to take effect.

``windowcontrols``
Default: auto (Show window controls if there is no window border)

Expand Down
73 changes: 58 additions & 15 deletions player/lua/osc.lua
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ local user_opts = {
visibility_modes = "never_auto_always", -- visibility modes to cycle through
boxmaxchars = 80, -- title crop threshold for box layout
boxvideo = false, -- apply osc_param.video_margins to video
dynamic_margins = false, -- update margins dynamically with OSC visibility
sub_margins = true, -- adjust sub-margin-y to not overlap with OSC
osd_margins = true, -- adjust osd-margin-y to not overlap with OSC
windowcontrols = "auto", -- whether to show window controls
windowcontrols_alignment = "right", -- which side to show window controls on
windowcontrols_title = "${media-title}", -- same as title but for windowcontrols
Expand Down Expand Up @@ -540,25 +543,39 @@ local function cache_enabled()
return state.cache_state and #state.cache_state["seekable-ranges"] > 0
end

local function set_margin_offset(prop, offset)
if offset > 0 then
if not state[prop] then
state[prop] = mp.get_property_number(prop)
end
mp.set_property_number(prop, state[prop] + offset)
elseif state[prop] then
mp.set_property_number(prop, state[prop])
state[prop] = nil
end
end

local function reset_margins()
if state.using_video_margins then
for _, mopt in ipairs(margins_opts) do
mp.set_property_number(mopt[2], 0.0)
end
state.using_video_margins = false
end
set_margin_offset("sub-margin-y", 0)
set_margin_offset("osd-margin-y", 0)
end

local function update_margins()
local margins = osc_param.video_margins

-- Don't use margins if it's visible only temporarily.
if not state.osc_visible or get_hidetimeout() >= 0 or
(state.fullscreen and not user_opts.showfullscreen) or
(not state.fullscreen and not user_opts.showwindowed)
then
margins = {l = 0, r = 0, t = 0, b = 0}
end
local use_margins = get_hidetimeout() < 0 or user_opts.dynamic_margins
local top_vis = (user_opts.layout:find("top") and state.osc_visible) or state.wc_visible
local bottom_vis = user_opts.layout:find("bottom") and state.osc_visible
local margins = {
l = use_margins and osc_param.video_margins.l or 0,
r = use_margins and osc_param.video_margins.r or 0,
t = (use_margins and top_vis) and osc_param.video_margins.t or 0,
b = (use_margins and bottom_vis) and osc_param.video_margins.b or 0,
}

if user_opts.boxvideo then
-- check whether any margin option has a non-default value
Expand All @@ -585,6 +602,24 @@ local function update_margins()
reset_margins()
end

local function get_margin(ent)
local margin = 0
if user_opts[ent .. "_margins"] then
local align = mp.get_property(ent .. "-align-y")
if align == "top" and top_vis then
margin = margins.t
elseif align == "bottom" and bottom_vis then
margin = margins.b
end
end
if ent == "sub" and user_opts.boxvideo and mp.get_property_bool("sub-use-margins") then
margin = 0
end
return margin * osc_param.playresy
end
set_margin_offset("sub-margin-y", get_margin("sub"))
set_margin_offset("osd-margin-y", get_margin("osd"))

mp.set_property_native("user-data/osc/margins", margins)
end

Expand Down Expand Up @@ -2241,20 +2276,20 @@ local function osc_init()
update_margins()
end

local function set_bar_visible(visible_key, visible, with_margins)
local function set_bar_visible(visible_key, visible)
if state[visible_key] ~= visible then
state[visible_key] = visible
if with_margins then update_margins() end
update_margins()
end
request_tick()
end

local function osc_visible(visible)
set_bar_visible("osc_visible", visible, true)
set_bar_visible("osc_visible", visible)
end

local function set_wc_visible(visible)
set_bar_visible("wc_visible", visible, false)
set_bar_visible("wc_visible", visible)
end

local function show_bar(label, showtime_key, visible_key, anitype_key, set_visible)
Expand Down Expand Up @@ -2413,8 +2448,16 @@ local function process_event(source, what)
)
) then
if window_controls_enabled() and user_opts.windowcontrols_independent then
if mouse_in_area("showhide_wc") then show_wc() else hide_wc() end
if mouse_in_area("showhide") then show_osc() else hide_osc() end
if mouse_in_area("showhide_wc") then
show_wc()
elseif user_opts.visibility ~= "always" then
hide_wc()
end
if mouse_in_area("showhide") then
show_osc()
elseif user_opts.visibility ~= "always" then
hide_osc()
end
else
show_osc()
if window_controls_enabled() then show_wc() end
Expand Down
Loading