mirror of
https://github.com/jellyfin/jellyfin-media-player.git
synced 2024-11-27 00:00:38 +00:00
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:
parent
50dbc42c8a
commit
47ac6466ee
@ -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})
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user