win32/smtc: add thumbnail support

This commit is contained in:
Kacper Michajłow 2024-06-15 01:05:15 +02:00
parent 3bef4a8599
commit 6a43514097
1 changed files with 114 additions and 1 deletions

View File

@ -24,10 +24,15 @@
#include <windows.h> #include <windows.h>
#include <systemmediatransportcontrolsinterop.h> #include <systemmediatransportcontrolsinterop.h>
#include <winrt/Windows.Foundation.h> #include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Graphics.Imaging.h>
#include <winrt/Windows.Media.h> #include <winrt/Windows.Media.h>
#include <winrt/Windows.Storage.h>
#include <winrt/Windows.Storage.Streams.h>
extern "C" { extern "C" {
#include "common/msg.h" #include "common/msg.h"
#include "misc/node.h"
#include "misc/path_utils.h"
#include "osdep/threads.h" #include "osdep/threads.h"
#include "player/client.h" #include "player/client.h"
} }
@ -36,8 +41,11 @@ EXTERN_C IMAGE_DOS_HEADER __ImageBase;
#define WM_MP_EVENT (WM_USER + 1) #define WM_MP_EVENT (WM_USER + 1)
using namespace std::chrono_literals; using namespace std::chrono_literals;
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Graphics::Imaging;
using namespace winrt::Windows::Media; using namespace winrt::Windows::Media;
using winrt::Windows::Foundation::TimeSpan; using namespace winrt::Windows::Storage;
using namespace winrt::Windows::Storage::Streams;
struct mpv_deleter { struct mpv_deleter {
void operator()(void *ptr) const { void operator()(void *ptr) const {
@ -45,11 +53,29 @@ struct mpv_deleter {
} }
}; };
using mp_string = std::unique_ptr<char, mpv_deleter>; using mp_string = std::unique_ptr<char, mpv_deleter>;
using talloc_ctx = std::unique_ptr<void, mpv_deleter>;
struct mp_node : mpv_node {
mp_node() = default;
~mp_node() {
if (valid)
mpv_free_node_contents(static_cast<mpv_node *>(this));
}
mp_node(const mp_node &) = delete;
mp_node& operator=(const mp_node &) = delete;
mp_node& operator=(mp_node &&) = delete;
mp_node(mp_node &&o) noexcept
: mpv_node(static_cast<mpv_node &>(o)),
valid(std::exchange(o.valid, false)) {}
bool valid = false;
};
template<mpv_format F> struct mp_fmt; template<mpv_format F> struct mp_fmt;
template<> struct mp_fmt<MPV_FORMAT_FLAG> { using type = int; }; template<> struct mp_fmt<MPV_FORMAT_FLAG> { using type = int; };
template<> struct mp_fmt<MPV_FORMAT_INT64> { using type = int64_t; }; template<> struct mp_fmt<MPV_FORMAT_INT64> { using type = int64_t; };
template<> struct mp_fmt<MPV_FORMAT_DOUBLE> { using type = double; }; template<> struct mp_fmt<MPV_FORMAT_DOUBLE> { using type = double; };
template<> struct mp_fmt<MPV_FORMAT_NODE> { using type = mp_node; };
template<mpv_format F> template<mpv_format F>
static inline std::optional<typename mp_fmt<F>::type> static inline std::optional<typename mp_fmt<F>::type>
@ -58,6 +84,8 @@ mp_get_property(mpv_handle *mpv, const char *name)
typename mp_fmt<F>::type val; typename mp_fmt<F>::type val;
if (mpv_get_property(mpv, name, F, &val) != MPV_ERROR_SUCCESS) if (mpv_get_property(mpv, name, F, &val) != MPV_ERROR_SUCCESS)
return std::nullopt; return std::nullopt;
if constexpr (F == MPV_FORMAT_NODE)
val.valid = true;
return val; return val;
} }
@ -79,6 +107,7 @@ struct smtc_ctx {
mp_log *log; mp_log *log;
mpv_handle *mpv; mpv_handle *mpv;
SystemMediaTransportControls smtc{ nullptr }; SystemMediaTransportControls smtc{ nullptr };
IAsyncOperation<FileProperties::StorageItemThumbnail> thumb_async{ nullptr };
std::atomic_bool close{ false }; std::atomic_bool close{ false };
std::atomic<HWND> hwnd{ nullptr }; std::atomic<HWND> hwnd{ nullptr };
}; };
@ -141,6 +170,85 @@ static void update_state(SystemMediaTransportControls &smtc, mpv_handle *mpv)
smtc.UpdateTimelineProperties(tl); smtc.UpdateTimelineProperties(tl);
} }
static void update_thumbnail(SystemMediaTransportControls &smtc, smtc_ctx &ctx)
{
auto track_list = mp_get_property<MPV_FORMAT_NODE>(ctx.mpv, "track-list");
if (!track_list)
return;
assert(track_list->format == MPV_FORMAT_NODE_ARRAY);
auto list = track_list->u.list;
const char *thumbnail = nullptr;
mp_string filepath{ mpv_get_property_string(ctx.mpv, "path") };
// Get first albumart or image from tracks
for (int i = 0; i < list->num; ++i) {
mpv_node *img = node_map_get(list->values + i, "image");
if (!img || img->format != MPV_FORMAT_FLAG || !img->u.flag)
continue;
// If this is the only track selected and image, try to use it directly
if (list->num == 1)
thumbnail = filepath.get();
mpv_node *file = node_map_get(list->values + i, "external-filename");
if (!file || file->format != MPV_FORMAT_STRING || !file->u.string)
continue;
// Select first image found
if (!thumbnail)
thumbnail = file->u.string;
mpv_node *art = node_map_get(list->values + i, "albumart");
if (!art || art->format != MPV_FORMAT_FLAG || !art->u.flag)
continue;
// Select first albumart found
thumbnail = file->u.string;
break;
}
try {
if (thumbnail) {
talloc_ctx ta_ctx{ talloc_new(nullptr) };
mp_string normalized_path{ mp_normalize_path(ta_ctx.get(), thumbnail) };
MP_TRACE(&ctx, "Using thumbnail: %s\n", normalized_path.get());
auto hstr = winrt::to_hstring(normalized_path.get());
RandomAccessStreamReference stream_ref{ nullptr };
if (mp_is_url(bstr0(thumbnail))) {
stream_ref = RandomAccessStreamReference::CreateFromUri(Uri(hstr));
} else {
auto storage_file = StorageFile::GetFileFromPathAsync(hstr);
stream_ref = RandomAccessStreamReference::CreateFromFile(storage_file.get());
}
if (!stream_ref)
return;
auto updater = smtc.DisplayUpdater();
updater.Thumbnail(stream_ref);
} else {
if (filepath && !mp_is_url(bstr0(filepath.get()))) {
talloc_ctx ta_ctx{ talloc_new(nullptr) };
mp_string path{ mp_normalize_path(ta_ctx.get(), filepath.get()) };
MP_TRACE(&ctx, "Generating thumbnail for: %s\n", path.get());
auto storage_file = StorageFile::GetFileFromPathAsync(winrt::to_hstring(path.get()));
if (ctx.thumb_async)
ctx.thumb_async.Cancel();
ctx.thumb_async = storage_file.get().GetThumbnailAsync(FileProperties::ThumbnailMode::SingleItem, 240);
ctx.thumb_async.Completed([&ctx](auto &&async, const AsyncStatus &status) {
if (status != AsyncStatus::Completed || ctx.close)
return;
auto stream_ref = RandomAccessStreamReference::CreateFromStream(async.GetResults());
auto updater = ctx.smtc.DisplayUpdater();
updater.Thumbnail(stream_ref);
updater.Update();
});
}
}
} catch (const winrt::hresult_error& e) {
MP_VERBOSE(&ctx, "%s: 0x%x - %ls\n", __func__, int32_t(e.code()), e.message().c_str());
}
}
static void update_metadata(SystemMediaTransportControls &smtc, smtc_ctx &ctx) static void update_metadata(SystemMediaTransportControls &smtc, smtc_ctx &ctx)
{ {
auto *mpv = ctx.mpv; auto *mpv = ctx.mpv;
@ -155,6 +263,8 @@ static void update_metadata(SystemMediaTransportControls &smtc, smtc_ctx &ctx)
if (!video && !image && !audio) if (!video && !image && !audio)
return; return;
update_thumbnail(smtc, ctx);
mp_string title{ mpv_get_property_osd_string(mpv, "media-title") }; mp_string title{ mpv_get_property_osd_string(mpv, "media-title") };
if (video && !image) { if (video && !image) {
updater.Type(MediaPlaybackType::Video); updater.Type(MediaPlaybackType::Video);
@ -356,6 +466,8 @@ static MP_THREAD_VOID win_event_loop_fn(void *arg)
mpv_wakeup(mpv); mpv_wakeup(mpv);
HWND hwnd = ctx.hwnd; HWND hwnd = ctx.hwnd;
ctx.hwnd = nullptr; ctx.hwnd = nullptr;
if (ctx.thumb_async)
ctx.thumb_async.Cancel();
DestroyWindow(hwnd); DestroyWindow(hwnd);
UnregisterClassW(wc.lpszClassName, HINSTANCE(&__ImageBase)); UnregisterClassW(wc.lpszClassName, HINSTANCE(&__ImageBase));
@ -396,6 +508,7 @@ static MP_THREAD_VOID mpv_event_loop_fn(void *arg)
mpv_observe_property(mpv, 0, "shuffle", MPV_FORMAT_DOUBLE); mpv_observe_property(mpv, 0, "shuffle", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "speed", MPV_FORMAT_DOUBLE); mpv_observe_property(mpv, 0, "speed", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_INT64); mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_INT64);
mpv_observe_property(mpv, 0, "track-list", MPV_FORMAT_NODE);
// TODO: Options are not observable, fix me! // TODO: Options are not observable, fix me!
mpv_observe_property(mpv, 0, "loop-file", MPV_FORMAT_DOUBLE); mpv_observe_property(mpv, 0, "loop-file", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "loop-playlist", MPV_FORMAT_DOUBLE); mpv_observe_property(mpv, 0, "loop-playlist", MPV_FORMAT_DOUBLE);