/* -*- 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"
staticinline 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<unsignedint>* 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());
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());
}
}
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;
}
}
// 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;
}
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()); returnfalse;
}
}
void WindowsSMTCProvider::OnPositionChangeRequested(double aPosition) const { if (!IsKeySupported(mozilla::dom::MediaControlKey::Seekto)) {
LOG("Seekto is not supported"); return;
}
for (constauto& 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) { returntrue;
}
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"); returnfalse;
}
MOZ_ASSERT(interop);
if (!mControls && FAILED(interop->GetForWindow(
mWindow, IID_PPV_ARGS(mControls.GetAddressOf())))) {
LOG("SystemMediaTransportControls: Failed at GetForWindow()"); returnfalse;
}
MOZ_ASSERT(mControls);
if (!mDisplay &&
FAILED(mControls->get_DisplayUpdater(mDisplay.GetAddressOf()))) {
LOG("SystemMediaTransportControls: Failed at get_DisplayUpdater()");
}
// 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;
}
} elseif (!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++);
}
if (aIndex >= mArtwork.Length()) {
LOG("Stop loading thumbnail. No more available images");
mImageFetchRequest.DisconnectIfExists();
mProcessingUrl.Truncate(); return;
}
// 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;
}
// 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);
}
// 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<unsignedint>>(
[this, self = RefPtr<WindowsSMTCProvider>(this),
aImageUrl = nsString(mProcessingUrl)](
IAsyncOperation<unsignedint>* 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");
}
}
if (FAILED(hr)) {
LOG("Failed to get an activation factory for " "IRandomAccessStreamReferenceStatics type"); returnfalse;
}
hr = streamRefFactory->CreateFromStream(mImageStream.Get(),
mImageStreamReference.GetAddressOf()); if (FAILED(hr)) {
LOG("Failed to create mImageStreamReference from mImageStream"); returnfalse;
}
hr = mDisplay->put_Thumbnail(mImageStreamReference.Get()); if (FAILED(hr)) {
LOG("Failed to update thumbnail"); returnfalse;
}
hr = mDisplay->Update(); if (FAILED(hr)) {
LOG("Failed to refresh display"); returnfalse;
}
// No need to clean mThumbnailUrl since thumbnail is set successfully
cleanup.release();
mThumbnailUrl = aUrl;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.