Backed out changeset b71c8c052463 (bug 1943846) for causing mass failures. CLOSED...
[gecko.git] / widget / cocoa / MediaHardwareKeysEventSourceMacMediaCenter.mm
blobb48d5a4b43d5c8a48a5fb5ea422a296966ecddfe
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #import <MediaPlayer/MediaPlayer.h>
7 #include "MediaHardwareKeysEventSourceMacMediaCenter.h"
9 #include "mozilla/dom/MediaControlUtils.h"
10 #include "nsCocoaUtils.h"
12 using namespace mozilla::dom;
14 // avoid redefined macro in unified build
15 #undef LOG
16 #define LOG(msg, ...)                                                   \
17   MOZ_LOG(gMediaControlLog, LogLevel::Debug,                            \
18           ("MediaHardwareKeysEventSourceMacMediaCenter=%p, " msg, this, \
19            ##__VA_ARGS__))
21 namespace mozilla {
22 namespace widget {
24 MediaCenterEventHandler
25 MediaHardwareKeysEventSourceMacMediaCenter::CreatePlayPauseHandler() {
26   return Block_copy(^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent* event) {
27     MPNowPlayingInfoCenter* center = [MPNowPlayingInfoCenter defaultCenter];
28     center.playbackState =
29         center.playbackState == MPNowPlayingPlaybackStatePlaying
30             ? MPNowPlayingPlaybackStatePaused
31             : MPNowPlayingPlaybackStatePlaying;
32     HandleEvent(MediaControlAction(MediaControlKey::Playpause));
33     return MPRemoteCommandHandlerStatusSuccess;
34   });
37 MediaCenterEventHandler
38 MediaHardwareKeysEventSourceMacMediaCenter::CreateNextTrackHandler() {
39   return Block_copy(^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent* event) {
40     HandleEvent(MediaControlAction(MediaControlKey::Nexttrack));
41     return MPRemoteCommandHandlerStatusSuccess;
42   });
45 MediaCenterEventHandler
46 MediaHardwareKeysEventSourceMacMediaCenter::CreatePreviousTrackHandler() {
47   return Block_copy(^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent* event) {
48     HandleEvent(MediaControlAction(MediaControlKey::Previoustrack));
49     return MPRemoteCommandHandlerStatusSuccess;
50   });
53 MediaCenterEventHandler
54 MediaHardwareKeysEventSourceMacMediaCenter::CreatePlayHandler() {
55   return Block_copy(^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent* event) {
56     MPNowPlayingInfoCenter* center = [MPNowPlayingInfoCenter defaultCenter];
57     if (center.playbackState != MPNowPlayingPlaybackStatePlaying) {
58       center.playbackState = MPNowPlayingPlaybackStatePlaying;
59     }
60     HandleEvent(MediaControlAction(MediaControlKey::Play));
61     return MPRemoteCommandHandlerStatusSuccess;
62   });
65 MediaCenterEventHandler
66 MediaHardwareKeysEventSourceMacMediaCenter::CreatePauseHandler() {
67   return Block_copy(^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent* event) {
68     MPNowPlayingInfoCenter* center = [MPNowPlayingInfoCenter defaultCenter];
69     if (center.playbackState != MPNowPlayingPlaybackStatePaused) {
70       center.playbackState = MPNowPlayingPlaybackStatePaused;
71     }
72     HandleEvent(MediaControlAction(MediaControlKey::Pause));
73     return MPRemoteCommandHandlerStatusSuccess;
74   });
77 MediaCenterEventHandler MediaHardwareKeysEventSourceMacMediaCenter::
78     CreateChangePlaybackPositionHandler() {
79   return Block_copy(^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent* event) {
80     MPChangePlaybackPositionCommandEvent* changePosEvent =
81         (MPChangePlaybackPositionCommandEvent*)event;
82     HandleEvent(
83         MediaControlAction(MediaControlKey::Seekto,
84                            SeekDetails(changePosEvent.positionTime, false)));
85     return MPRemoteCommandHandlerStatusSuccess;
86   });
89 MediaHardwareKeysEventSourceMacMediaCenter::
90     MediaHardwareKeysEventSourceMacMediaCenter() {
91   mPlayPauseHandler = CreatePlayPauseHandler();
92   mNextTrackHandler = CreateNextTrackHandler();
93   mPreviousTrackHandler = CreatePreviousTrackHandler();
94   mPlayHandler = CreatePlayHandler();
95   mPauseHandler = CreatePauseHandler();
96   mChangePlaybackPositionHandler = CreateChangePlaybackPositionHandler();
97   LOG("Create MediaHardwareKeysEventSourceMacMediaCenter");
100 MediaHardwareKeysEventSourceMacMediaCenter::
101     ~MediaHardwareKeysEventSourceMacMediaCenter() {
102   LOG("Destroy MediaHardwareKeysEventSourceMacMediaCenter");
103   EndListeningForEvents();
104   MPNowPlayingInfoCenter* center = [MPNowPlayingInfoCenter defaultCenter];
105   center.playbackState = MPNowPlayingPlaybackStateStopped;
108 void MediaHardwareKeysEventSourceMacMediaCenter::BeginListeningForEvents() {
109   MPNowPlayingInfoCenter* center = [MPNowPlayingInfoCenter defaultCenter];
110   center.playbackState = MPNowPlayingPlaybackStatePlaying;
111   MPRemoteCommandCenter* commandCenter =
112       [MPRemoteCommandCenter sharedCommandCenter];
113   commandCenter.togglePlayPauseCommand.enabled = false;
114   [commandCenter.togglePlayPauseCommand addTargetWithHandler:mPlayPauseHandler];
115   commandCenter.nextTrackCommand.enabled = false;
116   [commandCenter.nextTrackCommand addTargetWithHandler:mNextTrackHandler];
117   commandCenter.previousTrackCommand.enabled = false;
118   [commandCenter.previousTrackCommand
119       addTargetWithHandler:mPreviousTrackHandler];
120   commandCenter.playCommand.enabled = false;
121   [commandCenter.playCommand addTargetWithHandler:mPlayHandler];
122   commandCenter.pauseCommand.enabled = false;
123   [commandCenter.pauseCommand addTargetWithHandler:mPauseHandler];
124   commandCenter.changePlaybackPositionCommand.enabled = false;
125   [commandCenter.changePlaybackPositionCommand
126       addTargetWithHandler:mChangePlaybackPositionHandler];
129 void MediaHardwareKeysEventSourceMacMediaCenter::EndListeningForEvents() {
130   MPNowPlayingInfoCenter* center = [MPNowPlayingInfoCenter defaultCenter];
131   center.playbackState = MPNowPlayingPlaybackStatePaused;
132   center.nowPlayingInfo = nil;
133   MPRemoteCommandCenter* commandCenter =
134       [MPRemoteCommandCenter sharedCommandCenter];
135   commandCenter.togglePlayPauseCommand.enabled = false;
136   [commandCenter.togglePlayPauseCommand removeTarget:nil];
137   commandCenter.nextTrackCommand.enabled = false;
138   [commandCenter.nextTrackCommand removeTarget:nil];
139   commandCenter.previousTrackCommand.enabled = false;
140   [commandCenter.previousTrackCommand removeTarget:nil];
141   commandCenter.playCommand.enabled = false;
142   [commandCenter.playCommand removeTarget:nil];
143   commandCenter.pauseCommand.enabled = false;
144   [commandCenter.pauseCommand removeTarget:nil];
145   commandCenter.changePlaybackPositionCommand.enabled = false;
146   [commandCenter.changePlaybackPositionCommand removeTarget:nil];
149 bool MediaHardwareKeysEventSourceMacMediaCenter::Open() {
150   LOG("Open MediaHardwareKeysEventSourceMacMediaCenter");
151   mOpened = true;
152   BeginListeningForEvents();
153   return true;
156 void MediaHardwareKeysEventSourceMacMediaCenter::Close() {
157   LOG("Close MediaHardwareKeysEventSourceMacMediaCenter");
158   SetPlaybackState(MediaSessionPlaybackState::None);
159   mImageFetchRequest.DisconnectIfExists();
160   mCurrentImageUrl.Truncate();
161   mFetchingUrl.Truncate();
162   mNextImageIndex = 0;
163   EndListeningForEvents();
164   mOpened = false;
165   MediaControlKeySource::Close();
168 bool MediaHardwareKeysEventSourceMacMediaCenter::IsOpened() const {
169   return mOpened;
172 void MediaHardwareKeysEventSourceMacMediaCenter::HandleEvent(
173     const MediaControlAction& aAction) {
174   for (auto iter = mListeners.begin(); iter != mListeners.end(); ++iter) {
175     (*iter)->OnActionPerformed(aAction);
176   }
179 void MediaHardwareKeysEventSourceMacMediaCenter::SetPlaybackState(
180     MediaSessionPlaybackState aState) {
181   MPNowPlayingInfoCenter* center = [MPNowPlayingInfoCenter defaultCenter];
182   if (aState == MediaSessionPlaybackState::Playing) {
183     center.playbackState = MPNowPlayingPlaybackStatePlaying;
184   } else if (aState == MediaSessionPlaybackState::Paused) {
185     center.playbackState = MPNowPlayingPlaybackStatePaused;
186     UpdatePositionInfo();
187   } else if (aState == MediaSessionPlaybackState::None) {
188     center.playbackState = MPNowPlayingPlaybackStateStopped;
189   }
190   MediaControlKeySource::SetPlaybackState(aState);
193 void MediaHardwareKeysEventSourceMacMediaCenter::SetMediaMetadata(
194     const MediaMetadataBase& aMetadata) {
195   MOZ_ASSERT(NS_IsMainThread());
196   mMediaMetadata = aMetadata;
198   MPNowPlayingInfoCenter* center = [MPNowPlayingInfoCenter defaultCenter];
199   NSMutableDictionary* nowPlayingInfo =
200       [[center.nowPlayingInfo mutableCopy] autorelease]
201           ?: [NSMutableDictionary dictionary];
203   [nowPlayingInfo setObject:nsCocoaUtils::ToNSString(aMetadata.mTitle)
204                      forKey:MPMediaItemPropertyTitle];
205   [nowPlayingInfo setObject:nsCocoaUtils::ToNSString(aMetadata.mArtist)
206                      forKey:MPMediaItemPropertyArtist];
207   [nowPlayingInfo setObject:nsCocoaUtils::ToNSString(aMetadata.mAlbum)
208                      forKey:MPMediaItemPropertyAlbumTitle];
209   if (mCurrentImageUrl.IsEmpty() ||
210       !IsImageIn(aMetadata.mArtwork, mCurrentImageUrl)) {
211     [nowPlayingInfo removeObjectForKey:MPMediaItemPropertyArtwork];
213     if (mFetchingUrl.IsEmpty() ||
214         !IsImageIn(aMetadata.mArtwork, mFetchingUrl)) {
215       mNextImageIndex = 0;
216       LoadImageAtIndex(mNextImageIndex++);
217     }
218   }
220   // The procedure of updating `nowPlayingInfo` is actually an async operation
221   // from our testing, Apple's documentation doesn't mention that though. So be
222   // aware that checking `nowPlayingInfo` immedately after setting it might not
223   // yield the expected result.
224   center.nowPlayingInfo = nowPlayingInfo;
227 void MediaHardwareKeysEventSourceMacMediaCenter::SetSupportedMediaKeys(
228     const MediaKeysArray& aSupportedKeys) {
229   uint32_t supportedKeys = 0;
230   for (const MediaControlKey& key : aSupportedKeys) {
231     supportedKeys |= GetMediaKeyMask(key);
232   }
234   MPRemoteCommandCenter* commandCenter =
235       [MPRemoteCommandCenter sharedCommandCenter];
236   commandCenter.togglePlayPauseCommand.enabled =
237       (bool)(supportedKeys & GetMediaKeyMask(MediaControlKey::Playpause));
238   commandCenter.nextTrackCommand.enabled =
239       (bool)(supportedKeys & GetMediaKeyMask(MediaControlKey::Nexttrack));
240   commandCenter.previousTrackCommand.enabled =
241       (bool)(supportedKeys & GetMediaKeyMask(MediaControlKey::Previoustrack));
242   commandCenter.playCommand.enabled =
243       (bool)(supportedKeys & GetMediaKeyMask(MediaControlKey::Play));
244   commandCenter.pauseCommand.enabled =
245       (bool)(supportedKeys & GetMediaKeyMask(MediaControlKey::Pause));
246   commandCenter.changePlaybackPositionCommand.enabled =
247       (bool)(supportedKeys & GetMediaKeyMask(MediaControlKey::Seekto));
250 void MediaHardwareKeysEventSourceMacMediaCenter::SetPositionState(
251     const Maybe<PositionState>& aState) {
252   mPositionState = aState;
253   UpdatePositionInfo();
256 void MediaHardwareKeysEventSourceMacMediaCenter::UpdatePositionInfo() {
257   if (mPositionState.isSome()) {
258     MPNowPlayingInfoCenter* center = [MPNowPlayingInfoCenter defaultCenter];
259     NSMutableDictionary* nowPlayingInfo =
260         [[center.nowPlayingInfo mutableCopy] autorelease]
261             ?: [NSMutableDictionary dictionary];
263     [nowPlayingInfo setObject:@(mPositionState->mDuration)
264                        forKey:MPMediaItemPropertyPlaybackDuration];
265     [nowPlayingInfo setObject:@(mPositionState->CurrentPlaybackPosition())
266                        forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
267     [nowPlayingInfo
268         setObject:@(center.playbackState == MPNowPlayingPlaybackStatePlaying
269                         ? mPositionState->mPlaybackRate
270                         : 0.0)
271            forKey:MPNowPlayingInfoPropertyPlaybackRate];
272     center.nowPlayingInfo = nowPlayingInfo;
273   }
276 void MediaHardwareKeysEventSourceMacMediaCenter::LoadImageAtIndex(
277     const size_t aIndex) {
278   MOZ_ASSERT(NS_IsMainThread());
280   if (aIndex >= mMediaMetadata.mArtwork.Length()) {
281     LOG("Stop loading image. No available image");
282     mImageFetchRequest.DisconnectIfExists();
283     mFetchingUrl.Truncate();
284     return;
285   }
287   const MediaImage& image = mMediaMetadata.mArtwork[aIndex];
289   if (!IsValidImageUrl(image.mSrc)) {
290     LOG("Skip the image with invalid URL. Try next image");
291     LoadImageAtIndex(mNextImageIndex++);
292     return;
293   }
295   mImageFetchRequest.DisconnectIfExists();
296   mFetchingUrl = image.mSrc;
298   mImageFetcher = MakeUnique<dom::FetchImageHelper>(image);
299   RefPtr<MediaHardwareKeysEventSourceMacMediaCenter> self = this;
300   mImageFetcher->FetchImage()
301       ->Then(
302           AbstractThread::MainThread(), __func__,
303           [this, self](const nsCOMPtr<imgIContainer>& aImage) {
304             LOG("The image is fetched successfully");
305             mImageFetchRequest.Complete();
307             NSImage* image;
308             nsresult rv =
309                 nsCocoaUtils::CreateDualRepresentationNSImageFromImageContainer(
310                     aImage, imgIContainer::FRAME_CURRENT, nullptr,
311                     NSMakeSize(0, 0), &image);
312             if (NS_FAILED(rv) || !image) {
313               LOG("Failed to create cocoa image. Try next image");
314               LoadImageAtIndex(mNextImageIndex++);
315               return;
316             }
317             mCurrentImageUrl = mFetchingUrl;
319             MPNowPlayingInfoCenter* center =
320                 [MPNowPlayingInfoCenter defaultCenter];
321             NSMutableDictionary* nowPlayingInfo =
322                 [[center.nowPlayingInfo mutableCopy] autorelease]
323                     ?: [NSMutableDictionary dictionary];
325             MPMediaItemArtwork* artwork = [[MPMediaItemArtwork alloc]
326                 initWithBoundsSize:image.size
327                     requestHandler:^NSImage* _Nonnull(CGSize aSize) {
328                       return image;
329                     }];
330             [nowPlayingInfo setObject:artwork
331                                forKey:MPMediaItemPropertyArtwork];
332             [artwork release];
333             [image release];
335             center.nowPlayingInfo = nowPlayingInfo;
337             mFetchingUrl.Truncate();
338           },
339           [this, self](bool) {
340             LOG("Failed to fetch image. Try next image");
341             mImageFetchRequest.Complete();
342             mFetchingUrl.Truncate();
343             LoadImageAtIndex(mNextImageIndex++);
344           })
345       ->Track(mImageFetchRequest);
348 }  // namespace widget
349 }  // namespace mozilla