react-native-audio-api 0.11.0-alpha.3 → 0.11.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.cpp +34 -6
  2. package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.cpp +4 -0
  3. package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/ptrs.hpp +8 -0
  4. package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/utils.cpp +4 -0
  5. package/android/src/main/cpp/audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.h +1 -0
  6. package/android/src/main/java/com/swmansion/audioapi/AudioAPIModule.kt +164 -16
  7. package/android/src/main/java/com/swmansion/audioapi/core/NativeAudioPlayer.kt +10 -8
  8. package/android/src/main/java/com/swmansion/audioapi/core/NativeAudioRecorder.kt +10 -8
  9. package/android/src/main/java/com/swmansion/audioapi/system/AudioFocusListener.kt +3 -4
  10. package/android/src/main/java/com/swmansion/audioapi/system/CentralizedForegroundService.kt +128 -0
  11. package/android/src/main/java/com/swmansion/audioapi/system/ForegroundServiceManager.kt +116 -0
  12. package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt +115 -107
  13. package/android/src/main/java/com/swmansion/audioapi/system/PermissionRequestListener.kt +2 -1
  14. package/android/src/main/java/com/swmansion/audioapi/system/notification/BaseNotification.kt +47 -0
  15. package/android/src/main/java/com/swmansion/audioapi/system/notification/NotificationRegistry.kt +191 -0
  16. package/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt +669 -0
  17. package/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotificationReceiver.kt +33 -0
  18. package/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotification.kt +303 -0
  19. package/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotificationReceiver.kt +45 -0
  20. package/android/src/main/java/com/swmansion/audioapi/system/notification/SimpleNotification.kt +119 -0
  21. package/common/cpp/audioapi/core/utils/AudioFileWriter.h +1 -0
  22. package/common/cpp/audioapi/core/utils/AudioRecorderCallback.h +1 -0
  23. package/common/cpp/audioapi/utils/AudioFileProperties.h +17 -17
  24. package/ios/audioapi/ios/AudioAPIModule.h +2 -2
  25. package/ios/audioapi/ios/AudioAPIModule.mm +108 -18
  26. package/ios/audioapi/ios/core/IOSAudioRecorder.mm +8 -7
  27. package/ios/audioapi/ios/core/NativeAudioPlayer.m +1 -1
  28. package/ios/audioapi/ios/core/NativeAudioRecorder.m +9 -2
  29. package/ios/audioapi/ios/system/AudioEngine.h +2 -0
  30. package/ios/audioapi/ios/system/AudioEngine.mm +49 -6
  31. package/ios/audioapi/ios/system/AudioSessionManager.mm +12 -9
  32. package/ios/audioapi/ios/system/NotificationManager.mm +7 -4
  33. package/ios/audioapi/ios/system/notification/BaseNotification.h +58 -0
  34. package/ios/audioapi/ios/system/notification/NotificationRegistry.h +70 -0
  35. package/ios/audioapi/ios/system/notification/NotificationRegistry.mm +172 -0
  36. package/ios/audioapi/ios/system/notification/PlaybackNotification.h +27 -0
  37. package/ios/audioapi/ios/system/notification/PlaybackNotification.mm +427 -0
  38. package/lib/commonjs/api.js +72 -1
  39. package/lib/commonjs/api.js.map +1 -1
  40. package/lib/commonjs/api.web.js +27 -14
  41. package/lib/commonjs/api.web.js.map +1 -1
  42. package/lib/commonjs/specs/NativeAudioAPIModule.js.map +1 -1
  43. package/lib/commonjs/system/AudioManager.js +6 -9
  44. package/lib/commonjs/system/AudioManager.js.map +1 -1
  45. package/lib/commonjs/system/index.js +13 -0
  46. package/lib/commonjs/system/index.js.map +1 -1
  47. package/lib/commonjs/system/notification/PlaybackNotificationManager.js +135 -0
  48. package/lib/commonjs/system/notification/PlaybackNotificationManager.js.map +1 -0
  49. package/lib/commonjs/system/notification/RecordingNotificationManager.js +182 -0
  50. package/lib/commonjs/system/notification/RecordingNotificationManager.js.map +1 -0
  51. package/lib/commonjs/system/notification/SimpleNotificationManager.js +122 -0
  52. package/lib/commonjs/system/notification/SimpleNotificationManager.js.map +1 -0
  53. package/lib/commonjs/system/notification/index.js +45 -0
  54. package/lib/commonjs/system/notification/index.js.map +1 -0
  55. package/lib/commonjs/system/notification/types.js +6 -0
  56. package/lib/commonjs/system/notification/types.js.map +1 -0
  57. package/lib/commonjs/types.js +17 -17
  58. package/lib/commonjs/types.js.map +1 -1
  59. package/lib/commonjs/web-system/index.js +17 -0
  60. package/lib/commonjs/web-system/index.js.map +1 -0
  61. package/lib/commonjs/web-system/notification/PlaybackNotificationManager.js +34 -0
  62. package/lib/commonjs/web-system/notification/PlaybackNotificationManager.js.map +1 -0
  63. package/lib/commonjs/web-system/notification/RecordingNotificationManager.js +34 -0
  64. package/lib/commonjs/web-system/notification/RecordingNotificationManager.js.map +1 -0
  65. package/lib/commonjs/web-system/notification/index.js +21 -0
  66. package/lib/commonjs/web-system/notification/index.js.map +1 -0
  67. package/lib/module/api.js +4 -0
  68. package/lib/module/api.js.map +1 -1
  69. package/lib/module/api.web.js +3 -1
  70. package/lib/module/api.web.js.map +1 -1
  71. package/lib/module/specs/NativeAudioAPIModule.js.map +1 -1
  72. package/lib/module/system/AudioManager.js +6 -9
  73. package/lib/module/system/AudioManager.js.map +1 -1
  74. package/lib/module/system/index.js +1 -0
  75. package/lib/module/system/index.js.map +1 -1
  76. package/lib/module/system/notification/PlaybackNotificationManager.js +131 -0
  77. package/lib/module/system/notification/PlaybackNotificationManager.js.map +1 -0
  78. package/lib/module/system/notification/RecordingNotificationManager.js +178 -0
  79. package/lib/module/system/notification/RecordingNotificationManager.js.map +1 -0
  80. package/lib/module/system/notification/SimpleNotificationManager.js +118 -0
  81. package/lib/module/system/notification/SimpleNotificationManager.js.map +1 -0
  82. package/lib/module/system/notification/index.js +7 -0
  83. package/lib/module/system/notification/index.js.map +1 -0
  84. package/lib/module/system/notification/types.js +4 -0
  85. package/lib/module/system/notification/types.js.map +1 -0
  86. package/lib/module/types.js +17 -17
  87. package/lib/module/types.js.map +1 -1
  88. package/lib/module/web-system/index.js +4 -0
  89. package/lib/module/web-system/index.js.map +1 -0
  90. package/lib/module/web-system/notification/PlaybackNotificationManager.js +30 -0
  91. package/lib/module/web-system/notification/PlaybackNotificationManager.js.map +1 -0
  92. package/lib/module/web-system/notification/RecordingNotificationManager.js +30 -0
  93. package/lib/module/web-system/notification/RecordingNotificationManager.js.map +1 -0
  94. package/lib/module/web-system/notification/index.js +5 -0
  95. package/lib/module/web-system/notification/index.js.map +1 -0
  96. package/lib/typescript/api.d.ts +2 -0
  97. package/lib/typescript/api.d.ts.map +1 -1
  98. package/lib/typescript/api.web.d.ts +3 -1
  99. package/lib/typescript/api.web.d.ts.map +1 -1
  100. package/lib/typescript/events/types.d.ts +3 -3
  101. package/lib/typescript/events/types.d.ts.map +1 -1
  102. package/lib/typescript/specs/NativeAudioAPIModule.d.ts +16 -5
  103. package/lib/typescript/specs/NativeAudioAPIModule.d.ts.map +1 -1
  104. package/lib/typescript/system/AudioManager.d.ts +4 -5
  105. package/lib/typescript/system/AudioManager.d.ts.map +1 -1
  106. package/lib/typescript/system/index.d.ts +1 -0
  107. package/lib/typescript/system/index.d.ts.map +1 -1
  108. package/lib/typescript/system/notification/PlaybackNotificationManager.d.ts +22 -0
  109. package/lib/typescript/system/notification/PlaybackNotificationManager.d.ts.map +1 -0
  110. package/lib/typescript/system/notification/RecordingNotificationManager.d.ts +23 -0
  111. package/lib/typescript/system/notification/RecordingNotificationManager.d.ts.map +1 -0
  112. package/lib/typescript/system/notification/SimpleNotificationManager.d.ts +20 -0
  113. package/lib/typescript/system/notification/SimpleNotificationManager.d.ts.map +1 -0
  114. package/lib/typescript/system/notification/index.d.ts +5 -0
  115. package/lib/typescript/system/notification/index.d.ts.map +1 -0
  116. package/lib/typescript/system/notification/types.d.ts +65 -0
  117. package/lib/typescript/system/notification/types.d.ts.map +1 -0
  118. package/lib/typescript/system/types.d.ts +0 -16
  119. package/lib/typescript/system/types.d.ts.map +1 -1
  120. package/lib/typescript/types.d.ts +16 -16
  121. package/lib/typescript/types.d.ts.map +1 -1
  122. package/lib/typescript/web-system/index.d.ts +2 -0
  123. package/lib/typescript/web-system/index.d.ts.map +1 -0
  124. package/lib/typescript/web-system/notification/PlaybackNotificationManager.d.ts +19 -0
  125. package/lib/typescript/web-system/notification/PlaybackNotificationManager.d.ts.map +1 -0
  126. package/lib/typescript/web-system/notification/RecordingNotificationManager.d.ts +19 -0
  127. package/lib/typescript/web-system/notification/RecordingNotificationManager.d.ts.map +1 -0
  128. package/lib/typescript/web-system/notification/index.d.ts +3 -0
  129. package/lib/typescript/web-system/notification/index.d.ts.map +1 -0
  130. package/package.json +1 -1
  131. package/src/api.ts +17 -0
  132. package/src/api.web.ts +7 -2
  133. package/src/events/types.ts +3 -4
  134. package/src/specs/NativeAudioAPIModule.ts +23 -7
  135. package/src/system/AudioManager.ts +10 -23
  136. package/src/system/index.ts +1 -0
  137. package/src/system/notification/PlaybackNotificationManager.ts +193 -0
  138. package/src/system/notification/RecordingNotificationManager.ts +242 -0
  139. package/src/system/notification/SimpleNotificationManager.ts +170 -0
  140. package/src/system/notification/index.ts +4 -0
  141. package/src/system/notification/types.ts +111 -0
  142. package/src/system/types.ts +0 -18
  143. package/src/types.ts +17 -17
  144. package/src/web-system/index.ts +1 -0
  145. package/src/web-system/notification/PlaybackNotificationManager.ts +60 -0
  146. package/src/web-system/notification/RecordingNotificationManager.ts +60 -0
  147. package/src/web-system/notification/index.ts +2 -0
  148. package/android/src/main/java/com/swmansion/audioapi/system/LockScreenManager.kt +0 -347
  149. package/android/src/main/java/com/swmansion/audioapi/system/MediaNotificationManager.kt +0 -273
  150. package/android/src/main/java/com/swmansion/audioapi/system/MediaReceiver.kt +0 -57
  151. package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionCallback.kt +0 -61
@@ -0,0 +1,172 @@
1
+ #import <audioapi/ios/AudioAPIModule.h>
2
+ #import <audioapi/ios/system/notification/NotificationRegistry.h>
3
+ #import <audioapi/ios/system/notification/PlaybackNotification.h>
4
+
5
+ @implementation NotificationRegistry {
6
+ NSMutableDictionary<NSString *, id<BaseNotification>> *_notifications;
7
+ }
8
+
9
+ - (instancetype)initWithAudioAPIModule:(AudioAPIModule *)audioAPIModule
10
+ {
11
+ if (self = [super init]) {
12
+ self.audioAPIModule = audioAPIModule;
13
+ _notifications = [[NSMutableDictionary alloc] init];
14
+
15
+ NSLog(@"[NotificationRegistry] Initialized");
16
+ }
17
+
18
+ return self;
19
+ }
20
+
21
+ - (BOOL)registerNotificationType:(NSString *)type withKey:(NSString *)key
22
+ {
23
+ if (!type || !key) {
24
+ NSLog(@"[NotificationRegistry] Invalid type or key");
25
+ return false;
26
+ }
27
+
28
+ // Check if already registered
29
+ if (_notifications[key]) {
30
+ NSLog(@"[NotificationRegistry] Notification with key '%@' already registered", key);
31
+ return false;
32
+ }
33
+
34
+ // Create the appropriate notification type
35
+ id<BaseNotification> notification = [self createNotificationForType:type];
36
+
37
+ if (!notification) {
38
+ NSLog(@"[NotificationRegistry] Unknown notification type: %@", type);
39
+ return false;
40
+ }
41
+
42
+ // Store the notification
43
+ _notifications[key] = notification;
44
+
45
+ NSLog(@"[NotificationRegistry] Registered notification type '%@' with key '%@'", type, key);
46
+ return true;
47
+ }
48
+
49
+ - (BOOL)showNotificationWithKey:(NSString *)key options:(NSDictionary *)options
50
+ {
51
+ id<BaseNotification> notification = _notifications[key];
52
+
53
+ if (!notification) {
54
+ NSLog(@"[NotificationRegistry] No notification found with key: %@", key);
55
+ return false;
56
+ }
57
+
58
+ // Initialize if first time showing
59
+ if (![notification isActive]) {
60
+ if (![notification initializeWithOptions:options]) {
61
+ NSLog(@"[NotificationRegistry] Failed to initialize notification: %@", key);
62
+ return false;
63
+ }
64
+ }
65
+
66
+ BOOL success = [notification showWithOptions:options];
67
+
68
+ if (success) {
69
+ NSLog(@"[NotificationRegistry] Showed notification: %@", key);
70
+ } else {
71
+ NSLog(@"[NotificationRegistry] Failed to show notification: %@", key);
72
+ }
73
+
74
+ return success;
75
+ }
76
+
77
+ - (BOOL)updateNotificationWithKey:(NSString *)key options:(NSDictionary *)options
78
+ {
79
+ id<BaseNotification> notification = _notifications[key];
80
+
81
+ if (!notification) {
82
+ NSLog(@"[NotificationRegistry] No notification found with key: %@", key);
83
+ return false;
84
+ }
85
+
86
+ BOOL success = [notification updateWithOptions:options];
87
+
88
+ if (success) {
89
+ NSLog(@"[NotificationRegistry] Updated notification: %@", key);
90
+ } else {
91
+ NSLog(@"[NotificationRegistry] Failed to update notification: %@", key);
92
+ }
93
+
94
+ return success;
95
+ }
96
+
97
+ - (BOOL)hideNotificationWithKey:(NSString *)key
98
+ {
99
+ id<BaseNotification> notification = _notifications[key];
100
+
101
+ if (!notification) {
102
+ NSLog(@"[NotificationRegistry] No notification found with key: %@", key);
103
+ return false;
104
+ }
105
+
106
+ BOOL success = [notification hide];
107
+
108
+ if (success) {
109
+ NSLog(@"[NotificationRegistry] Hid notification: %@", key);
110
+ } else {
111
+ NSLog(@"[NotificationRegistry] Failed to hide notification: %@", key);
112
+ }
113
+
114
+ return success;
115
+ }
116
+
117
+ - (BOOL)unregisterNotificationWithKey:(NSString *)key
118
+ {
119
+ id<BaseNotification> notification = _notifications[key];
120
+
121
+ if (!notification) {
122
+ NSLog(@"[NotificationRegistry] No notification found with key: %@", key);
123
+ return false;
124
+ }
125
+
126
+ // Clean up and remove
127
+ [notification cleanup];
128
+ [_notifications removeObjectForKey:key];
129
+
130
+ NSLog(@"[NotificationRegistry] Unregistered notification: %@", key);
131
+ return true;
132
+ }
133
+
134
+ - (BOOL)isNotificationActiveWithKey:(NSString *)key
135
+ {
136
+ id<BaseNotification> notification = _notifications[key];
137
+
138
+ if (!notification) {
139
+ return false;
140
+ }
141
+
142
+ return [notification isActive];
143
+ }
144
+
145
+ - (void)cleanup
146
+ {
147
+ NSLog(@"[NotificationRegistry] Cleaning up all notifications");
148
+
149
+ // Clean up all notifications
150
+ for (id<BaseNotification> notification in [_notifications allValues]) {
151
+ [notification cleanup];
152
+ }
153
+
154
+ [_notifications removeAllObjects];
155
+ }
156
+
157
+ #pragma mark - Private Methods
158
+
159
+ - (id<BaseNotification>)createNotificationForType:(NSString *)type
160
+ {
161
+ if ([type isEqualToString:@"playback"]) {
162
+ return [[PlaybackNotification alloc] initWithAudioAPIModule:self.audioAPIModule];
163
+ }
164
+ // Future: Add more notification types here
165
+ // else if ([type isEqualToString:@"recording"]) {
166
+ // return [[RecordingNotification alloc] initWithAudioAPIModule:self.audioAPIModule];
167
+ // }
168
+
169
+ return nil;
170
+ }
171
+
172
+ @end
@@ -0,0 +1,27 @@
1
+ #pragma once
2
+
3
+ #import <Foundation/Foundation.h>
4
+ #import <MediaPlayer/MediaPlayer.h>
5
+ #import <audioapi/ios/system/notification/BaseNotification.h>
6
+
7
+ @class AudioAPIModule;
8
+
9
+ /**
10
+ * PlaybackNotification
11
+ *
12
+ * iOS playback notification using MPNowPlayingInfoCenter and MPRemoteCommandCenter.
13
+ * Provides lock screen controls, Control Center integration, and Now Playing display.
14
+ *
15
+ * Note: On iOS, this only manages metadata. Notification visibility is controlled
16
+ * by the AudioContext state (active audio session shows controls).
17
+ */
18
+ @interface PlaybackNotification : NSObject <BaseNotification>
19
+
20
+ @property (nonatomic, weak) AudioAPIModule *audioAPIModule;
21
+ @property (nonatomic, weak) MPNowPlayingInfoCenter *playingInfoCenter;
22
+ @property (nonatomic, copy) NSString *artworkUrl;
23
+ @property (nonatomic, assign) BOOL isActive;
24
+
25
+ - (instancetype)initWithAudioAPIModule:(AudioAPIModule *)audioAPIModule;
26
+
27
+ @end
@@ -0,0 +1,427 @@
1
+ #import <audioapi/ios/AudioAPIModule.h>
2
+ #import <audioapi/ios/system/notification/PlaybackNotification.h>
3
+
4
+ #define NOW_PLAYING_INFO_KEYS \
5
+ @{ \
6
+ @"title" : MPMediaItemPropertyTitle, \
7
+ @"artist" : MPMediaItemPropertyArtist, \
8
+ @"album" : MPMediaItemPropertyAlbumTitle, \
9
+ @"duration" : MPMediaItemPropertyPlaybackDuration, \
10
+ @"elapsedTime" : MPNowPlayingInfoPropertyElapsedPlaybackTime, \
11
+ @"speed" : MPNowPlayingInfoPropertyPlaybackRate, \
12
+ @"artwork" : MPMediaItemPropertyArtwork \
13
+ }
14
+
15
+ @implementation PlaybackNotification {
16
+ BOOL _isInitialized;
17
+ NSMutableDictionary *_currentInfo;
18
+ }
19
+
20
+ - (instancetype)initWithAudioAPIModule:(AudioAPIModule *)audioAPIModule
21
+ {
22
+ if (self = [super init]) {
23
+ self.audioAPIModule = audioAPIModule;
24
+ self.playingInfoCenter = [MPNowPlayingInfoCenter defaultCenter];
25
+ _isInitialized = false;
26
+ _isActive = false;
27
+ _currentInfo = [[NSMutableDictionary alloc] init];
28
+ }
29
+
30
+ return self;
31
+ }
32
+
33
+ #pragma mark - BaseNotification Protocol
34
+
35
+ - (BOOL)initializeWithOptions:(NSDictionary *)options
36
+ {
37
+ if (_isInitialized) {
38
+ return true;
39
+ }
40
+
41
+ // Enable remote control events
42
+ dispatch_async(dispatch_get_main_queue(), ^{
43
+ [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
44
+ });
45
+
46
+ // Enable default remote commands
47
+ [self enableRemoteCommand:@"play" enabled:true];
48
+ [self enableRemoteCommand:@"pause" enabled:true];
49
+ [self enableRemoteCommand:@"next" enabled:true];
50
+ [self enableRemoteCommand:@"previous" enabled:true];
51
+ [self enableRemoteCommand:@"skipForward" enabled:true];
52
+ [self enableRemoteCommand:@"skipBackward" enabled:true];
53
+ [self enableRemoteCommand:@"seek" enabled:true];
54
+
55
+ _isInitialized = true;
56
+ return true;
57
+ }
58
+
59
+ - (BOOL)showWithOptions:(NSDictionary *)options
60
+ {
61
+ if (!_isInitialized) {
62
+ if (![self initializeWithOptions:options]) {
63
+ return false;
64
+ }
65
+ }
66
+
67
+ // Update the now playing info
68
+ [self updateNowPlayingInfo:options];
69
+
70
+ _isActive = true;
71
+
72
+ return true;
73
+ }
74
+
75
+ - (BOOL)updateWithOptions:(NSDictionary *)options
76
+ {
77
+ if (!_isActive) {
78
+ return false;
79
+ }
80
+
81
+ // Handle control enable/disable
82
+ if (options[@"control"] && options[@"enabled"]) {
83
+ NSString *control = options[@"control"];
84
+ BOOL enabled = [options[@"enabled"] boolValue];
85
+ [self enableControl:control enabled:enabled];
86
+ return true;
87
+ }
88
+
89
+ // Update the now playing info
90
+ [self updateNowPlayingInfo:options];
91
+
92
+ return true;
93
+ }
94
+
95
+ - (BOOL)hide
96
+ {
97
+ if (!_isActive) {
98
+ return true;
99
+ }
100
+
101
+ // Clear now playing info
102
+ self.playingInfoCenter.nowPlayingInfo = nil;
103
+ self.artworkUrl = nil;
104
+ [_currentInfo removeAllObjects];
105
+
106
+ _isActive = false;
107
+
108
+ return true;
109
+ }
110
+
111
+ - (void)cleanup
112
+ {
113
+ // Hide if active
114
+ if (_isActive) {
115
+ [self hide];
116
+ }
117
+
118
+ // Disable all remote commands
119
+ MPRemoteCommandCenter *remoteCenter = [MPRemoteCommandCenter sharedCommandCenter];
120
+ [remoteCenter.playCommand removeTarget:self];
121
+ [remoteCenter.pauseCommand removeTarget:self];
122
+ [remoteCenter.stopCommand removeTarget:self];
123
+ [remoteCenter.togglePlayPauseCommand removeTarget:self];
124
+ [remoteCenter.nextTrackCommand removeTarget:self];
125
+ [remoteCenter.previousTrackCommand removeTarget:self];
126
+ [remoteCenter.skipForwardCommand removeTarget:self];
127
+ [remoteCenter.skipBackwardCommand removeTarget:self];
128
+ [remoteCenter.seekForwardCommand removeTarget:self];
129
+ [remoteCenter.seekBackwardCommand removeTarget:self];
130
+ [remoteCenter.changePlaybackPositionCommand removeTarget:self];
131
+
132
+ // Disable remote control events
133
+ dispatch_async(dispatch_get_main_queue(), ^{
134
+ [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
135
+ });
136
+
137
+ _isInitialized = false;
138
+ }
139
+
140
+ - (BOOL)isActive
141
+ {
142
+ return _isActive;
143
+ }
144
+
145
+ - (NSString *)getNotificationType
146
+ {
147
+ return @"playback";
148
+ }
149
+
150
+ #pragma mark - Private Methods
151
+
152
+ - (void)updateNowPlayingInfo:(NSDictionary *)info
153
+ {
154
+ if (!info) {
155
+ return;
156
+ }
157
+
158
+ // Get existing now playing info or create new one
159
+ NSMutableDictionary *nowPlayingInfo = [self.playingInfoCenter.nowPlayingInfo mutableCopy];
160
+ if (!nowPlayingInfo) {
161
+ nowPlayingInfo = [[NSMutableDictionary alloc] init];
162
+ }
163
+
164
+ // Map keys from our API to MPNowPlayingInfoCenter keys
165
+ NSDictionary *keyMap = NOW_PLAYING_INFO_KEYS;
166
+
167
+ // Only update the keys that are provided in this update
168
+ for (NSString *key in info) {
169
+ NSString *mpKey = keyMap[key];
170
+ if (mpKey) {
171
+ // Handle artwork specially - don't set it directly to nowPlayingInfo
172
+ if ([key isEqualToString:@"artwork"]) {
173
+ _currentInfo[key] = info[key];
174
+ } else {
175
+ nowPlayingInfo[mpKey] = info[key];
176
+ _currentInfo[key] = info[key];
177
+ }
178
+ }
179
+ }
180
+
181
+ self.playingInfoCenter.nowPlayingInfo = nowPlayingInfo;
182
+
183
+ // Handle playback state
184
+ NSString *state = _currentInfo[@"state"];
185
+ MPNowPlayingPlaybackState playbackState = MPNowPlayingPlaybackStatePaused;
186
+
187
+ if (state) {
188
+ if ([state isEqualToString:@"playing"]) {
189
+ playbackState = MPNowPlayingPlaybackStatePlaying;
190
+ } else if ([state isEqualToString:@"paused"]) {
191
+ playbackState = MPNowPlayingPlaybackStatePaused;
192
+ } else {
193
+ playbackState = MPNowPlayingPlaybackStatePaused;
194
+ }
195
+ }
196
+
197
+ self.playingInfoCenter.playbackState = playbackState;
198
+
199
+ // Handle artwork
200
+ NSString *artworkUrl = [self getArtworkUrl:_currentInfo[@"artwork"]];
201
+ [self updateArtworkIfNeeded:artworkUrl];
202
+ }
203
+
204
+ - (NSString *)getArtworkUrl:(id)artwork
205
+ {
206
+ if (!artwork) {
207
+ return nil;
208
+ }
209
+
210
+ // Handle both string and dictionary formats
211
+ if ([artwork isKindOfClass:[NSString class]]) {
212
+ return artwork;
213
+ } else if ([artwork isKindOfClass:[NSDictionary class]]) {
214
+ return artwork[@"uri"];
215
+ }
216
+
217
+ return nil;
218
+ }
219
+
220
+ - (void)updateArtworkIfNeeded:(NSString *)artworkUrl
221
+ {
222
+ if (!artworkUrl) {
223
+ return;
224
+ }
225
+
226
+ MPNowPlayingInfoCenter *center = [MPNowPlayingInfoCenter defaultCenter];
227
+ if ([artworkUrl isEqualToString:self.artworkUrl] &&
228
+ center.nowPlayingInfo[MPMediaItemPropertyArtwork] != nil) {
229
+ return;
230
+ }
231
+
232
+ self.artworkUrl = artworkUrl;
233
+
234
+ // Load artwork asynchronously
235
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
236
+ NSURL *url = nil;
237
+ NSData *imageData = nil;
238
+ UIImage *image = nil;
239
+
240
+ @try {
241
+ if ([artworkUrl hasPrefix:@"http://"] || [artworkUrl hasPrefix:@"https://"]) {
242
+ // Remote URL
243
+ url = [NSURL URLWithString:artworkUrl];
244
+ imageData = [NSData dataWithContentsOfURL:url];
245
+ } else {
246
+ // Local file - try as resource or file path
247
+ NSString *imagePath = [[NSBundle mainBundle] pathForResource:artworkUrl ofType:nil];
248
+ if (imagePath) {
249
+ imageData = [NSData dataWithContentsOfFile:imagePath];
250
+ } else {
251
+ // Try as absolute path
252
+ imageData = [NSData dataWithContentsOfFile:artworkUrl];
253
+ }
254
+ }
255
+
256
+ if (imageData) {
257
+ image = [UIImage imageWithData:imageData];
258
+ }
259
+ } @catch (NSException *exception) {
260
+ // Failed to load artwork
261
+ }
262
+
263
+ if (image) {
264
+ MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc]
265
+ initWithBoundsSize:image.size
266
+ requestHandler:^UIImage *_Nonnull(CGSize size) { return image; }];
267
+
268
+ dispatch_async(dispatch_get_main_queue(), ^{
269
+ NSMutableDictionary *nowPlayingInfo = [center.nowPlayingInfo mutableCopy];
270
+ if (!nowPlayingInfo) {
271
+ nowPlayingInfo = [[NSMutableDictionary alloc] init];
272
+ }
273
+ nowPlayingInfo[MPMediaItemPropertyArtwork] = artwork;
274
+ center.nowPlayingInfo = nowPlayingInfo;
275
+ });
276
+ }
277
+ });
278
+ }
279
+
280
+ - (void)enableControl:(NSString *)control enabled:(BOOL)enabled
281
+ {
282
+ NSSet *validControls = [NSSet setWithObjects:@"play",
283
+ @"pause",
284
+ @"next",
285
+ @"previous",
286
+ @"skipForward",
287
+ @"skipBackward",
288
+ @"seek",
289
+ nil];
290
+ if ([validControls containsObject:control]) {
291
+ [self enableRemoteCommand:control enabled:enabled];
292
+ }
293
+ }
294
+
295
+ - (void)enableRemoteCommand:(NSString *)name enabled:(BOOL)enabled
296
+ {
297
+ MPRemoteCommandCenter *remoteCenter = [MPRemoteCommandCenter sharedCommandCenter];
298
+
299
+ if ([name isEqualToString:@"play"]) {
300
+ [self enableCommand:remoteCenter.playCommand withSelector:@selector(onPlay:) enabled:enabled];
301
+ } else if ([name isEqualToString:@"pause"]) {
302
+ [self enableCommand:remoteCenter.pauseCommand withSelector:@selector(onPause:) enabled:enabled];
303
+ } else if ([name isEqualToString:@"stop"]) {
304
+ [self enableCommand:remoteCenter.stopCommand withSelector:@selector(onStop:) enabled:enabled];
305
+ } else if ([name isEqualToString:@"togglePlayPause"]) {
306
+ [self enableCommand:remoteCenter.togglePlayPauseCommand
307
+ withSelector:@selector(onTogglePlayPause:)
308
+ enabled:enabled];
309
+ } else if ([name isEqualToString:@"next"]) {
310
+ [self enableCommand:remoteCenter.nextTrackCommand
311
+ withSelector:@selector(onNextTrack:)
312
+ enabled:enabled];
313
+ } else if ([name isEqualToString:@"previous"]) {
314
+ [self enableCommand:remoteCenter.previousTrackCommand
315
+ withSelector:@selector(onPreviousTrack:)
316
+ enabled:enabled];
317
+ } else if ([name isEqualToString:@"skipForward"]) {
318
+ remoteCenter.skipForwardCommand.preferredIntervals = @[ @(15) ];
319
+ [self enableCommand:remoteCenter.skipForwardCommand
320
+ withSelector:@selector(onSkipForward:)
321
+ enabled:enabled];
322
+ } else if ([name isEqualToString:@"skipBackward"]) {
323
+ remoteCenter.skipBackwardCommand.preferredIntervals = @[ @(15) ];
324
+ [self enableCommand:remoteCenter.skipBackwardCommand
325
+ withSelector:@selector(onSkipBackward:)
326
+ enabled:enabled];
327
+ } else if ([name isEqualToString:@"seekForward"]) {
328
+ [self enableCommand:remoteCenter.seekForwardCommand
329
+ withSelector:@selector(onSeekForward:)
330
+ enabled:enabled];
331
+ } else if ([name isEqualToString:@"seekBackward"]) {
332
+ [self enableCommand:remoteCenter.seekBackwardCommand
333
+ withSelector:@selector(onSeekBackward:)
334
+ enabled:enabled];
335
+ } else if ([name isEqualToString:@"seek"]) {
336
+ [self enableCommand:remoteCenter.changePlaybackPositionCommand
337
+ withSelector:@selector(onChangePlaybackPosition:)
338
+ enabled:enabled];
339
+ }
340
+ }
341
+
342
+ - (void)enableCommand:(MPRemoteCommand *)command withSelector:(SEL)selector enabled:(BOOL)enabled
343
+ {
344
+ [command removeTarget:self];
345
+ command.enabled = enabled;
346
+ if (enabled) {
347
+ [command addTarget:self action:selector];
348
+ }
349
+ }
350
+
351
+ #pragma mark - Remote Command Handlers
352
+
353
+ - (MPRemoteCommandHandlerStatus)onPlay:(MPRemoteCommandEvent *)event
354
+ {
355
+ [self.audioAPIModule invokeHandlerWithEventName:@"playbackNotificationPlay" eventBody:@{}];
356
+ return MPRemoteCommandHandlerStatusSuccess;
357
+ }
358
+
359
+ - (MPRemoteCommandHandlerStatus)onPause:(MPRemoteCommandEvent *)event
360
+ {
361
+ [self.audioAPIModule invokeHandlerWithEventName:@"playbackNotificationPause" eventBody:@{}];
362
+ return MPRemoteCommandHandlerStatusSuccess;
363
+ }
364
+
365
+ - (MPRemoteCommandHandlerStatus)onStop:(MPRemoteCommandEvent *)event
366
+ {
367
+ [self.audioAPIModule invokeHandlerWithEventName:@"playbackNotificationStop" eventBody:@{}];
368
+ return MPRemoteCommandHandlerStatusSuccess;
369
+ }
370
+
371
+ - (MPRemoteCommandHandlerStatus)onTogglePlayPause:(MPRemoteCommandEvent *)event
372
+ {
373
+ [self.audioAPIModule invokeHandlerWithEventName:@"playbackNotificationTogglePlayPause"
374
+ eventBody:@{}];
375
+ return MPRemoteCommandHandlerStatusSuccess;
376
+ }
377
+
378
+ - (MPRemoteCommandHandlerStatus)onNextTrack:(MPRemoteCommandEvent *)event
379
+ {
380
+ [self.audioAPIModule invokeHandlerWithEventName:@"playbackNotificationNext" eventBody:@{}];
381
+ return MPRemoteCommandHandlerStatusSuccess;
382
+ }
383
+
384
+ - (MPRemoteCommandHandlerStatus)onPreviousTrack:(MPRemoteCommandEvent *)event
385
+ {
386
+ [self.audioAPIModule invokeHandlerWithEventName:@"playbackNotificationPrevious" eventBody:@{}];
387
+ return MPRemoteCommandHandlerStatusSuccess;
388
+ }
389
+
390
+ - (MPRemoteCommandHandlerStatus)onSeekForward:(MPRemoteCommandEvent *)event
391
+ {
392
+ [self.audioAPIModule invokeHandlerWithEventName:@"playbackNotificationSeekForward" eventBody:@{}];
393
+ return MPRemoteCommandHandlerStatusSuccess;
394
+ }
395
+
396
+ - (MPRemoteCommandHandlerStatus)onSeekBackward:(MPRemoteCommandEvent *)event
397
+ {
398
+ [self.audioAPIModule invokeHandlerWithEventName:@"playbackNotificationSeekBackward"
399
+ eventBody:@{}];
400
+ return MPRemoteCommandHandlerStatusSuccess;
401
+ }
402
+
403
+ - (MPRemoteCommandHandlerStatus)onSkipForward:(MPSkipIntervalCommandEvent *)event
404
+ {
405
+ NSDictionary *body = @{@"value" : @(event.interval)};
406
+ [self.audioAPIModule invokeHandlerWithEventName:@"playbackNotificationSkipForward"
407
+ eventBody:body];
408
+ return MPRemoteCommandHandlerStatusSuccess;
409
+ }
410
+
411
+ - (MPRemoteCommandHandlerStatus)onSkipBackward:(MPSkipIntervalCommandEvent *)event
412
+ {
413
+ NSDictionary *body = @{@"value" : @(event.interval)};
414
+ [self.audioAPIModule invokeHandlerWithEventName:@"playbackNotificationSkipBackward"
415
+ eventBody:body];
416
+ return MPRemoteCommandHandlerStatusSuccess;
417
+ }
418
+
419
+ - (MPRemoteCommandHandlerStatus)onChangePlaybackPosition:
420
+ (MPChangePlaybackPositionCommandEvent *)event
421
+ {
422
+ NSDictionary *body = @{@"value" : @(event.positionTime)};
423
+ [self.audioAPIModule invokeHandlerWithEventName:@"playbackNotificationSeekTo" eventBody:body];
424
+ return MPRemoteCommandHandlerStatusSuccess;
425
+ }
426
+
427
+ @end