@capgo/native-audio 8.3.7 → 8.3.8

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/README.md CHANGED
@@ -246,6 +246,16 @@ The media control buttons automatically handle:
246
246
  - **Play** - Resumes paused audio
247
247
  - **Pause** - Pauses playing audio
248
248
  - **Stop** - Stops audio and clears the notification
249
+ - **Rewind 15s** (Android only) - Skips backward 15 seconds
250
+ - **Forward 15s** (Android only) - Skips forward 15 seconds
251
+
252
+ **Android Notification Controls:**
253
+ On Android, the notification displays three action buttons in this order:
254
+ 1. ⏪ **Rewind 15s** - Skip backward 15 seconds
255
+ 2. ▶️/⏸️ **Play/Pause** - Toggle playback (icon updates automatically)
256
+ 3. ⏩ **Forward 15s** - Skip forward 15 seconds
257
+
258
+ The skip forward/backward buttons are automatically available when `showNotification: true` is configured. No additional setup is required.
249
259
 
250
260
  **Notes:**
251
261
  - All metadata fields are optional
@@ -720,6 +720,7 @@ public class NativeAudio extends Plugin implements AudioManager.OnAudioFocusChan
720
720
  // Update notification when paused
721
721
  if (showNotification) {
722
722
  updatePlaybackState(PlaybackStateCompat.STATE_PAUSED);
723
+ updateNotification(audioId);
723
724
  }
724
725
 
725
726
  call.resolve();
@@ -764,6 +765,7 @@ public class NativeAudio extends Plugin implements AudioManager.OnAudioFocusChan
764
765
  // Update notification when resumed
765
766
  if (showNotification) {
766
767
  updatePlaybackState(PlaybackStateCompat.STATE_PLAYING);
768
+ updateNotification(audioId);
767
769
  }
768
770
 
769
771
  call.resolve();
@@ -1492,7 +1494,12 @@ public class NativeAudio extends Plugin implements AudioManager.OnAudioFocusChan
1492
1494
  mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
1493
1495
 
1494
1496
  PlaybackStateCompat.Builder stateBuilder = new PlaybackStateCompat.Builder().setActions(
1495
- PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_STOP
1497
+ PlaybackStateCompat.ACTION_PLAY |
1498
+ PlaybackStateCompat.ACTION_PAUSE |
1499
+ PlaybackStateCompat.ACTION_STOP |
1500
+ PlaybackStateCompat.ACTION_REWIND |
1501
+ PlaybackStateCompat.ACTION_FAST_FORWARD |
1502
+ PlaybackStateCompat.ACTION_SEEK_TO
1496
1503
  );
1497
1504
  mediaSession.setPlaybackState(stateBuilder.build());
1498
1505
 
@@ -1507,6 +1514,7 @@ public class NativeAudio extends Plugin implements AudioManager.OnAudioFocusChan
1507
1514
  if (asset != null && !asset.isPlaying()) {
1508
1515
  asset.resume();
1509
1516
  updatePlaybackState(PlaybackStateCompat.STATE_PLAYING);
1517
+ updateNotification(currentlyPlayingAssetId);
1510
1518
  }
1511
1519
  } catch (Exception e) {
1512
1520
  Log.e(TAG, "Error resuming audio from media session", e);
@@ -1522,6 +1530,7 @@ public class NativeAudio extends Plugin implements AudioManager.OnAudioFocusChan
1522
1530
  if (asset != null) {
1523
1531
  asset.pause();
1524
1532
  updatePlaybackState(PlaybackStateCompat.STATE_PAUSED);
1533
+ updateNotification(currentlyPlayingAssetId);
1525
1534
  }
1526
1535
  } catch (Exception e) {
1527
1536
  Log.e(TAG, "Error pausing audio from media session", e);
@@ -1541,6 +1550,60 @@ public class NativeAudio extends Plugin implements AudioManager.OnAudioFocusChan
1541
1550
  }
1542
1551
  }
1543
1552
  }
1553
+
1554
+ @Override
1555
+ public void onRewind() {
1556
+ if (currentlyPlayingAssetId != null && audioAssetList.containsKey(currentlyPlayingAssetId)) {
1557
+ AudioAsset asset = audioAssetList.get(currentlyPlayingAssetId);
1558
+ try {
1559
+ if (asset != null) {
1560
+ // Skip backward 15 seconds
1561
+ double currentPosition = asset.getCurrentPosition();
1562
+ double newPosition = Math.max(0, currentPosition - 15.0);
1563
+ asset.setCurrentPosition(newPosition);
1564
+ Log.d(TAG, "Rewind 15s: " + currentPosition + " -> " + newPosition);
1565
+ }
1566
+ } catch (Exception e) {
1567
+ Log.e(TAG, "Error rewinding audio from media session", e);
1568
+ }
1569
+ }
1570
+ }
1571
+
1572
+ @Override
1573
+ public void onFastForward() {
1574
+ if (currentlyPlayingAssetId != null && audioAssetList.containsKey(currentlyPlayingAssetId)) {
1575
+ AudioAsset asset = audioAssetList.get(currentlyPlayingAssetId);
1576
+ try {
1577
+ if (asset != null) {
1578
+ // Skip forward 15 seconds
1579
+ double currentPosition = asset.getCurrentPosition();
1580
+ double duration = asset.getDuration();
1581
+ double newPosition = Math.min(duration, currentPosition + 15.0);
1582
+ asset.setCurrentPosition(newPosition);
1583
+ Log.d(TAG, "Fast forward 15s: " + currentPosition + " -> " + newPosition);
1584
+ }
1585
+ } catch (Exception e) {
1586
+ Log.e(TAG, "Error fast forwarding audio from media session", e);
1587
+ }
1588
+ }
1589
+ }
1590
+
1591
+ @Override
1592
+ public void onSeekTo(long pos) {
1593
+ if (currentlyPlayingAssetId != null && audioAssetList.containsKey(currentlyPlayingAssetId)) {
1594
+ AudioAsset asset = audioAssetList.get(currentlyPlayingAssetId);
1595
+ try {
1596
+ if (asset != null) {
1597
+ // Convert milliseconds to seconds
1598
+ double positionInSeconds = pos / 1000.0;
1599
+ asset.setCurrentPosition(positionInSeconds);
1600
+ Log.d(TAG, "Seek to: " + positionInSeconds);
1601
+ }
1602
+ } catch (Exception e) {
1603
+ Log.e(TAG, "Error seeking audio from media session", e);
1604
+ }
1605
+ }
1606
+ }
1544
1607
  }
1545
1608
  );
1546
1609
 
@@ -1591,18 +1654,62 @@ public class NativeAudio extends Plugin implements AudioManager.OnAudioFocusChan
1591
1654
  }
1592
1655
 
1593
1656
  private void showNotification(String title, String artist) {
1657
+ // Determine if currently playing
1658
+ boolean isPlaying = false;
1659
+ if (currentlyPlayingAssetId != null && audioAssetList.containsKey(currentlyPlayingAssetId)) {
1660
+ AudioAsset asset = audioAssetList.get(currentlyPlayingAssetId);
1661
+ if (asset != null) {
1662
+ try {
1663
+ isPlaying = asset.isPlaying();
1664
+ } catch (Exception e) {
1665
+ Log.e(TAG, "Error checking playback state", e);
1666
+ }
1667
+ }
1668
+ }
1669
+
1670
+ // Build notification with proper action order: Rewind, Play/Pause, Fast Forward
1671
+ // Use MediaButtonReceiver to properly wire actions to MediaSession callbacks
1594
1672
  NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(getContext(), CHANNEL_ID)
1595
1673
  .setSmallIcon(android.R.drawable.ic_media_play)
1596
1674
  .setContentTitle(title)
1597
1675
  .setContentText(artist)
1676
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
1677
+ // Add actions BEFORE setStyle() for proper wiring
1678
+ .addAction(
1679
+ new NotificationCompat.Action.Builder(
1680
+ android.R.drawable.ic_media_rew,
1681
+ "Rewind 15 seconds",
1682
+ androidx.media.session.MediaButtonReceiver.buildMediaButtonPendingIntent(
1683
+ getContext(),
1684
+ PlaybackStateCompat.ACTION_REWIND
1685
+ )
1686
+ ).build()
1687
+ )
1688
+ .addAction(
1689
+ new NotificationCompat.Action.Builder(
1690
+ isPlaying ? android.R.drawable.ic_media_pause : android.R.drawable.ic_media_play,
1691
+ isPlaying ? "Pause" : "Play",
1692
+ androidx.media.session.MediaButtonReceiver.buildMediaButtonPendingIntent(
1693
+ getContext(),
1694
+ isPlaying ? PlaybackStateCompat.ACTION_PAUSE : PlaybackStateCompat.ACTION_PLAY
1695
+ )
1696
+ ).build()
1697
+ )
1698
+ .addAction(
1699
+ new NotificationCompat.Action.Builder(
1700
+ android.R.drawable.ic_media_ff,
1701
+ "Fast forward 15 seconds",
1702
+ androidx.media.session.MediaButtonReceiver.buildMediaButtonPendingIntent(
1703
+ getContext(),
1704
+ PlaybackStateCompat.ACTION_FAST_FORWARD
1705
+ )
1706
+ ).build()
1707
+ )
1598
1708
  .setStyle(
1599
1709
  new androidx.media.app.NotificationCompat.MediaStyle()
1600
1710
  .setMediaSession(mediaSession.getSessionToken())
1601
1711
  .setShowActionsInCompactView(0, 1, 2)
1602
1712
  )
1603
- .addAction(android.R.drawable.ic_media_previous, "Previous", null)
1604
- .addAction(android.R.drawable.ic_media_pause, "Pause", null)
1605
- .addAction(android.R.drawable.ic_media_next, "Next", null)
1606
1713
  .setPriority(NotificationCompat.PRIORITY_LOW)
1607
1714
  .setOnlyAlertOnce(true);
1608
1715
 
@@ -1624,7 +1731,14 @@ public class NativeAudio extends Plugin implements AudioManager.OnAudioFocusChan
1624
1731
 
1625
1732
  PlaybackStateCompat.Builder stateBuilder = new PlaybackStateCompat.Builder()
1626
1733
  .setState(state, 0, state == PlaybackStateCompat.STATE_PLAYING ? 1.0f : 0.0f)
1627
- .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_STOP);
1734
+ .setActions(
1735
+ PlaybackStateCompat.ACTION_PLAY |
1736
+ PlaybackStateCompat.ACTION_PAUSE |
1737
+ PlaybackStateCompat.ACTION_STOP |
1738
+ PlaybackStateCompat.ACTION_REWIND |
1739
+ PlaybackStateCompat.ACTION_FAST_FORWARD |
1740
+ PlaybackStateCompat.ACTION_SEEK_TO
1741
+ );
1628
1742
  mediaSession.setPlaybackState(stateBuilder.build());
1629
1743
  }
1630
1744
 
@@ -318,6 +318,10 @@ public class AudioAsset: NSObject, AVAudioPlayerDelegate {
318
318
  return result
319
319
  }
320
320
 
321
+ internal func shouldStopCurrentTimeUpdatesWhenNotPlaying() -> Bool {
322
+ true
323
+ }
324
+
321
325
  public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
322
326
  owner?.executeOnAudioQueue { [weak self] in
323
327
  guard let self else { return }
@@ -345,7 +349,7 @@ public class AudioAsset: NSObject, AVAudioPlayerDelegate {
345
349
  }
346
350
  if self.isPlaying() {
347
351
  owner.notifyCurrentTime(self)
348
- } else {
352
+ } else if self.shouldStopCurrentTimeUpdatesWhenNotPlaying() {
349
353
  self.stopCurrentTimeUpdates()
350
354
  }
351
355
  }
@@ -12,7 +12,7 @@ enum MyError: Error {
12
12
  @objc(NativeAudio)
13
13
  // swiftlint:disable:next type_body_length
14
14
  public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
15
- private let pluginVersion: String = "8.3.7"
15
+ private let pluginVersion: String = "8.3.8"
16
16
  public let identifier = "NativeAudio"
17
17
  public let jsName = "NativeAudio"
18
18
  public let pluginMethods: [CAPPluginMethod] = [
@@ -265,6 +265,21 @@ public class RemoteAudioAsset: AudioAsset {
265
265
  return result
266
266
  }
267
267
 
268
+ override func shouldStopCurrentTimeUpdatesWhenNotPlaying() -> Bool {
269
+ var shouldStop = true
270
+ owner?.executeOnAudioQueue { [weak self] in
271
+ guard let self else { return }
272
+ guard !players.isEmpty && playIndex < players.count else {
273
+ shouldStop = true
274
+ return
275
+ }
276
+
277
+ let status = players[playIndex].timeControlStatus
278
+ shouldStop = status != .waitingToPlayAtSpecifiedRate
279
+ }
280
+ return shouldStop
281
+ }
282
+
268
283
  override func getCurrentTime() -> TimeInterval {
269
284
  var result: TimeInterval = 0
270
285
  owner?.executeOnAudioQueue { [weak self] in
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/native-audio",
3
- "version": "8.3.7",
3
+ "version": "8.3.8",
4
4
  "description": "A native plugin for native audio engine",
5
5
  "license": "MPL-2.0",
6
6
  "main": "dist/plugin.cjs.js",