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
16 #define LOG(msg, ...) \
17 MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
18 ("MediaHardwareKeysEventSourceMacMediaCenter=%p, " msg, this, \
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;
37 MediaCenterEventHandler
38 MediaHardwareKeysEventSourceMacMediaCenter::CreateNextTrackHandler() {
39 return Block_copy(^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent* event) {
40 HandleEvent(MediaControlAction(MediaControlKey::Nexttrack));
41 return MPRemoteCommandHandlerStatusSuccess;
45 MediaCenterEventHandler
46 MediaHardwareKeysEventSourceMacMediaCenter::CreatePreviousTrackHandler() {
47 return Block_copy(^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent* event) {
48 HandleEvent(MediaControlAction(MediaControlKey::Previoustrack));
49 return MPRemoteCommandHandlerStatusSuccess;
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;
60 HandleEvent(MediaControlAction(MediaControlKey::Play));
61 return MPRemoteCommandHandlerStatusSuccess;
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;
72 HandleEvent(MediaControlAction(MediaControlKey::Pause));
73 return MPRemoteCommandHandlerStatusSuccess;
77 MediaCenterEventHandler MediaHardwareKeysEventSourceMacMediaCenter::
78 CreateChangePlaybackPositionHandler() {
79 return Block_copy(^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent* event) {
80 MPChangePlaybackPositionCommandEvent* changePosEvent =
81 (MPChangePlaybackPositionCommandEvent*)event;
83 MediaControlAction(MediaControlKey::Seekto,
84 SeekDetails(changePosEvent.positionTime, false)));
85 return MPRemoteCommandHandlerStatusSuccess;
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");
152 BeginListeningForEvents();
156 void MediaHardwareKeysEventSourceMacMediaCenter::Close() {
157 LOG("Close MediaHardwareKeysEventSourceMacMediaCenter");
158 SetPlaybackState(MediaSessionPlaybackState::None);
159 mImageFetchRequest.DisconnectIfExists();
160 mCurrentImageUrl.Truncate();
161 mFetchingUrl.Truncate();
163 EndListeningForEvents();
165 MediaControlKeySource::Close();
168 bool MediaHardwareKeysEventSourceMacMediaCenter::IsOpened() const {
172 void MediaHardwareKeysEventSourceMacMediaCenter::HandleEvent(
173 const MediaControlAction& aAction) {
174 for (auto iter = mListeners.begin(); iter != mListeners.end(); ++iter) {
175 (*iter)->OnActionPerformed(aAction);
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;
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)) {
216 LoadImageAtIndex(mNextImageIndex++);
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);
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];
268 setObject:@(center.playbackState == MPNowPlayingPlaybackStatePlaying
269 ? mPositionState->mPlaybackRate
271 forKey:MPNowPlayingInfoPropertyPlaybackRate];
272 center.nowPlayingInfo = nowPlayingInfo;
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();
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++);
295 mImageFetchRequest.DisconnectIfExists();
296 mFetchingUrl = image.mSrc;
298 mImageFetcher = MakeUnique<dom::FetchImageHelper>(image);
299 RefPtr<MediaHardwareKeysEventSourceMacMediaCenter> self = this;
300 mImageFetcher->FetchImage()
302 AbstractThread::MainThread(), __func__,
303 [this, self](const nsCOMPtr<imgIContainer>& aImage) {
304 LOG("The image is fetched successfully");
305 mImageFetchRequest.Complete();
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++);
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) {
330 [nowPlayingInfo setObject:artwork
331 forKey:MPMediaItemPropertyArtwork];
335 center.nowPlayingInfo = nowPlayingInfo;
337 mFetchingUrl.Truncate();
340 LOG("Failed to fetch image. Try next image");
341 mImageFetchRequest.Complete();
342 mFetchingUrl.Truncate();
343 LoadImageAtIndex(mNextImageIndex++);
345 ->Track(mImageFetchRequest);
348 } // namespace widget
349 } // namespace mozilla