From 5d1ecd3dd8591b074848de92014fb51fd8259e12 Mon Sep 17 00:00:00 2001 From: Barbara Wojtarowicz Date: Tue, 14 Jul 2026 11:58:24 +0200 Subject: [PATCH 1/8] feat(rnaa-474): make playback notification skipInterval configurable --- .../src/examples/AudioFile/AudioFile.tsx | 1 + .../FabricExample.xcodeproj/project.pbxproj | 8 ++--- apps/fabric-example/ios/Podfile.lock | 6 ++-- .../system/playback-notification-manager.mdx | 29 +++++++++++++++++ .../notification/PlaybackNotification.kt | 28 ++++++++++++++-- .../PlaybackNotificationReceiver.kt | 4 +-- .../notification/PlaybackNotification.mm | 32 +++++++++++++++++-- .../src/system/notification/types.ts | 2 ++ 8 files changed, 97 insertions(+), 13 deletions(-) diff --git a/apps/common-app/src/examples/AudioFile/AudioFile.tsx b/apps/common-app/src/examples/AudioFile/AudioFile.tsx index 20a25c1b4..0574400af 100644 --- a/apps/common-app/src/examples/AudioFile/AudioFile.tsx +++ b/apps/common-app/src/examples/AudioFile/AudioFile.tsx @@ -76,6 +76,7 @@ const AudioFile: FC = () => { state: 'paused', speed: 1.0, elapsedTime: 0, + skipInterval: 10 }); await PlaybackNotificationManager.enableControl('skipBackward', true); await PlaybackNotificationManager.enableControl('nextTrack', true); diff --git a/apps/fabric-example/ios/FabricExample.xcodeproj/project.pbxproj b/apps/fabric-example/ios/FabricExample.xcodeproj/project.pbxproj index 6bc4df0bb..4b874271e 100644 --- a/apps/fabric-example/ios/FabricExample.xcodeproj/project.pbxproj +++ b/apps/fabric-example/ios/FabricExample.xcodeproj/project.pbxproj @@ -392,7 +392,7 @@ CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_TEAM = 852ZJU38RC; + DEVELOPMENT_TEAM = K3Z69XB7ZF; ENABLE_BITCODE = NO; INFOPLIST_FILE = FabricExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; @@ -412,7 +412,7 @@ "-lc++", ); PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; - PRODUCT_BUNDLE_IDENTIFIER = "fdsfsdfsdcom-eseg3222233e.--PRODUCT-NAME-rfc1034identifier-"; + PRODUCT_BUNDLE_IDENTIFIER = "fdsfsdfsdcom-eseg3222233e.--PRODUCT-NAME-rfc1034identifier-basia"; PRODUCT_NAME = FabricExample; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; @@ -431,7 +431,7 @@ CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_TEAM = 852ZJU38RC; + DEVELOPMENT_TEAM = K3Z69XB7ZF; INFOPLIST_FILE = FabricExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; LD_RUNPATH_SEARCH_PATHS = ( @@ -450,7 +450,7 @@ "-lc++", ); PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; - PRODUCT_BUNDLE_IDENTIFIER = "fdsfsdfsdcom-eseg3222233e.--PRODUCT-NAME-rfc1034identifier-"; + PRODUCT_BUNDLE_IDENTIFIER = "fdsfsdfsdcom-eseg3222233e.--PRODUCT-NAME-rfc1034identifier-basia"; PRODUCT_NAME = FabricExample; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; diff --git a/apps/fabric-example/ios/Podfile.lock b/apps/fabric-example/ios/Podfile.lock index 26b95a6a6..191d33bc6 100644 --- a/apps/fabric-example/ios/Podfile.lock +++ b/apps/fabric-example/ios/Podfile.lock @@ -2523,7 +2523,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: FBLazyVector: c00c20551d40126351a6783c47ce75f5b374851b - hermes-engine: c399a2e224a0b13c589d76b4fc05e14bdd76fa88 + hermes-engine: fd79291075647a2216de246626b26b8c068f1039 RCTDeprecation: 3bb167081b134461cfeb875ff7ae1945f8635257 RCTRequired: 74839f55d5058a133a0bc4569b0afec750957f64 RCTSwiftUI: 87a316382f3eab4dd13d2a0d0fd2adcce917361a @@ -2532,7 +2532,7 @@ SPEC CHECKSUMS: React: 1b1536b9099195944034e65b1830f463caaa8390 React-callinvoker: 6dff6d17d1d6cc8fdf85468a649bafed473c65f5 React-Core: 00faa4d038298089a1d5a5b21dde8660c4f0820d - React-Core-prebuilt: ab26be1216323aea7c76f96ca450bffa7bcd4a72 + React-Core-prebuilt: 6ff532f696eb538a6b1beb5875de609724d9b426 React-CoreModules: a17807f849bfd86045b0b9a75ec8c19373b482f6 React-cxxreact: c7b53ace5827be54048288bce5c55f337c41e95f React-debug: e1f00fcd2cef58a2897471a6d76a4ef5f5f90c74 @@ -2596,7 +2596,7 @@ SPEC CHECKSUMS: ReactAppDependencyProvider: 5787b37b8e2e51dfeab697ec031cc7c4080dcea2 ReactCodegen: d07ee3c8db75b43d1cbe479ae6affebf9925c733 ReactCommon: fe2a3af8975e63efa60f95fca8c34dc85deee360 - ReactNativeDependencies: 212738cc51e6c4cc34ee487890497d6f41979ec0 + ReactNativeDependencies: bdd62b51872418996ef6cb305f40de2b1e95b084 RNAudioAPI: 6bba1527c091e2702e3d879de7564ef1ccf30a78 RNAudioWorklets: febe470be646585d9b8b0de320a5fb8684cb012c RNGestureHandler: 187c5c7936abf427bc4d22d6c3b1ac80ad1f63c0 diff --git a/packages/audiodocs/docs/system/playback-notification-manager.mdx b/packages/audiodocs/docs/system/playback-notification-manager.mdx index 0d42fc03d..d2c54841f 100644 --- a/packages/audiodocs/docs/system/playback-notification-manager.mdx +++ b/packages/audiodocs/docs/system/playback-notification-manager.mdx @@ -35,6 +35,7 @@ await PlaybackNotificationManager.show({ artist: 'My Artist', duration: 180, state: 'paused', + skipInterval: 30, // seconds to skip on fast-forward/rewind; default: 15 }); // Listen for notification controls @@ -54,6 +55,21 @@ const pauseListener = PlaybackNotificationManager.addEventListener( } ); +const skipForwardListener = PlaybackNotificationManager.addEventListener( + 'playbackNotificationSkipForward', + (event) => { + // event.value is the skip interval in seconds + seekBy(event.value); + } +); + +const skipBackwardListener = PlaybackNotificationManager.addEventListener( + 'playbackNotificationSkipBackward', + (event) => { + seekBy(-event.value); + } +); + const seekToListener = PlaybackNotificationManager.addEventListener( 'playbackNotificationSeekTo', (event) => { @@ -68,6 +84,8 @@ PlaybackNotificationManager.show({ elapsedTime: 60 }); // Cleanup playListener.remove(); pauseListener.remove(); +skipForwardListener.remove(); +skipBackwardListener.remove(); seekToListener.remove(); PlaybackNotificationManager.hide(); ``` @@ -152,10 +170,21 @@ interface PlaybackNotificationInfo { elapsedTime?: number; speed?: number; state?: 'playing' | 'paused'; + + // Skip interval in seconds for skipForward/skipBackward; default: 15 + skipInterval?: number; } ``` +#### `skipInterval` + +- Configures how many seconds `playbackNotificationSkipForward` and `playbackNotificationSkipBackward` advance or rewind. +- Defaults to **15** when omitted. +- Clamped to **1–120** seconds on native platforms. +- Pass `skipInterval` in the same `show()` call (or earlier) before enabling skip controls, so the interval is applied when controls are first set up. +- On Android, notification action icons may still display "15" regardless of the configured interval. + ### `PlaybackControlName`
diff --git a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt index 3364c7ab0..f9d110f04 100644 --- a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt +++ b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt @@ -45,6 +45,19 @@ class PlaybackNotification( const val ACTION_SKIP_FORWARD = "com.swmansion.audioapi.ACTION_SKIP_FORWARD" const val ACTION_SKIP_BACKWARD = "com.swmansion.audioapi.ACTION_SKIP_BACKWARD" const val ID = 100 + + private const val DEFAULT_SKIP_INTERVAL_SECONDS = 15 + private const val MIN_SKIP_INTERVAL_SECONDS = 1 + private const val MAX_SKIP_INTERVAL_SECONDS = 120 + + @JvmStatic + var skipIntervalSeconds: Int = DEFAULT_SKIP_INTERVAL_SECONDS + private set + + @JvmStatic + fun setSkipIntervalSeconds(seconds: Int) { + skipIntervalSeconds = seconds.coerceIn(MIN_SKIP_INTERVAL_SECONDS, MAX_SKIP_INTERVAL_SECONDS) + } } private var mediaSession: MediaSessionCompat? = null @@ -99,12 +112,12 @@ class PlaybackNotification( } override fun onFastForward() { - val body = HashMap().apply { put("value", 15) } + val body = HashMap().apply { put("value", skipIntervalSeconds) } audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody(AudioEvent.PLAYBACK_NOTIFICATION_SKIP_FORWARD.ordinal, body) } override fun onRewind() { - val body = HashMap().apply { put("value", 15) } + val body = HashMap().apply { put("value", skipIntervalSeconds) } audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody(AudioEvent.PLAYBACK_NOTIFICATION_SKIP_BACKWARD.ordinal, body) } @@ -164,6 +177,9 @@ class PlaybackNotification( } override fun show(options: ReadableMap?): Notification { + if (options != null) { + updateSkipIntervalFromOptions(options) + } initializeIfNeeded() if (options != null) { updateInternal(options) @@ -194,7 +210,15 @@ class PlaybackNotification( override fun getChannelId(): String = channelId + private fun updateSkipIntervalFromOptions(info: ReadableMap) { + if (info.hasKey("skipInterval")) { + setSkipIntervalSeconds(info.getDouble("skipInterval").toInt()) + } + } + private fun updateInternal(info: ReadableMap) { + updateSkipIntervalFromOptions(info) + if (info.hasKey("control") && info.hasKey("enabled")) { enableControl(info.getString("control"), info.getBoolean("enabled")) } diff --git a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotificationReceiver.kt b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotificationReceiver.kt index d1cb3b679..9df508161 100644 --- a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotificationReceiver.kt +++ b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotificationReceiver.kt @@ -32,12 +32,12 @@ class PlaybackNotificationReceiver : BroadcastReceiver() { } ACTION_SKIP_FORWARD -> { - val body = HashMap().apply { put("value", 15) } + val body = HashMap().apply { put("value", PlaybackNotification.skipIntervalSeconds) } audioAPIModule?.invokeHandlerWithEventNameAndEventBody(AudioEvent.PLAYBACK_NOTIFICATION_SKIP_FORWARD.ordinal, body) } ACTION_SKIP_BACKWARD -> { - val body = HashMap().apply { put("value", 15) } + val body = HashMap().apply { put("value", PlaybackNotification.skipIntervalSeconds) } audioAPIModule?.invokeHandlerWithEventNameAndEventBody(AudioEvent.PLAYBACK_NOTIFICATION_SKIP_BACKWARD.ordinal, body) } } diff --git a/packages/react-native-audio-api/ios/audioapi/ios/system/notification/PlaybackNotification.mm b/packages/react-native-audio-api/ios/audioapi/ios/system/notification/PlaybackNotification.mm index ce533ba8e..0d9814656 100644 --- a/packages/react-native-audio-api/ios/audioapi/ios/system/notification/PlaybackNotification.mm +++ b/packages/react-native-audio-api/ios/audioapi/ios/system/notification/PlaybackNotification.mm @@ -12,9 +12,14 @@ @"artwork" : MPMediaItemPropertyArtwork \ } +static const NSInteger kDefaultSkipIntervalSeconds = 15; +static const NSInteger kMinSkipIntervalSeconds = 1; +static const NSInteger kMaxSkipIntervalSeconds = 120; + @implementation PlaybackNotification { BOOL _isInitialized; NSMutableDictionary *_currentInfo; + NSInteger _skipInterval; } - (instancetype)initWithAudioAPIModule:(AudioAPIModule *)audioAPIModule @@ -25,6 +30,7 @@ - (instancetype)initWithAudioAPIModule:(AudioAPIModule *)audioAPIModule _isInitialized = false; _isActive = false; _currentInfo = [[NSMutableDictionary alloc] init]; + _skipInterval = kDefaultSkipIntervalSeconds; } return self; @@ -58,6 +64,8 @@ - (BOOL)initializeWithOptions:(NSDictionary *)options - (BOOL)showWithOptions:(NSDictionary *)options { + [self updateSkipIntervalFromOptions:options]; + if (!_isInitialized) { if (![self initializeWithOptions:options]) { return false; @@ -137,6 +145,26 @@ - (NSString *)getNotificationType #pragma mark - Private Methods +- (void)updateSkipIntervalFromOptions:(NSDictionary *)options +{ + id skipIntervalValue = options[@"skipInterval"]; + if (skipIntervalValue == nil) { + return; + } + + NSInteger interval = (NSInteger)[skipIntervalValue doubleValue]; + interval = MAX(kMinSkipIntervalSeconds, MIN(kMaxSkipIntervalSeconds, interval)); + _skipInterval = interval; + [self applySkipIntervals]; +} + +- (void)applySkipIntervals +{ + MPRemoteCommandCenter *remoteCenter = [MPRemoteCommandCenter sharedCommandCenter]; + remoteCenter.skipForwardCommand.preferredIntervals = @[ @(_skipInterval) ]; + remoteCenter.skipBackwardCommand.preferredIntervals = @[ @(_skipInterval) ]; +} + - (void)updateNowPlayingInfo:(NSDictionary *)info { if (!info) { @@ -300,12 +328,12 @@ - (void)enableRemoteCommand:(NSString *)name enabled:(BOOL)enabled withSelector:@selector(onPreviousTrack:) enabled:enabled]; } else if ([name isEqualToString:@"skipForward"]) { - remoteCenter.skipForwardCommand.preferredIntervals = @[ @(15) ]; + [self applySkipIntervals]; [self enableCommand:remoteCenter.skipForwardCommand withSelector:@selector(onSkipForward:) enabled:enabled]; } else if ([name isEqualToString:@"skipBackward"]) { - remoteCenter.skipBackwardCommand.preferredIntervals = @[ @(15) ]; + [self applySkipIntervals]; [self enableCommand:remoteCenter.skipBackwardCommand withSelector:@selector(onSkipBackward:) enabled:enabled]; diff --git a/packages/react-native-audio-api/src/system/notification/types.ts b/packages/react-native-audio-api/src/system/notification/types.ts index 533146432..3f1060233 100644 --- a/packages/react-native-audio-api/src/system/notification/types.ts +++ b/packages/react-native-audio-api/src/system/notification/types.ts @@ -35,6 +35,8 @@ export interface PlaybackNotificationInfo { elapsedTime?: number; speed?: number; state?: 'playing' | 'paused'; + /** Skip interval in seconds for skipForward/skipBackward. Default: 15 */ + skipInterval?: number; } /// Available playback control actions. From 631218067c8617d12b957839243e119bb1dd69a7 Mon Sep 17 00:00:00 2001 From: Barbara Wojtarowicz Date: Tue, 14 Jul 2026 12:40:11 +0200 Subject: [PATCH 2/8] feat(rnaa-474): update Android generic skip icon --- .../system/playback-notification-manager.mdx | 2 +- .../notification/PlaybackNotification.kt | 36 ++++++++++++++++--- .../src/main/res/drawable/skip_backward.xml | 9 +++++ .../src/main/res/drawable/skip_forward.xml | 9 +++++ 4 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 packages/react-native-audio-api/android/src/main/res/drawable/skip_backward.xml create mode 100644 packages/react-native-audio-api/android/src/main/res/drawable/skip_forward.xml diff --git a/packages/audiodocs/docs/system/playback-notification-manager.mdx b/packages/audiodocs/docs/system/playback-notification-manager.mdx index d2c54841f..5e9939d01 100644 --- a/packages/audiodocs/docs/system/playback-notification-manager.mdx +++ b/packages/audiodocs/docs/system/playback-notification-manager.mdx @@ -183,7 +183,7 @@ interface PlaybackNotificationInfo { - Defaults to **15** when omitted. - Clamped to **1–120** seconds on native platforms. - Pass `skipInterval` in the same `show()` call (or earlier) before enabling skip controls, so the interval is applied when controls are first set up. -- On Android, notification action icons may still display "15" regardless of the configured interval. +- On Android, skip action icons show **15** when `skipInterval` is 15 (default); otherwise generic skip icons without a number are used. ### `PlaybackControlName` diff --git a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt index f9d110f04..15f1dab8a 100644 --- a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt +++ b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt @@ -212,8 +212,36 @@ class PlaybackNotification( private fun updateSkipIntervalFromOptions(info: ReadableMap) { if (info.hasKey("skipInterval")) { + val previous = skipIntervalSeconds setSkipIntervalSeconds(info.getDouble("skipInterval").toInt()) + if (isInitialized && previous != skipIntervalSeconds) { + refreshSkipControlIcons() + } + } + } + + private fun skipForwardIcon(): Int = + if (skipIntervalSeconds == DEFAULT_SKIP_INTERVAL_SECONDS) { + R.drawable.skip_forward_15 + } else { + R.drawable.skip_forward } + + private fun skipBackwardIcon(): Int = + if (skipIntervalSeconds == DEFAULT_SKIP_INTERVAL_SECONDS) { + R.drawable.skip_backward_15 + } else { + R.drawable.skip_backward + } + + private fun refreshSkipControlIcons() { + if (hasControl(PlaybackStateCompat.ACTION_REWIND) || + hasControl(PlaybackStateCompat.ACTION_FAST_FORWARD) + ) { + updatePlaybackActionState() + updatePlaybackState(playbackStateVal) + } + updateNotificationsActions() } private fun updateInternal(info: ReadableMap) { @@ -352,7 +380,7 @@ class PlaybackNotification( .Builder( "SkipBackward", "Skip Backward", - R.drawable.skip_backward_15, + skipBackwardIcon(), ).build(), ) } @@ -363,7 +391,7 @@ class PlaybackNotification( .Builder( "SkipForward", "Skip Forward", - R.drawable.skip_forward_15, + skipForwardIcon(), ).build(), ) } @@ -401,7 +429,7 @@ class PlaybackNotification( if (hasControl(PlaybackStateCompat.ACTION_REWIND)) { notificationBuilder?.addAction( - createAction("skip_backward", "Skip Backward", R.drawable.skip_backward_15, PlaybackStateCompat.ACTION_REWIND), + createAction("skip_backward", "Skip Backward", skipBackwardIcon(), PlaybackStateCompat.ACTION_REWIND), ) actionsList.add(index++) } @@ -427,7 +455,7 @@ class PlaybackNotification( if (hasControl(PlaybackStateCompat.ACTION_FAST_FORWARD)) { notificationBuilder?.addAction( - createAction("skip_forward", "Skip Forward", R.drawable.skip_forward_15, PlaybackStateCompat.ACTION_FAST_FORWARD), + createAction("skip_forward", "Skip Forward", skipForwardIcon(), PlaybackStateCompat.ACTION_FAST_FORWARD), ) actionsList.add(index++) } diff --git a/packages/react-native-audio-api/android/src/main/res/drawable/skip_backward.xml b/packages/react-native-audio-api/android/src/main/res/drawable/skip_backward.xml new file mode 100644 index 000000000..14d85a0e0 --- /dev/null +++ b/packages/react-native-audio-api/android/src/main/res/drawable/skip_backward.xml @@ -0,0 +1,9 @@ + + + diff --git a/packages/react-native-audio-api/android/src/main/res/drawable/skip_forward.xml b/packages/react-native-audio-api/android/src/main/res/drawable/skip_forward.xml new file mode 100644 index 000000000..5b935a2d4 --- /dev/null +++ b/packages/react-native-audio-api/android/src/main/res/drawable/skip_forward.xml @@ -0,0 +1,9 @@ + + + From 85a942242244eba9eea535e1a93ce6cbf50fbea2 Mon Sep 17 00:00:00 2001 From: Barbara Wojtarowicz Date: Tue, 14 Jul 2026 12:52:23 +0200 Subject: [PATCH 3/8] feat(rnaa-474): update docs styling --- .../audiodocs/docs/system/playback-notification-manager.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/audiodocs/docs/system/playback-notification-manager.mdx b/packages/audiodocs/docs/system/playback-notification-manager.mdx index 5e9939d01..433ccc906 100644 --- a/packages/audiodocs/docs/system/playback-notification-manager.mdx +++ b/packages/audiodocs/docs/system/playback-notification-manager.mdx @@ -180,10 +180,10 @@ interface PlaybackNotificationInfo { #### `skipInterval` - Configures how many seconds `playbackNotificationSkipForward` and `playbackNotificationSkipBackward` advance or rewind. -- Defaults to **15** when omitted. -- Clamped to **1–120** seconds on native platforms. +- Defaults to `15` when omitted. +- Clamped to `1–120` seconds on native platforms. - Pass `skipInterval` in the same `show()` call (or earlier) before enabling skip controls, so the interval is applied when controls are first set up. -- On Android, skip action icons show **15** when `skipInterval` is 15 (default); otherwise generic skip icons without a number are used. +- On Android, skip action icons show `15` when `skipInterval` is `15` (default); otherwise generic skip icons without a number are used. ### `PlaybackControlName` From d43c81817fb5159c4e11335b0fd7886b7e40a64a Mon Sep 17 00:00:00 2001 From: Barbara Wojtarowicz Date: Tue, 14 Jul 2026 13:13:49 +0200 Subject: [PATCH 4/8] revert: restore FabricExample Xcode project settings --- .../ios/FabricExample.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/fabric-example/ios/FabricExample.xcodeproj/project.pbxproj b/apps/fabric-example/ios/FabricExample.xcodeproj/project.pbxproj index 4b874271e..6bc4df0bb 100644 --- a/apps/fabric-example/ios/FabricExample.xcodeproj/project.pbxproj +++ b/apps/fabric-example/ios/FabricExample.xcodeproj/project.pbxproj @@ -392,7 +392,7 @@ CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_TEAM = K3Z69XB7ZF; + DEVELOPMENT_TEAM = 852ZJU38RC; ENABLE_BITCODE = NO; INFOPLIST_FILE = FabricExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; @@ -412,7 +412,7 @@ "-lc++", ); PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; - PRODUCT_BUNDLE_IDENTIFIER = "fdsfsdfsdcom-eseg3222233e.--PRODUCT-NAME-rfc1034identifier-basia"; + PRODUCT_BUNDLE_IDENTIFIER = "fdsfsdfsdcom-eseg3222233e.--PRODUCT-NAME-rfc1034identifier-"; PRODUCT_NAME = FabricExample; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; @@ -431,7 +431,7 @@ CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_TEAM = K3Z69XB7ZF; + DEVELOPMENT_TEAM = 852ZJU38RC; INFOPLIST_FILE = FabricExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; LD_RUNPATH_SEARCH_PATHS = ( @@ -450,7 +450,7 @@ "-lc++", ); PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; - PRODUCT_BUNDLE_IDENTIFIER = "fdsfsdfsdcom-eseg3222233e.--PRODUCT-NAME-rfc1034identifier-basia"; + PRODUCT_BUNDLE_IDENTIFIER = "fdsfsdfsdcom-eseg3222233e.--PRODUCT-NAME-rfc1034identifier-"; PRODUCT_NAME = FabricExample; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; From 1ad016c1e28122d633f58dae79340ee7b74b2eb1 Mon Sep 17 00:00:00 2001 From: Barbara Wojtarowicz Date: Tue, 14 Jul 2026 13:22:55 +0200 Subject: [PATCH 5/8] feat(rnaa-474): update skipInterval value in AudioFile.tsx --- apps/common-app/src/examples/AudioFile/AudioFile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/common-app/src/examples/AudioFile/AudioFile.tsx b/apps/common-app/src/examples/AudioFile/AudioFile.tsx index 0574400af..e571eef7f 100644 --- a/apps/common-app/src/examples/AudioFile/AudioFile.tsx +++ b/apps/common-app/src/examples/AudioFile/AudioFile.tsx @@ -76,7 +76,7 @@ const AudioFile: FC = () => { state: 'paused', speed: 1.0, elapsedTime: 0, - skipInterval: 10 + skipInterval: 15 // default is 15 }); await PlaybackNotificationManager.enableControl('skipBackward', true); await PlaybackNotificationManager.enableControl('nextTrack', true); From 190f4b990f3be8589f83ec543eb0012799be0abe Mon Sep 17 00:00:00 2001 From: Barbara Wojtarowicz Date: Tue, 14 Jul 2026 13:30:56 +0200 Subject: [PATCH 6/8] feat(rnaa-474): update skipInterval value in AudioFile.tsx to 10 and update relevant docs example --- apps/common-app/src/examples/AudioFile/AudioFile.tsx | 2 +- .../audiodocs/docs/system/playback-notification-manager.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/common-app/src/examples/AudioFile/AudioFile.tsx b/apps/common-app/src/examples/AudioFile/AudioFile.tsx index e571eef7f..6e88994bc 100644 --- a/apps/common-app/src/examples/AudioFile/AudioFile.tsx +++ b/apps/common-app/src/examples/AudioFile/AudioFile.tsx @@ -76,7 +76,7 @@ const AudioFile: FC = () => { state: 'paused', speed: 1.0, elapsedTime: 0, - skipInterval: 15 // default is 15 + skipInterval: 10, // seconds to skip on fast-forward/rewind; default: 15 }); await PlaybackNotificationManager.enableControl('skipBackward', true); await PlaybackNotificationManager.enableControl('nextTrack', true); diff --git a/packages/audiodocs/docs/system/playback-notification-manager.mdx b/packages/audiodocs/docs/system/playback-notification-manager.mdx index 433ccc906..2478259fc 100644 --- a/packages/audiodocs/docs/system/playback-notification-manager.mdx +++ b/packages/audiodocs/docs/system/playback-notification-manager.mdx @@ -35,7 +35,7 @@ await PlaybackNotificationManager.show({ artist: 'My Artist', duration: 180, state: 'paused', - skipInterval: 30, // seconds to skip on fast-forward/rewind; default: 15 + skipInterval: 10, // seconds to skip on fast-forward/rewind; default: 15 }); // Listen for notification controls From 1e6f8a21fae1c6c730a733b1a38b1450d36fb95e Mon Sep 17 00:00:00 2001 From: Barbara Wojtarowicz Date: Tue, 14 Jul 2026 17:39:03 +0200 Subject: [PATCH 7/8] refactor(notification): apply review suggestions, move skipInterval validation and defaults to JS --- .../system/playback-notification-manager.mdx | 6 ++---- .../notification/PlaybackNotification.kt | 4 +--- .../notification/PlaybackNotification.mm | 6 +----- .../PlaybackNotificationManager.ts | 19 ++++++++++++++++++- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/packages/audiodocs/docs/system/playback-notification-manager.mdx b/packages/audiodocs/docs/system/playback-notification-manager.mdx index 2478259fc..b84cf25d7 100644 --- a/packages/audiodocs/docs/system/playback-notification-manager.mdx +++ b/packages/audiodocs/docs/system/playback-notification-manager.mdx @@ -35,7 +35,7 @@ await PlaybackNotificationManager.show({ artist: 'My Artist', duration: 180, state: 'paused', - skipInterval: 10, // seconds to skip on fast-forward/rewind; default: 15 + skipInterval: 10 }); // Listen for notification controls @@ -58,7 +58,6 @@ const pauseListener = PlaybackNotificationManager.addEventListener( const skipForwardListener = PlaybackNotificationManager.addEventListener( 'playbackNotificationSkipForward', (event) => { - // event.value is the skip interval in seconds seekBy(event.value); } ); @@ -171,7 +170,6 @@ interface PlaybackNotificationInfo { speed?: number; state?: 'playing' | 'paused'; - // Skip interval in seconds for skipForward/skipBackward; default: 15 skipInterval?: number; } ``` @@ -181,7 +179,7 @@ interface PlaybackNotificationInfo { - Configures how many seconds `playbackNotificationSkipForward` and `playbackNotificationSkipBackward` advance or rewind. - Defaults to `15` when omitted. -- Clamped to `1–120` seconds on native platforms. +- Clamped to `1–120` seconds in JavaScript before being passed to native. - Pass `skipInterval` in the same `show()` call (or earlier) before enabling skip controls, so the interval is applied when controls are first set up. - On Android, skip action icons show `15` when `skipInterval` is `15` (default); otherwise generic skip icons without a number are used. diff --git a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt index 15f1dab8a..24b8cb0b3 100644 --- a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt +++ b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt @@ -47,8 +47,6 @@ class PlaybackNotification( const val ID = 100 private const val DEFAULT_SKIP_INTERVAL_SECONDS = 15 - private const val MIN_SKIP_INTERVAL_SECONDS = 1 - private const val MAX_SKIP_INTERVAL_SECONDS = 120 @JvmStatic var skipIntervalSeconds: Int = DEFAULT_SKIP_INTERVAL_SECONDS @@ -56,7 +54,7 @@ class PlaybackNotification( @JvmStatic fun setSkipIntervalSeconds(seconds: Int) { - skipIntervalSeconds = seconds.coerceIn(MIN_SKIP_INTERVAL_SECONDS, MAX_SKIP_INTERVAL_SECONDS) + skipIntervalSeconds = seconds } } diff --git a/packages/react-native-audio-api/ios/audioapi/ios/system/notification/PlaybackNotification.mm b/packages/react-native-audio-api/ios/audioapi/ios/system/notification/PlaybackNotification.mm index 0d9814656..b7561ebc0 100644 --- a/packages/react-native-audio-api/ios/audioapi/ios/system/notification/PlaybackNotification.mm +++ b/packages/react-native-audio-api/ios/audioapi/ios/system/notification/PlaybackNotification.mm @@ -13,8 +13,6 @@ } static const NSInteger kDefaultSkipIntervalSeconds = 15; -static const NSInteger kMinSkipIntervalSeconds = 1; -static const NSInteger kMaxSkipIntervalSeconds = 120; @implementation PlaybackNotification { BOOL _isInitialized; @@ -152,9 +150,7 @@ - (void)updateSkipIntervalFromOptions:(NSDictionary *)options return; } - NSInteger interval = (NSInteger)[skipIntervalValue doubleValue]; - interval = MAX(kMinSkipIntervalSeconds, MIN(kMaxSkipIntervalSeconds, interval)); - _skipInterval = interval; + _skipInterval = (NSInteger)[skipIntervalValue doubleValue]; [self applySkipIntervals]; } diff --git a/packages/react-native-audio-api/src/system/notification/PlaybackNotificationManager.ts b/packages/react-native-audio-api/src/system/notification/PlaybackNotificationManager.ts index 6be6c729b..3304d46e2 100644 --- a/packages/react-native-audio-api/src/system/notification/PlaybackNotificationManager.ts +++ b/packages/react-native-audio-api/src/system/notification/PlaybackNotificationManager.ts @@ -8,6 +8,11 @@ import type { PlaybackNotificationInfo, } from './types'; import { AudioApiError } from '../../errors'; +import { clamp } from '../../utils'; + +const DEFAULT_SKIP_INTERVAL_SECONDS = 15; +const MIN_SKIP_INTERVAL_SECONDS = 1; +const MAX_SKIP_INTERVAL_SECONDS = 120; class PlaybackNotificationManager implements NotificationManager< PlaybackNotificationInfo, @@ -15,6 +20,7 @@ class PlaybackNotificationManager implements NotificationManager< > { private notificationKey = 'playback'; private audioEventEmitter: AudioEventEmitter; + private skipIntervalSeconds = DEFAULT_SKIP_INTERVAL_SECONDS; constructor() { this.audioEventEmitter = new AudioEventEmitter( @@ -34,10 +40,21 @@ class PlaybackNotificationManager implements NotificationManager< throw new AudioApiError('NativeAudioAPIModule is not available'); } + if (info.skipInterval !== undefined) { + this.skipIntervalSeconds = clamp( + info.skipInterval, + MIN_SKIP_INTERVAL_SECONDS, + MAX_SKIP_INTERVAL_SECONDS + ); + } + const result = await NativeAudioAPIModule.showNotification( 'playback', this.notificationKey, - info as Record + { + ...info, + skipInterval: this.skipIntervalSeconds, + } as Record ); if (result.error) { From 7d2d379105907b6a8228c3599c99329fc3ab122f Mon Sep 17 00:00:00 2001 From: Barbara Wojtarowicz Date: Wed, 15 Jul 2026 10:05:17 +0200 Subject: [PATCH 8/8] refactor(notification): address PR feedback and simplify skip interval logic --- .../notification/PlaybackNotification.kt | 20 +++++++------------ .../PlaybackNotificationReceiver.kt | 15 ++++++++++++-- .../notification/PlaybackNotification.mm | 1 + 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt index 24b8cb0b3..2b3d7c3dd 100644 --- a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt +++ b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt @@ -46,18 +46,12 @@ class PlaybackNotification( const val ACTION_SKIP_BACKWARD = "com.swmansion.audioapi.ACTION_SKIP_BACKWARD" const val ID = 100 - private const val DEFAULT_SKIP_INTERVAL_SECONDS = 15 - - @JvmStatic - var skipIntervalSeconds: Int = DEFAULT_SKIP_INTERVAL_SECONDS - private set - - @JvmStatic - fun setSkipIntervalSeconds(seconds: Int) { - skipIntervalSeconds = seconds - } + // Must match kDefaultSkipIntervalSeconds on iOS. + const val DEFAULT_SKIP_INTERVAL_SECONDS = 15 } + private var skipIntervalSeconds: Int = DEFAULT_SKIP_INTERVAL_SECONDS + private var mediaSession: MediaSessionCompat? = null private var notificationBuilder: NotificationCompat.Builder? = null private var pb: PlaybackStateCompat.Builder = PlaybackStateCompat.Builder() @@ -210,9 +204,8 @@ class PlaybackNotification( private fun updateSkipIntervalFromOptions(info: ReadableMap) { if (info.hasKey("skipInterval")) { - val previous = skipIntervalSeconds - setSkipIntervalSeconds(info.getDouble("skipInterval").toInt()) - if (isInitialized && previous != skipIntervalSeconds) { + skipIntervalSeconds = info.getDouble("skipInterval").toInt() + if (isInitialized) { refreshSkipControlIcons() } } @@ -487,6 +480,7 @@ class PlaybackNotification( val customActionName = if (name == "skip_forward") ACTION_SKIP_FORWARD else ACTION_SKIP_BACKWARD val intent = Intent(customActionName) intent.setPackage(context.packageName) + intent.putExtra(PlaybackNotificationReceiver.EXTRA_SKIP_INTERVAL_SECONDS, skipIntervalSeconds) pendingIntent = PendingIntent.getBroadcast( context, diff --git a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotificationReceiver.kt b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotificationReceiver.kt index 9df508161..f5f4f0701 100644 --- a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotificationReceiver.kt +++ b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotificationReceiver.kt @@ -14,6 +14,7 @@ class PlaybackNotificationReceiver : BroadcastReceiver() { const val ACTION_NOTIFICATION_DISMISSED = "com.swmansion.audioapi.PLAYBACK_NOTIFICATION_DISMISSED" const val ACTION_SKIP_FORWARD = "com.swmansion.audioapi.ACTION_SKIP_FORWARD" const val ACTION_SKIP_BACKWARD = "com.swmansion.audioapi.ACTION_SKIP_BACKWARD" + const val EXTRA_SKIP_INTERVAL_SECONDS = "skipIntervalSeconds" private var audioAPIModule: AudioAPIModule? = null @@ -32,12 +33,22 @@ class PlaybackNotificationReceiver : BroadcastReceiver() { } ACTION_SKIP_FORWARD -> { - val body = HashMap().apply { put("value", PlaybackNotification.skipIntervalSeconds) } + val skipInterval = + intent.getIntExtra( + EXTRA_SKIP_INTERVAL_SECONDS, + PlaybackNotification.DEFAULT_SKIP_INTERVAL_SECONDS, + ) + val body = HashMap().apply { put("value", skipInterval) } audioAPIModule?.invokeHandlerWithEventNameAndEventBody(AudioEvent.PLAYBACK_NOTIFICATION_SKIP_FORWARD.ordinal, body) } ACTION_SKIP_BACKWARD -> { - val body = HashMap().apply { put("value", PlaybackNotification.skipIntervalSeconds) } + val skipInterval = + intent.getIntExtra( + EXTRA_SKIP_INTERVAL_SECONDS, + PlaybackNotification.DEFAULT_SKIP_INTERVAL_SECONDS, + ) + val body = HashMap().apply { put("value", skipInterval) } audioAPIModule?.invokeHandlerWithEventNameAndEventBody(AudioEvent.PLAYBACK_NOTIFICATION_SKIP_BACKWARD.ordinal, body) } } diff --git a/packages/react-native-audio-api/ios/audioapi/ios/system/notification/PlaybackNotification.mm b/packages/react-native-audio-api/ios/audioapi/ios/system/notification/PlaybackNotification.mm index b7561ebc0..b9181a218 100644 --- a/packages/react-native-audio-api/ios/audioapi/ios/system/notification/PlaybackNotification.mm +++ b/packages/react-native-audio-api/ios/audioapi/ios/system/notification/PlaybackNotification.mm @@ -12,6 +12,7 @@ @"artwork" : MPMediaItemPropertyArtwork \ } +// Must match PlaybackNotification.DEFAULT_SKIP_INTERVAL_SECONDS on Android. static const NSInteger kDefaultSkipIntervalSeconds = 15; @implementation PlaybackNotification {