Add System Media Transport Control support for Windows.

This is fancy name for global media controls with keyboard media keys and OSD.
This commit is contained in:
Denis Shemanaev 2021-04-27 15:04:11 +03:00
parent 50dbc42c8a
commit 47ac6466ee
5 changed files with 337 additions and 1 deletions

View File

@ -8,4 +8,5 @@ find_library(VERLIB version)
find_library(DWMLIB dwmapi)
find_library(AVRTLIB avrt)
find_library(POWRPROFLIB PowrProf)
set(OS_LIBS ${WINMM} ${IMMLIB} ${VERLIB} ${DWMLIB} ${AVRTLIB} ${POWRPROFLIB})
find_library(RUNTIMEOBJECTLIB RuntimeObject)
set(OS_LIBS ${WINMM} ${IMMLIB} ${VERLIB} ${DWMLIB} ${AVRTLIB} ${POWRPROFLIB} ${RUNTIMEOBJECTLIB})

View File

@ -341,6 +341,8 @@ void PlayerComponent::queueMedia(const QString& url, const QVariantMap& options,
command << extraArgs;
mpv::qt::command(m_mpv, command);
emit onMetaData(metadata["metadata"].toMap(), qurl.adjusted(QUrl::RemovePath | QUrl::RemoveQuery));
}
/////////////////////////////////////////////////////////////////////////////////////////

View File

@ -186,6 +186,8 @@ Q_SIGNALS:
void onVideoRecangleChanged();
void onMpvEvents();
void onMetaData(const QVariantMap &meta, QUrl baseUrl);
private:
// this is the function actually implemented in the backends. the variantmap contains

View File

@ -1,14 +1,38 @@
#include <QApplication>
#include <QStyle>
#include <QUrlQuery>
#include <Windows.Foundation.h>
#include <systemmediatransportcontrolsinterop.h>
#include <wrl\client.h>
#include <wrl\wrappers\corewrappers.h>
#include "TaskbarComponentWin.h"
#include "PlayerComponent.h"
#include "input/InputComponent.h"
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::Media;
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using ABI::Windows::Storage::Streams::IRandomAccessStreamReference;
using ABI::Windows::Storage::Streams::IRandomAccessStreamReferenceStatics;
/////////////////////////////////////////////////////////////////////////////////////////
TaskbarComponentWin::~TaskbarComponentWin()
{
if (m_initialized)
{
m_systemControls->remove_ButtonPressed(m_buttonPressedToken);
m_displayUpdater->ClearAll();
}
}
/////////////////////////////////////////////////////////////////////////////////////////
void TaskbarComponentWin::setWindow(QQuickWindow* window)
{
QLOG_DEBUG() << "Taskbar initialization started";
TaskbarComponent::setWindow(window);
m_button = new QWinTaskbarButton(m_window);
@ -37,9 +61,12 @@ void TaskbarComponentWin::setWindow(QQuickWindow* window)
connect(&PlayerComponent::Get(), &PlayerComponent::playing, this, &TaskbarComponentWin::playing);
connect(&PlayerComponent::Get(), &PlayerComponent::paused, this, &TaskbarComponentWin::paused);
connect(&PlayerComponent::Get(), &PlayerComponent::stopped, this, &TaskbarComponentWin::stopped);
connect(&PlayerComponent::Get(), &PlayerComponent::onMetaData, this, &TaskbarComponentWin::onMetaData);
setControlsVisible(false);
setPaused(false);
initializeMediaTransport((HWND)window->winId());
}
/////////////////////////////////////////////////////////////////////////////////////////
@ -88,6 +115,12 @@ void TaskbarComponentWin::setControlsVisible(bool value)
{
button->setVisible(value);
}
if (m_initialized)
{
m_systemControls->put_PlaybackStatus(MediaPlaybackStatus::MediaPlaybackStatus_Stopped);
m_systemControls->put_IsEnabled(value);
}
}
/////////////////////////////////////////////////////////////////////////////////////////
@ -116,4 +149,285 @@ void TaskbarComponentWin::setPaused(bool value)
}
m_button->progress()->setPaused(value);
if (m_initialized)
{
auto status = value ? MediaPlaybackStatus::MediaPlaybackStatus_Paused : MediaPlaybackStatus::MediaPlaybackStatus_Playing;
m_systemControls->put_PlaybackStatus(status);
}
}
/////////////////////////////////////////////////////////////////////////////////////////
void TaskbarComponentWin::initializeMediaTransport(HWND hwnd)
{
ComPtr<ISystemMediaTransportControlsInterop> interop;
auto hr = GetActivationFactory(HStringReference(RuntimeClass_Windows_Media_SystemMediaTransportControls).Get(), &interop);
if (FAILED(hr))
{
QLOG_WARN() << "Failed instantiating interop object";
return;
}
hr = interop->GetForWindow(hwnd, IID_PPV_ARGS(&m_systemControls));
if (FAILED(hr))
{
QLOG_WARN() << "Failed to GetForWindow";
return;
}
auto handler = Callback<
ITypedEventHandler<
SystemMediaTransportControls*,
SystemMediaTransportControlsButtonPressedEventArgs*>>(
[this](ISystemMediaTransportControls* sender, ISystemMediaTransportControlsButtonPressedEventArgs* args) -> HRESULT {
return buttonPressed(sender, args);
});
hr = m_systemControls->add_ButtonPressed(handler.Get(), &m_buttonPressedToken);
if (FAILED(hr))
{
QLOG_WARN() << "Failed to add callback handler";
return;
}
hr = m_systemControls->put_IsEnabled(false);
hr = m_systemControls->put_IsPlayEnabled(true);
hr = m_systemControls->put_IsPauseEnabled(true);
hr = m_systemControls->put_IsPreviousEnabled(true);
hr = m_systemControls->put_IsNextEnabled(true);
hr = m_systemControls->get_DisplayUpdater(&m_displayUpdater);
if (FAILED(hr))
{
QLOG_WARN() << "Failed to get Display updater";
return;
}
m_initialized = true;
QLOG_INFO() << "SystemMediaTransportControls successfully initialized";
}
/////////////////////////////////////////////////////////////////////////////////////////
void TaskbarComponentWin::onMetaData(const QVariantMap& meta, QUrl baseUrl)
{
if (!m_initialized)
return;
HRESULT hr;
auto mediaType = meta["MediaType"].toString();
hr = m_displayUpdater->ClearAll();
if (FAILED(hr))
{
QLOG_WARN() << "Failed to clear display metadata";
return;
}
if (mediaType == "Video")
{
setVideoMeta(meta);
}
else // if (mediaType == "Audio") most likely
{
setAudioMeta(meta);
}
setThumbnail(meta, baseUrl);
hr = m_displayUpdater->Update();
if (FAILED(hr))
{
QLOG_WARN() << "Failed to update the display";
return;
}
}
/////////////////////////////////////////////////////////////////////////////////////////
void TaskbarComponentWin::setAudioMeta(const QVariantMap& meta)
{
HRESULT hr;
hr = m_displayUpdater->put_Type(MediaPlaybackType::MediaPlaybackType_Music);
if (FAILED(hr))
{
QLOG_WARN() << "Failed to set the media type to music";
return;
}
ComPtr<IMusicDisplayProperties> musicProps;
hr = m_displayUpdater->get_MusicProperties(musicProps.GetAddressOf());
if (FAILED(hr))
{
QLOG_WARN() << "Failed to get music properties";
return;
}
auto artist = meta["Artists"].toStringList().join(", ");
hr = musicProps->put_Artist(HStringReference((const wchar_t*)artist.utf16()).Get());
if (FAILED(hr))
{
QLOG_WARN() << "Failed to set the music's artist";
return;
}
auto title = meta["Name"].toString();
hr = musicProps->put_Title(HStringReference((const wchar_t*)title.utf16()).Get());
if (FAILED(hr))
{
QLOG_WARN() << "Failed to set the music's title";
return;
}
auto albumArtist = meta["AlbumArtist"].toString();
hr = musicProps->put_AlbumArtist(HStringReference((const wchar_t*)albumArtist.utf16()).Get());
if (FAILED(hr))
{
QLOG_WARN() << "Failed to set the music's album artist";
return;
}
}
/////////////////////////////////////////////////////////////////////////////////////////
void TaskbarComponentWin::setVideoMeta(const QVariantMap& meta)
{
HRESULT hr;
hr = m_displayUpdater->put_Type(MediaPlaybackType::MediaPlaybackType_Video);
if (FAILED(hr))
{
QLOG_WARN() << "Failed to set the media type to video";
return;
}
ComPtr<IVideoDisplayProperties> videoProps;
hr = m_displayUpdater->get_VideoProperties(videoProps.GetAddressOf());
if (FAILED(hr))
{
QLOG_WARN() << "Failed to get video properties";
return;
}
auto title = meta["Name"].toString();
hr = videoProps->put_Title(HStringReference((const wchar_t*)title.utf16()).Get());
if (FAILED(hr))
{
QLOG_WARN() << "Failed to set the video title";
return;
}
if (meta["Type"] == "Episode")
{
auto subtitle = meta["SeriesName"].toString();
hr = videoProps->put_Subtitle(HStringReference((const wchar_t*)subtitle.utf16()).Get());
if (FAILED(hr))
{
QLOG_WARN() << "Failed to set the video subtitle";
return;
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////
void TaskbarComponentWin::setThumbnail(const QVariantMap& meta, QUrl baseUrl)
{
auto images = meta["ImageTags"].toMap();
if (!images.contains("Primary"))
{
QLOG_DEBUG() << "No Primary image found. Do nothing";
return;
}
HRESULT hr;
auto itemId = meta["Id"].toString();
auto imgTag = images["Primary"].toString();
QUrlQuery query;
query.addQueryItem("tag", imgTag);
baseUrl.setPath("/Items/" + itemId + "/Images/Primary");
baseUrl.setQuery(query);
auto imgUrl = baseUrl.toString();
ComPtr<IRandomAccessStreamReferenceStatics> streamRefFactory;
hr = GetActivationFactory(HStringReference(RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference).Get(),
&streamRefFactory);
if (FAILED(hr))
{
QLOG_WARN() << "Failed instantiating stream factory";
return;
}
ComPtr<IUriRuntimeClassFactory> uriFactory;
hr = GetActivationFactory(HStringReference(RuntimeClass_Windows_Foundation_Uri).Get(), &uriFactory);
if (FAILED(hr))
{
QLOG_WARN() << "Failed instantiating uri factory";
return;
}
ComPtr<IUriRuntimeClass> uri;
hr = uriFactory->CreateUri(HStringReference((const wchar_t*)imgUrl.utf16()).Get(), &uri);
if (FAILED(hr))
{
QLOG_WARN() << "Failed to create uri";
return;
}
hr = streamRefFactory->CreateFromUri(uri.Get(), &m_thumbnail);
if (FAILED(hr))
{
QLOG_WARN() << "Failed to create stream from uri";
return;
}
hr = m_displayUpdater->put_Thumbnail(m_thumbnail.Get());
if (FAILED(hr))
{
QLOG_WARN() << "Failed to set thumbnail";
return;
}
}
/////////////////////////////////////////////////////////////////////////////////////////
HRESULT TaskbarComponentWin::buttonPressed(ISystemMediaTransportControls* sender,
ISystemMediaTransportControlsButtonPressedEventArgs* args)
{
SystemMediaTransportControlsButton button;
auto hr = args->get_Button(&button);
if (FAILED(hr))
{
QLOG_WARN() << "Failed to get the pressed button";
return hr;
}
switch (button)
{
case SystemMediaTransportControlsButton::SystemMediaTransportControlsButton_Play:
InputComponent::Get().sendAction("play");
QLOG_DEBUG() << "Received play button press";
break;
case SystemMediaTransportControlsButton::SystemMediaTransportControlsButton_Pause:
InputComponent::Get().sendAction("pause");
QLOG_DEBUG() << "Received pause button press";
break;
case SystemMediaTransportControlsButton::SystemMediaTransportControlsButton_Next:
InputComponent::Get().sendAction("next");
QLOG_DEBUG() << "Received next button press";
break;
case SystemMediaTransportControlsButton::SystemMediaTransportControlsButton_Previous:
InputComponent::Get().sendAction("previous");
QLOG_DEBUG() << "Received previous button press";
break;
case SystemMediaTransportControlsButton::SystemMediaTransportControlsButton_Stop:
InputComponent::Get().sendAction("stop");
QLOG_DEBUG() << "Received stop button press";
break;
case SystemMediaTransportControlsButton::SystemMediaTransportControlsButton_Record:
case SystemMediaTransportControlsButton::SystemMediaTransportControlsButton_FastForward:
case SystemMediaTransportControlsButton::SystemMediaTransportControlsButton_Rewind:
case SystemMediaTransportControlsButton::SystemMediaTransportControlsButton_ChannelUp:
case SystemMediaTransportControlsButton::SystemMediaTransportControlsButton_ChannelDown:
QLOG_DEBUG() << "Received unsupported button press";
break;
}
return S_OK;
}

View File

@ -6,6 +6,8 @@
#include <QWinThumbnailToolBar>
#include <QWinThumbnailToolButton>
#include <wrl.h>
#include <Windows.Media.h>
#include "TaskbarComponent.h"
@ -13,6 +15,7 @@ class TaskbarComponentWin : public TaskbarComponent
{
public:
TaskbarComponentWin(): TaskbarComponent(nullptr) {}
~TaskbarComponentWin() override;
virtual void setWindow(QQuickWindow* window) override;
private:
@ -22,15 +25,29 @@ private:
void setProgress(quint64 value);
void setControlsVisible(bool value);
void setPaused(bool value);
void initializeMediaTransport(HWND hwnd);
void onMetaData(const QVariantMap &meta, QUrl baseUrl);
void setAudioMeta(const QVariantMap &meta);
void setVideoMeta(const QVariantMap &meta);
void setThumbnail(const QVariantMap &meta, QUrl baseUrl);
void playing();
void stopped();
void paused();
HRESULT buttonPressed(ABI::Windows::Media::ISystemMediaTransportControls* sender,
ABI::Windows::Media::ISystemMediaTransportControlsButtonPressedEventArgs* args);
QWinTaskbarButton* m_button;
QWinThumbnailToolBar* m_toolbar;
QWinThumbnailToolButton* m_pause;
QWinThumbnailToolButton* m_prev;
QWinThumbnailToolButton* m_next;
bool m_initialized;
EventRegistrationToken m_buttonPressedToken;
Microsoft::WRL::ComPtr<ABI::Windows::Media::ISystemMediaTransportControls> m_systemControls;
Microsoft::WRL::ComPtr<ABI::Windows::Media::ISystemMediaTransportControlsDisplayUpdater> m_displayUpdater;
Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStreamReference> m_thumbnail;
};
#endif // TASKBARCOMPONENTWIN_H