зеркало из https://github.com/mozilla/gecko-dev.git
890 строки
28 KiB
C++
890 строки
28 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/* mingw currently doesn't support windows.media.h, so we disable
|
|
* the whole related class until this is fixed.
|
|
* @TODO: Maybe contact MinGW Team for inclusion?*/
|
|
#ifndef __MINGW32__
|
|
|
|
# include "WindowsSMTCProvider.h"
|
|
|
|
# include <windows.h>
|
|
# include <windows.media.h>
|
|
# include <wrl.h>
|
|
|
|
# include "nsMimeTypes.h"
|
|
# include "mozilla/Assertions.h"
|
|
# include "mozilla/Logging.h"
|
|
# include "mozilla/Maybe.h"
|
|
# include "mozilla/WidgetUtils.h"
|
|
# include "mozilla/ScopeExit.h"
|
|
# include "mozilla/dom/MediaControlUtils.h"
|
|
# include "mozilla/media/MediaUtils.h"
|
|
# include "nsThreadUtils.h"
|
|
|
|
# pragma comment(lib, "runtimeobject.lib")
|
|
|
|
using namespace ABI::Windows::Foundation;
|
|
using namespace ABI::Windows::Media;
|
|
using namespace ABI::Windows::Storage::Streams;
|
|
using namespace Microsoft::WRL;
|
|
using namespace Microsoft::WRL::Wrappers;
|
|
using namespace mozilla;
|
|
|
|
# ifndef RuntimeClass_Windows_Media_SystemMediaTransportControls
|
|
# define RuntimeClass_Windows_Media_SystemMediaTransportControls \
|
|
L"Windows.Media.SystemMediaTransportControls"
|
|
# endif
|
|
|
|
# ifndef RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference
|
|
# define RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference \
|
|
L"Windows.Storage.Streams.RandomAccessStreamReference"
|
|
# endif
|
|
|
|
# ifndef ISystemMediaTransportControlsInterop
|
|
EXTERN_C const IID IID_ISystemMediaTransportControlsInterop;
|
|
MIDL_INTERFACE("ddb0472d-c911-4a1f-86d9-dc3d71a95f5a")
|
|
ISystemMediaTransportControlsInterop : public IInspectable {
|
|
public:
|
|
virtual HRESULT STDMETHODCALLTYPE GetForWindow(
|
|
/* [in] */ __RPC__in HWND appWindow,
|
|
/* [in] */ __RPC__in REFIID riid,
|
|
/* [iid_is][retval][out] */
|
|
__RPC__deref_out_opt void** mediaTransportControl) = 0;
|
|
};
|
|
# endif /* __ISystemMediaTransportControlsInterop_INTERFACE_DEFINED__ */
|
|
|
|
extern mozilla::LazyLogModule gMediaControlLog;
|
|
|
|
# undef LOG
|
|
# define LOG(msg, ...) \
|
|
MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
|
|
("WindowSMTCProvider=%p, " msg, this, ##__VA_ARGS__))
|
|
|
|
static inline Maybe<mozilla::dom::MediaControlKey> TranslateKeycode(
|
|
SystemMediaTransportControlsButton keycode) {
|
|
switch (keycode) {
|
|
case SystemMediaTransportControlsButton_Play:
|
|
return Some(mozilla::dom::MediaControlKey::Play);
|
|
case SystemMediaTransportControlsButton_Pause:
|
|
return Some(mozilla::dom::MediaControlKey::Pause);
|
|
case SystemMediaTransportControlsButton_Next:
|
|
return Some(mozilla::dom::MediaControlKey::Nexttrack);
|
|
case SystemMediaTransportControlsButton_Previous:
|
|
return Some(mozilla::dom::MediaControlKey::Previoustrack);
|
|
case SystemMediaTransportControlsButton_Stop:
|
|
return Some(mozilla::dom::MediaControlKey::Stop);
|
|
case SystemMediaTransportControlsButton_FastForward:
|
|
return Some(mozilla::dom::MediaControlKey::Seekforward);
|
|
case SystemMediaTransportControlsButton_Rewind:
|
|
return Some(mozilla::dom::MediaControlKey::Seekbackward);
|
|
default:
|
|
return Nothing(); // Not supported Button
|
|
}
|
|
}
|
|
|
|
static IAsyncInfo* GetIAsyncInfo(IAsyncOperation<unsigned int>* aAsyncOp) {
|
|
MOZ_ASSERT(aAsyncOp);
|
|
IAsyncInfo* asyncInfo;
|
|
HRESULT hr = aAsyncOp->QueryInterface(IID_IAsyncInfo,
|
|
reinterpret_cast<void**>(&asyncInfo));
|
|
// The assertion always works since IAsyncOperation implements IAsyncInfo
|
|
MOZ_ASSERT(SUCCEEDED(hr));
|
|
Unused << hr;
|
|
MOZ_ASSERT(asyncInfo);
|
|
return asyncInfo;
|
|
}
|
|
|
|
WindowsSMTCProvider::WindowsSMTCProvider() {
|
|
LOG("Creating an empty and invisible window");
|
|
|
|
// In order to create a SMTC-Provider, we need a hWnd, which shall be created
|
|
// dynamically from an invisible window. This leads to the following
|
|
// boilerplate code.
|
|
WNDCLASS wnd{};
|
|
wnd.lpszClassName = L"Firefox-MediaKeys";
|
|
wnd.hInstance = nullptr;
|
|
wnd.lpfnWndProc = DefWindowProc;
|
|
GetLastError(); // Clear the error
|
|
RegisterClass(&wnd);
|
|
MOZ_ASSERT(!GetLastError());
|
|
|
|
mWindow = CreateWindowExW(0, L"Firefox-MediaKeys", L"Firefox Media Keys", 0,
|
|
CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, nullptr,
|
|
nullptr, nullptr, nullptr);
|
|
MOZ_ASSERT(mWindow);
|
|
MOZ_ASSERT(!GetLastError());
|
|
}
|
|
|
|
WindowsSMTCProvider::~WindowsSMTCProvider() {
|
|
// Dispose the window
|
|
MOZ_ASSERT(mWindow);
|
|
if (!DestroyWindow(mWindow)) {
|
|
LOG("Failed to destroy the hidden window. Error Code: %lu", GetLastError());
|
|
}
|
|
if (!UnregisterClass(L"Firefox-MediaKeys", nullptr)) {
|
|
// Note that this is logged when the class wasn't even registered.
|
|
LOG("Failed to unregister the class. Error Code: %lu", GetLastError());
|
|
}
|
|
}
|
|
|
|
bool WindowsSMTCProvider::IsOpened() const { return mInitialized; }
|
|
|
|
bool WindowsSMTCProvider::Open() {
|
|
LOG("Opening Source");
|
|
MOZ_ASSERT(!mInitialized);
|
|
|
|
if (!InitDisplayAndControls()) {
|
|
LOG("Failed to initialize the SMTC and its display");
|
|
return false;
|
|
}
|
|
|
|
if (!UpdateButtons()) {
|
|
LOG("Failed to initialize the buttons");
|
|
return false;
|
|
}
|
|
|
|
if (!RegisterEvents()) {
|
|
LOG("Failed to register SMTC key-event listener");
|
|
return false;
|
|
}
|
|
|
|
if (!EnableControl(true)) {
|
|
LOG("Failed to enable SMTC control");
|
|
return false;
|
|
}
|
|
|
|
mInitialized = true;
|
|
SetPlaybackState(mozilla::dom::MediaSessionPlaybackState::None);
|
|
return mInitialized;
|
|
}
|
|
|
|
void WindowsSMTCProvider::Close() {
|
|
MediaControlKeySource::Close();
|
|
// Prevent calling Set methods when init failed
|
|
if (mInitialized) {
|
|
SetPlaybackState(mozilla::dom::MediaSessionPlaybackState::None);
|
|
UnregisterEvents();
|
|
ClearMetadata();
|
|
// We have observed an Windows issue, if we modify `mControls` , (such as
|
|
// setting metadata, disable buttons) before disabling control, and those
|
|
// operations are not done sequentially within a same main thread task,
|
|
// then it would cause a problem where the SMTC wasn't clean up completely
|
|
// and show the executable name.
|
|
EnableControl(false);
|
|
mInitialized = false;
|
|
}
|
|
}
|
|
|
|
void WindowsSMTCProvider::SetPlaybackState(
|
|
mozilla::dom::MediaSessionPlaybackState aState) {
|
|
MOZ_ASSERT(mInitialized);
|
|
MediaControlKeySource::SetPlaybackState(aState);
|
|
|
|
HRESULT hr;
|
|
|
|
// Note: we can't return the status of put_PlaybackStatus, but we can at least
|
|
// assert it.
|
|
switch (aState) {
|
|
case mozilla::dom::MediaSessionPlaybackState::Paused:
|
|
hr = mControls->put_PlaybackStatus(
|
|
ABI::Windows::Media::MediaPlaybackStatus_Paused);
|
|
break;
|
|
case mozilla::dom::MediaSessionPlaybackState::Playing:
|
|
hr = mControls->put_PlaybackStatus(
|
|
ABI::Windows::Media::MediaPlaybackStatus_Playing);
|
|
break;
|
|
case mozilla::dom::MediaSessionPlaybackState::None:
|
|
hr = mControls->put_PlaybackStatus(
|
|
ABI::Windows::Media::MediaPlaybackStatus_Stopped);
|
|
break;
|
|
// MediaPlaybackStatus still supports Closed and Changing, which we don't
|
|
// use (yet)
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE(
|
|
"Enum Inconsitency between PlaybackState and WindowsSMTCProvider");
|
|
break;
|
|
}
|
|
|
|
MOZ_ASSERT(SUCCEEDED(hr));
|
|
Unused << hr;
|
|
}
|
|
|
|
void WindowsSMTCProvider::SetMediaMetadata(
|
|
const mozilla::dom::MediaMetadataBase& aMetadata) {
|
|
MOZ_ASSERT(mInitialized);
|
|
SetMusicMetadata(aMetadata.mArtist, aMetadata.mTitle);
|
|
LoadThumbnail(aMetadata.mArtwork);
|
|
}
|
|
|
|
void WindowsSMTCProvider::ClearMetadata() {
|
|
MOZ_ASSERT(mDisplay);
|
|
if (FAILED(mDisplay->ClearAll())) {
|
|
LOG("Failed to clear SMTC display");
|
|
}
|
|
mImageFetchRequest.DisconnectIfExists();
|
|
CancelPendingStoreAsyncOperation();
|
|
mThumbnailUrl.Truncate();
|
|
mProcessingUrl.Truncate();
|
|
mNextImageIndex = 0;
|
|
mSupportedKeys = 0;
|
|
}
|
|
|
|
void WindowsSMTCProvider::SetSupportedMediaKeys(
|
|
const MediaKeysArray& aSupportedKeys) {
|
|
MOZ_ASSERT(mInitialized);
|
|
|
|
uint32_t supportedKeys = 0;
|
|
for (const mozilla::dom::MediaControlKey& key : aSupportedKeys) {
|
|
supportedKeys |= GetMediaKeyMask(key);
|
|
}
|
|
|
|
if (supportedKeys == mSupportedKeys) {
|
|
LOG("Supported keys stay the same");
|
|
return;
|
|
}
|
|
|
|
LOG("Update supported keys");
|
|
mSupportedKeys = supportedKeys;
|
|
UpdateButtons();
|
|
}
|
|
|
|
void WindowsSMTCProvider::UnregisterEvents() {
|
|
if (mControls && mButtonPressedToken.value != 0) {
|
|
mControls->remove_ButtonPressed(mButtonPressedToken);
|
|
}
|
|
|
|
if (mControls && mSeekRegistrationToken.value != 0) {
|
|
ComPtr<ISystemMediaTransportControls2> smtc2;
|
|
HRESULT hr = mControls.As(&smtc2);
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to cast controls to ISystemMediaTransportControls2 (hr=%lx)",
|
|
hr);
|
|
return;
|
|
}
|
|
MOZ_ASSERT(smtc2);
|
|
|
|
hr = smtc2->remove_PlaybackPositionChangeRequested(mSeekRegistrationToken);
|
|
if (FAILED(hr)) {
|
|
LOG("SystemMediaTransportControls: Failed unregister position change "
|
|
"event (hr=%lx)",
|
|
hr);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WindowsSMTCProvider::RegisterEvents() {
|
|
MOZ_ASSERT(mControls);
|
|
auto self = RefPtr<WindowsSMTCProvider>(this);
|
|
auto callbackbtnPressed = Callback<
|
|
ITypedEventHandler<SystemMediaTransportControls*,
|
|
SystemMediaTransportControlsButtonPressedEventArgs*>>(
|
|
[this, self](ISystemMediaTransportControls*,
|
|
ISystemMediaTransportControlsButtonPressedEventArgs* pArgs)
|
|
-> HRESULT {
|
|
MOZ_ASSERT(pArgs);
|
|
SystemMediaTransportControlsButton btn;
|
|
|
|
if (FAILED(pArgs->get_Button(&btn))) {
|
|
LOG("SystemMediaTransportControls: ButtonPressedEvent - Could "
|
|
"not get Button.");
|
|
return S_OK; // Propagating the error probably wouldn't help.
|
|
}
|
|
|
|
Maybe<mozilla::dom::MediaControlKey> keyCode = TranslateKeycode(btn);
|
|
if (keyCode.isSome() && IsOpened()) {
|
|
OnButtonPressed(keyCode.value());
|
|
}
|
|
return S_OK;
|
|
});
|
|
|
|
if (FAILED(mControls->add_ButtonPressed(callbackbtnPressed.Get(),
|
|
&mButtonPressedToken))) {
|
|
LOG("SystemMediaTransportControls: Failed at "
|
|
"registerEvents().add_ButtonPressed()");
|
|
return false;
|
|
}
|
|
|
|
ComPtr<ISystemMediaTransportControls2> smtc2;
|
|
HRESULT hr = mControls.As(&smtc2);
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to cast controls to ISystemMediaTransportControls2 (hr=%lx)",
|
|
hr);
|
|
return false;
|
|
}
|
|
MOZ_ASSERT(smtc2);
|
|
|
|
auto positionChangeRequested =
|
|
Callback<ITypedEventHandler<SystemMediaTransportControls*,
|
|
PlaybackPositionChangeRequestedEventArgs*>>(
|
|
[this, self](
|
|
ISystemMediaTransportControls*,
|
|
IPlaybackPositionChangeRequestedEventArgs* pArgs) -> HRESULT {
|
|
MOZ_ASSERT(pArgs);
|
|
|
|
TimeSpan value;
|
|
HRESULT hr = pArgs->get_RequestedPlaybackPosition(&value);
|
|
if (FAILED(hr)) {
|
|
LOG("SystemMediaTransportControls: Playback Position Change - "
|
|
"failed to get requested position (hr=%lx)",
|
|
hr);
|
|
return S_OK; // Propagating the error probably wouldn't help.
|
|
}
|
|
|
|
double position =
|
|
static_cast<double>(value.Duration) / (1e6 * 10.0);
|
|
this->OnPositionChangeRequested(position);
|
|
|
|
return S_OK;
|
|
});
|
|
|
|
hr = smtc2->add_PlaybackPositionChangeRequested(positionChangeRequested.Get(),
|
|
&mSeekRegistrationToken);
|
|
if (FAILED(hr)) {
|
|
LOG("SystemMediaTransportControls: Failed to register position change "
|
|
"event (hr=%lx)",
|
|
hr);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WindowsSMTCProvider::OnButtonPressed(
|
|
mozilla::dom::MediaControlKey aKey) const {
|
|
if (!IsKeySupported(aKey)) {
|
|
LOG("key: %s is not supported", dom::GetEnumString(aKey).get());
|
|
return;
|
|
}
|
|
|
|
for (auto& listener : mListeners) {
|
|
listener->OnActionPerformed(mozilla::dom::MediaControlAction(aKey));
|
|
}
|
|
}
|
|
|
|
bool WindowsSMTCProvider::EnableControl(bool aEnabled) const {
|
|
MOZ_ASSERT(mControls);
|
|
return SUCCEEDED(mControls->put_IsEnabled(aEnabled));
|
|
}
|
|
|
|
bool WindowsSMTCProvider::UpdateButtons() {
|
|
static const mozilla::dom::MediaControlKey kKeys[] = {
|
|
mozilla::dom::MediaControlKey::Play,
|
|
mozilla::dom::MediaControlKey::Pause,
|
|
mozilla::dom::MediaControlKey::Previoustrack,
|
|
mozilla::dom::MediaControlKey::Nexttrack,
|
|
mozilla::dom::MediaControlKey::Stop,
|
|
mozilla::dom::MediaControlKey::Seekto};
|
|
|
|
bool success = true;
|
|
for (const mozilla::dom::MediaControlKey& key : kKeys) {
|
|
if (!EnableKey(key, IsKeySupported(key))) {
|
|
success = false;
|
|
LOG("Failed to set %s=%s", dom::GetEnumString(key).get(),
|
|
IsKeySupported(key) ? "true" : "false");
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
bool WindowsSMTCProvider::IsKeySupported(
|
|
mozilla::dom::MediaControlKey aKey) const {
|
|
return mSupportedKeys & GetMediaKeyMask(aKey);
|
|
}
|
|
|
|
bool WindowsSMTCProvider::EnableKey(mozilla::dom::MediaControlKey aKey,
|
|
bool aEnable) const {
|
|
MOZ_ASSERT(mControls);
|
|
switch (aKey) {
|
|
case mozilla::dom::MediaControlKey::Play:
|
|
return SUCCEEDED(mControls->put_IsPlayEnabled(aEnable));
|
|
case mozilla::dom::MediaControlKey::Pause:
|
|
return SUCCEEDED(mControls->put_IsPauseEnabled(aEnable));
|
|
case mozilla::dom::MediaControlKey::Previoustrack:
|
|
return SUCCEEDED(mControls->put_IsPreviousEnabled(aEnable));
|
|
case mozilla::dom::MediaControlKey::Nexttrack:
|
|
return SUCCEEDED(mControls->put_IsNextEnabled(aEnable));
|
|
case mozilla::dom::MediaControlKey::Stop:
|
|
return SUCCEEDED(mControls->put_IsStopEnabled(aEnable));
|
|
case mozilla::dom::MediaControlKey::Seekto:
|
|
// The callback for the event checks if the key is supported
|
|
return mSeekRegistrationToken.value != 0;
|
|
default:
|
|
LOG("No button for %s", dom::GetEnumString(aKey).get());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void WindowsSMTCProvider::OnPositionChangeRequested(double aPosition) const {
|
|
if (!IsKeySupported(mozilla::dom::MediaControlKey::Seekto)) {
|
|
LOG("Seekto is not supported");
|
|
return;
|
|
}
|
|
|
|
for (const auto& listener : mListeners) {
|
|
listener->OnActionPerformed(mozilla::dom::MediaControlAction(
|
|
mozilla::dom::MediaControlKey::Seekto,
|
|
mozilla::dom::SeekDetails(aPosition, false /* fast seek */)));
|
|
}
|
|
}
|
|
|
|
bool WindowsSMTCProvider::InitDisplayAndControls() {
|
|
// As Open() might be called multiple times, "cache" the results of the COM
|
|
// API
|
|
if (mControls && mDisplay) {
|
|
return true;
|
|
}
|
|
ComPtr<ISystemMediaTransportControlsInterop> interop;
|
|
HRESULT hr = GetActivationFactory(
|
|
HStringReference(RuntimeClass_Windows_Media_SystemMediaTransportControls)
|
|
.Get(),
|
|
interop.GetAddressOf());
|
|
if (FAILED(hr)) {
|
|
LOG("SystemMediaTransportControls: Failed at instantiating the "
|
|
"Interop object");
|
|
return false;
|
|
}
|
|
MOZ_ASSERT(interop);
|
|
|
|
if (!mControls && FAILED(interop->GetForWindow(
|
|
mWindow, IID_PPV_ARGS(mControls.GetAddressOf())))) {
|
|
LOG("SystemMediaTransportControls: Failed at GetForWindow()");
|
|
return false;
|
|
}
|
|
MOZ_ASSERT(mControls);
|
|
|
|
if (!mDisplay &&
|
|
FAILED(mControls->get_DisplayUpdater(mDisplay.GetAddressOf()))) {
|
|
LOG("SystemMediaTransportControls: Failed at get_DisplayUpdater()");
|
|
}
|
|
|
|
MOZ_ASSERT(mDisplay);
|
|
return true;
|
|
}
|
|
|
|
bool WindowsSMTCProvider::SetMusicMetadata(const nsString& aArtist,
|
|
const nsString& aTitle) {
|
|
MOZ_ASSERT(mDisplay);
|
|
ComPtr<IMusicDisplayProperties> musicProps;
|
|
|
|
HRESULT hr = mDisplay->put_Type(MediaPlaybackType::MediaPlaybackType_Music);
|
|
MOZ_ASSERT(SUCCEEDED(hr));
|
|
Unused << hr;
|
|
hr = mDisplay->get_MusicProperties(musicProps.GetAddressOf());
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to get music properties");
|
|
return false;
|
|
}
|
|
|
|
hr = musicProps->put_Artist(HStringReference(aArtist.get()).Get());
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to set the music's artist");
|
|
return false;
|
|
}
|
|
|
|
hr = musicProps->put_Title(HStringReference(aTitle.get()).Get());
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to set the music's title");
|
|
return false;
|
|
}
|
|
|
|
hr = mDisplay->Update();
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to refresh the display");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WindowsSMTCProvider::SetPositionState(
|
|
const mozilla::Maybe<mozilla::dom::PositionState>& aState) {
|
|
ComPtr<ISystemMediaTransportControls2> smtc2;
|
|
HRESULT hr = mControls.As(&smtc2);
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to cast controls to ISystemMediaTransportControls2 (hr=%lx)",
|
|
hr);
|
|
return;
|
|
}
|
|
MOZ_ASSERT(smtc2);
|
|
|
|
ComPtr<ISystemMediaTransportControlsTimelineProperties> properties;
|
|
hr = RoActivateInstance(
|
|
HStringReference(
|
|
RuntimeClass_Windows_Media_SystemMediaTransportControlsTimelineProperties)
|
|
.Get(),
|
|
&properties);
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to create timeline properties (hr=%lx)", hr);
|
|
return;
|
|
}
|
|
MOZ_ASSERT(properties);
|
|
|
|
// Converts a value in seconds to a TimeSpan
|
|
// The TimeSpan's Duration is a value in 100ns ticks
|
|
// https://learn.microsoft.com/en-us/windows/win32/api/windows.foundation/ns-windows-foundation-timespan#members
|
|
auto toTimeSpan = [this](double seconds) {
|
|
constexpr double kMaxMicroseconds =
|
|
static_cast<double>(std::numeric_limits<LONG64>::max() / 10);
|
|
|
|
double microseconds = seconds * 1e6;
|
|
|
|
if (microseconds > kMaxMicroseconds) {
|
|
LOG("Failed to convert %f microseconds to TimeSpan (overflow)",
|
|
microseconds);
|
|
return TimeSpan{0};
|
|
}
|
|
|
|
return TimeSpan{static_cast<LONG64>(microseconds * 10.0)};
|
|
};
|
|
|
|
TimeSpan endTime{0};
|
|
TimeSpan position{0};
|
|
double playbackRate = 1.0;
|
|
|
|
if (aState) {
|
|
endTime = toTimeSpan(aState->mDuration);
|
|
position = toTimeSpan(aState->CurrentPlaybackPosition());
|
|
playbackRate = aState->mPlaybackRate;
|
|
}
|
|
|
|
hr = properties->put_StartTime({0});
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to set the start time (hr=%lx)", hr);
|
|
return;
|
|
}
|
|
|
|
hr = properties->put_MinSeekTime({0});
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to set the min seek time (hr=%lx)", hr);
|
|
return;
|
|
}
|
|
|
|
hr = properties->put_EndTime(endTime);
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to set the end time (hr=%lx)", hr);
|
|
return;
|
|
}
|
|
|
|
hr = properties->put_MaxSeekTime(endTime);
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to set the max seek time (hr=%lx)", hr);
|
|
return;
|
|
}
|
|
|
|
hr = properties->put_Position(position);
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to set the playback position (hr=%lx)", hr);
|
|
return;
|
|
}
|
|
|
|
hr = smtc2->UpdateTimelineProperties(properties.Get());
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to update timeline properties (hr=%lx)", hr);
|
|
return;
|
|
}
|
|
|
|
hr = smtc2->put_PlaybackRate(playbackRate);
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to set the playback rate (hr=%lx)", hr);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void WindowsSMTCProvider::LoadThumbnail(
|
|
const nsTArray<mozilla::dom::MediaImage>& aArtwork) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// TODO: Sort the images by the preferred size or format.
|
|
mArtwork = aArtwork;
|
|
mNextImageIndex = 0;
|
|
|
|
// Abort the loading if
|
|
// 1) thumbnail is being updated, and one in processing is in the artwork
|
|
// 2) thumbnail is not being updated, and one in use is in the artwork
|
|
if (!mProcessingUrl.IsEmpty()) {
|
|
LOG("Load thumbnail while image: %s is being processed",
|
|
NS_ConvertUTF16toUTF8(mProcessingUrl).get());
|
|
if (mozilla::dom::IsImageIn(mArtwork, mProcessingUrl)) {
|
|
LOG("No need to load thumbnail. The one being processed is in the "
|
|
"artwork");
|
|
return;
|
|
}
|
|
} else if (!mThumbnailUrl.IsEmpty()) {
|
|
if (mozilla::dom::IsImageIn(mArtwork, mThumbnailUrl)) {
|
|
LOG("No need to load thumbnail. The one in use is in the artwork");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If there is a pending image store operation, that image must be different
|
|
// from the new image will be loaded below, so the pending one should be
|
|
// cancelled.
|
|
CancelPendingStoreAsyncOperation();
|
|
// Remove the current thumbnail on the interface
|
|
ClearThumbnail();
|
|
// Then load the new thumbnail asynchronously
|
|
LoadImageAtIndex(mNextImageIndex++);
|
|
}
|
|
|
|
void WindowsSMTCProvider::LoadImageAtIndex(const size_t aIndex) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (aIndex >= mArtwork.Length()) {
|
|
LOG("Stop loading thumbnail. No more available images");
|
|
mImageFetchRequest.DisconnectIfExists();
|
|
mProcessingUrl.Truncate();
|
|
return;
|
|
}
|
|
|
|
const mozilla::dom::MediaImage& image = mArtwork[aIndex];
|
|
|
|
// TODO: No need to fetch the default image and do image processing since the
|
|
// the default image is local file and it's trustworthy. For the default
|
|
// image, we can use `CreateFromFile` to create the IRandomAccessStream. We
|
|
// should probably cache it since it could be used very often (Bug 1643102)
|
|
|
|
if (!mozilla::dom::IsValidImageUrl(image.mSrc)) {
|
|
LOG("Skip the image with invalid URL. Try next image");
|
|
mImageFetchRequest.DisconnectIfExists();
|
|
LoadImageAtIndex(mNextImageIndex++);
|
|
return;
|
|
}
|
|
|
|
mImageFetchRequest.DisconnectIfExists();
|
|
mProcessingUrl = image.mSrc;
|
|
|
|
mImageFetcher = mozilla::MakeUnique<mozilla::dom::FetchImageHelper>(image);
|
|
RefPtr<WindowsSMTCProvider> self = this;
|
|
mImageFetcher->FetchImage()
|
|
->Then(
|
|
AbstractThread::MainThread(), __func__,
|
|
[this, self](const nsCOMPtr<imgIContainer>& aImage) {
|
|
LOG("The image is fetched successfully");
|
|
mImageFetchRequest.Complete();
|
|
|
|
// Although IMAGE_JPEG or IMAGE_BMP are valid types as well, but a
|
|
// png image with transparent background will be converted into a
|
|
// jpeg/bmp file with a colored background. IMAGE_PNG format seems
|
|
// to be the best choice for now.
|
|
uint32_t size = 0;
|
|
char* src = nullptr;
|
|
// Only used to hold the image data
|
|
nsCOMPtr<nsIInputStream> inputStream;
|
|
nsresult rv = mozilla::dom::GetEncodedImageBuffer(
|
|
aImage, nsLiteralCString(IMAGE_PNG),
|
|
getter_AddRefs(inputStream), &size, &src);
|
|
if (NS_FAILED(rv) || !inputStream || size == 0 || !src) {
|
|
LOG("Failed to get the image buffer info. Try next image");
|
|
LoadImageAtIndex(mNextImageIndex++);
|
|
return;
|
|
}
|
|
|
|
LoadImage(src, size);
|
|
},
|
|
[this, self](bool) {
|
|
LOG("Failed to fetch image. Try next image");
|
|
mImageFetchRequest.Complete();
|
|
LoadImageAtIndex(mNextImageIndex++);
|
|
})
|
|
->Track(mImageFetchRequest);
|
|
}
|
|
|
|
void WindowsSMTCProvider::LoadImage(const char* aImageData,
|
|
uint32_t aDataSize) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// 1. Use mImageDataWriter to write the binary data of image into mImageStream
|
|
// 2. Refer the image by mImageStreamReference and then set it to the SMTC
|
|
// In case of the race condition between they are being destroyed and the
|
|
// async operation for image loading, mImageDataWriter, mImageStream, and
|
|
// mImageStreamReference are member variables
|
|
|
|
HRESULT hr = ActivateInstance(
|
|
HStringReference(
|
|
RuntimeClass_Windows_Storage_Streams_InMemoryRandomAccessStream)
|
|
.Get(),
|
|
mImageStream.GetAddressOf());
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to make mImageStream refer to an instance of "
|
|
"InMemoryRandomAccessStream");
|
|
return;
|
|
}
|
|
|
|
ComPtr<IOutputStream> outputStream;
|
|
hr = mImageStream.As(&outputStream);
|
|
if (FAILED(hr)) {
|
|
LOG("Failed when query IOutputStream interface from mImageStream");
|
|
return;
|
|
}
|
|
|
|
ComPtr<IDataWriterFactory> dataWriterFactory;
|
|
hr = GetActivationFactory(
|
|
HStringReference(RuntimeClass_Windows_Storage_Streams_DataWriter).Get(),
|
|
dataWriterFactory.GetAddressOf());
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to get an activation factory for IDataWriterFactory");
|
|
return;
|
|
}
|
|
|
|
hr = dataWriterFactory->CreateDataWriter(outputStream.Get(),
|
|
mImageDataWriter.GetAddressOf());
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to create mImageDataWriter that writes data to mImageStream");
|
|
return;
|
|
}
|
|
|
|
hr = mImageDataWriter->WriteBytes(
|
|
aDataSize, reinterpret_cast<BYTE*>(const_cast<char*>(aImageData)));
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to write data to mImageStream");
|
|
return;
|
|
}
|
|
|
|
hr = mImageDataWriter->StoreAsync(&mStoreAsyncOperation);
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to create a DataWriterStoreOperation for mStoreAsyncOperation");
|
|
return;
|
|
}
|
|
|
|
// Upon the image is stored in mImageStream, set the image to the SMTC
|
|
// interface
|
|
auto onStoreCompleted = Callback<
|
|
IAsyncOperationCompletedHandler<unsigned int>>(
|
|
[this, self = RefPtr<WindowsSMTCProvider>(this),
|
|
aImageUrl = nsString(mProcessingUrl)](
|
|
IAsyncOperation<unsigned int>* aAsyncOp, AsyncStatus aStatus) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (aStatus != AsyncStatus::Completed) {
|
|
LOG("Asynchronous operation is not completed");
|
|
return E_ABORT;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
IAsyncInfo* asyncInfo = GetIAsyncInfo(aAsyncOp);
|
|
asyncInfo->get_ErrorCode(&hr);
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to get termination status of the asynchronous operation");
|
|
return hr;
|
|
}
|
|
|
|
if (!UpdateThumbnail(aImageUrl)) {
|
|
LOG("Failed to update thumbnail");
|
|
}
|
|
|
|
// If an error occurs above:
|
|
// - If aImageUrl is not mProcessingUrl. It's fine.
|
|
// - If aImageUrl is mProcessingUrl, then mProcessingUrl won't be reset.
|
|
// Therefore the thumbnail will remain empty until a new image whose
|
|
// url is different from mProcessingUrl is loaded.
|
|
|
|
return S_OK;
|
|
});
|
|
|
|
hr = mStoreAsyncOperation->put_Completed(onStoreCompleted.Get());
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to set callback on completeing the asynchronous operation");
|
|
}
|
|
}
|
|
|
|
bool WindowsSMTCProvider::SetThumbnail(const nsAString& aUrl) {
|
|
MOZ_ASSERT(mDisplay);
|
|
MOZ_ASSERT(mImageStream);
|
|
MOZ_ASSERT(!aUrl.IsEmpty());
|
|
|
|
ComPtr<IRandomAccessStreamReferenceStatics> streamRefFactory;
|
|
|
|
HRESULT hr = GetActivationFactory(
|
|
HStringReference(
|
|
RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference)
|
|
.Get(),
|
|
streamRefFactory.GetAddressOf());
|
|
auto cleanup =
|
|
MakeScopeExit([this, self = RefPtr<WindowsSMTCProvider>(this)] {
|
|
LOG("Clean mThumbnailUrl");
|
|
mThumbnailUrl.Truncate();
|
|
});
|
|
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to get an activation factory for "
|
|
"IRandomAccessStreamReferenceStatics type");
|
|
return false;
|
|
}
|
|
|
|
hr = streamRefFactory->CreateFromStream(mImageStream.Get(),
|
|
mImageStreamReference.GetAddressOf());
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to create mImageStreamReference from mImageStream");
|
|
return false;
|
|
}
|
|
|
|
hr = mDisplay->put_Thumbnail(mImageStreamReference.Get());
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to update thumbnail");
|
|
return false;
|
|
}
|
|
|
|
hr = mDisplay->Update();
|
|
if (FAILED(hr)) {
|
|
LOG("Failed to refresh display");
|
|
return false;
|
|
}
|
|
|
|
// No need to clean mThumbnailUrl since thumbnail is set successfully
|
|
cleanup.release();
|
|
mThumbnailUrl = aUrl;
|
|
|
|
return true;
|
|
}
|
|
|
|
void WindowsSMTCProvider::ClearThumbnail() {
|
|
MOZ_ASSERT(mDisplay);
|
|
HRESULT hr = mDisplay->put_Thumbnail(nullptr);
|
|
MOZ_ASSERT(SUCCEEDED(hr));
|
|
hr = mDisplay->Update();
|
|
MOZ_ASSERT(SUCCEEDED(hr));
|
|
Unused << hr;
|
|
mThumbnailUrl.Truncate();
|
|
}
|
|
|
|
bool WindowsSMTCProvider::UpdateThumbnail(const nsAString& aUrl) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!IsOpened()) {
|
|
LOG("Abort the thumbnail update: SMTC is closed");
|
|
return false;
|
|
}
|
|
|
|
if (aUrl != mProcessingUrl) {
|
|
LOG("Abort the thumbnail update: The image from %s is out of date",
|
|
NS_ConvertUTF16toUTF8(aUrl).get());
|
|
return false;
|
|
}
|
|
|
|
mProcessingUrl.Truncate();
|
|
|
|
if (!SetThumbnail(aUrl)) {
|
|
LOG("Failed to update thumbnail");
|
|
return false;
|
|
}
|
|
|
|
MOZ_ASSERT(mThumbnailUrl == aUrl);
|
|
LOG("The thumbnail is updated to the image from: %s",
|
|
NS_ConvertUTF16toUTF8(mThumbnailUrl).get());
|
|
return true;
|
|
}
|
|
|
|
void WindowsSMTCProvider::CancelPendingStoreAsyncOperation() const {
|
|
if (mStoreAsyncOperation) {
|
|
IAsyncInfo* asyncInfo = GetIAsyncInfo(mStoreAsyncOperation.Get());
|
|
asyncInfo->Cancel();
|
|
}
|
|
}
|
|
|
|
#endif // __MINGW32__
|