History

win32: add Media Control support

Add support for SystemMediaTransportControls interface. This allows to control mpv from Windows media control ui.

Kacper Michajłow committed 2 years ago

Commit 5fed12e1 parent 06da68011 files · +497 −0Browse files

Unified · Split

11 files
  1. DOCS/interface-changes/media-controls.txt +1 −0
  2. DOCS/man/options.rst +7 −0
  3. meson.build +4 −0
  4. meson_options.txt +3 −0
  5. misc/path_utils.c +2 −0
  6. options/options.c +3 −0
  7. options/options.h +1 −0
  8. osdep/win32/meson.build +8 −0
  9. osdep/win32/smtc.cpp +433 −0
  10. osdep/win32/smtc.h +29 −0
  11. player/main.c +6 −0
DOCS/interface-changes/media-controls.txt+1−0
@@ -0,0 +1 @@
1add `--media-controls` option
DOCS/man/options.rst+7−0
@@ -7420,6 +7420,13 @@ Miscellaneous · 7419 unmodified lines
7420 7420
7421 .. warning:: Using realtime priority can cause system lockup.7421 .. warning:: Using realtime priority can cause system lockup.
7422 7422
7423``--media-controls=<yes|player|no>``
7424 (Windows only)
7425 Enable integration of media control interface SystemMediaTransportControls.
7426 If set to ``player``, only the player will use the controls. Setting it to
7427 ``yes`` will also enable the controls for libmpv integrations.
7428 (default: ``player``)
7429
7423``--force-media-title=<string>``7430``--force-media-title=<string>``
7424 Force the contents of the ``media-title`` property to this value. Useful7431 Force the contents of the ``media-title`` property to this value. Useful
7425 for scripts which want to set a title, without overriding the user's7432 for scripts which want to set a title, without overriding the user's
meson.build+4−0
@@ -530,6 +530,10 @@ if not posix and not features['win32-desktop'] · 529 unmodified lines
530 'osdep/terminal-dummy.c')530 'osdep/terminal-dummy.c')
531endif531endif
532 532
533if win32
534 subdir('osdep/win32')
535endif
536
533features += {'glob-posix': cc.has_function('glob', prefix: '#include <glob.h>')}537features += {'glob-posix': cc.has_function('glob', prefix: '#include <glob.h>')}
534 538
535features += {'glob-win32': win32 and not features['glob-posix']}539features += {'glob-win32': win32 and not features['glob-posix']}
meson_options.txt+3−0
@@ -116,6 +116,9 @@ option('macos-touchbar', type: 'feature', value: 'auto', description: 'macOS Tou · 115 unmodified lines
116option('swift-build', type: 'feature', value: 'auto', description: 'macOS Swift build tools')116option('swift-build', type: 'feature', value: 'auto', description: 'macOS Swift build tools')
117option('swift-flags', type: 'string', description: 'Optional Swift compiler flags')117option('swift-flags', type: 'string', description: 'Optional Swift compiler flags')
118 118
119# Windows features
120option('win32-smtc', type: 'feature', value: 'auto', description: 'Enable Media Control support')
121
119# manpages122# manpages
120option('html-build', type: 'feature', value: 'disabled', description: 'HTML manual generation')123option('html-build', type: 'feature', value: 'disabled', description: 'HTML manual generation')
121option('manpage-build', type: 'feature', value: 'auto', description: 'manpage generation')124option('manpage-build', type: 'feature', value: 'auto', description: 'manpage generation')
misc/path_utils.c+2−0
@@ -156,6 +156,8 @@ char *mp_getcwd(void *talloc_ctx) · 155 unmodified lines
156 156
157char *mp_normalize_path(void *talloc_ctx, const char *path)157char *mp_normalize_path(void *talloc_ctx, const char *path)
158{158{
159 assert(talloc_ctx && "mp_normalize_path requires talloc_ctx!");
160
159 if (!path)161 if (!path)
160 return NULL;162 return NULL;
161 163
options/options.c+3−0
@@ -543,6 +543,8 @@ static const m_option_t mp_opts[] = { · 542 unmodified lines
543 {"idle", IDLE_PRIORITY_CLASS}),543 {"idle", IDLE_PRIORITY_CLASS}),
544 .flags = UPDATE_PRIORITY},544 .flags = UPDATE_PRIORITY},
545#endif545#endif
546 {"media-controls", OPT_CHOICE(media_controls,
547 {"no", 0}, {"player", 1}, {"yes", 2})},
546 {"config", OPT_BOOL(load_config), .flags = CONF_PRE_PARSE},548 {"config", OPT_BOOL(load_config), .flags = CONF_PRE_PARSE},
547 {"config-dir", OPT_STRING(force_configdir),549 {"config-dir", OPT_STRING(force_configdir),
548 .flags = CONF_NOCFG | CONF_PRE_PARSE | M_OPT_FILE},550 .flags = CONF_NOCFG | CONF_PRE_PARSE | M_OPT_FILE},
@@ -1039,6 +1041,7 @@ static const struct MPOpts mp_default_opts = { · 490 unmodified lines
1039 .osd_bar_visible = true,1041 .osd_bar_visible = true,
1040 .screenshot_template = "mpv-shot%n",1042 .screenshot_template = "mpv-shot%n",
1041 .play_dir = 1,1043 .play_dir = 1,
1044 .media_controls = 1,
1042 1045
1043 .audiofile_auto_exts = (char *[]){1046 .audiofile_auto_exts = (char *[]){
1044 "aac",1047 "aac",
options/options.h+1−0
@@ -344,6 +344,7 @@ typedef struct MPOpts { · 343 unmodified lines
344 bool osd_bar_visible;344 bool osd_bar_visible;
345 345
346 int w32_priority;346 int w32_priority;
347 int media_controls;
347 348
348 struct bluray_opts *stream_bluray_opts;349 struct bluray_opts *stream_bluray_opts;
349 struct cdda_opts *stream_cdda_opts;350 struct cdda_opts *stream_cdda_opts;
osdep/win32/meson.build+8−0
@@ -0,0 +1,8 @@
1add_languages('cpp', native: false)
2cpp = meson.get_compiler('cpp')
3
4features += {'win32-smtc': cpp.has_header('winrt/base.h', required: get_option('win32-smtc'))}
5if features['win32-smtc']
6 dependencies += cpp.find_library('runtimeobject')
7 sources += meson.current_source_dir() / 'smtc.cpp'
8endif
osdep/win32/smtc.cpp+433−0
@@ -0,0 +1,433 @@
1/*
2 * This file is part of mpv.
3 *
4 * mpv is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * mpv is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "smtc.h"
19
20#include <chrono>
21#include <format>
22#include <utility>
23
24#include <windows.h>
25#include <systemmediatransportcontrolsinterop.h>
26#include <winrt/Windows.Foundation.h>
27#include <winrt/Windows.Media.h>
28
29extern "C" {
30#include "common/msg.h"
31#include "osdep/threads.h"
32#include "player/client.h"
33}
34
35EXTERN_C IMAGE_DOS_HEADER __ImageBase;
36#define WM_MP_EVENT (WM_USER + 1)
37
38using namespace std::chrono_literals;
39using namespace winrt::Windows::Media;
40using winrt::Windows::Foundation::TimeSpan;
41
42struct mpv_deleter {
43 void operator()(void *ptr) const {
44 mpv_free(ptr);
45 }
46};
47using mp_string = std::unique_ptr<char, mpv_deleter>;
48
49template<mpv_format F> struct mp_fmt;
50template<> struct mp_fmt<MPV_FORMAT_FLAG> { using type = int; };
51template<> struct mp_fmt<MPV_FORMAT_INT64> { using type = int64_t; };
52template<> struct mp_fmt<MPV_FORMAT_DOUBLE> { using type = double; };
53
54template<mpv_format F>
55static inline std::optional<typename mp_fmt<F>::type>
56mp_get_property(mpv_handle *mpv, const char *name)
57{
58 typename mp_fmt<F>::type val;
59 if (mpv_get_property(mpv, name, F, &val) != MPV_ERROR_SUCCESS)
60 return std::nullopt;
61 return val;
62}
63
64template<typename T> struct mp_fmt_e;
65template<> struct mp_fmt_e<int> { static constexpr mpv_format value = MPV_FORMAT_FLAG; };
66template<> struct mp_fmt_e<bool> { static constexpr mpv_format value = MPV_FORMAT_FLAG; };
67template<> struct mp_fmt_e<int64_t> { static constexpr mpv_format value = MPV_FORMAT_INT64; };
68template<> struct mp_fmt_e<double> { static constexpr mpv_format value = MPV_FORMAT_DOUBLE; };
69
70template<typename T>
71static inline int mp_set_property(mpv_handle *mpv, const char *name, T &&val)
72{
73 using val_t = std::remove_reference_t<T>;
74 typename mp_fmt<mp_fmt_e<val_t>::value>::type mpv_val = std::forward<T>(val);
75 return mpv_set_property(mpv, name, mp_fmt_e<val_t>::value, &mpv_val);
76}
77
78struct smtc_ctx {
79 mp_log *log;
80 mpv_handle *mpv;
81 SystemMediaTransportControls smtc{ nullptr };
82 std::atomic_bool close{ false };
83 std::atomic<HWND> hwnd{ nullptr };
84};
85
86static void update_state(SystemMediaTransportControls &smtc, mpv_handle *mpv)
87{
88 auto closed = mp_get_property<MPV_FORMAT_FLAG>(mpv, "idle-active");
89 if (!closed.value_or(false)) {
90 auto paused = mp_get_property<MPV_FORMAT_FLAG>(mpv, "pause");
91 smtc.PlaybackStatus(paused.value_or(true) ? MediaPlaybackStatus::Paused : MediaPlaybackStatus::Playing);
92 smtc.IsPlayEnabled(true);
93 smtc.IsPauseEnabled(true);
94 smtc.IsStopEnabled(true);
95 auto ch_index = mp_get_property<MPV_FORMAT_INT64>(mpv, "chapter");
96 auto ch_count = mp_get_property<MPV_FORMAT_INT64>(mpv, "chapter-list/count");
97 auto pl_count = mp_get_property<MPV_FORMAT_INT64>(mpv, "playlist-count");
98 smtc.IsNextEnabled(pl_count > 1 || ch_count > ch_index.value_or(0));
99 smtc.IsPreviousEnabled(pl_count > 1 || ch_index > 0);
100 smtc.IsRewindEnabled(true);
101 } else {
102 smtc.PlaybackStatus(MediaPlaybackStatus::Closed);
103 smtc.IsPlayEnabled(false);
104 smtc.IsPauseEnabled(false);
105 smtc.IsStopEnabled(false);
106 smtc.IsNextEnabled(false);
107 smtc.IsPreviousEnabled(false);
108 smtc.IsRewindEnabled(false);
109 }
110
111 auto shuffle = mp_get_property<MPV_FORMAT_FLAG>(mpv, "shuffle");
112 smtc.ShuffleEnabled(shuffle.value_or(false));
113
114 auto speed = mp_get_property<MPV_FORMAT_DOUBLE>(mpv, "speed");
115 smtc.PlaybackRate(speed.value_or(1.0));
116
117 mp_string loop_file_opt{ mpv_get_property_string(mpv, "loop-file") };
118 bool loop_file = loop_file_opt && strcmp(loop_file_opt.get(), "no");
119 mp_string loop_playlist_opt{ mpv_get_property_string(mpv, "loop-playlist") };
120 bool loop_playlist = loop_playlist_opt && strcmp(loop_playlist_opt.get(), "no");
121 if (loop_file) {
122 smtc.AutoRepeatMode(MediaPlaybackAutoRepeatMode::Track);
123 } else if (loop_playlist) {
124 smtc.AutoRepeatMode(MediaPlaybackAutoRepeatMode::List);
125 } else {
126 smtc.AutoRepeatMode(MediaPlaybackAutoRepeatMode::None);
127 }
128
129 auto pos = mp_get_property<MPV_FORMAT_DOUBLE>(mpv, "time-pos");
130 auto duration = mp_get_property<MPV_FORMAT_DOUBLE>(mpv, "duration");
131
132 if (!pos || !duration)
133 return;
134
135 SystemMediaTransportControlsTimelineProperties tl;
136 tl.StartTime(0s);
137 tl.MinSeekTime(0s);
138 tl.Position(std::chrono::duration_cast<TimeSpan>(std::chrono::duration<double>(*pos)));
139 tl.MaxSeekTime(std::chrono::duration_cast<TimeSpan>(std::chrono::duration<double>(*duration)));
140 tl.EndTime(std::chrono::duration_cast<TimeSpan>(std::chrono::duration<double>(*duration)));
141 smtc.UpdateTimelineProperties(tl);
142}
143
144static void update_metadata(SystemMediaTransportControls &smtc, smtc_ctx &ctx)
145{
146 auto *mpv = ctx.mpv;
147 auto updater = smtc.DisplayUpdater();
148 updater.ClearAll();
149
150 auto image_opt = mp_get_property<MPV_FORMAT_FLAG>(mpv, "current-tracks/video/image");
151 bool video = bool(image_opt);
152 bool image = image_opt.value_or(false);
153 auto audio = mp_get_property<MPV_FORMAT_FLAG>(mpv, "current-tracks/audio/selected");
154
155 if (!video && !image && !audio)
156 return;
157
158 mp_string title{ mpv_get_property_osd_string(mpv, "media-title") };
159 if (video && !image) {
160 updater.Type(MediaPlaybackType::Video);
161 const auto &props = updater.VideoProperties();
162 if (title)
163 props.Title(winrt::to_hstring(title.get()));
164 auto ch_index = mp_get_property<MPV_FORMAT_INT64>(mpv, "chapter").value_or(-1);
165 if (ch_index >= 0) {
166 mp_string ch_title {
167 mpv_get_property_string(mpv, std::format("chapter-list/{}/title", ch_index).c_str())
168 };
169 if (ch_title) {
170 auto ch_count = mp_get_property<MPV_FORMAT_INT64>(mpv, "chapter-list/count").value_or(0);
171 props.Subtitle(winrt::to_hstring(std::format("{} ({}/{})", ch_title.get(), ch_index + 1, ch_count)));
172 }
173 }
174 } else if (image && !audio) {
175 updater.Type(MediaPlaybackType::Image);
176 const auto &props = updater.ImageProperties();
177 if (title)
178 props.Title(winrt::to_hstring(title.get()));
179 } else {
180 updater.Type(MediaPlaybackType::Music);
181 const auto &props = updater.MusicProperties();
182 if (title)
183 props.Title(winrt::to_hstring(title.get()));
184 if (mp_string str{ mpv_get_property_string(mpv, "metadata/by-key/Album_Artist") })
185 props.AlbumArtist(winrt::to_hstring(str.get()));
186 if (mp_string str{ mpv_get_property_string(mpv, "metadata/by-key/Album") })
187 props.AlbumTitle(winrt::to_hstring(str.get()));
188 if (mp_string str{ mpv_get_property_string(mpv, "metadata/by-key/Album_Track_Count") })
189 props.AlbumTrackCount(std::atoi(str.get()));
190 if (mp_string str{ mpv_get_property_string(mpv, "metadata/by-key/Artist") })
191 props.Artist(winrt::to_hstring(str.get()));
192 if (mp_string str{ mpv_get_property_string(mpv, "metadata/by-key/Track") })
193 props.TrackNumber(std::atoi(str.get()));
194 }
195
196 updater.Update();
197}
198
199static void handle_mp_event(smtc_ctx *ctx, mpv_event *event)
200{
201 if (!ctx || !ctx->smtc || !ctx->mpv || ctx->close)
202 return;
203
204 try {
205 update_state(ctx->smtc, ctx->mpv);
206 if (event->event_id == MPV_EVENT_PROPERTY_CHANGE) {
207 auto &prop = *static_cast<mpv_event_property *>(event->data);
208 if (!strcmp(prop.name, "time-pos") || !strcmp(prop.name, "duration"))
209 return;
210 }
211 update_metadata(ctx->smtc, *ctx);
212 } catch (const winrt::hresult_error& e) {
213 MP_VERBOSE(ctx, "%s: 0x%x - %ls\n", __func__, int32_t(e.code()), e.message().c_str());
214 }
215}
216
217static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
218{
219 if (uMsg == WM_DESTROY)
220 PostQuitMessage(0);
221
222 smtc_ctx *ctx = reinterpret_cast<smtc_ctx *>(GetWindowLongPtrW(hWnd, GWLP_USERDATA));
223
224 switch (uMsg)
225 {
226 case WM_MP_EVENT:
227 handle_mp_event(ctx, reinterpret_cast<mpv_event *>(lParam));
228 return 0;
229 case WM_SETFOCUS:
230 if (!ctx)
231 return 0;
232 if (auto wid { mp_get_property<MPV_FORMAT_INT64>(ctx->mpv, "window-id") })
233 SetFocus(HWND(*wid));
234 return 0;
235 case WM_ACTIVATE:
236 if (!ctx)
237 return 0;
238 if (auto wid { mp_get_property<MPV_FORMAT_INT64>(ctx->mpv, "window-id") }) {
239 if (IsIconic(HWND(*wid)))
240 ShowWindow(HWND(*wid), SW_RESTORE);
241 BringWindowToTop(HWND(*wid));
242 }
243 return 0;
244 }
245
246 return DefWindowProc(hWnd, uMsg, wParam, lParam);
247}
248
249static MP_THREAD_VOID win_event_loop_fn(void *arg)
250{
251 mp_thread_set_name("smtc/win");
252 auto &ctx = *static_cast<smtc_ctx *>(arg);
253 auto *mpv = ctx.mpv;
254
255 WNDCLASS wc = {
256 .lpfnWndProc = WindowProc,
257 .hInstance = HINSTANCE(&__ImageBase),
258 .hIcon = LoadIconW(HINSTANCE(&__ImageBase), L"IDI_ICON1"),
259 .lpszClassName = L"mpv-smtc"
260 };
261 RegisterClassW(&wc);
262
263 try {
264 // Dummy window is used to allow SMTC to work also in audio only mode,
265 // where VO may not be created.
266 ctx.hwnd = CreateWindowExW(0, wc.lpszClassName, L"mpv smtc",
267 WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
268 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
269 nullptr, nullptr, wc.hInstance, nullptr);
270 if (!ctx.hwnd)
271 winrt::throw_last_error();
272
273 SystemMediaTransportControls &smtc = ctx.smtc;
274 auto interop = winrt::get_activation_factory<SystemMediaTransportControls,
275 ISystemMediaTransportControlsInterop>();
276 HRESULT hr = interop->GetForWindow(ctx.hwnd,
277 winrt::guid_of<SystemMediaTransportControls>(),
278 winrt::put_abi(smtc));
279 if (FAILED(hr))
280 winrt::throw_hresult(hr);
281 SetWindowLongPtrW(ctx.hwnd, GWLP_USERDATA, LONG_PTR(&ctx));
282
283 smtc.IsEnabled(true);
284
285 smtc.ButtonPressed([&](const SystemMediaTransportControls &,
286 const SystemMediaTransportControlsButtonPressedEventArgs &args) {
287 switch (args.Button()) {
288 case SystemMediaTransportControlsButton::Play:
289 mp_set_property(mpv, "pause", false);
290 break;
291 case SystemMediaTransportControlsButton::Pause:
292 mp_set_property(mpv, "pause", true);
293 break;
294 case SystemMediaTransportControlsButton::Stop:
295 mpv_command_string(mpv, "stop");
296 break;
297 case SystemMediaTransportControlsButton::Next: {
298 auto ch_index = mp_get_property<MPV_FORMAT_INT64>(mpv, "chapter").value_or(0);
299 auto ch_count = mp_get_property<MPV_FORMAT_INT64>(mpv, "chapter-list/count");
300 // mpv allows to jump past last chapter
301 mpv_command_string(mpv, ch_index < ch_count ? "add chapter 1" : "playlist-next");
302 break;
303 }
304 case SystemMediaTransportControlsButton::Previous: {
305 auto ch_index = mp_get_property<MPV_FORMAT_INT64>(mpv, "chapter");
306 mpv_command_string(mpv, ch_index > 0 ? "add chapter -1" : "playlist-prev");
307 break;
308 }
309 default:
310 break;
311 }
312 });
313 smtc.PlaybackPositionChangeRequested([&](const SystemMediaTransportControls &,
314 const PlaybackPositionChangeRequestedEventArgs &args) {
315 auto position = args.RequestedPlaybackPosition();
316 auto pos = std::chrono::duration_cast<std::chrono::duration<double>>(position).count();
317 mp_set_property(mpv, "time-pos", pos);
318 });
319 smtc.PlaybackRateChangeRequested([&](const SystemMediaTransportControls &,
320 const PlaybackRateChangeRequestedEventArgs &args) {
321 mp_set_property(mpv, "speed", args.RequestedPlaybackRate());
322 });
323 smtc.ShuffleEnabledChangeRequested([&](const SystemMediaTransportControls &,
324 const ShuffleEnabledChangeRequestedEventArgs &args) {
325 mp_set_property(mpv, "shuffle", args.RequestedShuffleEnabled());
326 });
327 smtc.AutoRepeatModeChangeRequested([&](const SystemMediaTransportControls &,
328 const AutoRepeatModeChangeRequestedEventArgs &args) {
329 bool loop_file = false, loop_playlist = false;
330 switch (args.RequestedAutoRepeatMode()) {
331 case MediaPlaybackAutoRepeatMode::Track:
332 loop_file = true;
333 break;
334 case MediaPlaybackAutoRepeatMode::List:
335 loop_playlist = true;
336 break;
337 case MediaPlaybackAutoRepeatMode::None:
338 break;
339 }
340 mp_set_property(mpv, "loop-file", loop_file);
341 mp_set_property(mpv, "loop-playlist", loop_playlist);
342 });
343
344 MSG msg;
345 while(BOOL ret = GetMessageW(&msg, nullptr, 0, 0)) {
346 if (ret == -1)
347 winrt::throw_last_error();
348 TranslateMessage(&msg);
349 DispatchMessageW(&msg);
350 }
351 } catch (const winrt::hresult_error& e) {
352 MP_ERR(&ctx, "%s: 0x%x - %ls\n", __func__, int32_t(e.code()), e.message().c_str());
353 }
354
355 ctx.close = true;
356 mpv_wakeup(mpv);
357 HWND hwnd = ctx.hwnd;
358 ctx.hwnd = nullptr;
359 DestroyWindow(hwnd);
360 UnregisterClassW(wc.lpszClassName, HINSTANCE(&__ImageBase));
361
362 MP_THREAD_RETURN();
363}
364
365static MP_THREAD_VOID mpv_event_loop_fn(void *arg)
366{
367 mp_thread_set_name("smtc/mpv");
368 auto mpv = static_cast<mpv_handle *>(arg);
369 smtc_ctx ctx = {
370 .log = mp_client_get_log(mpv),
371 .mpv = mpv
372 };
373
374 // Create a dedicated window and event loop. We could use the mpv main window,
375 // but it is not always available, especially in audio-only/console mode.
376 mp_thread win_event_loop;
377 if (mp_thread_create(&win_event_loop, win_event_loop_fn, &ctx)) {
378 MP_ERR(&ctx, "Failed to create window event thread!\n");
379 goto error;
380 }
381
382 // It is recommended that you keep the system controls in sync with your
383 // media playback by updating these properties approximately every 5 seconds
384 // during playback and again whenever the state of playback changes, such as
385 // pausing or seeking to a new position.
386 // https://learn.microsoft.com/windows/uwp/audio-video-camera/system-media-transport-controls
387 // For simplicity we observe time-pos and duration as integers, so we get
388 // update every second, faster than recommended, but should be fine.
389
390 mpv_observe_property(mpv, 0, "current-tracks", MPV_FORMAT_NODE_MAP);
391 mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_INT64);
392 mpv_observe_property(mpv, 0, "idle-active", MPV_FORMAT_FLAG);
393 mpv_observe_property(mpv, 0, "media-title", MPV_FORMAT_STRING);
394 mpv_observe_property(mpv, 0, "metadata", MPV_FORMAT_NODE_MAP);
395 mpv_observe_property(mpv, 0, "pause", MPV_FORMAT_FLAG);
396 mpv_observe_property(mpv, 0, "shuffle", MPV_FORMAT_DOUBLE);
397 mpv_observe_property(mpv, 0, "speed", MPV_FORMAT_DOUBLE);
398 mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_INT64);
399 // TODO: Options are not observable, fix me!
400 mpv_observe_property(mpv, 0, "loop-file", MPV_FORMAT_DOUBLE);
401 mpv_observe_property(mpv, 0, "loop-playlist", MPV_FORMAT_DOUBLE);
402
403 while (!ctx.close) {
404 mpv_event *event = mpv_wait_event(mpv, -1);
405 if (ctx.close)
406 break;
407 if (event->event_id == MPV_EVENT_SHUTDOWN) {
408 HWND hwnd = ctx.hwnd;
409 if (hwnd)
410 PostMessageW(hwnd, WM_CLOSE, 0, 0);
411 break;
412 }
413 if (event->event_id == MPV_EVENT_PROPERTY_CHANGE ||
414 event->event_id == MPV_EVENT_PLAYBACK_RESTART)
415 {
416 HWND hwnd = ctx.hwnd;
417 if (hwnd)
418 SendMessageW(hwnd, WM_MP_EVENT, 0, LPARAM(event));
419 }
420 }
421 mp_thread_join(win_event_loop);
422
423error:
424 mpv_destroy(mpv);
425 MP_THREAD_RETURN();
426}
427
428void mp_smtc_init(mpv_handle *mpv)
429{
430 mp_thread mpv_event_loop;
431 if (!mp_thread_create(&mpv_event_loop, mpv_event_loop_fn, mpv))
432 mp_thread_detach(mpv_event_loop);
433}
osdep/win32/smtc.h+29−0
@@ -0,0 +1,29 @@
1/*
2 * This file is part of mpv.
3 *
4 * mpv is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * mpv is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#pragma once
19
20#ifdef __cplusplus
21extern "C" {
22#endif
23
24typedef struct mpv_handle mpv_handle;
25void mp_smtc_init(mpv_handle *client);
26
27#ifdef __cplusplus
28}
29#endif
player/main.c+6−0
@@ -36,6 +36,7 @@ · 35 unmodified lines
36#include "osdep/threads.h"36#include "osdep/threads.h"
37#include "osdep/timer.h"37#include "osdep/timer.h"
38#include "osdep/main-fn.h"38#include "osdep/main-fn.h"
39#include "osdep/win32/smtc.h"
39 40
40#include "common/av_log.h"41#include "common/av_log.h"
41#include "common/codecs.h"42#include "common/codecs.h"
@@ -399,6 +400,11 @@ int mp_initialize(struct MPContext *mpctx, char **options) · 357 unmodified lines
399 cocoa_set_mpv_handle(ctx);400 cocoa_set_mpv_handle(ctx);
400#endif401#endif
401 402
403#if defined(HAVE_WIN32_SMTC) && HAVE_WIN32_SMTC
404 if (opts->media_controls == 2 || (mpctx->is_cli && opts->media_controls == 1))
405 mp_smtc_init(mp_new_client(mpctx->clients, "SystemMediaTransportControls"));
406#endif
407
402 if (opts->encode_opts->file && opts->encode_opts->file[0]) {408 if (opts->encode_opts->file && opts->encode_opts->file[0]) {
403 mpctx->encode_lavc_ctx = encode_lavc_init(mpctx->global);409 mpctx->encode_lavc_ctx = encode_lavc_init(mpctx->global);
404 if(!mpctx->encode_lavc_ctx) {410 if(!mpctx->encode_lavc_ctx) {

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t