Browse Source

Add System Media Transport Control support for Windows.

This is fancy name for global media controls with keyboard media keys and OSD.
Denis Shemanaev 3 years ago
parent
commit
47ac6466ee

+ 2 - 1
CMakeModules/Win32Configuration.cmake

@@ -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})

+ 2 - 0
src/player/PlayerComponent.cpp

@@ -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));
 }
 
 /////////////////////////////////////////////////////////////////////////////////////////

+ 2 - 0
src/player/PlayerComponent.h

@@ -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

+ 314 - 0
src/taskbar/TaskbarComponentWin.cpp

@@ -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;
 }

+ 17 - 0
src/taskbar/TaskbarComponentWin.h

@@ -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