diff --git a/src/mpc-hc/DpiHelper.cpp b/src/mpc-hc/DpiHelper.cpp index 045f7d8a42e..47572a72e8c 100644 --- a/src/mpc-hc/DpiHelper.cpp +++ b/src/mpc-hc/DpiHelper.cpp @@ -39,11 +39,9 @@ namespace } MONITOR_DPI_TYPE; typedef int (WINAPI* tpGetSystemMetricsForDpi)(int nIndex, UINT dpi); - HRESULT WINAPI GetDpiForMonitor(HMONITOR hmonitor, MONITOR_DPI_TYPE dpiType, UINT* dpiX, UINT* dpiY); - BOOL WINAPI SystemParametersInfoForDpi(UINT uiAction, UINT uiParam, PVOID pvParam, UINT fWinIni, UINT dpi); - int WINAPI GetSystemMetricsForDpi(int nIndex); - UINT WINAPI GetDpiForWindow(HWND hwnd); - double WINAPI TextScaleFactor(void); + typedef HRESULT WINAPI tpGetDpiForMonitor(HMONITOR hmonitor, MONITOR_DPI_TYPE dpiType, UINT* dpiX, UINT* dpiY); + typedef BOOL WINAPI tpSystemParametersInfoForDpi(UINT uiAction, UINT uiParam, PVOID pvParam, UINT fWinIni, UINT dpi); + typedef int WINAPI tpGetSystemMetricsForDpiFunc(int nIndex, UINT dpi); } DpiHelper::DpiHelper() @@ -56,17 +54,8 @@ DpiHelper::DpiHelper() m_dpiy = m_sdpiy; } -UINT DpiHelper::GetDPIForWindow(HWND wnd) { - const WinapiFunc - fnGetDpiForWindow = { _T("user32.dll"), "GetDpiForWindow" }; - if (fnGetDpiForWindow) { - return fnGetDpiForWindow(wnd); - } - return 0; -} - UINT DpiHelper::GetDPIForMonitor(HMONITOR hMonitor) { - const WinapiFunc + const WinapiFunc fnGetDpiForMonitor = { _T("Shcore.dll"), "GetDpiForMonitor" }; if (hMonitor && fnGetDpiForMonitor) { @@ -80,7 +69,7 @@ UINT DpiHelper::GetDPIForMonitor(HMONITOR hMonitor) { void DpiHelper::Override(HWND hWindow) { - const WinapiFunc + const WinapiFunc fnGetDpiForMonitor = { _T("Shcore.dll"), "GetDpiForMonitor" }; if (hWindow && fnGetDpiForMonitor) { @@ -121,7 +110,7 @@ void DpiHelper::GetMessageFont(LOGFONT* lf) { } bool DpiHelper::GetNonClientMetrics(PNONCLIENTMETRICSW ncm, bool& dpiCorrected) { - const WinapiFunc + const WinapiFunc fnSystemParametersInfoForDpi = { L"user32.dll", "SystemParametersInfoForDpi" }; ZeroMemory(ncm, sizeof(NONCLIENTMETRICS)); @@ -141,19 +130,15 @@ bool DpiHelper::GetNonClientMetrics(PNONCLIENTMETRICSW ncm, bool& dpiCorrected) } int DpiHelper::GetSystemMetrics(int type) { - const WinapiFunc + const WinapiFunc fnGetSystemMetricsForDpi = { L"user32.dll", "GetSystemMetricsForDpi" }; - bool dpiCorrected = false; - if (fnGetSystemMetricsForDpi) { - dpiCorrected = true; - return fnGetSystemMetricsForDpi(type); - } - if (!dpiCorrected) { - int ret = fnGetSystemMetricsForDpi(type); - return ScaleSystemToOverrideY(ret); + return fnGetSystemMetricsForDpi(type, m_dpix); } + + int ret = ::GetSystemMetrics(type); + return ScaleSystemToOverrideY(ret); } bool DpiHelper::CanUsePerMonitorV2() { diff --git a/src/mpc-hc/DpiHelper.h b/src/mpc-hc/DpiHelper.h index d7f8b421cfc..fed025e6b36 100644 --- a/src/mpc-hc/DpiHelper.h +++ b/src/mpc-hc/DpiHelper.h @@ -33,7 +33,6 @@ class DpiHelper final void GetMessageFont(LOGFONT* lf); bool GetNonClientMetrics(PNONCLIENTMETRICSW, bool& dpiCorrected); int GetSystemMetrics(int type); - static UINT GetDPIForWindow(HWND wnd); static UINT GetDPIForMonitor(HMONITOR hMonitor); static double GetTextScaleFactor(); int CalculateListCtrlItemHeight(CListCtrl* wnd); diff --git a/src/mpc-hc/MainFrm.cpp b/src/mpc-hc/MainFrm.cpp index 79504698250..0a4a4345ae2 100644 --- a/src/mpc-hc/MainFrm.cpp +++ b/src/mpc-hc/MainFrm.cpp @@ -113,6 +113,8 @@ #include "stb/stb_image.h" #include "stb/stb_image_resize2.h" +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb/stb_image_write.h" #include #undef SubclassWindow @@ -2209,6 +2211,11 @@ void CMainFrame::OnTimer(UINT_PTR nIDEvent) } m_wndStatusBar.SetStatusTimer(rtNow, rtDur, IsSubresyncBarVisible(), GetTimeFormat()); + + // Update media transport controls timeline + if (m_media_trans_control.IsActive() && rtDur > 0) { + m_media_trans_control.UpdateTimelineProperties(0, rtDur, rtNow); + } } break; case PM_DVD: @@ -2239,6 +2246,11 @@ void CMainFrame::OnTimer(UINT_PTR nIDEvent) } } m_wndStatusBar.SetStatusTimer(rtNow, rtDur, IsSubresyncBarVisible(), GetTimeFormat()); + + // Update media transport controls timeline for DVD + if (m_media_trans_control.IsActive() && rtDur > 0) { + m_media_trans_control.UpdateTimelineProperties(0, rtDur, rtNow); + } break; case PM_ANALOG_CAPTURE: g_bExternalSubtitleTime = true; @@ -5644,18 +5656,24 @@ bool CMainFrame::GetDIB(BYTE** ppData, long& size, bool fSilent) return true; } -void CMainFrame::SaveDIB(LPCTSTR fn, BYTE* pData, long size) -{ - CPath path(fn); +// Callback for stb_image_write to append to vector +static void stbi_write_to_vector(void* context, void* data, int size) { + std::vector* vec = (std::vector*)context; + size_t oldSize = vec->size(); + vec->resize(oldSize + size); + memcpy(vec->data() + oldSize, data, size); +} +BYTE* CMainFrame::ConvertDIBTo24bppRGB(BYTE* pData, long size, int& outWidth, int& outHeight, int& outPitch) +{ PBITMAPINFO bi = reinterpret_cast(pData); PBITMAPINFOHEADER bih = &bi->bmiHeader; int bpp = bih->biBitCount; if (bpp != 16 && bpp != 24 && bpp != 32) { - AfxMessageBox(IDS_SCREENSHOT_ERROR, MB_ICONWARNING | MB_OK, 0); - return; + return nullptr; } + bool topdown = (bih->biHeight < 0); int w = bih->biWidth; int h = abs(bih->biHeight); @@ -5663,7 +5681,6 @@ void CMainFrame::SaveDIB(LPCTSTR fn, BYTE* pData, long size) int dstpitch = (w * 3 + 3) / 4 * 4; // round w * 3 to next multiple of 4 BYTE* p = DEBUG_NEW BYTE[dstpitch * h]; - const BYTE* src = pData + sizeof(*bih); if (topdown) { @@ -5672,6 +5689,23 @@ void CMainFrame::SaveDIB(LPCTSTR fn, BYTE* pData, long size) BitBltFromRGBToRGB(w, h, p, dstpitch, 24, (BYTE*)src + srcpitch * (h - 1), -srcpitch, bpp); } + outWidth = w; + outHeight = h; + outPitch = dstpitch; + return p; +} + +void CMainFrame::SaveDIB(LPCTSTR fn, BYTE* pData, long size) +{ + CPath path(fn); + + int w, h, dstpitch; + BYTE* p = ConvertDIBTo24bppRGB(pData, size, w, h, dstpitch); + if (!p) { + AfxMessageBox(IDS_SCREENSHOT_ERROR, MB_ICONWARNING | MB_OK, 0); + return; + } + { Gdiplus::GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; @@ -5746,6 +5780,43 @@ void CMainFrame::SaveDIB(LPCTSTR fn, BYTE* pData, long size) SendStatusMessage(m_wndStatusBar.PreparePathStatusMessage(path), 3000); } +bool CMainFrame::CaptureVideoThumbnail(std::vector& thumbnail) +{ + // Get the current video frame as DIB + std::vector dib; + CString errmsg; + HRESULT hr = GetCurrentFrame(dib, errmsg); + if (FAILED(hr) || dib.empty()) { + return false; + } + + // Convert DIB to 24bpp BGR + int w, h, dstpitch; + BYTE* bgr = ConvertDIBTo24bppRGB(dib.data(), (long)dib.size(), w, h, dstpitch); + if (!bgr) { + return false; + } + + // Allocate buffer for RGB output (tightly packed, no padding) + int rgbPitch = w * 3; + BYTE* rgb = DEBUG_NEW BYTE[rgbPitch * h]; + + // Convert BGR to RGB using stb_image_resize2 (even with same dimensions, it can reorder channels) + STBIR_RESIZE resize; + stbir_resize_init(&resize, bgr, w, h, dstpitch, rgb, w, h, rgbPitch, STBIR_BGR, STBIR_TYPE_UINT8); + stbir_set_pixel_layouts(&resize, STBIR_BGR, STBIR_RGB); + stbir_resize_extended(&resize); + + delete[] bgr; + + // Encode to JPEG using stb_image_write + int quality = AfxGetAppSettings().nJpegQuality; + int result = stbi_write_jpg_to_func(stbi_write_to_vector, &thumbnail, w, h, 3, rgb, quality); + + delete[] rgb; + return result != 0; +} + HRESULT GetBasicVideoFrame(IBasicVideo* pBasicVideo, std::vector& dib) { // IBasicVideo::GetCurrentImage() gives the original frame @@ -18419,6 +18490,14 @@ void CMainFrame::DoSeekTo(REFERENCE_TIME rtPos, bool bShowOSD /*= true*/) if (abRepeat.positionA && rtPos == abRepeat.positionA) { DisableABRepeat(); } + } else { + // Update media transport controls timeline after seek + if (m_media_trans_control.IsActive()) { + REFERENCE_TIME rtDur = 0; + if (SUCCEEDED(m_pMS->GetDuration(&rtDur)) && rtDur > 0) { + m_media_trans_control.UpdateTimelineProperties(0, rtDur, rtPos); + } + } } UpdateChapterInInfoBar(); if (fs == State_Stopped) { @@ -22550,35 +22629,49 @@ void CMainFrame::MediaTransportControlSetMedia() { } // Thumbnail - CComQIPtr pFilterGraph = m_pGB; - std::vector internalCover; - if (CoverArt::FindEmbedded(pFilterGraph, internalCover)) { - m_media_trans_control.loadThumbnail(internalCover.data(), internalCover.size()); - } else { - CPlaylistItem pli; - if (m_wndPlaylistBar.GetCur(pli) && !pli.m_cover.IsEmpty()) { - m_media_trans_control.loadThumbnail(pli.m_cover); + if (m_fAudioOnly) { + // For audio files, look for cover art + CComQIPtr pFilterGraph = m_pGB; + std::vector internalCover; + if (CoverArt::FindEmbedded(pFilterGraph, internalCover)) { + m_media_trans_control.loadThumbnail(internalCover.data(), internalCover.size()); } else { - CString filename = m_wndPlaylistBar.GetCurFileName(); - CString filename_no_ext; - CString filedir; - if (!PathUtils::IsURL(filename)) { - CPath path = CPath(filename); - if (path.FileExists()) { - path.RemoveExtension(); - filename_no_ext = path.m_strPath; - path.RemoveFileSpec(); - filedir = path.m_strPath; - bool is_file_art = false; - CString img = CoverArt::FindExternal(filename_no_ext, filedir, author, is_file_art); - if (!img.IsEmpty()) { - if (m_fAudioOnly || is_file_art) { + CPlaylistItem pli; + if (m_wndPlaylistBar.GetCur(pli) && !pli.m_cover.IsEmpty()) { + m_media_trans_control.loadThumbnail(pli.m_cover); + } else { + CString filename = m_wndPlaylistBar.GetCurFileName(); + if (!PathUtils::IsURL(filename)) { + CPath path = CPath(filename); + if (path.FileExists()) { + path.RemoveExtension(); + CString filename_no_ext = path.m_strPath; + path.RemoveFileSpec(); + CString filedir = path.m_strPath; + bool is_file_art = false; + CString img = CoverArt::FindExternal(filename_no_ext, filedir, author, is_file_art); + if (!img.IsEmpty()) { m_media_trans_control.loadThumbnail(img); } } } } } + } else { + // For video files, capture current frame as thumbnail + std::vector thumbnail; + if (CaptureVideoThumbnail(thumbnail)) { + m_media_trans_control.loadThumbnail(thumbnail.data(), thumbnail.size()); + } + } + + // Update timeline properties (duration and position) + if (m_pMS) { + REFERENCE_TIME rtDur = 0, rtNow = 0; + if (SUCCEEDED(m_pMS->GetDuration(&rtDur)) && rtDur > 0) { + m_pMS->GetCurrentPosition(&rtNow); + m_media_trans_control.UpdateTimelineProperties(0, rtDur, rtNow); + } } // Update data and status diff --git a/src/mpc-hc/MainFrm.h b/src/mpc-hc/MainFrm.h index 9bbfdfc66c0..371415492a1 100644 --- a/src/mpc-hc/MainFrm.h +++ b/src/mpc-hc/MainFrm.h @@ -479,7 +479,9 @@ class CMainFrame : public CFrameWnd, public CDropClient HRESULT GetOriginalFrame(std::vector& dib, CString& errmsg); HRESULT RenderCurrentSubtitles(BYTE* pData); bool GetDIB(BYTE** ppData, long& size, bool fSilent = false); + BYTE* ConvertDIBTo24bppRGB(BYTE* pData, long size, int& outWidth, int& outHeight, int& outPitch); void SaveDIB(LPCTSTR fn, BYTE* pData, long size); + bool CaptureVideoThumbnail(std::vector& thumbnail); CString MakeSnapshotFileName(BOOL thumbnails); BOOL IsRendererCompatibleWithSaveImage(); void SaveImage(LPCTSTR fn, bool displayed, bool includeSubtitles); diff --git a/src/mpc-hc/MediaTransControls.cpp b/src/mpc-hc/MediaTransControls.cpp index 17a37ffe518..09b6c72c137 100644 --- a/src/mpc-hc/MediaTransControls.cpp +++ b/src/mpc-hc/MediaTransControls.cpp @@ -68,6 +68,13 @@ bool MediaTransControls::Init(CMainFrame* main) { TRACE(_T("MediaTransControls: GetForWindow error %ld\n"), ret); return false; } + + // Try to get ISystemMediaTransportControls2 (Windows 10 1607+) + ret = smtc_controls->QueryInterface(IID_PPV_ARGS(&smtc_controls2)); + if (ret != S_OK) { + smtc_controls2 = nullptr; + } + ret = smtc_controls->get_DisplayUpdater(&smtc_updater); if (ret != S_OK) { smtc_controls = nullptr; @@ -113,6 +120,23 @@ bool MediaTransControls::Init(CMainFrame* main) { smtc_controls->put_IsPreviousEnabled(true); smtc_controls->put_IsNextEnabled(true); + // Register for playback position change requests (for timeline seeking) + if (smtc_controls2) { + auto callbackPositionChange = Callback>( + [this](ISystemMediaTransportControls*, IPlaybackPositionChangeRequestedEventArgs* pArgs) { + HRESULT hr; + ABI::Windows::Foundation::TimeSpan requestedPosition; + if ((hr = pArgs->get_RequestedPlaybackPosition(&requestedPosition)) == S_OK) { + OnPlaybackPositionChangeRequested(requestedPosition.Duration); + } + return S_OK; + }); + ret = smtc_controls2->add_PlaybackPositionChangeRequested(callbackPositionChange.Get(), &m_EventRegistrationTokenPositionChange); + if (ret != S_OK) { + // Non-fatal, continue + } + } + m_pMainFrame = main; return true; @@ -182,7 +206,9 @@ void MediaTransControls::loadThumbnail(CString fn) { } void MediaTransControls::loadThumbnail(BYTE* content, size_t size) { - if (!content || !size || !smtc_updater) return; + if (!content || !size || !smtc_updater) { + return; + } ComPtr s; HRESULT ret; @@ -249,6 +275,13 @@ void MediaTransControls::OnButtonPressed(SystemMediaTransportControlsButton butt } } +void MediaTransControls::OnPlaybackPositionChangeRequested(REFERENCE_TIME position) { + if (!m_pMainFrame) return; + + // Seek to the requested position + m_pMainFrame->SeekTo(position, false); +} + bool MediaTransControls::IsActive() { if (smtc_controls) { boolean enabled; @@ -259,3 +292,72 @@ bool MediaTransControls::IsActive() { } return false; } + +void MediaTransControls::SetAutoRepeatMode(ABI::Windows::Media::MediaPlaybackAutoRepeatMode mode) { + if (smtc_controls2) { + smtc_controls2->put_AutoRepeatMode(mode); + } +} + +void MediaTransControls::SetShuffleEnabled(bool enabled) { + if (smtc_controls2) { + smtc_controls2->put_ShuffleEnabled(enabled); + } +} + +void MediaTransControls::SetPlaybackRate(double rate) { + if (smtc_controls2) { + smtc_controls2->put_PlaybackRate(rate); + } +} + +void MediaTransControls::UpdateTimelineProperties(REFERENCE_TIME startTime, REFERENCE_TIME endTime, REFERENCE_TIME position) { + if (!smtc_controls2) { + return; + } + + HRESULT hr; + CComPtr timeline; + + hr = ActivateInstance(HStringReference(RuntimeClass_Windows_Media_SystemMediaTransportControlsTimelineProperties).Get(), &timeline); + if (hr != S_OK) { + return; + } + + // Convert REFERENCE_TIME (100ns units) to TimeSpan (also 100ns units) + ABI::Windows::Foundation::TimeSpan start, end, pos, minSeek, maxSeek; + start.Duration = startTime; + end.Duration = endTime; + pos.Duration = position; + minSeek.Duration = startTime; // Min seekable position (usually start) + maxSeek.Duration = endTime; // Max seekable position (usually end) + + // Set timeline properties + hr = timeline->put_StartTime(start); + if (hr != S_OK) { + return; + } + + hr = timeline->put_EndTime(end); + if (hr != S_OK) { + return; + } + + hr = timeline->put_MinSeekTime(minSeek); + if (hr != S_OK) { + return; + } + + hr = timeline->put_MaxSeekTime(maxSeek); + if (hr != S_OK) { + return; + } + + hr = timeline->put_Position(pos); + if (hr != S_OK) { + return; + } + + // Update the timeline + smtc_controls2->UpdateTimelineProperties(timeline); +} diff --git a/src/mpc-hc/MediaTransControls.h b/src/mpc-hc/MediaTransControls.h index cfeb67d90c1..34f1f6324a6 100644 --- a/src/mpc-hc/MediaTransControls.h +++ b/src/mpc-hc/MediaTransControls.h @@ -28,12 +28,18 @@ class MediaTransControls { public: MediaTransControls(void) { smtc_controls = nullptr; + smtc_controls2 = nullptr; smtc_updater = nullptr; + m_EventRegistrationToken.value = 0; + m_EventRegistrationTokenPositionChange.value = 0; } ~MediaTransControls(void) { if (smtc_controls && m_EventRegistrationToken.value) { smtc_controls->remove_ButtonPressed(m_EventRegistrationToken); } + if (smtc_controls2 && m_EventRegistrationTokenPositionChange.value) { + smtc_controls2->remove_PlaybackPositionChangeRequested(m_EventRegistrationTokenPositionChange); + } } bool Init(CMainFrame* main); @@ -41,14 +47,23 @@ class MediaTransControls { void close(); CComPtr smtc_controls; + CComPtr smtc_controls2; CComPtr smtc_updater; void loadThumbnail(CString fn); void loadThumbnail(BYTE* content, size_t size); void loadThumbnailFromUrl(CString url); bool IsActive(); + + // ISystemMediaTransportControls2 features (Windows 10 1607+) + void SetAutoRepeatMode(ABI::Windows::Media::MediaPlaybackAutoRepeatMode mode); + void SetShuffleEnabled(bool enabled); + void SetPlaybackRate(double rate); + void UpdateTimelineProperties(REFERENCE_TIME startTime, REFERENCE_TIME endTime, REFERENCE_TIME position); protected: CMainFrame* m_pMainFrame; EventRegistrationToken m_EventRegistrationToken; + EventRegistrationToken m_EventRegistrationTokenPositionChange; void OnButtonPressed(ABI::Windows::Media::SystemMediaTransportControlsButton button); + void OnPlaybackPositionChangeRequested(REFERENCE_TIME position); }; diff --git a/src/platform.props b/src/platform.props index 1d3e932f14b..09a2ff5ee4e 100644 --- a/src/platform.props +++ b/src/platform.props @@ -1,7 +1,7 @@ - 8.1 + 10.0 $(MPCHC_WINSDK_VER) v143 v142