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.
- package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.cpp +34 -6
- package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.cpp +4 -0
- package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/ptrs.hpp +8 -0
- package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/utils.cpp +4 -0
- package/android/src/main/cpp/audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.h +1 -0
- package/android/src/main/java/com/swmansion/audioapi/AudioAPIModule.kt +164 -16
- package/android/src/main/java/com/swmansion/audioapi/core/NativeAudioPlayer.kt +10 -8
- package/android/src/main/java/com/swmansion/audioapi/core/NativeAudioRecorder.kt +10 -8
- package/android/src/main/java/com/swmansion/audioapi/system/AudioFocusListener.kt +3 -4
- package/android/src/main/java/com/swmansion/audioapi/system/CentralizedForegroundService.kt +128 -0
- package/android/src/main/java/com/swmansion/audioapi/system/ForegroundServiceManager.kt +116 -0
- package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt +115 -107
- package/android/src/main/java/com/swmansion/audioapi/system/PermissionRequestListener.kt +2 -1
- package/android/src/main/java/com/swmansion/audioapi/system/notification/BaseNotification.kt +47 -0
- package/android/src/main/java/com/swmansion/audioapi/system/notification/NotificationRegistry.kt +191 -0
- package/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt +669 -0
- package/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotificationReceiver.kt +33 -0
- package/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotification.kt +303 -0
- package/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotificationReceiver.kt +45 -0
- package/android/src/main/java/com/swmansion/audioapi/system/notification/SimpleNotification.kt +119 -0
- package/common/cpp/audioapi/core/utils/AudioFileWriter.h +1 -0
- package/common/cpp/audioapi/core/utils/AudioRecorderCallback.h +1 -0
- package/common/cpp/audioapi/utils/AudioFileProperties.h +17 -17
- package/ios/audioapi/ios/AudioAPIModule.h +2 -2
- package/ios/audioapi/ios/AudioAPIModule.mm +108 -18
- package/ios/audioapi/ios/core/IOSAudioRecorder.mm +8 -7
- package/ios/audioapi/ios/core/NativeAudioPlayer.m +1 -1
- package/ios/audioapi/ios/core/NativeAudioRecorder.m +9 -2
- package/ios/audioapi/ios/system/AudioEngine.h +2 -0
- package/ios/audioapi/ios/system/AudioEngine.mm +49 -6
- package/ios/audioapi/ios/system/AudioSessionManager.mm +12 -9
- package/ios/audioapi/ios/system/NotificationManager.mm +7 -4
- package/ios/audioapi/ios/system/notification/BaseNotification.h +58 -0
- package/ios/audioapi/ios/system/notification/NotificationRegistry.h +70 -0
- package/ios/audioapi/ios/system/notification/NotificationRegistry.mm +172 -0
- package/ios/audioapi/ios/system/notification/PlaybackNotification.h +27 -0
- package/ios/audioapi/ios/system/notification/PlaybackNotification.mm +427 -0
- package/lib/commonjs/api.js +72 -1
- package/lib/commonjs/api.js.map +1 -1
- package/lib/commonjs/api.web.js +27 -14
- package/lib/commonjs/api.web.js.map +1 -1
- package/lib/commonjs/specs/NativeAudioAPIModule.js.map +1 -1
- package/lib/commonjs/system/AudioManager.js +6 -9
- package/lib/commonjs/system/AudioManager.js.map +1 -1
- package/lib/commonjs/system/index.js +13 -0
- package/lib/commonjs/system/index.js.map +1 -1
- package/lib/commonjs/system/notification/PlaybackNotificationManager.js +135 -0
- package/lib/commonjs/system/notification/PlaybackNotificationManager.js.map +1 -0
- package/lib/commonjs/system/notification/RecordingNotificationManager.js +182 -0
- package/lib/commonjs/system/notification/RecordingNotificationManager.js.map +1 -0
- package/lib/commonjs/system/notification/SimpleNotificationManager.js +122 -0
- package/lib/commonjs/system/notification/SimpleNotificationManager.js.map +1 -0
- package/lib/commonjs/system/notification/index.js +45 -0
- package/lib/commonjs/system/notification/index.js.map +1 -0
- package/lib/commonjs/system/notification/types.js +6 -0
- package/lib/commonjs/system/notification/types.js.map +1 -0
- package/lib/commonjs/types.js +17 -17
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/web-system/index.js +17 -0
- package/lib/commonjs/web-system/index.js.map +1 -0
- package/lib/commonjs/web-system/notification/PlaybackNotificationManager.js +34 -0
- package/lib/commonjs/web-system/notification/PlaybackNotificationManager.js.map +1 -0
- package/lib/commonjs/web-system/notification/RecordingNotificationManager.js +34 -0
- package/lib/commonjs/web-system/notification/RecordingNotificationManager.js.map +1 -0
- package/lib/commonjs/web-system/notification/index.js +21 -0
- package/lib/commonjs/web-system/notification/index.js.map +1 -0
- package/lib/module/api.js +4 -0
- package/lib/module/api.js.map +1 -1
- package/lib/module/api.web.js +3 -1
- package/lib/module/api.web.js.map +1 -1
- package/lib/module/specs/NativeAudioAPIModule.js.map +1 -1
- package/lib/module/system/AudioManager.js +6 -9
- package/lib/module/system/AudioManager.js.map +1 -1
- package/lib/module/system/index.js +1 -0
- package/lib/module/system/index.js.map +1 -1
- package/lib/module/system/notification/PlaybackNotificationManager.js +131 -0
- package/lib/module/system/notification/PlaybackNotificationManager.js.map +1 -0
- package/lib/module/system/notification/RecordingNotificationManager.js +178 -0
- package/lib/module/system/notification/RecordingNotificationManager.js.map +1 -0
- package/lib/module/system/notification/SimpleNotificationManager.js +118 -0
- package/lib/module/system/notification/SimpleNotificationManager.js.map +1 -0
- package/lib/module/system/notification/index.js +7 -0
- package/lib/module/system/notification/index.js.map +1 -0
- package/lib/module/system/notification/types.js +4 -0
- package/lib/module/system/notification/types.js.map +1 -0
- package/lib/module/types.js +17 -17
- package/lib/module/types.js.map +1 -1
- package/lib/module/web-system/index.js +4 -0
- package/lib/module/web-system/index.js.map +1 -0
- package/lib/module/web-system/notification/PlaybackNotificationManager.js +30 -0
- package/lib/module/web-system/notification/PlaybackNotificationManager.js.map +1 -0
- package/lib/module/web-system/notification/RecordingNotificationManager.js +30 -0
- package/lib/module/web-system/notification/RecordingNotificationManager.js.map +1 -0
- package/lib/module/web-system/notification/index.js +5 -0
- package/lib/module/web-system/notification/index.js.map +1 -0
- package/lib/typescript/api.d.ts +2 -0
- package/lib/typescript/api.d.ts.map +1 -1
- package/lib/typescript/api.web.d.ts +3 -1
- package/lib/typescript/api.web.d.ts.map +1 -1
- package/lib/typescript/events/types.d.ts +3 -3
- package/lib/typescript/events/types.d.ts.map +1 -1
- package/lib/typescript/specs/NativeAudioAPIModule.d.ts +16 -5
- package/lib/typescript/specs/NativeAudioAPIModule.d.ts.map +1 -1
- package/lib/typescript/system/AudioManager.d.ts +4 -5
- package/lib/typescript/system/AudioManager.d.ts.map +1 -1
- package/lib/typescript/system/index.d.ts +1 -0
- package/lib/typescript/system/index.d.ts.map +1 -1
- package/lib/typescript/system/notification/PlaybackNotificationManager.d.ts +22 -0
- package/lib/typescript/system/notification/PlaybackNotificationManager.d.ts.map +1 -0
- package/lib/typescript/system/notification/RecordingNotificationManager.d.ts +23 -0
- package/lib/typescript/system/notification/RecordingNotificationManager.d.ts.map +1 -0
- package/lib/typescript/system/notification/SimpleNotificationManager.d.ts +20 -0
- package/lib/typescript/system/notification/SimpleNotificationManager.d.ts.map +1 -0
- package/lib/typescript/system/notification/index.d.ts +5 -0
- package/lib/typescript/system/notification/index.d.ts.map +1 -0
- package/lib/typescript/system/notification/types.d.ts +65 -0
- package/lib/typescript/system/notification/types.d.ts.map +1 -0
- package/lib/typescript/system/types.d.ts +0 -16
- package/lib/typescript/system/types.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +16 -16
- package/lib/typescript/types.d.ts.map +1 -1
- package/lib/typescript/web-system/index.d.ts +2 -0
- package/lib/typescript/web-system/index.d.ts.map +1 -0
- package/lib/typescript/web-system/notification/PlaybackNotificationManager.d.ts +19 -0
- package/lib/typescript/web-system/notification/PlaybackNotificationManager.d.ts.map +1 -0
- package/lib/typescript/web-system/notification/RecordingNotificationManager.d.ts +19 -0
- package/lib/typescript/web-system/notification/RecordingNotificationManager.d.ts.map +1 -0
- package/lib/typescript/web-system/notification/index.d.ts +3 -0
- package/lib/typescript/web-system/notification/index.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/api.ts +17 -0
- package/src/api.web.ts +7 -2
- package/src/events/types.ts +3 -4
- package/src/specs/NativeAudioAPIModule.ts +23 -7
- package/src/system/AudioManager.ts +10 -23
- package/src/system/index.ts +1 -0
- package/src/system/notification/PlaybackNotificationManager.ts +193 -0
- package/src/system/notification/RecordingNotificationManager.ts +242 -0
- package/src/system/notification/SimpleNotificationManager.ts +170 -0
- package/src/system/notification/index.ts +4 -0
- package/src/system/notification/types.ts +111 -0
- package/src/system/types.ts +0 -18
- package/src/types.ts +17 -17
- package/src/web-system/index.ts +1 -0
- package/src/web-system/notification/PlaybackNotificationManager.ts +60 -0
- package/src/web-system/notification/RecordingNotificationManager.ts +60 -0
- package/src/web-system/notification/index.ts +2 -0
- package/android/src/main/java/com/swmansion/audioapi/system/LockScreenManager.kt +0 -347
- package/android/src/main/java/com/swmansion/audioapi/system/MediaNotificationManager.kt +0 -273
- package/android/src/main/java/com/swmansion/audioapi/system/MediaReceiver.kt +0 -57
- 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
|