1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 /* mingw currently doesn't support windows.media.h, so we disable 7 * the whole related class until this is fixed. 8 * @TODO: Maybe contact MinGW Team for inclusion?*/ 9 #ifndef __MINGW32__ 10 11 # include "WindowsSMTCProvider.h" 12 13 # include <windows.h> 14 # include <windows.media.h> 15 # include <winsdkver.h> 16 # include <wrl.h> 17 18 # include "nsMimeTypes.h" 19 # include "mozilla/Assertions.h" 20 # include "mozilla/Logging.h" 21 # include "mozilla/Maybe.h" 22 # include "mozilla/WidgetUtils.h" 23 # include "mozilla/WindowsVersion.h" 24 # include "mozilla/ScopeExit.h" 25 # include "mozilla/dom/MediaControlUtils.h" 26 # include "mozilla/media/MediaUtils.h" 27 # include "nsThreadUtils.h" 28 29 # pragma comment(lib, "runtimeobject.lib") 30 31 using namespace ABI::Windows::Foundation; 32 using namespace ABI::Windows::Media; 33 using namespace ABI::Windows::Storage::Streams; 34 using namespace Microsoft::WRL; 35 using namespace Microsoft::WRL::Wrappers; 36 using namespace mozilla; 37 38 # ifndef RuntimeClass_Windows_Media_SystemMediaTransportControls 39 # define RuntimeClass_Windows_Media_SystemMediaTransportControls \ 40 L"Windows.Media.SystemMediaTransportControls" 41 # endif 42 43 # ifndef RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference 44 # define RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference \ 45 L"Windows.Storage.Streams.RandomAccessStreamReference" 46 # endif 47 48 # ifndef ISystemMediaTransportControlsInterop 49 EXTERN_C const IID IID_ISystemMediaTransportControlsInterop; 50 MIDL_INTERFACE("ddb0472d-c911-4a1f-86d9-dc3d71a95f5a") 51 ISystemMediaTransportControlsInterop : public IInspectable { 52 public: 53 virtual HRESULT STDMETHODCALLTYPE GetForWindow( 54 /* [in] */ __RPC__in HWND appWindow, 55 /* [in] */ __RPC__in REFIID riid, 56 /* [iid_is][retval][out] */ 57 __RPC__deref_out_opt void** mediaTransportControl) = 0; 58 }; 59 # endif /* __ISystemMediaTransportControlsInterop_INTERFACE_DEFINED__ */ 60 61 extern mozilla::LazyLogModule gMediaControlLog; 62 63 # undef LOG 64 # define LOG(msg, ...) \ 65 MOZ_LOG(gMediaControlLog, LogLevel::Debug, \ 66 ("WindowSMTCProvider=%p, " msg, this, ##__VA_ARGS__)) 67 68 static inline Maybe<mozilla::dom::MediaControlKey> TranslateKeycode( 69 SystemMediaTransportControlsButton keycode) { 70 switch (keycode) { 71 case SystemMediaTransportControlsButton_Play: 72 return Some(mozilla::dom::MediaControlKey::Play); 73 case SystemMediaTransportControlsButton_Pause: 74 return Some(mozilla::dom::MediaControlKey::Pause); 75 case SystemMediaTransportControlsButton_Next: 76 return Some(mozilla::dom::MediaControlKey::Nexttrack); 77 case SystemMediaTransportControlsButton_Previous: 78 return Some(mozilla::dom::MediaControlKey::Previoustrack); 79 case SystemMediaTransportControlsButton_Stop: 80 return Some(mozilla::dom::MediaControlKey::Stop); 81 case SystemMediaTransportControlsButton_FastForward: 82 return Some(mozilla::dom::MediaControlKey::Seekforward); 83 case SystemMediaTransportControlsButton_Rewind: 84 return Some(mozilla::dom::MediaControlKey::Seekbackward); 85 default: 86 return Nothing(); // Not supported Button 87 } 88 } 89 90 static IAsyncInfo* GetIAsyncInfo(IAsyncOperation<unsigned int>* aAsyncOp) { 91 MOZ_ASSERT(aAsyncOp); 92 IAsyncInfo* asyncInfo; 93 HRESULT hr = aAsyncOp->QueryInterface(IID_IAsyncInfo, 94 reinterpret_cast<void**>(&asyncInfo)); 95 // The assertion always works since IAsyncOperation implements IAsyncInfo 96 MOZ_ASSERT(SUCCEEDED(hr)); 97 Unused << hr; 98 MOZ_ASSERT(asyncInfo); 99 return asyncInfo; 100 } 101 102 WindowsSMTCProvider::WindowsSMTCProvider() { 103 LOG("Creating an empty and invisible window"); 104 105 // In order to create a SMTC-Provider, we need a hWnd, which shall be created 106 // dynamically from an invisible window. This leads to the following 107 // boilerplate code. 108 WNDCLASS wnd{}; 109 wnd.lpszClassName = L"Firefox-MediaKeys"; 110 wnd.hInstance = nullptr; 111 wnd.lpfnWndProc = DefWindowProc; 112 GetLastError(); // Clear the error 113 RegisterClass(&wnd); 114 MOZ_ASSERT(!GetLastError()); 115 116 mWindow = CreateWindowExW(0, L"Firefox-MediaKeys", L"Firefox Media Keys", 0, 117 CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, nullptr, 118 nullptr, nullptr, nullptr); 119 MOZ_ASSERT(mWindow); 120 MOZ_ASSERT(!GetLastError()); 121 } 122 123 WindowsSMTCProvider::~WindowsSMTCProvider() { 124 // Dispose the window 125 MOZ_ASSERT(mWindow); 126 if (!DestroyWindow(mWindow)) { 127 LOG("Failed to destroy the hidden window. Error Code: %d", GetLastError()); 128 } 129 if (!UnregisterClass(L"Firefox-MediaKeys", nullptr)) { 130 // Note that this is logged when the class wasn't even registered. 131 LOG("Failed to unregister the class. Error Code: %d", GetLastError()); 132 } 133 } 134 135 bool WindowsSMTCProvider::IsOpened() const { return mInitialized; } 136 137 bool WindowsSMTCProvider::Open() { 138 LOG("Opening Source"); 139 MOZ_ASSERT(!mInitialized); 140 141 if (!IsWin8Point1OrLater()) { 142 LOG("Windows 8.1 or later is required for Media Key Support"); 143 return false; 144 } 145 146 if (!InitDisplayAndControls()) { 147 LOG("Failed to initialize the SMTC and its display"); 148 return false; 149 } 150 151 if (!UpdateButtons()) { 152 LOG("Failed to initialize the buttons"); 153 return false; 154 } 155 156 if (!RegisterEvents()) { 157 LOG("Failed to register SMTC key-event listener"); 158 return false; 159 } 160 161 if (!EnableControl(true)) { 162 LOG("Failed to enable SMTC control"); 163 return false; 164 } 165 166 mInitialized = true; 167 SetPlaybackState(mozilla::dom::MediaSessionPlaybackState::None); 168 return mInitialized; 169 } 170 171 void WindowsSMTCProvider::Close() { 172 MediaControlKeySource::Close(); 173 // Prevent calling Set methods when init failed 174 if (mInitialized) { 175 SetPlaybackState(mozilla::dom::MediaSessionPlaybackState::None); 176 UnregisterEvents(); 177 ClearMetadata(); 178 // We have observed an Windows issue, if we modify `mControls` , (such as 179 // setting metadata, disable buttons) before disabling control, and those 180 // operations are not done sequentially within a same main thread task, 181 // then it would cause a problem where the SMTC wasn't clean up completely 182 // and show the executable name. 183 EnableControl(false); 184 mInitialized = false; 185 } 186 } 187 188 void WindowsSMTCProvider::SetPlaybackState( 189 mozilla::dom::MediaSessionPlaybackState aState) { 190 MOZ_ASSERT(mInitialized); 191 MediaControlKeySource::SetPlaybackState(aState); 192 193 HRESULT hr; 194 195 // Note: we can't return the status of put_PlaybackStatus, but we can at least 196 // assert it. 197 switch (aState) { 198 case mozilla::dom::MediaSessionPlaybackState::Paused: 199 hr = mControls->put_PlaybackStatus( 200 ABI::Windows::Media::MediaPlaybackStatus_Paused); 201 break; 202 case mozilla::dom::MediaSessionPlaybackState::Playing: 203 hr = mControls->put_PlaybackStatus( 204 ABI::Windows::Media::MediaPlaybackStatus_Playing); 205 break; 206 case mozilla::dom::MediaSessionPlaybackState::None: 207 hr = mControls->put_PlaybackStatus( 208 ABI::Windows::Media::MediaPlaybackStatus_Stopped); 209 break; 210 // MediaPlaybackStatus still supports Closed and Changing, which we don't 211 // use (yet) 212 default: 213 MOZ_ASSERT_UNREACHABLE( 214 "Enum Inconsitency between PlaybackState and WindowsSMTCProvider"); 215 break; 216 } 217 218 MOZ_ASSERT(SUCCEEDED(hr)); 219 Unused << hr; 220 } 221 222 void WindowsSMTCProvider::SetMediaMetadata( 223 const mozilla::dom::MediaMetadataBase& aMetadata) { 224 MOZ_ASSERT(mInitialized); 225 SetMusicMetadata(aMetadata.mArtist.get(), aMetadata.mTitle.get(), 226 aMetadata.mAlbum.get()); 227 LoadThumbnail(aMetadata.mArtwork); 228 } 229 230 void WindowsSMTCProvider::ClearMetadata() { 231 MOZ_ASSERT(mDisplay); 232 if (FAILED(mDisplay->ClearAll())) { 233 LOG("Failed to clear SMTC display"); 234 } 235 mImageFetchRequest.DisconnectIfExists(); 236 CancelPendingStoreAsyncOperation(); 237 mThumbnailUrl.Truncate(); 238 mProcessingUrl.Truncate(); 239 mNextImageIndex = 0; 240 mSupportedKeys = 0; 241 } 242 243 void WindowsSMTCProvider::SetSupportedMediaKeys( 244 const MediaKeysArray& aSupportedKeys) { 245 MOZ_ASSERT(mInitialized); 246 247 uint32_t supportedKeys = 0; 248 for (const mozilla::dom::MediaControlKey& key : aSupportedKeys) { 249 supportedKeys |= GetMediaKeyMask(key); 250 } 251 252 if (supportedKeys == mSupportedKeys) { 253 LOG("Supported keys stay the same"); 254 return; 255 } 256 257 LOG("Update supported keys"); 258 mSupportedKeys = supportedKeys; 259 UpdateButtons(); 260 } 261 262 void WindowsSMTCProvider::UnregisterEvents() { 263 if (mControls && mButtonPressedToken.value != 0) { 264 mControls->remove_ButtonPressed(mButtonPressedToken); 265 } 266 } 267 268 bool WindowsSMTCProvider::RegisterEvents() { 269 MOZ_ASSERT(mControls); 270 auto self = RefPtr<WindowsSMTCProvider>(this); 271 auto callbackbtnPressed = Callback< 272 ITypedEventHandler<SystemMediaTransportControls*, 273 SystemMediaTransportControlsButtonPressedEventArgs*>>( 274 [this, self](ISystemMediaTransportControls*, 275 ISystemMediaTransportControlsButtonPressedEventArgs* pArgs) 276 -> HRESULT { 277 MOZ_ASSERT(pArgs); 278 SystemMediaTransportControlsButton btn; 279 280 if (FAILED(pArgs->get_Button(&btn))) { 281 LOG("SystemMediaTransportControls: ButtonPressedEvent - Could " 282 "not get Button."); 283 return S_OK; // Propagating the error probably wouldn't help. 284 } 285 286 Maybe<mozilla::dom::MediaControlKey> keyCode = TranslateKeycode(btn); 287 if (keyCode.isSome() && IsOpened()) { 288 OnButtonPressed(keyCode.value()); 289 } 290 return S_OK; 291 }); 292 293 if (FAILED(mControls->add_ButtonPressed(callbackbtnPressed.Get(), 294 &mButtonPressedToken))) { 295 LOG("SystemMediaTransportControls: Failed at " 296 "registerEvents().add_ButtonPressed()"); 297 return false; 298 } 299 300 return true; 301 } 302 303 void WindowsSMTCProvider::OnButtonPressed( 304 mozilla::dom::MediaControlKey aKey) const { 305 if (!IsKeySupported(aKey)) { 306 LOG("key: %s is not supported", ToMediaControlKeyStr(aKey)); 307 return; 308 } 309 310 for (auto& listener : mListeners) { 311 listener->OnActionPerformed(mozilla::dom::MediaControlAction(aKey)); 312 } 313 } 314 315 bool WindowsSMTCProvider::EnableControl(bool aEnabled) const { 316 MOZ_ASSERT(mControls); 317 return SUCCEEDED(mControls->put_IsEnabled(aEnabled)); 318 } 319 320 bool WindowsSMTCProvider::UpdateButtons() const { 321 static const mozilla::dom::MediaControlKey kKeys[] = { 322 mozilla::dom::MediaControlKey::Play, mozilla::dom::MediaControlKey::Pause, 323 mozilla::dom::MediaControlKey::Previoustrack, 324 mozilla::dom::MediaControlKey::Nexttrack, 325 mozilla::dom::MediaControlKey::Stop}; 326 327 bool success = true; 328 for (const mozilla::dom::MediaControlKey& key : kKeys) { 329 if (!EnableKey(key, IsKeySupported(key))) { 330 success = false; 331 LOG("Failed to set %s=%s", ToMediaControlKeyStr(key), 332 IsKeySupported(key) ? "true" : "false"); 333 } 334 } 335 336 return success; 337 } 338 339 bool WindowsSMTCProvider::IsKeySupported( 340 mozilla::dom::MediaControlKey aKey) const { 341 return mSupportedKeys & GetMediaKeyMask(aKey); 342 } 343 344 bool WindowsSMTCProvider::EnableKey(mozilla::dom::MediaControlKey aKey, 345 bool aEnable) const { 346 MOZ_ASSERT(mControls); 347 switch (aKey) { 348 case mozilla::dom::MediaControlKey::Play: 349 return SUCCEEDED(mControls->put_IsPlayEnabled(aEnable)); 350 case mozilla::dom::MediaControlKey::Pause: 351 return SUCCEEDED(mControls->put_IsPauseEnabled(aEnable)); 352 case mozilla::dom::MediaControlKey::Previoustrack: 353 return SUCCEEDED(mControls->put_IsPreviousEnabled(aEnable)); 354 case mozilla::dom::MediaControlKey::Nexttrack: 355 return SUCCEEDED(mControls->put_IsNextEnabled(aEnable)); 356 case mozilla::dom::MediaControlKey::Stop: 357 return SUCCEEDED(mControls->put_IsStopEnabled(aEnable)); 358 default: 359 LOG("No button for %s", ToMediaControlKeyStr(aKey)); 360 return false; 361 } 362 } 363 364 bool WindowsSMTCProvider::InitDisplayAndControls() { 365 // As Open() might be called multiple times, "cache" the results of the COM 366 // API 367 if (mControls && mDisplay) { 368 return true; 369 } 370 ComPtr<ISystemMediaTransportControlsInterop> interop; 371 HRESULT hr = GetActivationFactory( 372 HStringReference(RuntimeClass_Windows_Media_SystemMediaTransportControls) 373 .Get(), 374 interop.GetAddressOf()); 375 if (FAILED(hr)) { 376 LOG("SystemMediaTransportControls: Failed at instantiating the " 377 "Interop object"); 378 return false; 379 } 380 MOZ_ASSERT(interop); 381 382 if (!mControls && FAILED(interop->GetForWindow( 383 mWindow, IID_PPV_ARGS(mControls.GetAddressOf())))) { 384 LOG("SystemMediaTransportControls: Failed at GetForWindow()"); 385 return false; 386 } 387 MOZ_ASSERT(mControls); 388 389 if (!mDisplay && 390 FAILED(mControls->get_DisplayUpdater(mDisplay.GetAddressOf()))) { 391 LOG("SystemMediaTransportControls: Failed at get_DisplayUpdater()"); 392 } 393 394 MOZ_ASSERT(mDisplay); 395 return true; 396 } 397 398 bool WindowsSMTCProvider::SetMusicMetadata(const wchar_t* aArtist, 399 const wchar_t* aTitle, 400 const wchar_t* aAlbumArtist) { 401 MOZ_ASSERT(mDisplay); 402 MOZ_ASSERT(aArtist); 403 MOZ_ASSERT(aTitle); 404 MOZ_ASSERT(aAlbumArtist); 405 ComPtr<IMusicDisplayProperties> musicProps; 406 407 HRESULT hr = mDisplay->put_Type(MediaPlaybackType::MediaPlaybackType_Music); 408 MOZ_ASSERT(SUCCEEDED(hr)); 409 Unused << hr; 410 hr = mDisplay->get_MusicProperties(musicProps.GetAddressOf()); 411 if (FAILED(hr)) { 412 LOG("Failed to get music properties"); 413 return false; 414 } 415 416 hr = musicProps->put_Artist(HStringReference(aArtist).Get()); 417 if (FAILED(hr)) { 418 LOG("Failed to set the music's artist"); 419 return false; 420 } 421 422 hr = musicProps->put_Title(HStringReference(aTitle).Get()); 423 if (FAILED(hr)) { 424 LOG("Failed to set the music's title"); 425 return false; 426 } 427 428 hr = musicProps->put_AlbumArtist(HStringReference(aAlbumArtist).Get()); 429 if (FAILED(hr)) { 430 LOG("Failed to set the music's album"); 431 return false; 432 } 433 434 hr = mDisplay->Update(); 435 if (FAILED(hr)) { 436 LOG("Failed to refresh the display"); 437 return false; 438 } 439 440 return true; 441 } 442 443 void WindowsSMTCProvider::LoadThumbnail( 444 const nsTArray<mozilla::dom::MediaImage>& aArtwork) { 445 MOZ_ASSERT(NS_IsMainThread()); 446 447 // TODO: Sort the images by the preferred size or format. 448 mArtwork = aArtwork; 449 mNextImageIndex = 0; 450 451 // Abort the loading if 452 // 1) thumbnail is being updated, and one in processing is in the artwork 453 // 2) thumbnail is not being updated, and one in use is in the artwork 454 if (!mProcessingUrl.IsEmpty()) { 455 LOG("Load thumbnail while image: %s is being processed", 456 NS_ConvertUTF16toUTF8(mProcessingUrl).get()); 457 if (mozilla::dom::IsImageIn(mArtwork, mProcessingUrl)) { 458 LOG("No need to load thumbnail. The one being processed is in the " 459 "artwork"); 460 return; 461 } 462 } else if (!mThumbnailUrl.IsEmpty()) { 463 if (mozilla::dom::IsImageIn(mArtwork, mThumbnailUrl)) { 464 LOG("No need to load thumbnail. The one in use is in the artwork"); 465 return; 466 } 467 } 468 469 // If there is a pending image store operation, that image must be different 470 // from the new image will be loaded below, so the pending one should be 471 // cancelled. 472 CancelPendingStoreAsyncOperation(); 473 // Remove the current thumbnail on the interface 474 ClearThumbnail(); 475 // Then load the new thumbnail asynchronously 476 LoadImageAtIndex(mNextImageIndex++); 477 } 478 479 void WindowsSMTCProvider::LoadImageAtIndex(const size_t aIndex) { 480 MOZ_ASSERT(NS_IsMainThread()); 481 482 if (aIndex >= mArtwork.Length()) { 483 LOG("Stop loading thumbnail. No more available images"); 484 mImageFetchRequest.DisconnectIfExists(); 485 mProcessingUrl.Truncate(); 486 return; 487 } 488 489 const mozilla::dom::MediaImage& image = mArtwork[aIndex]; 490 491 // TODO: No need to fetch the default image and do image processing since the 492 // the default image is local file and it's trustworthy. For the default 493 // image, we can use `CreateFromFile` to create the IRandomAccessStream. We 494 // should probably cache it since it could be used very often (Bug 1643102) 495 496 if (!mozilla::dom::IsValidImageUrl(image.mSrc)) { 497 LOG("Skip the image with invalid URL. Try next image"); 498 mImageFetchRequest.DisconnectIfExists(); 499 LoadImageAtIndex(mNextImageIndex++); 500 return; 501 } 502 503 mImageFetchRequest.DisconnectIfExists(); 504 mProcessingUrl = image.mSrc; 505 506 mImageFetcher = mozilla::MakeUnique<mozilla::dom::FetchImageHelper>(image); 507 RefPtr<WindowsSMTCProvider> self = this; 508 mImageFetcher->FetchImage() 509 ->Then( 510 AbstractThread::MainThread(), __func__, 511 [this, self](const nsCOMPtr<imgIContainer>& aImage) { 512 LOG("The image is fetched successfully"); 513 mImageFetchRequest.Complete(); 514 515 // Although IMAGE_JPEG or IMAGE_BMP are valid types as well, but a 516 // png image with transparent background will be converted into a 517 // jpeg/bmp file with a colored background. IMAGE_PNG format seems 518 // to be the best choice for now. 519 uint32_t size = 0; 520 char* src = nullptr; 521 // Only used to hold the image data 522 nsCOMPtr<nsIInputStream> inputStream; 523 nsresult rv = mozilla::dom::GetEncodedImageBuffer( 524 aImage, nsLiteralCString(IMAGE_PNG), 525 getter_AddRefs(inputStream), &size, &src); 526 if (NS_FAILED(rv) || !inputStream || size == 0 || !src) { 527 LOG("Failed to get the image buffer info. Try next image"); 528 LoadImageAtIndex(mNextImageIndex++); 529 return; 530 } 531 532 LoadImage(src, size); 533 }, 534 [this, self](bool) { 535 LOG("Failed to fetch image. Try next image"); 536 mImageFetchRequest.Complete(); 537 LoadImageAtIndex(mNextImageIndex++); 538 }) 539 ->Track(mImageFetchRequest); 540 } 541 542 void WindowsSMTCProvider::LoadImage(const char* aImageData, 543 uint32_t aDataSize) { 544 MOZ_ASSERT(NS_IsMainThread()); 545 546 // 1. Use mImageDataWriter to write the binary data of image into mImageStream 547 // 2. Refer the image by mImageStreamReference and then set it to the SMTC 548 // In case of the race condition between they are being destroyed and the 549 // async operation for image loading, mImageDataWriter, mImageStream, and 550 // mImageStreamReference are member variables 551 552 HRESULT hr = ActivateInstance( 553 HStringReference( 554 RuntimeClass_Windows_Storage_Streams_InMemoryRandomAccessStream) 555 .Get(), 556 mImageStream.GetAddressOf()); 557 if (FAILED(hr)) { 558 LOG("Failed to make mImageStream refer to an instance of " 559 "InMemoryRandomAccessStream"); 560 return; 561 } 562 563 ComPtr<IOutputStream> outputStream; 564 hr = mImageStream.As(&outputStream); 565 if (FAILED(hr)) { 566 LOG("Failed when query IOutputStream interface from mImageStream"); 567 return; 568 } 569 570 ComPtr<IDataWriterFactory> dataWriterFactory; 571 hr = GetActivationFactory( 572 HStringReference(RuntimeClass_Windows_Storage_Streams_DataWriter).Get(), 573 dataWriterFactory.GetAddressOf()); 574 if (FAILED(hr)) { 575 LOG("Failed to get an activation factory for IDataWriterFactory"); 576 return; 577 } 578 579 hr = dataWriterFactory->CreateDataWriter(outputStream.Get(), 580 mImageDataWriter.GetAddressOf()); 581 if (FAILED(hr)) { 582 LOG("Failed to create mImageDataWriter that writes data to mImageStream"); 583 return; 584 } 585 586 hr = mImageDataWriter->WriteBytes( 587 aDataSize, reinterpret_cast<BYTE*>(const_cast<char*>(aImageData))); 588 if (FAILED(hr)) { 589 LOG("Failed to write data to mImageStream"); 590 return; 591 } 592 593 hr = mImageDataWriter->StoreAsync(&mStoreAsyncOperation); 594 if (FAILED(hr)) { 595 LOG("Failed to create a DataWriterStoreOperation for mStoreAsyncOperation"); 596 return; 597 } 598 599 // Upon the image is stored in mImageStream, set the image to the SMTC 600 // interface 601 auto onStoreCompleted = Callback< 602 IAsyncOperationCompletedHandler<unsigned int>>( 603 [this, self = RefPtr<WindowsSMTCProvider>(this), 604 aImageUrl = nsString(mProcessingUrl)]( 605 IAsyncOperation<unsigned int>* aAsyncOp, AsyncStatus aStatus) { 606 MOZ_ASSERT(NS_IsMainThread()); 607 608 if (aStatus != AsyncStatus::Completed) { 609 LOG("Asynchronous operation is not completed"); 610 return E_ABORT; 611 } 612 613 HRESULT hr = S_OK; 614 IAsyncInfo* asyncInfo = GetIAsyncInfo(aAsyncOp); 615 asyncInfo->get_ErrorCode(&hr); 616 if (FAILED(hr)) { 617 LOG("Failed to get termination status of the asynchronous operation"); 618 return hr; 619 } 620 621 if (!UpdateThumbnail(aImageUrl)) { 622 LOG("Failed to update thumbnail"); 623 } 624 625 // If an error occurs above: 626 // - If aImageUrl is not mProcessingUrl. It's fine. 627 // - If aImageUrl is mProcessingUrl, then mProcessingUrl won't be reset. 628 // Therefore the thumbnail will remain empty until a new image whose 629 // url is different from mProcessingUrl is loaded. 630 631 return S_OK; 632 }); 633 634 hr = mStoreAsyncOperation->put_Completed(onStoreCompleted.Get()); 635 if (FAILED(hr)) { 636 LOG("Failed to set callback on completeing the asynchronous operation"); 637 } 638 } 639 640 bool WindowsSMTCProvider::SetThumbnail(const nsAString& aUrl) { 641 MOZ_ASSERT(mDisplay); 642 MOZ_ASSERT(mImageStream); 643 MOZ_ASSERT(!aUrl.IsEmpty()); 644 645 ComPtr<IRandomAccessStreamReferenceStatics> streamRefFactory; 646 647 HRESULT hr = GetActivationFactory( 648 HStringReference( 649 RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference) 650 .Get(), 651 streamRefFactory.GetAddressOf()); 652 auto cleanup = 653 MakeScopeExit([this, self = RefPtr<WindowsSMTCProvider>(this)] { 654 LOG("Clean mThumbnailUrl"); 655 mThumbnailUrl.Truncate(); 656 }); 657 658 if (FAILED(hr)) { 659 LOG("Failed to get an activation factory for " 660 "IRandomAccessStreamReferenceStatics type"); 661 return false; 662 } 663 664 hr = streamRefFactory->CreateFromStream(mImageStream.Get(), 665 mImageStreamReference.GetAddressOf()); 666 if (FAILED(hr)) { 667 LOG("Failed to create mImageStreamReference from mImageStream"); 668 return false; 669 } 670 671 hr = mDisplay->put_Thumbnail(mImageStreamReference.Get()); 672 if (FAILED(hr)) { 673 LOG("Failed to update thumbnail"); 674 return false; 675 } 676 677 hr = mDisplay->Update(); 678 if (FAILED(hr)) { 679 LOG("Failed to refresh display"); 680 return false; 681 } 682 683 // No need to clean mThumbnailUrl since thumbnail is set successfully 684 cleanup.release(); 685 mThumbnailUrl = aUrl; 686 687 return true; 688 } 689 690 void WindowsSMTCProvider::ClearThumbnail() { 691 MOZ_ASSERT(mDisplay); 692 HRESULT hr = mDisplay->put_Thumbnail(nullptr); 693 MOZ_ASSERT(SUCCEEDED(hr)); 694 hr = mDisplay->Update(); 695 MOZ_ASSERT(SUCCEEDED(hr)); 696 Unused << hr; 697 mThumbnailUrl.Truncate(); 698 } 699 700 bool WindowsSMTCProvider::UpdateThumbnail(const nsAString& aUrl) { 701 MOZ_ASSERT(NS_IsMainThread()); 702 703 if (!IsOpened()) { 704 LOG("Abort the thumbnail update: SMTC is closed"); 705 return false; 706 } 707 708 if (aUrl != mProcessingUrl) { 709 LOG("Abort the thumbnail update: The image from %s is out of date", 710 NS_ConvertUTF16toUTF8(aUrl).get()); 711 return false; 712 } 713 714 mProcessingUrl.Truncate(); 715 716 if (!SetThumbnail(aUrl)) { 717 LOG("Failed to update thumbnail"); 718 return false; 719 } 720 721 MOZ_ASSERT(mThumbnailUrl == aUrl); 722 LOG("The thumbnail is updated to the image from: %s", 723 NS_ConvertUTF16toUTF8(mThumbnailUrl).get()); 724 return true; 725 } 726 727 void WindowsSMTCProvider::CancelPendingStoreAsyncOperation() const { 728 if (mStoreAsyncOperation) { 729 IAsyncInfo* asyncInfo = GetIAsyncInfo(mStoreAsyncOperation.Get()); 730 asyncInfo->Cancel(); 731 } 732 } 733 734 #endif // __MINGW32__ 735