Выход не ждёт ядро mpv: под Wine это ожидание не кончалось

Журнал закрытия показал место точно: последняя запись «Закрытие: закрываю
плеер», дальше ничего. Значит, зависание внутри mpv_terminate_destroy —
она не просто отпускает клиента, а ждёт, пока ядро mpv свернёт работу и
добьёт свои потоки. Под Linux это мгновенно, под Wine не кончалось вовсе,
причём даже когда ничего не открывали.

Теперь плеер отсоединяется через mpv_destroy: она отпускает клиента и
возвращает управление сразу, а ядро без клиентов сворачивается само.
Команда «завершиться» перед этим намеренно не посылается — её тоже
выполняет ядро, и встать она могла бы точно так же. Если в библиотеке
mpv_destroy не окажется, остаётся прежний путь с ожиданием, и в журнале
будет видно, что пошли по нему.

Отсюда следствие: libmpv больше не выгружается при выходе. Потоки ядра в
этот момент могут ещё жить внутри неё, и выдёргивать её из-под них
нельзя. Программа всё равно закрывается, память освободит система.

Плеер получил свою строчку в журнале — теперь видно и отсоединение, и
завершение, а не только шаги окна.
This commit is contained in:
4aiman 2026-08-21 13:27:27 +03:00
commit 1e6da9b592
3 changed files with 54 additions and 5 deletions

View file

@ -399,8 +399,14 @@ begin
FreeAndNil(FSettings);
Log('Закрытие: закрываю плеер.');
FreeAndNil(FPlayer);
Log('Закрытие: выгружаю libmpv.');
UnloadMpvLib;
{ Библиотеку не выгружаем.
Плеер отсоединяется от mpv, не дожидаясь, пока ядро добьёт свои
потоки, — иначе под Wine ожидание не кончалось вовсе. Значит, в
момент выхода эти потоки могут ещё жить внутри библиотеки, и
выгружать её из-под них нельзя. Программа всё равно закрывается,
и система освободит всё сама. }
Log('Закрытие: освобождаю проект.');
FreeAndNil(FProject);
Log('Закрытие завершено.');

View file

@ -104,6 +104,10 @@ var
mpv_create: function: Pmpv_handle; cdecl;
mpv_initialize: function(ctx: Pmpv_handle): cint; cdecl;
mpv_terminate_destroy: procedure(ctx: Pmpv_handle); cdecl;
{ Необязательная: отсоединяет клиента, не дожидаясь, пока ядро добьёт
свои потоки. Есть в libmpv с давних версий, но если вдруг не найдётся,
работаем без неё. }
mpv_destroy: procedure(ctx: Pmpv_handle); cdecl;
mpv_set_option_string: function(ctx: Pmpv_handle; name, data: PAnsiChar): cint; cdecl;
mpv_set_property: function(ctx: Pmpv_handle; name: PAnsiChar; format: cint;
data: Pointer): cint; cdecl;
@ -279,6 +283,8 @@ begin
Need('mpv_request_log_messages', Pointer(mpv_request_log_messages));
Need('mpv_wait_event', Pointer(mpv_wait_event));
Need('mpv_event_name', Pointer(mpv_event_name));
{ Без Need: её отсутствие не повод отказываться от предпросмотра. }
Bind('mpv_destroy', Pointer(mpv_destroy));
if Missing <> '' then
begin

View file

@ -97,6 +97,9 @@ type
вовсе — он приходил из видеофайла, а не с звуковой дорожки. }
procedure SelectExternalAudio;
{ Цепочка смешивания звуковых дорожек с их громкостями. }
{ Собственная запись в журнал программы. Идёт тем же путём, что и
сообщения самого mpv: у окна для них уже есть обработчик. }
procedure Note(const AText: string);
procedure BuildAudioGraph;
function SetOption(const AName, AValue: string): Integer;
public
@ -377,16 +380,50 @@ begin
FPump.OnTimer := @PumpEvents;
end;
procedure TMpvPlayer.Note(const AText: string);
begin
if Assigned(FOnLog) then
FOnLog(Self, 'nlve', 'info', AText);
end;
{ Закрытие плеера.
Порядок здесь не случайный, и стоил зависания под Wine.
mpv_terminate_destroy не просто отпускает клиента — она ждёт, пока ядро
mpv свернёт всю свою работу и добьёт свои потоки. Под Wine это ожидание
не кончалось никогда: журнал обрывался на «закрываю плеер», и дальше
ничего. Под Linux та же функция отрабатывает мгновенно.
Поэтому отсоединяемся, не дожидаясь ядра: mpv_destroy отпускает клиента
и возвращает управление сразу, а ядро без клиентов сворачивается само.
Программа в этот момент и так закрывается — ждать её незачем.
Команду «завершиться» перед этим не посылаем намеренно: она тоже
выполняется ядром и под Wine могла бы встать точно так же. Отсоединение
ни от чего не зависит.
Окно, в которое рисовал mpv, убирается только после этого: в обратном
порядке он успел бы обратиться к уже несуществующему окну. }
destructor TMpvPlayer.Destroy;
begin
FPump.Enabled := False;
FreeAndNil(FPump);
{ Сначала гасим mpv, и только потом убираем окно, в которое он рисовал:
в обратном порядке он успел бы обратиться к уже несуществующему окну. }
if FHandle <> nil then
begin
mpv_terminate_destroy(FHandle);
if Assigned(mpv_destroy) then
begin
Note('плеер: отсоединяюсь от mpv');
mpv_destroy(FHandle);
end
else
begin
{ Старая libmpv без mpv_destroy: другого способа нет, ждём. }
Note('плеер: закрываю mpv с ожиданием');
mpv_terminate_destroy(FHandle);
end;
FHandle := nil;
Note('плеер: закрыт');
end;
{$IFDEF LCLGTK2}
if (FVideoWin <> 0) and (FDisplay <> nil) then