@neoskola/auto-play 0.2.18 → 0.3.1

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 (27) hide show
  1. package/ios/hybrid/HybridNowPlayingTemplate.swift +90 -0
  2. package/ios/templates/NowPlayingTemplate.swift +251 -11
  3. package/lib/specs/NowPlayingTemplate.nitro.d.ts +6 -0
  4. package/lib/templates/NowPlayingTemplate.d.ts +8 -0
  5. package/lib/templates/NowPlayingTemplate.js +18 -0
  6. package/nitrogen/generated/android/ReactNativeAutoPlayOnLoad.cpp +2 -0
  7. package/nitrogen/generated/android/c++/JFunc_void_double_double.hpp +75 -0
  8. package/nitrogen/generated/android/c++/JHybridNowPlayingTemplateSpec.cpp +92 -0
  9. package/nitrogen/generated/android/c++/JHybridNowPlayingTemplateSpec.hpp +6 -0
  10. package/nitrogen/generated/android/c++/JNowPlayingTemplateConfig.hpp +27 -2
  11. package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/Func_void_double_double.kt +80 -0
  12. package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/HybridNowPlayingTemplateSpec.kt +24 -0
  13. package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NowPlayingTemplateConfig.kt +11 -5
  14. package/nitrogen/generated/ios/ReactNativeAutoPlay-Swift-Cxx-Bridge.cpp +8 -0
  15. package/nitrogen/generated/ios/ReactNativeAutoPlay-Swift-Cxx-Bridge.hpp +58 -0
  16. package/nitrogen/generated/ios/c++/HybridNowPlayingTemplateSpecSwift.hpp +48 -0
  17. package/nitrogen/generated/ios/swift/Func_void_bool.swift +5 -5
  18. package/nitrogen/generated/ios/swift/Func_void_double_double.swift +47 -0
  19. package/nitrogen/generated/ios/swift/HybridNowPlayingTemplateSpec.swift +6 -0
  20. package/nitrogen/generated/ios/swift/HybridNowPlayingTemplateSpec_cxx.swift +114 -0
  21. package/nitrogen/generated/ios/swift/NowPlayingTemplateConfig.swift +83 -1
  22. package/nitrogen/generated/shared/c++/HybridNowPlayingTemplateSpec.cpp +6 -0
  23. package/nitrogen/generated/shared/c++/HybridNowPlayingTemplateSpec.hpp +6 -0
  24. package/nitrogen/generated/shared/c++/NowPlayingTemplateConfig.hpp +10 -2
  25. package/package.json +1 -1
  26. package/src/specs/NowPlayingTemplate.nitro.ts +8 -0
  27. package/src/templates/NowPlayingTemplate.ts +26 -0
@@ -58,4 +58,94 @@ class HybridNowPlayingTemplate: HybridNowPlayingTemplateSpec {
58
58
  await template.updateElapsedTime(elapsedTime: elapsedTime, duration: duration)
59
59
  }
60
60
  }
61
+
62
+ // MARK: - Native Audio Playback
63
+
64
+ func playAudio(
65
+ templateId: String,
66
+ url: String,
67
+ startFrom: Double
68
+ ) throws -> Promise<Bool> {
69
+ return Promise.async {
70
+ guard
71
+ let template = TemplateStore.getTemplate(templateId: templateId)
72
+ as? NowPlayingTemplate
73
+ else {
74
+ throw AutoPlayError.templateNotFound(templateId)
75
+ }
76
+
77
+ return await template.playAudio(url: url, startFrom: startFrom)
78
+ }
79
+ }
80
+
81
+ func pauseAudio(templateId: String) throws -> Promise<Void> {
82
+ return Promise.async {
83
+ guard
84
+ let template = TemplateStore.getTemplate(templateId: templateId)
85
+ as? NowPlayingTemplate
86
+ else {
87
+ throw AutoPlayError.templateNotFound(templateId)
88
+ }
89
+
90
+ await template.pauseAudio()
91
+ }
92
+ }
93
+
94
+ func resumeAudio(templateId: String) throws -> Promise<Void> {
95
+ return Promise.async {
96
+ guard
97
+ let template = TemplateStore.getTemplate(templateId: templateId)
98
+ as? NowPlayingTemplate
99
+ else {
100
+ throw AutoPlayError.templateNotFound(templateId)
101
+ }
102
+
103
+ await template.resumeAudio()
104
+ }
105
+ }
106
+
107
+ func seekForward(
108
+ templateId: String,
109
+ seconds: Double
110
+ ) throws -> Promise<Void> {
111
+ return Promise.async {
112
+ guard
113
+ let template = TemplateStore.getTemplate(templateId: templateId)
114
+ as? NowPlayingTemplate
115
+ else {
116
+ throw AutoPlayError.templateNotFound(templateId)
117
+ }
118
+
119
+ await template.seekForward(seconds: seconds)
120
+ }
121
+ }
122
+
123
+ func seekBackward(
124
+ templateId: String,
125
+ seconds: Double
126
+ ) throws -> Promise<Void> {
127
+ return Promise.async {
128
+ guard
129
+ let template = TemplateStore.getTemplate(templateId: templateId)
130
+ as? NowPlayingTemplate
131
+ else {
132
+ throw AutoPlayError.templateNotFound(templateId)
133
+ }
134
+
135
+ await template.seekBackward(seconds: seconds)
136
+ }
137
+ }
138
+
139
+ func stopAudio(templateId: String) throws -> Promise<Void> {
140
+ return Promise.async {
141
+ guard
142
+ let template = TemplateStore.getTemplate(templateId: templateId)
143
+ as? NowPlayingTemplate
144
+ else {
145
+ throw AutoPlayError.templateNotFound(templateId)
146
+ }
147
+
148
+ await template.stopAudio()
149
+ }
150
+ }
61
151
  }
@@ -1,5 +1,6 @@
1
1
  import CarPlay
2
2
  import MediaPlayer
3
+ import AVFoundation
3
4
 
4
5
  class NowPlayingTemplate: AutoPlayTemplate {
5
6
  var template: CPNowPlayingTemplate
@@ -9,6 +10,16 @@ class NowPlayingTemplate: AutoPlayTemplate {
9
10
  private var currentElapsedTime: Double = 0
10
11
  private var currentDuration: Double = 0
11
12
 
13
+ // Native audio player
14
+ private var player: AVPlayer?
15
+ private var playerItem: AVPlayerItem?
16
+ private var timeObserver: Any?
17
+ private var progressReportTimer: Timer?
18
+ private var lastReportedSecond: Int = 0
19
+ private var didFinishObserver: NSObjectProtocol?
20
+ private var statusObservation: NSKeyValueObservation?
21
+ private var completionFired = false
22
+
12
23
  var autoDismissMs: Double? {
13
24
  return config.autoDismissMs
14
25
  }
@@ -42,12 +53,213 @@ class NowPlayingTemplate: AutoPlayTemplate {
42
53
  }
43
54
  }
44
55
 
56
+ // MARK: - Native Audio Playback
57
+
58
+ @MainActor
59
+ func playAudio(url: String, startFrom: Double) -> Bool {
60
+ // Clean up any existing player
61
+ cleanupPlayer()
62
+ completionFired = false
63
+ lastReportedSecond = Int(startFrom)
64
+
65
+ guard let audioURL = URL(string: url) else {
66
+ print("[NowPlayingTemplate] Invalid audio URL: \(url)")
67
+ return false
68
+ }
69
+
70
+ // Ensure AVAudioSession is active
71
+ NowPlayingSessionManager.shared.ensureSessionActive()
72
+
73
+ let asset = AVURLAsset(url: audioURL)
74
+ playerItem = AVPlayerItem(asset: asset)
75
+
76
+ // KVO: detect duration as soon as asset loads (critical for progress bar)
77
+ statusObservation = playerItem?.observe(\.status, options: [.new]) { [weak self] item, _ in
78
+ guard item.status == .readyToPlay else {
79
+ if item.status == .failed {
80
+ print("[NowPlayingTemplate] AVPlayerItem failed: \(item.error?.localizedDescription ?? "unknown")")
81
+ }
82
+ return
83
+ }
84
+ DispatchQueue.main.async { [weak self] in
85
+ guard let self = self else { return }
86
+ let duration = CMTimeGetSeconds(item.duration)
87
+ if !duration.isNaN && !duration.isInfinite && duration > 0 {
88
+ self.currentDuration = duration
89
+ self.updateNowPlayingInfo()
90
+ print("[NowPlayingTemplate] Duration resolved via KVO: \(duration)s")
91
+ }
92
+ }
93
+ }
94
+
95
+ player = AVPlayer(playerItem: playerItem)
96
+
97
+ // Seek to start position
98
+ if startFrom > 0 {
99
+ let time = CMTime(seconds: startFrom, preferredTimescale: 600)
100
+ player?.seek(to: time)
101
+ }
102
+
103
+ // Periodic time observer (every 1 second) for MPNowPlayingInfoCenter updates
104
+ let interval = CMTime(seconds: 1.0, preferredTimescale: 600)
105
+ timeObserver = player?.addPeriodicTimeObserver(
106
+ forInterval: interval,
107
+ queue: .main
108
+ ) { [weak self] time in
109
+ self?.handleTimeUpdate(time: time)
110
+ }
111
+
112
+ // Playback finished notification
113
+ didFinishObserver = NotificationCenter.default.addObserver(
114
+ forName: .AVPlayerItemDidPlayToEndTime,
115
+ object: playerItem,
116
+ queue: .main
117
+ ) { [weak self] _ in
118
+ self?.handlePlaybackFinished()
119
+ }
120
+
121
+ // Progress report timer (every 30 seconds) — calls JS callback for backend reporting
122
+ progressReportTimer = Timer.scheduledTimer(
123
+ withTimeInterval: 30.0,
124
+ repeats: true
125
+ ) { [weak self] _ in
126
+ self?.reportProgress()
127
+ }
128
+
129
+ // Play
130
+ player?.play()
131
+
132
+ // Update NowPlaying UI
133
+ config.isPlaying = true
134
+ updateNowPlayingInfo()
135
+ MPNowPlayingInfoCenter.default().playbackState = .playing
136
+
137
+ print("[NowPlayingTemplate] Native audio playback started: \(url)")
138
+ return true
139
+ }
140
+
141
+ private func handleTimeUpdate(time: CMTime) {
142
+ let currentTime = CMTimeGetSeconds(time)
143
+ let duration = CMTimeGetSeconds(playerItem?.duration ?? .zero)
144
+
145
+ guard !currentTime.isNaN else { return }
146
+
147
+ currentElapsedTime = currentTime
148
+ if !duration.isNaN && !duration.isInfinite && duration > 0 {
149
+ currentDuration = duration
150
+ }
151
+
152
+ // Use existing info or create fresh if nil (race condition safety)
153
+ var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [String: Any]()
154
+
155
+ // Ensure title/artist are present if this is a fresh dictionary
156
+ if nowPlayingInfo[MPMediaItemPropertyTitle] == nil {
157
+ let titleText = Parser.parseText(text: config.title) ?? ""
158
+ nowPlayingInfo[MPMediaItemPropertyTitle] = titleText
159
+ if let subtitle = config.subtitle {
160
+ nowPlayingInfo[MPMediaItemPropertyArtist] = Parser.parseText(text: subtitle)
161
+ }
162
+ }
163
+
164
+ if currentDuration > 0 {
165
+ nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = currentDuration
166
+ }
167
+ nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentElapsedTime
168
+ nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = config.isPlaying ? 1.0 : 0.0
169
+ MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
170
+
171
+ // 95% completion check
172
+ if !completionFired && currentDuration > 0 && currentTime / currentDuration >= 0.95 {
173
+ completionFired = true
174
+ config.onComplete?()
175
+ }
176
+ }
177
+
178
+ private func reportProgress() {
179
+ let currentSecond = Int(currentElapsedTime)
180
+ guard currentSecond != lastReportedSecond && currentSecond > 0 else { return }
181
+ lastReportedSecond = currentSecond
182
+ config.onProgressUpdate?(currentElapsedTime, currentDuration)
183
+ }
184
+
185
+ private func handlePlaybackFinished() {
186
+ config.isPlaying = false
187
+ MPNowPlayingInfoCenter.default().playbackState = .stopped
188
+ // Fire completion if not already fired
189
+ if !completionFired {
190
+ completionFired = true
191
+ config.onComplete?()
192
+ }
193
+ config.onPlaybackFinished?()
194
+ }
195
+
196
+ @MainActor
197
+ func pauseAudio() {
198
+ player?.pause()
199
+ config.isPlaying = false
200
+ updatePlaybackState(isPlaying: false)
201
+ reportProgress()
202
+ }
203
+
204
+ @MainActor
205
+ func resumeAudio() {
206
+ NowPlayingSessionManager.shared.ensureSessionActive()
207
+ player?.play()
208
+ config.isPlaying = true
209
+ updatePlaybackState(isPlaying: true)
210
+ }
211
+
212
+ @MainActor
213
+ func seekForward(seconds: Double) {
214
+ guard let player = player else { return }
215
+ let newTime = CMTimeGetSeconds(player.currentTime()) + seconds
216
+ let clampedTime = min(newTime, currentDuration)
217
+ player.seek(to: CMTime(seconds: clampedTime, preferredTimescale: 600))
218
+ }
219
+
220
+ @MainActor
221
+ func seekBackward(seconds: Double) {
222
+ guard let player = player else { return }
223
+ let newTime = CMTimeGetSeconds(player.currentTime()) - seconds
224
+ let clampedTime = max(newTime, 0)
225
+ player.seek(to: CMTime(seconds: clampedTime, preferredTimescale: 600))
226
+ }
227
+
228
+ @MainActor
229
+ func stopAudio() {
230
+ reportProgress()
231
+ cleanupPlayer()
232
+ config.isPlaying = false
233
+ MPNowPlayingInfoCenter.default().playbackState = .stopped
234
+ }
235
+
236
+ private func cleanupPlayer() {
237
+ statusObservation?.invalidate()
238
+ statusObservation = nil
239
+ if let timeObserver = timeObserver {
240
+ player?.removeTimeObserver(timeObserver)
241
+ self.timeObserver = nil
242
+ }
243
+ if let didFinishObserver = didFinishObserver {
244
+ NotificationCenter.default.removeObserver(didFinishObserver)
245
+ self.didFinishObserver = nil
246
+ }
247
+ progressReportTimer?.invalidate()
248
+ progressReportTimer = nil
249
+ player?.pause()
250
+ player = nil
251
+ playerItem = nil
252
+ }
253
+
254
+ // MARK: - CarPlay UI
255
+
45
256
  private func setupNowPlayingButtons() {
46
257
  var buttons: [CPNowPlayingButton] = []
47
258
 
48
259
  let skipBackButton = CPNowPlayingImageButton(
49
260
  image: UIImage(systemName: "gobackward.30")!
50
261
  ) { [weak self] _ in
262
+ self?.seekBackward(seconds: 30)
51
263
  self?.config.onSkipBackward?()
52
264
  }
53
265
  buttons.append(skipBackButton)
@@ -55,6 +267,7 @@ class NowPlayingTemplate: AutoPlayTemplate {
55
267
  let skipForwardButton = CPNowPlayingImageButton(
56
268
  image: UIImage(systemName: "goforward.30")!
57
269
  ) { [weak self] _ in
270
+ self?.seekForward(seconds: 30)
58
271
  self?.config.onSkipForward?()
59
272
  }
60
273
  buttons.append(skipForwardButton)
@@ -97,6 +310,7 @@ class NowPlayingTemplate: AutoPlayTemplate {
97
310
  commandCenter.playCommand.isEnabled = true
98
311
  commandCenter.playCommand.removeTarget(nil)
99
312
  commandCenter.playCommand.addTarget { [weak self] _ in
313
+ self?.resumeAudio()
100
314
  self?.config.onPlay?()
101
315
  return .success
102
316
  }
@@ -104,6 +318,7 @@ class NowPlayingTemplate: AutoPlayTemplate {
104
318
  commandCenter.pauseCommand.isEnabled = true
105
319
  commandCenter.pauseCommand.removeTarget(nil)
106
320
  commandCenter.pauseCommand.addTarget { [weak self] _ in
321
+ self?.pauseAudio()
107
322
  self?.config.onPause?()
108
323
  return .success
109
324
  }
@@ -112,6 +327,7 @@ class NowPlayingTemplate: AutoPlayTemplate {
112
327
  commandCenter.skipForwardCommand.preferredIntervals = [30]
113
328
  commandCenter.skipForwardCommand.removeTarget(nil)
114
329
  commandCenter.skipForwardCommand.addTarget { [weak self] _ in
330
+ self?.seekForward(seconds: 30)
115
331
  self?.config.onSkipForward?()
116
332
  return .success
117
333
  }
@@ -120,6 +336,7 @@ class NowPlayingTemplate: AutoPlayTemplate {
120
336
  commandCenter.skipBackwardCommand.preferredIntervals = [30]
121
337
  commandCenter.skipBackwardCommand.removeTarget(nil)
122
338
  commandCenter.skipBackwardCommand.addTarget { [weak self] _ in
339
+ self?.seekBackward(seconds: 30)
123
340
  self?.config.onSkipBackward?()
124
341
  return .success
125
342
  }
@@ -131,6 +348,8 @@ class NowPlayingTemplate: AutoPlayTemplate {
131
348
  let positionEvent = event as? MPChangePlaybackPositionCommandEvent else {
132
349
  return .commandFailed
133
350
  }
351
+ let time = CMTime(seconds: positionEvent.positionTime, preferredTimescale: 600)
352
+ self.player?.seek(to: time)
134
353
  self.currentElapsedTime = positionEvent.positionTime
135
354
  self.updateNowPlayingInfo()
136
355
  return .success
@@ -153,6 +372,9 @@ class NowPlayingTemplate: AutoPlayTemplate {
153
372
  }
154
373
  }
155
374
 
375
+ // MARK: - AutoPlayTemplate Protocol
376
+
377
+ @MainActor
156
378
  func invalidate() {
157
379
  setupNowPlayingButtons()
158
380
  updateNowPlayingInfo()
@@ -180,6 +402,8 @@ class NowPlayingTemplate: AutoPlayTemplate {
180
402
 
181
403
  func onPopped() {
182
404
  config.onPopped?()
405
+ cleanupPlayer()
406
+
183
407
  let commandCenter = MPRemoteCommandCenter.shared()
184
408
  commandCenter.playCommand.removeTarget(nil)
185
409
  commandCenter.pauseCommand.removeTarget(nil)
@@ -207,13 +431,21 @@ class NowPlayingTemplate: AutoPlayTemplate {
207
431
  isSetupComplete = true
208
432
  }
209
433
 
210
- // Update playback rate in existing nowPlayingInfo
211
- if var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo {
212
- nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = isPlaying ? 1.0 : 0.0
213
- nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentElapsedTime
214
- MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
434
+ // Update playback rate use existing info or create fresh if nil
435
+ var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [String: Any]()
436
+
437
+ if nowPlayingInfo[MPMediaItemPropertyTitle] == nil {
438
+ let titleText = Parser.parseText(text: config.title) ?? ""
439
+ nowPlayingInfo[MPMediaItemPropertyTitle] = titleText
440
+ if let subtitle = config.subtitle {
441
+ nowPlayingInfo[MPMediaItemPropertyArtist] = Parser.parseText(text: subtitle)
442
+ }
215
443
  }
216
444
 
445
+ nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = isPlaying ? 1.0 : 0.0
446
+ nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentElapsedTime
447
+ MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
448
+
217
449
  MPNowPlayingInfoCenter.default().playbackState = isPlaying ? .playing : .paused
218
450
  }
219
451
 
@@ -229,12 +461,20 @@ class NowPlayingTemplate: AutoPlayTemplate {
229
461
  self.currentElapsedTime = elapsedTime
230
462
  self.currentDuration = duration
231
463
 
232
- // Update only time-related fields without rebuilding everything
233
- if var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo {
234
- nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = duration
235
- nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = elapsedTime
236
- nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = config.isPlaying ? 1.0 : 0.0
237
- MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
464
+ // Update time-related fields use existing info or create fresh if nil
465
+ var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [String: Any]()
466
+
467
+ if nowPlayingInfo[MPMediaItemPropertyTitle] == nil {
468
+ let titleText = Parser.parseText(text: config.title) ?? ""
469
+ nowPlayingInfo[MPMediaItemPropertyTitle] = titleText
470
+ if let subtitle = config.subtitle {
471
+ nowPlayingInfo[MPMediaItemPropertyArtist] = Parser.parseText(text: subtitle)
472
+ }
238
473
  }
474
+
475
+ nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = duration
476
+ nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = elapsedTime
477
+ nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = config.isPlaying ? 1.0 : 0.0
478
+ MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
239
479
  }
240
480
  }
@@ -11,5 +11,11 @@ export interface NowPlayingTemplate extends HybridObject<{
11
11
  updateNowPlayingTemplatePlaybackState(templateId: string, isPlaying: boolean): Promise<void>;
12
12
  updateNowPlayingTemplateInfo(templateId: string, title: string, subtitle: string): Promise<void>;
13
13
  updateNowPlayingTemplateElapsedTime(templateId: string, elapsedTime: number, duration: number): Promise<void>;
14
+ playAudio(templateId: string, url: string, startFrom: number): Promise<boolean>;
15
+ pauseAudio(templateId: string): Promise<void>;
16
+ resumeAudio(templateId: string): Promise<void>;
17
+ seekForward(templateId: string, seconds: number): Promise<void>;
18
+ seekBackward(templateId: string, seconds: number): Promise<void>;
19
+ stopAudio(templateId: string): Promise<void>;
14
20
  }
15
21
  export {};
@@ -14,6 +14,8 @@ export interface NitroNowPlayingTemplateConfig extends TemplateConfig {
14
14
  onSkipForward?: () => void;
15
15
  onSkipBackward?: () => void;
16
16
  onComplete?: () => void;
17
+ onProgressUpdate?: (currentTime: number, duration: number) => void;
18
+ onPlaybackFinished?: () => void;
17
19
  }
18
20
  export type NowPlayingTemplateConfig = Omit<NitroNowPlayingTemplateConfig, 'image'> & {
19
21
  image?: AutoImage;
@@ -23,4 +25,10 @@ export declare class NowPlayingTemplate extends Template<NowPlayingTemplateConfi
23
25
  updatePlaybackState(isPlaying: boolean): Promise<void>;
24
26
  updateNowPlayingInfo(title: string, subtitle: string): Promise<void>;
25
27
  updateElapsedTime(elapsedTime: number, duration: number): Promise<void>;
28
+ playAudio(url: string, startFrom?: number): Promise<boolean>;
29
+ pauseAudio(): Promise<void>;
30
+ resumeAudio(): Promise<void>;
31
+ seekForward(seconds?: number): Promise<void>;
32
+ seekBackward(seconds?: number): Promise<void>;
33
+ stopAudio(): Promise<void>;
26
34
  }
@@ -22,4 +22,22 @@ export class NowPlayingTemplate extends Template {
22
22
  updateElapsedTime(elapsedTime, duration) {
23
23
  return HybridNowPlayingTemplate.updateNowPlayingTemplateElapsedTime(this.id, elapsedTime, duration);
24
24
  }
25
+ playAudio(url, startFrom = 0) {
26
+ return HybridNowPlayingTemplate.playAudio(this.id, url, startFrom);
27
+ }
28
+ pauseAudio() {
29
+ return HybridNowPlayingTemplate.pauseAudio(this.id);
30
+ }
31
+ resumeAudio() {
32
+ return HybridNowPlayingTemplate.resumeAudio(this.id);
33
+ }
34
+ seekForward(seconds = 30) {
35
+ return HybridNowPlayingTemplate.seekForward(this.id, seconds);
36
+ }
37
+ seekBackward(seconds = 30) {
38
+ return HybridNowPlayingTemplate.seekBackward(this.id, seconds);
39
+ }
40
+ stopAudio() {
41
+ return HybridNowPlayingTemplate.stopAudio(this.id);
42
+ }
25
43
  }
@@ -45,6 +45,7 @@
45
45
  #include "JHybridMessageTemplateSpec.hpp"
46
46
  #include "JHybridNeoSkolaTemplateSpec.hpp"
47
47
  #include "JHybridNowPlayingTemplateSpec.hpp"
48
+ #include "JFunc_void_double_double.hpp"
48
49
  #include "JHybridSearchTemplateSpec.hpp"
49
50
  #include "JHybridSectionListTemplateSpec.hpp"
50
51
  #include "JHybridSubscriptionGateTemplateSpec.hpp"
@@ -89,6 +90,7 @@ int initialize(JavaVM* vm) {
89
90
  margelo::nitro::swe::iternio::reactnativeautoplay::JHybridMessageTemplateSpec::registerNatives();
90
91
  margelo::nitro::swe::iternio::reactnativeautoplay::JHybridNeoSkolaTemplateSpec::registerNatives();
91
92
  margelo::nitro::swe::iternio::reactnativeautoplay::JHybridNowPlayingTemplateSpec::registerNatives();
93
+ margelo::nitro::swe::iternio::reactnativeautoplay::JFunc_void_double_double_cxx::registerNatives();
92
94
  margelo::nitro::swe::iternio::reactnativeautoplay::JHybridSearchTemplateSpec::registerNatives();
93
95
  margelo::nitro::swe::iternio::reactnativeautoplay::JHybridSectionListTemplateSpec::registerNatives();
94
96
  margelo::nitro::swe::iternio::reactnativeautoplay::JHybridSubscriptionGateTemplateSpec::registerNatives();
@@ -0,0 +1,75 @@
1
+ ///
2
+ /// JFunc_void_double_double.hpp
3
+ /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
+ /// https://github.com/mrousavy/nitro
5
+ /// Copyright © 2026 Marc Rousavy @ Margelo
6
+ ///
7
+
8
+ #pragma once
9
+
10
+ #include <fbjni/fbjni.h>
11
+ #include <functional>
12
+
13
+ #include <functional>
14
+ #include <NitroModules/JNICallable.hpp>
15
+
16
+ namespace margelo::nitro::swe::iternio::reactnativeautoplay {
17
+
18
+ using namespace facebook;
19
+
20
+ /**
21
+ * Represents the Java/Kotlin callback `(currentTime: Double, duration: Double) -> Unit`.
22
+ * This can be passed around between C++ and Java/Kotlin.
23
+ */
24
+ struct JFunc_void_double_double: public jni::JavaClass<JFunc_void_double_double> {
25
+ public:
26
+ static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/swe/iternio/reactnativeautoplay/Func_void_double_double;";
27
+
28
+ public:
29
+ /**
30
+ * Invokes the function this `JFunc_void_double_double` instance holds through JNI.
31
+ */
32
+ void invoke(double currentTime, double duration) const {
33
+ static const auto method = javaClassStatic()->getMethod<void(double /* currentTime */, double /* duration */)>("invoke");
34
+ method(self(), currentTime, duration);
35
+ }
36
+ };
37
+
38
+ /**
39
+ * An implementation of Func_void_double_double that is backed by a C++ implementation (using `std::function<...>`)
40
+ */
41
+ class JFunc_void_double_double_cxx final: public jni::HybridClass<JFunc_void_double_double_cxx, JFunc_void_double_double> {
42
+ public:
43
+ static jni::local_ref<JFunc_void_double_double::javaobject> fromCpp(const std::function<void(double /* currentTime */, double /* duration */)>& func) {
44
+ return JFunc_void_double_double_cxx::newObjectCxxArgs(func);
45
+ }
46
+
47
+ public:
48
+ /**
49
+ * Invokes the C++ `std::function<...>` this `JFunc_void_double_double_cxx` instance holds.
50
+ */
51
+ void invoke_cxx(double currentTime, double duration) {
52
+ _func(currentTime, duration);
53
+ }
54
+
55
+ public:
56
+ [[nodiscard]]
57
+ inline const std::function<void(double /* currentTime */, double /* duration */)>& getFunction() const {
58
+ return _func;
59
+ }
60
+
61
+ public:
62
+ static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/swe/iternio/reactnativeautoplay/Func_void_double_double_cxx;";
63
+ static void registerNatives() {
64
+ registerHybrid({makeNativeMethod("invoke_cxx", JFunc_void_double_double_cxx::invoke_cxx)});
65
+ }
66
+
67
+ private:
68
+ explicit JFunc_void_double_double_cxx(const std::function<void(double /* currentTime */, double /* duration */)>& func): _func(func) { }
69
+
70
+ private:
71
+ friend HybridBase;
72
+ std::function<void(double /* currentTime */, double /* duration */)> _func;
73
+ };
74
+
75
+ } // namespace margelo::nitro::swe::iternio::reactnativeautoplay