summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-06-11 14:24:48 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-06-11 14:24:48 +0300
commita1fdf46087de8e1719ec80ac1ca145dacfc8e640 (patch)
tree9abfbe628947a02f54dcf68385b3d66ff4470d42 /src
parent31f7eafd9c6897cdf0ee7d6eeaade9dcc65cb006 (diff)
iOS: Audio remote control, Now Playing info
Update the basic Now Playing info about the currently playing music, and respond to remote control commands. Seems to work with MP3 but not other audio formats, probably because those are played via custom decoders. There must be some API for updating the playback status manually.
Diffstat (limited to 'src')
-rw-r--r--src/app.c7
-rw-r--r--src/audio/player.c14
-rw-r--r--src/audio/player.h2
-rw-r--r--src/ios.h3
-rw-r--r--src/ios.m87
5 files changed, 111 insertions, 2 deletions
diff --git a/src/app.c b/src/app.c
index 73908a09..c2bd3da0 100644
--- a/src/app.c
+++ b/src/app.c
@@ -1089,6 +1089,13 @@ void processEvents_App(enum iAppEventMode eventMode) {
1089 postRefresh_App(); 1089 postRefresh_App();
1090 break; 1090 break;
1091 case SDL_APP_WILLENTERBACKGROUND: 1091 case SDL_APP_WILLENTERBACKGROUND:
1092#if defined (iPlatformAppleMobile)
1093 updateNowPlayingInfo_iOS();
1094#endif
1095 setFreezeDraw_Window(d->window, iTrue);
1096 savePrefs_App_(d);
1097 saveState_App_(d);
1098 break;
1092 case SDL_APP_TERMINATING: 1099 case SDL_APP_TERMINATING:
1093 setFreezeDraw_Window(d->window, iTrue); 1100 setFreezeDraw_Window(d->window, iTrue);
1094 savePrefs_App_(d); 1101 savePrefs_App_(d);
diff --git a/src/audio/player.c b/src/audio/player.c
index a2a3955b..94bcd065 100644
--- a/src/audio/player.c
+++ b/src/audio/player.c
@@ -455,6 +455,8 @@ struct Impl_Player {
455 iAVFAudioPlayer * avfPlayer; /* iOS */ 455 iAVFAudioPlayer * avfPlayer; /* iOS */
456}; 456};
457 457
458static iPlayer *activePlayer_;
459
458iDefineTypeConstruction(Player) 460iDefineTypeConstruction(Player)
459 461
460static size_t sampleSize_Player_(const iPlayer *d) { 462static size_t sampleSize_Player_(const iPlayer *d) {
@@ -655,8 +657,14 @@ void deinit_Player(iPlayer *d) {
655#if defined (iPlatformAppleMobile) 657#if defined (iPlatformAppleMobile)
656 if (d->avfPlayer) { 658 if (d->avfPlayer) {
657 delete_AVFAudioPlayer(d->avfPlayer); 659 delete_AVFAudioPlayer(d->avfPlayer);
660 if (activePlayer_ == d) {
661 clearNowPlayingInfo_iOS();
662 }
658 } 663 }
659#endif 664#endif
665 if (activePlayer_ == d) {
666 activePlayer_ = NULL;
667 }
660} 668}
661 669
662iBool isStarted_Player(const iPlayer *d) { 670iBool isStarted_Player(const iPlayer *d) {
@@ -739,6 +747,7 @@ iBool start_Player(iPlayer *d) {
739 if (d->avfPlayer) { 747 if (d->avfPlayer) {
740 play_AVFAudioPlayer(d->avfPlayer); 748 play_AVFAudioPlayer(d->avfPlayer);
741 setNotIdle_Player(d); 749 setNotIdle_Player(d);
750 activePlayer_ = d;
742 return iTrue; 751 return iTrue;
743 } 752 }
744#endif 753#endif
@@ -756,6 +765,7 @@ iBool start_Player(iPlayer *d) {
756 d->decoder->gain = d->volume; 765 d->decoder->gain = d->volume;
757 SDL_PauseAudioDevice(d->device, SDL_FALSE); 766 SDL_PauseAudioDevice(d->device, SDL_FALSE);
758 setNotIdle_Player(d); 767 setNotIdle_Player(d);
768 activePlayer_ = d;
759 return iTrue; 769 return iTrue;
760} 770}
761 771
@@ -889,3 +899,7 @@ iString *metadataLabel_Player(const iPlayer *d) {
889 } 899 }
890 return meta; 900 return meta;
891} 901}
902
903iPlayer *active_Player(void) {
904 return activePlayer_;
905}
diff --git a/src/audio/player.h b/src/audio/player.h
index b131838d..ca307dc4 100644
--- a/src/audio/player.h
+++ b/src/audio/player.h
@@ -68,3 +68,5 @@ float streamProgress_Player (const iPlayer *); /* normalized 0...1 */
68 68
69uint32_t idleTimeMs_Player (const iPlayer *); 69uint32_t idleTimeMs_Player (const iPlayer *);
70iString * metadataLabel_Player (const iPlayer *); 70iString * metadataLabel_Player (const iPlayer *);
71
72iPlayer * active_Player (void);
diff --git a/src/ios.h b/src/ios.h
index 64c0fad1..a8039c1c 100644
--- a/src/ios.h
+++ b/src/ios.h
@@ -56,3 +56,6 @@ double currentTime_AVFAudioPlayer (const iAVFAudioPlayer *);
56double duration_AVFAudioPlayer (const iAVFAudioPlayer *); 56double duration_AVFAudioPlayer (const iAVFAudioPlayer *);
57iBool isStarted_AVFAudioPlayer (const iAVFAudioPlayer *); 57iBool isStarted_AVFAudioPlayer (const iAVFAudioPlayer *);
58iBool isPaused_AVFAudioPlayer (const iAVFAudioPlayer *); 58iBool isPaused_AVFAudioPlayer (const iAVFAudioPlayer *);
59
60void clearNowPlayingInfo_iOS (void);
61void updateNowPlayingInfo_iOS (void);
diff --git a/src/ios.m b/src/ios.m
index e7288677..b50f4ecb 100644
--- a/src/ios.m
+++ b/src/ios.m
@@ -22,6 +22,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23#include "ios.h" 23#include "ios.h"
24#include "app.h" 24#include "app.h"
25#include "audio/player.h"
25#include "ui/command.h" 26#include "ui/command.h"
26#include "ui/window.h" 27#include "ui/window.h"
27 28
@@ -32,9 +33,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
32#include <SDL_syswm.h> 33#include <SDL_syswm.h>
33#include <SDL_timer.h> 34#include <SDL_timer.h>
34 35
35#import <UIKit/UIKit.h>
36#import <CoreHaptics/CoreHaptics.h>
37#import <AVFAudio/AVFAudio.h> 36#import <AVFAudio/AVFAudio.h>
37#import <CoreHaptics/CoreHaptics.h>
38#import <UIKit/UIKit.h>
39#import <MediaPlayer/MediaPlayer.h>
38 40
39static iBool isSystemDarkMode_ = iFalse; 41static iBool isSystemDarkMode_ = iFalse;
40static iBool isPhone_ = iFalse; 42static iBool isPhone_ = iFalse;
@@ -241,6 +243,55 @@ void setupApplication_iOS(void) {
241 name:UIKeyboardWillHideNotification 243 name:UIKeyboardWillHideNotification
242 object:nil]; 244 object:nil];
243 [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil]; 245 [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
246 /* Media player remote controls. */
247 MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
248 [[commandCenter pauseCommand] addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
249 iPlayer *player = active_Player();
250 if (player) {
251 setPaused_Player(player, iTrue);
252 return MPRemoteCommandHandlerStatusSuccess;
253 }
254 return MPRemoteCommandHandlerStatusCommandFailed;
255 }];
256 [[commandCenter playCommand] addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
257 iPlayer *player = active_Player();
258 if (player) {
259 if (isPaused_Player(player)) {
260 setPaused_Player(player, iFalse);
261 }
262 else {
263 start_Player(player);
264 }
265 return MPRemoteCommandHandlerStatusSuccess;
266 }
267 return MPRemoteCommandHandlerStatusCommandFailed;
268 }];
269 [[commandCenter stopCommand] addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
270 iPlayer *player = active_Player();
271 if (player) {
272 stop_Player(player);
273 return MPRemoteCommandHandlerStatusSuccess;
274 }
275 return MPRemoteCommandHandlerStatusCommandFailed;
276 }];
277 [[commandCenter togglePlayPauseCommand] addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
278 iPlayer *player = active_Player();
279 if (player) {
280 setPaused_Player(player, !isPaused_Player(player));
281 return MPRemoteCommandHandlerStatusSuccess;
282 }
283 return MPRemoteCommandHandlerStatusCommandFailed;
284 }];
285 [[commandCenter nextTrackCommand] setEnabled:NO];
286 [[commandCenter previousTrackCommand] setEnabled:NO];
287 [[commandCenter changeRepeatModeCommand] setEnabled:NO];
288 [[commandCenter changeShuffleModeCommand] setEnabled:NO];
289 [[commandCenter changePlaybackRateCommand] setEnabled:NO];
290 [[commandCenter seekForwardCommand] setEnabled:NO];
291 [[commandCenter seekBackwardCommand] setEnabled:NO];
292 [[commandCenter skipForwardCommand] setEnabled:NO];
293 [[commandCenter skipBackwardCommand] setEnabled:NO];
294 [[commandCenter changePlaybackPositionCommand] setEnabled:NO];
244} 295}
245 296
246static iBool isDarkMode_(iWindow *window) { 297static iBool isDarkMode_(iWindow *window) {
@@ -335,6 +386,38 @@ iBool processEvent_iOS(const SDL_Event *ev) {
335 return iFalse; /* allow normal processing */ 386 return iFalse; /* allow normal processing */
336} 387}
337 388
389void updateNowPlayingInfo_iOS(void) {
390 const iPlayer *player = active_Player();
391 if (!player) {
392 clearNowPlayingInfo_iOS();
393 return;
394 }
395 NSMutableDictionary<NSString *, id> *info = [[NSMutableDictionary<NSString *, id> alloc] init];
396 [info setObject:[NSNumber numberWithDouble:time_Player(player)]
397 forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
398 [info setObject:[NSNumber numberWithInt:MPNowPlayingInfoMediaTypeAudio]
399 forKey:MPNowPlayingInfoPropertyMediaType];
400 [info setObject:[NSNumber numberWithDouble:duration_Player(player)]
401 forKey:MPMediaItemPropertyPlaybackDuration];
402 const iString *title = tag_Player(player, title_PlayerTag);
403 const iString *artist = tag_Player(player, artist_PlayerTag);
404 if (isEmpty_String(title)) {
405 title = collectNewCStr_String("Audio"); /* TODO: Use link label or URL file name */
406 }
407 if (isEmpty_String(artist)) {
408 artist = collectNewCStr_String("Lagrange"); /* TODO: Use domain or base URL */
409 }
410 [info setObject:[NSString stringWithUTF8String:cstr_String(title)]
411 forKey:MPMediaItemPropertyTitle];
412 [info setObject:[NSString stringWithUTF8String:cstr_String(artist)]
413 forKey:MPMediaItemPropertyArtist];
414 [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:info];
415}
416
417void clearNowPlayingInfo_iOS(void) {
418 [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:nil];
419}
420
338void exportDownloadedFile_iOS(const iString *path) { 421void exportDownloadedFile_iOS(const iString *path) {
339 NSURL *url = [NSURL fileURLWithPath:[[NSString alloc] initWithCString:cstr_String(path) 422 NSURL *url = [NSURL fileURLWithPath:[[NSString alloc] initWithCString:cstr_String(path)
340 encoding:NSUTF8StringEncoding]]; 423 encoding:NSUTF8StringEncoding]];