one 2.11.4 → 2.12.0
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/java/com/margelo/nitro/one/HybridOneAudio.kt +309 -124
- package/dist/cjs/cli/build.cjs +31 -33
- package/dist/cjs/cli/build.native.cjs +31 -33
- package/dist/cjs/cli/build.native.js +31 -33
- package/dist/cjs/cli/build.native.js.map +1 -1
- package/dist/cjs/cli/buildPage.cjs +11 -7
- package/dist/cjs/cli/buildPage.native.cjs +11 -7
- package/dist/cjs/cli/buildPage.native.js +11 -7
- package/dist/cjs/cli/buildPage.native.js.map +1 -1
- package/dist/cjs/cli/buildPageWorker.cjs +1 -1
- package/dist/cjs/cli/buildPageWorker.native.cjs +1 -1
- package/dist/cjs/cli/buildPageWorker.native.js +1 -1
- package/dist/cjs/cli/buildPageWorker.native.js.map +1 -1
- package/dist/cjs/headless-server.cjs +2 -5
- package/dist/cjs/headless-server.native.cjs +2 -5
- package/dist/cjs/headless-server.native.js +2 -5
- package/dist/cjs/headless-server.native.js.map +1 -1
- package/dist/cjs/platform/audio/index.cjs +83 -76
- package/dist/cjs/platform/audio/index.native.cjs +2 -2
- package/dist/cjs/platform/audio/index.native.js +2 -2
- package/dist/cjs/platform/audio/index.native.js.map +1 -1
- package/dist/cjs/platform/audio/nowPlaying.cjs +126 -0
- package/dist/cjs/platform/audio/nowPlaying.native.cjs +160 -0
- package/dist/cjs/platform/audio/nowPlaying.native.js +162 -0
- package/dist/cjs/platform/audio/nowPlaying.native.js.map +1 -0
- package/dist/cjs/platform/audio/player.cjs +15 -0
- package/dist/cjs/platform/audio/player.native.cjs +14 -0
- package/dist/cjs/platform/audio/player.native.js +14 -0
- package/dist/cjs/platform/audio/player.native.js.map +1 -1
- package/dist/cjs/platform/audio/unavailable.cjs +1 -1
- package/dist/cjs/platform/audio/unavailable.native.cjs +1 -1
- package/dist/cjs/platform/audio/unavailable.native.js +1 -1
- package/dist/cjs/platform/audio/unavailable.native.js.map +1 -1
- package/dist/cjs/platform/location/index.native.cjs +9 -2
- package/dist/cjs/platform/location/index.native.js +9 -2
- package/dist/cjs/platform/location/index.native.js.map +1 -1
- package/dist/cjs/serve-worker.cjs +0 -3
- package/dist/cjs/serve-worker.native.cjs +0 -3
- package/dist/cjs/serve-worker.native.js +0 -3
- package/dist/cjs/serve-worker.native.js.map +1 -1
- package/dist/cjs/server/oneServe.cjs +0 -6
- package/dist/cjs/server/oneServe.native.cjs +0 -6
- package/dist/cjs/server/oneServe.native.js +0 -6
- package/dist/cjs/server/oneServe.native.js.map +1 -1
- package/dist/cjs/server/workerHandler.cjs +12 -10
- package/dist/cjs/server/workerHandler.native.cjs +14 -10
- package/dist/cjs/server/workerHandler.native.js +14 -10
- package/dist/cjs/server/workerHandler.native.js.map +1 -1
- package/dist/cjs/vite/plugins/workerdDevPlugin.cjs +33 -37
- package/dist/cjs/vite/plugins/workerdDevPlugin.native.cjs +33 -37
- package/dist/cjs/vite/plugins/workerdDevPlugin.native.js +33 -37
- package/dist/cjs/vite/plugins/workerdDevPlugin.native.js.map +1 -1
- package/dist/esm/cli/build.mjs +31 -33
- package/dist/esm/cli/build.mjs.map +1 -1
- package/dist/esm/cli/build.native.js +31 -33
- package/dist/esm/cli/build.native.js.map +1 -1
- package/dist/esm/cli/buildPage.mjs +11 -7
- package/dist/esm/cli/buildPage.mjs.map +1 -1
- package/dist/esm/cli/buildPage.native.js +11 -7
- package/dist/esm/cli/buildPage.native.js.map +1 -1
- package/dist/esm/cli/buildPageWorker.mjs +1 -1
- package/dist/esm/cli/buildPageWorker.mjs.map +1 -1
- package/dist/esm/cli/buildPageWorker.native.js +1 -1
- package/dist/esm/cli/buildPageWorker.native.js.map +1 -1
- package/dist/esm/headless-server.mjs +2 -3
- package/dist/esm/headless-server.mjs.map +1 -1
- package/dist/esm/headless-server.native.js +2 -3
- package/dist/esm/headless-server.native.js.map +1 -1
- package/dist/esm/platform/audio/index.mjs +83 -76
- package/dist/esm/platform/audio/index.mjs.map +1 -1
- package/dist/esm/platform/audio/index.native.js +2 -2
- package/dist/esm/platform/audio/index.native.js.map +1 -1
- package/dist/esm/platform/audio/nowPlaying.mjs +102 -0
- package/dist/esm/platform/audio/nowPlaying.mjs.map +1 -0
- package/dist/esm/platform/audio/nowPlaying.native.js +134 -0
- package/dist/esm/platform/audio/nowPlaying.native.js.map +1 -0
- package/dist/esm/platform/audio/player.mjs +15 -0
- package/dist/esm/platform/audio/player.mjs.map +1 -1
- package/dist/esm/platform/audio/player.native.js +14 -0
- package/dist/esm/platform/audio/player.native.js.map +1 -1
- package/dist/esm/platform/audio/unavailable.mjs +1 -1
- package/dist/esm/platform/audio/unavailable.mjs.map +1 -1
- package/dist/esm/platform/audio/unavailable.native.js +1 -1
- package/dist/esm/platform/audio/unavailable.native.js.map +1 -1
- package/dist/esm/platform/location/index.native.js +9 -2
- package/dist/esm/platform/location/index.native.js.map +1 -1
- package/dist/esm/serve-worker.mjs +1 -2
- package/dist/esm/serve-worker.mjs.map +1 -1
- package/dist/esm/serve-worker.native.js +1 -2
- package/dist/esm/serve-worker.native.js.map +1 -1
- package/dist/esm/server/oneServe.mjs +0 -6
- package/dist/esm/server/oneServe.mjs.map +1 -1
- package/dist/esm/server/oneServe.native.js +0 -6
- package/dist/esm/server/oneServe.native.js.map +1 -1
- package/dist/esm/server/workerHandler.mjs +12 -10
- package/dist/esm/server/workerHandler.mjs.map +1 -1
- package/dist/esm/server/workerHandler.native.js +14 -10
- package/dist/esm/server/workerHandler.native.js.map +1 -1
- package/dist/esm/vite/plugins/workerdDevPlugin.mjs +33 -37
- package/dist/esm/vite/plugins/workerdDevPlugin.mjs.map +1 -1
- package/dist/esm/vite/plugins/workerdDevPlugin.native.js +33 -37
- package/dist/esm/vite/plugins/workerdDevPlugin.native.js.map +1 -1
- package/ios/Nitro/HybridOneAudio.swift +309 -96
- package/nitrogen/generated/android/c++/JAudioPlayOptions.hpp +61 -0
- package/nitrogen/generated/android/c++/JAudioRemoteCommandEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_void_AudioRemoteCommandEvent.hpp +1 -0
- package/nitrogen/generated/android/c++/JHybridOneAudioSpec.cpp +37 -3
- package/nitrogen/generated/android/c++/JHybridOneAudioSpec.hpp +3 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/one/AudioPlayOptions.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/one/AudioRemoteCommandEvent.kt +9 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/one/HybridOneAudioSpec.kt +40 -32
- package/nitrogen/generated/ios/One-Swift-Cxx-Umbrella.hpp +3 -0
- package/nitrogen/generated/ios/c++/HybridOneAudioSpecSwift.hpp +21 -2
- package/nitrogen/generated/ios/swift/AudioPlayOptions.swift +60 -0
- package/nitrogen/generated/ios/swift/AudioRemoteCommandEvent.swift +19 -1
- package/nitrogen/generated/ios/swift/HybridOneAudioSpec.swift +3 -1
- package/nitrogen/generated/ios/swift/HybridOneAudioSpec_cxx.swift +70 -32
- package/nitrogen/generated/shared/c++/AudioPlayOptions.hpp +87 -0
- package/nitrogen/generated/shared/c++/AudioRemoteCommandEvent.hpp +7 -2
- package/nitrogen/generated/shared/c++/HybridOneAudioSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridOneAudioSpec.hpp +6 -1
- package/package.json +16 -16
- package/src/cli/build.ts +35 -32
- package/src/cli/buildPage.test.ts +152 -0
- package/src/cli/buildPage.ts +14 -9
- package/src/cli/buildPageWorker.ts +2 -1
- package/src/cli/workerPool.ts +1 -0
- package/src/headless-server.ts +2 -2
- package/src/index.ts +1 -0
- package/src/platform/audio/index.native.ts +4 -2
- package/src/platform/audio/index.ts +102 -74
- package/src/platform/audio/nowPlaying.ts +122 -0
- package/src/platform/audio/player.native.ts +17 -1
- package/src/platform/audio/player.ts +23 -1
- package/src/platform/audio/types.ts +6 -1
- package/src/platform/audio/unavailable.ts +3 -1
- package/src/platform/index.native.ts +1 -0
- package/src/platform/index.ts +1 -0
- package/src/platform/location/index.native.ts +14 -3
- package/src/platform/specs/OneAudio.nitro.ts +11 -1
- package/src/serve-worker.ts +2 -3
- package/src/server/oneServe.ts +0 -6
- package/src/server/workerHandler.ts +34 -18
- package/src/vite/plugins/workerdDevPlugin.ts +33 -37
- package/src/vite/resolveResponse.ts +2 -2
- package/types/cli/build.d.ts.map +1 -1
- package/types/cli/buildPage.d.ts +1 -1
- package/types/cli/buildPage.d.ts.map +1 -1
- package/types/cli/workerPool.d.ts +1 -0
- package/types/cli/workerPool.d.ts.map +1 -1
- package/types/headless-server.d.ts +3 -3
- package/types/headless-server.d.ts.map +1 -1
- package/types/index.d.ts +1 -1
- package/types/index.d.ts.map +1 -1
- package/types/platform/audio/index.d.ts +2 -2
- package/types/platform/audio/index.d.ts.map +1 -1
- package/types/platform/audio/index.native.d.ts +3 -3
- package/types/platform/audio/index.native.d.ts.map +1 -1
- package/types/platform/audio/nowPlaying.d.ts +12 -0
- package/types/platform/audio/nowPlaying.d.ts.map +1 -0
- package/types/platform/audio/player.d.ts +3 -1
- package/types/platform/audio/player.d.ts.map +1 -1
- package/types/platform/audio/player.native.d.ts +3 -1
- package/types/platform/audio/player.native.d.ts.map +1 -1
- package/types/platform/audio/types.d.ts +3 -1
- package/types/platform/audio/types.d.ts.map +1 -1
- package/types/platform/audio/unavailable.d.ts +3 -3
- package/types/platform/audio/unavailable.d.ts.map +1 -1
- package/types/platform/index.d.ts +1 -1
- package/types/platform/index.d.ts.map +1 -1
- package/types/platform/index.native.d.ts +1 -1
- package/types/platform/index.native.d.ts.map +1 -1
- package/types/platform/location/index.native.d.ts.map +1 -1
- package/types/platform/specs/OneAudio.nitro.d.ts +8 -1
- package/types/platform/specs/OneAudio.nitro.d.ts.map +1 -1
- package/types/serve-worker.d.ts +3 -3
- package/types/serve-worker.d.ts.map +1 -1
- package/types/server/oneServe.d.ts.map +1 -1
- package/types/server/workerHandler.d.ts +1 -1
- package/types/server/workerHandler.d.ts.map +1 -1
- package/types/vite/plugins/workerdDevPlugin.d.ts.map +1 -1
- package/types/vite/resolveResponse.d.ts +1 -1
- package/types/vite/resolveResponse.d.ts.map +1 -1
- package/dist/cjs/server/staticHtmlFetcher.cjs +0 -33
- package/dist/cjs/server/staticHtmlFetcher.native.cjs +0 -35
- package/dist/cjs/server/staticHtmlFetcher.native.js +0 -37
- package/dist/cjs/server/staticHtmlFetcher.native.js.map +0 -1
- package/dist/esm/server/staticHtmlFetcher.mjs +0 -10
- package/dist/esm/server/staticHtmlFetcher.mjs.map +0 -1
- package/dist/esm/server/staticHtmlFetcher.native.js +0 -10
- package/dist/esm/server/staticHtmlFetcher.native.js.map +0 -1
- package/src/server/staticHtmlFetcher.ts +0 -12
- package/types/server/staticHtmlFetcher.d.ts +0 -3
- package/types/server/staticHtmlFetcher.d.ts.map +0 -1
|
@@ -18,6 +18,7 @@ final class HybridOneAudio: HybridOneAudioSpec {
|
|
|
18
18
|
case artwork
|
|
19
19
|
case unknownPlayer
|
|
20
20
|
case rate
|
|
21
|
+
case replaced
|
|
21
22
|
|
|
22
23
|
var errorDescription: String? {
|
|
23
24
|
switch self {
|
|
@@ -33,6 +34,7 @@ final class HybridOneAudio: HybridOneAudioSpec {
|
|
|
33
34
|
case .artwork: return "now playing artwork must be an existing image file"
|
|
34
35
|
case .unknownPlayer: return "the audio player id is not active"
|
|
35
36
|
case .rate: return "the playback rate must be within 0 to 2"
|
|
37
|
+
case .replaced: return "playback was replaced before it was ready"
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
}
|
|
@@ -51,6 +53,12 @@ final class HybridOneAudio: HybridOneAudioSpec {
|
|
|
51
53
|
private var nowPlayingInfo: AudioNowPlayingInfo?
|
|
52
54
|
private var nowPlayingArtwork: UIImage?
|
|
53
55
|
private var remoteTargets: [(MPRemoteCommand, Any)] = []
|
|
56
|
+
// one now playing session at a time, owned by the singleton or by one
|
|
57
|
+
// managed player. owners clear only their own claim, so an old owner's
|
|
58
|
+
// cleanup never clears a newer one.
|
|
59
|
+
private var nowPlayingOwner: NowPlayingOwner?
|
|
60
|
+
private var ownerObservations: [NSKeyValueObservation] = []
|
|
61
|
+
private var pendingLoad: PendingLoad?
|
|
54
62
|
private var remoteCommandListeners: [UUID: (AudioRemoteCommandEvent) -> Void] = [:]
|
|
55
63
|
|
|
56
64
|
private final class ManagedAudioPlayer {
|
|
@@ -73,6 +81,24 @@ final class HybridOneAudio: HybridOneAudioSpec {
|
|
|
73
81
|
|
|
74
82
|
private var managedPlayers: [String: ManagedAudioPlayer] = [:]
|
|
75
83
|
|
|
84
|
+
private enum NowPlayingOwner: Equatable {
|
|
85
|
+
case global
|
|
86
|
+
case managed(String)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// a singleton play with a position or paused flag: it resolves after the
|
|
90
|
+
// item is ready and the seek lands, before any sound.
|
|
91
|
+
private final class PendingLoad {
|
|
92
|
+
let promise: Promise<AudioPlaybackStatus>
|
|
93
|
+
let positionMs: Double
|
|
94
|
+
var seeking = false
|
|
95
|
+
|
|
96
|
+
init(promise: Promise<AudioPlaybackStatus>, positionMs: Double) {
|
|
97
|
+
self.promise = promise
|
|
98
|
+
self.positionMs = positionMs
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
76
102
|
override init() {
|
|
77
103
|
super.init()
|
|
78
104
|
interruptionObserver = NotificationCenter.default.addObserver(
|
|
@@ -121,38 +147,102 @@ final class HybridOneAudio: HybridOneAudioSpec {
|
|
|
121
147
|
return promise
|
|
122
148
|
}
|
|
123
149
|
|
|
124
|
-
func play(uri: String) throws -> Promise<AudioPlaybackStatus> {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
try session.setActive(true)
|
|
134
|
-
self.clearPlayer()
|
|
135
|
-
let item = AVPlayerItem(url: url)
|
|
136
|
-
let player = AVPlayer(playerItem: item)
|
|
137
|
-
self.player = player
|
|
138
|
-
self.playerStatusObservation = player.observe(\.timeControlStatus, options: [.new]) {
|
|
139
|
-
[weak self] _, _ in
|
|
140
|
-
DispatchQueue.main.async { self?.syncNowPlaying() }
|
|
141
|
-
}
|
|
142
|
-
self.itemStatusObservation = item.observe(\.status, options: [.new]) { [weak self] _, _ in
|
|
143
|
-
DispatchQueue.main.async { self?.syncNowPlaying() }
|
|
150
|
+
func play(uri: String, options: AudioPlayOptions) throws -> Promise<AudioPlaybackStatus> {
|
|
151
|
+
let promise = Promise<AudioPlaybackStatus>()
|
|
152
|
+
DispatchQueue.main.async {
|
|
153
|
+
do {
|
|
154
|
+
if let status = try self.startPlayback(uri: uri, options: options, promise: promise) {
|
|
155
|
+
promise.resolve(withResult: status)
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
promise.reject(withError: Self.failure("play", error))
|
|
144
159
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
160
|
+
}
|
|
161
|
+
return promise
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// returns the status to resolve now, or nil when a prepared load resolves
|
|
165
|
+
// the promise later.
|
|
166
|
+
private func startPlayback(
|
|
167
|
+
uri: String, options: AudioPlayOptions, promise: Promise<AudioPlaybackStatus>
|
|
168
|
+
) throws -> AudioPlaybackStatus? {
|
|
169
|
+
guard self.recorder == nil else { throw Problem.busy }
|
|
170
|
+
let url = try Self.audioURL(uri)
|
|
171
|
+
if url.isFileURL && !FileManager.default.fileExists(atPath: url.path) {
|
|
172
|
+
throw Problem.missingFile
|
|
173
|
+
}
|
|
174
|
+
let prepare = options.positionMs != nil || options.paused != nil
|
|
175
|
+
let positionMs = options.positionMs ?? 0
|
|
176
|
+
guard positionMs.isFinite && positionMs >= 0 else { throw Problem.position }
|
|
177
|
+
let session = AVAudioSession.sharedInstance()
|
|
178
|
+
try session.setCategory(.playback, mode: .default)
|
|
179
|
+
try session.setActive(true)
|
|
180
|
+
self.clearPlayer()
|
|
181
|
+
let item = AVPlayerItem(url: url)
|
|
182
|
+
let player = AVPlayer(playerItem: item)
|
|
183
|
+
self.player = player
|
|
184
|
+
self.playerStatusObservation = player.observe(\.timeControlStatus, options: [.new]) {
|
|
185
|
+
[weak self] _, _ in
|
|
186
|
+
DispatchQueue.main.async { self?.syncNowPlaying() }
|
|
187
|
+
}
|
|
188
|
+
self.itemStatusObservation = item.observe(\.status, options: [.new]) { [weak self] _, _ in
|
|
189
|
+
DispatchQueue.main.async {
|
|
190
|
+
self?.advanceLoad()
|
|
152
191
|
self?.syncNowPlaying()
|
|
153
192
|
}
|
|
154
|
-
|
|
155
|
-
|
|
193
|
+
}
|
|
194
|
+
self.playerURI = url.absoluteString
|
|
195
|
+
self.playbackPaused = options.paused ?? false
|
|
196
|
+
self.playbackEnded = false
|
|
197
|
+
self.endObserver = NotificationCenter.default.addObserver(
|
|
198
|
+
forName: AVPlayerItem.didPlayToEndTimeNotification, object: item, queue: .main
|
|
199
|
+
) { [weak self] _ in
|
|
200
|
+
self?.playbackEnded = true
|
|
201
|
+
self?.syncNowPlaying()
|
|
202
|
+
}
|
|
203
|
+
if prepare {
|
|
204
|
+
self.pendingLoad = PendingLoad(promise: promise, positionMs: positionMs)
|
|
205
|
+
self.advanceLoad()
|
|
206
|
+
return nil
|
|
207
|
+
}
|
|
208
|
+
player.play()
|
|
209
|
+
return self.playbackStatus()
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// moves a prepared load forward once its item settles: seek exactly to
|
|
213
|
+
// the position, then play unless paused. replacement or stop rejects the
|
|
214
|
+
// load in clearPlayer, so a late completion finds a different load and
|
|
215
|
+
// does nothing.
|
|
216
|
+
private func advanceLoad() {
|
|
217
|
+
guard let load = pendingLoad, !load.seeking, let player, let item = player.currentItem
|
|
218
|
+
else { return }
|
|
219
|
+
switch item.status {
|
|
220
|
+
case .failed:
|
|
221
|
+
pendingLoad = nil
|
|
222
|
+
load.promise.reject(withError: Self.failure("play", item.error ?? Problem.state))
|
|
223
|
+
case .readyToPlay:
|
|
224
|
+
load.seeking = true
|
|
225
|
+
var seconds = load.positionMs / 1000
|
|
226
|
+
let duration = item.duration.seconds
|
|
227
|
+
if duration.isFinite && duration >= 0 { seconds = min(seconds, duration) }
|
|
228
|
+
player.seek(
|
|
229
|
+
to: CMTime(seconds: seconds, preferredTimescale: 600),
|
|
230
|
+
toleranceBefore: .zero, toleranceAfter: .zero
|
|
231
|
+
) { [weak self] finished in
|
|
232
|
+
DispatchQueue.main.async {
|
|
233
|
+
guard let self, self.pendingLoad === load else { return }
|
|
234
|
+
self.pendingLoad = nil
|
|
235
|
+
guard finished, self.player === player else {
|
|
236
|
+
load.promise.reject(withError: Self.failure("play", Problem.state))
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
if !self.playbackPaused { player.play() }
|
|
240
|
+
self.syncNowPlaying()
|
|
241
|
+
load.promise.resolve(withResult: self.playbackStatus())
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
default:
|
|
245
|
+
return
|
|
156
246
|
}
|
|
157
247
|
}
|
|
158
248
|
|
|
@@ -178,7 +268,8 @@ final class HybridOneAudio: HybridOneAudioSpec {
|
|
|
178
268
|
self.playbackEnded = false
|
|
179
269
|
}
|
|
180
270
|
self.playbackPaused = false
|
|
181
|
-
|
|
271
|
+
// a prepared load plays itself once its seek lands.
|
|
272
|
+
if self.pendingLoad == nil { player.play() }
|
|
182
273
|
self.syncNowPlaying()
|
|
183
274
|
return self.playbackStatus()
|
|
184
275
|
}
|
|
@@ -191,7 +282,9 @@ final class HybridOneAudio: HybridOneAudioSpec {
|
|
|
191
282
|
promise.reject(withError: Self.failure("seek", Problem.position))
|
|
192
283
|
return
|
|
193
284
|
}
|
|
194
|
-
guard let player = self.player, player.currentItem?.status == .readyToPlay
|
|
285
|
+
guard let player = self.player, player.currentItem?.status == .readyToPlay,
|
|
286
|
+
self.pendingLoad == nil
|
|
287
|
+
else {
|
|
195
288
|
promise.reject(withError: Self.failure("seek", Problem.state))
|
|
196
289
|
return
|
|
197
290
|
}
|
|
@@ -311,43 +404,29 @@ final class HybridOneAudio: HybridOneAudioSpec {
|
|
|
311
404
|
func setNowPlayingInfo(info: AudioNowPlayingInfo) throws -> Promise<Void> {
|
|
312
405
|
perform("setNowPlayingInfo") {
|
|
313
406
|
guard let player = self.player else { throw Problem.state }
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
}
|
|
317
|
-
var artwork: UIImage?
|
|
318
|
-
if let artworkUri = info.artworkUri {
|
|
319
|
-
guard let url = URL(string: artworkUri), url.isFileURL,
|
|
320
|
-
FileManager.default.fileExists(atPath: url.path),
|
|
321
|
-
let image = UIImage(contentsOfFile: url.path)
|
|
322
|
-
else { throw Problem.artwork }
|
|
323
|
-
artwork = image
|
|
324
|
-
}
|
|
325
|
-
if let session = self.nowPlayingSession, session.players.first === player {
|
|
326
|
-
self.nowPlayingInfo = info
|
|
327
|
-
self.nowPlayingArtwork = artwork
|
|
328
|
-
self.syncNowPlaying()
|
|
329
|
-
return
|
|
330
|
-
}
|
|
331
|
-
self.clearNowPlaying()
|
|
332
|
-
let session = MPNowPlayingSession(players: [player])
|
|
333
|
-
session.automaticallyPublishesNowPlayingInfo = false
|
|
334
|
-
self.nowPlayingSession = session
|
|
335
|
-
self.nowPlayingInfo = info
|
|
336
|
-
self.nowPlayingArtwork = artwork
|
|
337
|
-
self.installRemoteCommands(session.remoteCommandCenter)
|
|
338
|
-
self.syncNowPlaying()
|
|
339
|
-
session.becomeActiveIfPossible { [weak self, weak session] active in
|
|
340
|
-
guard active, let session else { return }
|
|
341
|
-
DispatchQueue.main.async { [weak self] in
|
|
342
|
-
guard let self, self.nowPlayingSession === session else { return }
|
|
343
|
-
self.syncNowPlaying()
|
|
344
|
-
}
|
|
345
|
-
}
|
|
407
|
+
let artwork = try Self.nowPlayingArtwork(info)
|
|
408
|
+
self.claimNowPlaying(.global, player: player, info: info, artwork: artwork)
|
|
346
409
|
}
|
|
347
410
|
}
|
|
348
411
|
|
|
349
412
|
func clearNowPlayingInfo() throws -> Promise<Void> {
|
|
350
|
-
perform("clearNowPlayingInfo") {
|
|
413
|
+
perform("clearNowPlayingInfo") {
|
|
414
|
+
if self.nowPlayingOwner == .global { self.clearNowPlaying() }
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
func setPlayerNowPlayingInfo(playerId: String, info: AudioNowPlayingInfo) throws -> Promise<Void> {
|
|
419
|
+
perform("setPlayerNowPlayingInfo") {
|
|
420
|
+
let managed = try self.managedPlayer(playerId)
|
|
421
|
+
let artwork = try Self.nowPlayingArtwork(info)
|
|
422
|
+
self.claimNowPlaying(.managed(playerId), player: managed.player, info: info, artwork: artwork)
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
func clearPlayerNowPlayingInfo(playerId: String) throws -> Promise<Void> {
|
|
427
|
+
perform("clearPlayerNowPlayingInfo") {
|
|
428
|
+
if self.nowPlayingOwner == .managed(playerId) { self.clearNowPlaying() }
|
|
429
|
+
}
|
|
351
430
|
}
|
|
352
431
|
|
|
353
432
|
func addRemoteCommandListener(
|
|
@@ -381,16 +460,7 @@ final class HybridOneAudio: HybridOneAudioSpec {
|
|
|
381
460
|
|
|
382
461
|
func playerPlay(playerId: String) throws -> Void {
|
|
383
462
|
try mainSync("playerPlay") {
|
|
384
|
-
|
|
385
|
-
let session = AVAudioSession.sharedInstance()
|
|
386
|
-
try session.setCategory(.playback, mode: .default)
|
|
387
|
-
try session.setActive(true)
|
|
388
|
-
if managed.ended {
|
|
389
|
-
managed.player.seek(to: .zero)
|
|
390
|
-
managed.ended = false
|
|
391
|
-
}
|
|
392
|
-
managed.paused = false
|
|
393
|
-
managed.player.rate = Float(managed.rate)
|
|
463
|
+
try self.startManaged(try self.managedPlayer(playerId))
|
|
394
464
|
}
|
|
395
465
|
}
|
|
396
466
|
|
|
@@ -399,6 +469,7 @@ final class HybridOneAudio: HybridOneAudioSpec {
|
|
|
399
469
|
let managed = try self.managedPlayer(playerId)
|
|
400
470
|
managed.player.pause()
|
|
401
471
|
managed.paused = true
|
|
472
|
+
self.syncOwner(playerId)
|
|
402
473
|
}
|
|
403
474
|
}
|
|
404
475
|
|
|
@@ -426,6 +497,7 @@ final class HybridOneAudio: HybridOneAudioSpec {
|
|
|
426
497
|
return
|
|
427
498
|
}
|
|
428
499
|
managed.ended = false
|
|
500
|
+
self.syncOwner(playerId)
|
|
429
501
|
promise.resolve()
|
|
430
502
|
}
|
|
431
503
|
}
|
|
@@ -450,6 +522,10 @@ final class HybridOneAudio: HybridOneAudioSpec {
|
|
|
450
522
|
managed.paused = true
|
|
451
523
|
managed.ended = false
|
|
452
524
|
self.installPlayerEndObserver(id: playerId, managed: managed)
|
|
525
|
+
if self.nowPlayingOwner == .managed(playerId) {
|
|
526
|
+
self.observeOwner()
|
|
527
|
+
self.syncNowPlaying()
|
|
528
|
+
}
|
|
453
529
|
}
|
|
454
530
|
}
|
|
455
531
|
|
|
@@ -459,6 +535,7 @@ final class HybridOneAudio: HybridOneAudioSpec {
|
|
|
459
535
|
return
|
|
460
536
|
}
|
|
461
537
|
guard let managed = managedPlayers.removeValue(forKey: playerId) else { return }
|
|
538
|
+
if nowPlayingOwner == .managed(playerId) { clearNowPlaying() }
|
|
462
539
|
clearPlayerEndObserver(managed)
|
|
463
540
|
managed.player.pause()
|
|
464
541
|
managed.player.replaceCurrentItem(with: nil)
|
|
@@ -518,6 +595,22 @@ final class HybridOneAudio: HybridOneAudioSpec {
|
|
|
518
595
|
}
|
|
519
596
|
}
|
|
520
597
|
|
|
598
|
+
private func startManaged(_ managed: ManagedAudioPlayer) throws {
|
|
599
|
+
let session = AVAudioSession.sharedInstance()
|
|
600
|
+
try session.setCategory(.playback, mode: .default)
|
|
601
|
+
try session.setActive(true)
|
|
602
|
+
if managed.ended {
|
|
603
|
+
managed.player.seek(to: .zero)
|
|
604
|
+
managed.ended = false
|
|
605
|
+
}
|
|
606
|
+
managed.paused = false
|
|
607
|
+
managed.player.rate = Float(managed.rate)
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
private func syncOwner(_ playerId: String) {
|
|
611
|
+
if nowPlayingOwner == .managed(playerId) { syncNowPlaying() }
|
|
612
|
+
}
|
|
613
|
+
|
|
521
614
|
private func managedPlayer(_ id: String) throws -> ManagedAudioPlayer {
|
|
522
615
|
guard let managed = managedPlayers[id] else { throw Problem.unknownPlayer }
|
|
523
616
|
return managed
|
|
@@ -587,6 +680,7 @@ final class HybridOneAudio: HybridOneAudioSpec {
|
|
|
587
680
|
}
|
|
588
681
|
} else {
|
|
589
682
|
managed.ended = true
|
|
683
|
+
self.syncOwner(id)
|
|
590
684
|
}
|
|
591
685
|
}
|
|
592
686
|
}
|
|
@@ -640,6 +734,7 @@ final class HybridOneAudio: HybridOneAudioSpec {
|
|
|
640
734
|
let duration = item.duration.seconds
|
|
641
735
|
let state: AudioPlaybackState
|
|
642
736
|
if item.status == .failed { state = .failed }
|
|
737
|
+
else if pendingLoad != nil { state = .loading }
|
|
643
738
|
else if playbackEnded { state = .ended }
|
|
644
739
|
else if playbackPaused { state = .paused }
|
|
645
740
|
else if item.status != .readyToPlay || player.timeControlStatus == .waitingToPlayAtSpecifiedRate {
|
|
@@ -690,9 +785,9 @@ final class HybridOneAudio: HybridOneAudioSpec {
|
|
|
690
785
|
|
|
691
786
|
private func handleRemoteToggle() -> MPRemoteCommandHandlerStatus {
|
|
692
787
|
if !Thread.isMainThread { return DispatchQueue.main.sync { self.handleRemoteToggle() } }
|
|
693
|
-
guard let player else { return .noSuchContent }
|
|
788
|
+
guard let player = ownerPlayer() else { return .noSuchContent }
|
|
694
789
|
return handleRemoteCommand(
|
|
695
|
-
|
|
790
|
+
ownerPaused() || player.timeControlStatus != .playing ? .play : .pause)
|
|
696
791
|
}
|
|
697
792
|
|
|
698
793
|
private func handleRemoteCommand(
|
|
@@ -701,33 +796,143 @@ final class HybridOneAudio: HybridOneAudioSpec {
|
|
|
701
796
|
if !Thread.isMainThread {
|
|
702
797
|
return DispatchQueue.main.sync { self.handleRemoteCommand(type, positionMs: positionMs) }
|
|
703
798
|
}
|
|
704
|
-
guard
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
case .
|
|
711
|
-
player.
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
799
|
+
guard nowPlayingSession != nil, let owner = nowPlayingOwner else { return .noSuchContent }
|
|
800
|
+
if type == .seek {
|
|
801
|
+
guard let positionMs, positionMs.isFinite, positionMs >= 0 else { return .commandFailed }
|
|
802
|
+
}
|
|
803
|
+
var playerId: String?
|
|
804
|
+
switch owner {
|
|
805
|
+
case .global:
|
|
806
|
+
guard let player else { return .noSuchContent }
|
|
807
|
+
switch type {
|
|
808
|
+
case .play:
|
|
809
|
+
if playbackEnded { player.seek(to: .zero); playbackEnded = false }
|
|
810
|
+
playbackPaused = false
|
|
811
|
+
if pendingLoad == nil { player.play() }
|
|
812
|
+
case .pause:
|
|
813
|
+
player.pause()
|
|
814
|
+
playbackPaused = true
|
|
815
|
+
case .seek:
|
|
816
|
+
guard pendingLoad == nil else { return .commandFailed }
|
|
817
|
+
seekOwner(player, positionMs: positionMs!)
|
|
818
|
+
playbackEnded = false
|
|
716
819
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
820
|
+
case .managed(let id):
|
|
821
|
+
guard let managed = managedPlayers[id] else { return .noSuchContent }
|
|
822
|
+
playerId = id
|
|
823
|
+
switch type {
|
|
824
|
+
case .play:
|
|
825
|
+
do { try startManaged(managed) } catch { return .commandFailed }
|
|
826
|
+
case .pause:
|
|
827
|
+
managed.player.pause()
|
|
828
|
+
managed.paused = true
|
|
829
|
+
case .seek:
|
|
830
|
+
guard managed.player.currentItem?.status == .readyToPlay else { return .commandFailed }
|
|
831
|
+
seekOwner(managed.player, positionMs: positionMs!)
|
|
832
|
+
managed.ended = false
|
|
720
833
|
}
|
|
721
|
-
playbackEnded = false
|
|
722
834
|
}
|
|
723
835
|
syncNowPlaying()
|
|
724
|
-
let event = AudioRemoteCommandEvent(type: type, positionMs: positionMs)
|
|
836
|
+
let event = AudioRemoteCommandEvent(type: type, positionMs: positionMs, playerId: playerId)
|
|
725
837
|
for listener in Array(remoteCommandListeners.values) { listener(event) }
|
|
726
838
|
return .success
|
|
727
839
|
}
|
|
728
840
|
|
|
841
|
+
private func seekOwner(_ player: AVPlayer, positionMs: Double) {
|
|
842
|
+
player.seek(to: CMTime(seconds: positionMs / 1000, preferredTimescale: 600)) {
|
|
843
|
+
[weak self] _ in
|
|
844
|
+
DispatchQueue.main.async { self?.syncNowPlaying() }
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
private func ownerPlayer() -> AVPlayer? {
|
|
849
|
+
switch nowPlayingOwner {
|
|
850
|
+
case .global: return player
|
|
851
|
+
case .managed(let id): return managedPlayers[id]?.player
|
|
852
|
+
case nil: return nil
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
private func ownerPaused() -> Bool {
|
|
857
|
+
switch nowPlayingOwner {
|
|
858
|
+
case .global: return playbackPaused
|
|
859
|
+
case .managed(let id): return managedPlayers[id]?.paused ?? true
|
|
860
|
+
case nil: return true
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
private func ownerEnded() -> Bool {
|
|
865
|
+
switch nowPlayingOwner {
|
|
866
|
+
case .global: return playbackEnded
|
|
867
|
+
case .managed(let id): return managedPlayers[id]?.ended ?? false
|
|
868
|
+
case nil: return false
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
private static func nowPlayingArtwork(_ info: AudioNowPlayingInfo) throws -> UIImage? {
|
|
873
|
+
guard !info.title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
|
874
|
+
throw Problem.metadata
|
|
875
|
+
}
|
|
876
|
+
guard let artworkUri = info.artworkUri else { return nil }
|
|
877
|
+
guard let url = URL(string: artworkUri), url.isFileURL,
|
|
878
|
+
FileManager.default.fileExists(atPath: url.path),
|
|
879
|
+
let image = UIImage(contentsOfFile: url.path)
|
|
880
|
+
else { throw Problem.artwork }
|
|
881
|
+
return image
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// a claim for the session's own player keeps it. a transfer to another
|
|
885
|
+
// player starts a session for that player and publishes the new card in
|
|
886
|
+
// the same pass, without clearing the old one first. swapping players on
|
|
887
|
+
// one session raised an objective-c exception on the ios 27 simulator.
|
|
888
|
+
private func claimNowPlaying(
|
|
889
|
+
_ owner: NowPlayingOwner, player: AVPlayer, info: AudioNowPlayingInfo, artwork: UIImage?
|
|
890
|
+
) {
|
|
891
|
+
nowPlayingInfo = info
|
|
892
|
+
nowPlayingArtwork = artwork
|
|
893
|
+
nowPlayingOwner = owner
|
|
894
|
+
let session: MPNowPlayingSession
|
|
895
|
+
if let current = nowPlayingSession, current.players.count == 1,
|
|
896
|
+
current.players[0] === player
|
|
897
|
+
{
|
|
898
|
+
session = current
|
|
899
|
+
} else {
|
|
900
|
+
for (command, target) in remoteTargets { command.removeTarget(target) }
|
|
901
|
+
session = MPNowPlayingSession(players: [player])
|
|
902
|
+
session.automaticallyPublishesNowPlayingInfo = false
|
|
903
|
+
nowPlayingSession = session
|
|
904
|
+
installRemoteCommands(session.remoteCommandCenter)
|
|
905
|
+
}
|
|
906
|
+
observeOwner()
|
|
907
|
+
syncNowPlaying()
|
|
908
|
+
session.becomeActiveIfPossible { [weak self, weak session] active in
|
|
909
|
+
guard active, let session else { return }
|
|
910
|
+
DispatchQueue.main.async { [weak self] in
|
|
911
|
+
guard let self, self.nowPlayingSession === session else { return }
|
|
912
|
+
self.syncNowPlaying()
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
private func observeOwner() {
|
|
918
|
+
ownerObservations.forEach { $0.invalidate() }
|
|
919
|
+
ownerObservations.removeAll()
|
|
920
|
+
guard let player = ownerPlayer() else { return }
|
|
921
|
+
ownerObservations.append(
|
|
922
|
+
player.observe(\.timeControlStatus, options: [.new]) { [weak self] _, _ in
|
|
923
|
+
DispatchQueue.main.async { self?.syncNowPlaying() }
|
|
924
|
+
})
|
|
925
|
+
if let item = player.currentItem {
|
|
926
|
+
ownerObservations.append(
|
|
927
|
+
item.observe(\.status, options: [.new]) { [weak self] _, _ in
|
|
928
|
+
DispatchQueue.main.async { self?.syncNowPlaying() }
|
|
929
|
+
})
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
729
933
|
private func syncNowPlaying() {
|
|
730
|
-
guard let session = nowPlayingSession, let info = nowPlayingInfo, let player
|
|
934
|
+
guard let session = nowPlayingSession, let info = nowPlayingInfo, let player = ownerPlayer()
|
|
935
|
+
else { return }
|
|
731
936
|
let position = player.currentTime().seconds
|
|
732
937
|
let duration = player.currentItem?.duration.seconds ?? .nan
|
|
733
938
|
var metadata: [String: Any] = [
|
|
@@ -735,7 +940,7 @@ final class HybridOneAudio: HybridOneAudioSpec {
|
|
|
735
940
|
MPNowPlayingInfoPropertyMediaType: MPNowPlayingInfoMediaType.audio.rawValue,
|
|
736
941
|
MPNowPlayingInfoPropertyElapsedPlaybackTime: position.isFinite ? max(0, position) : 0,
|
|
737
942
|
MPNowPlayingInfoPropertyPlaybackRate:
|
|
738
|
-
!
|
|
943
|
+
!ownerEnded() && player.timeControlStatus == .playing ? player.rate : 0.0,
|
|
739
944
|
MPNowPlayingInfoPropertyDefaultPlaybackRate: 1.0,
|
|
740
945
|
]
|
|
741
946
|
if let artist = info.artist { metadata[MPMediaItemPropertyArtist] = artist }
|
|
@@ -756,10 +961,17 @@ final class HybridOneAudio: HybridOneAudioSpec {
|
|
|
756
961
|
nowPlayingSession = nil
|
|
757
962
|
nowPlayingInfo = nil
|
|
758
963
|
nowPlayingArtwork = nil
|
|
964
|
+
nowPlayingOwner = nil
|
|
965
|
+
ownerObservations.forEach { $0.invalidate() }
|
|
966
|
+
ownerObservations.removeAll()
|
|
759
967
|
}
|
|
760
968
|
|
|
761
969
|
private func clearPlayer() {
|
|
762
|
-
clearNowPlaying()
|
|
970
|
+
if nowPlayingOwner == .global { clearNowPlaying() }
|
|
971
|
+
if let load = pendingLoad {
|
|
972
|
+
pendingLoad = nil
|
|
973
|
+
load.promise.reject(withError: Self.failure("play", Problem.replaced))
|
|
974
|
+
}
|
|
763
975
|
playerStatusObservation?.invalidate()
|
|
764
976
|
playerStatusObservation = nil
|
|
765
977
|
itemStatusObservation?.invalidate()
|
|
@@ -818,6 +1030,7 @@ final class HybridOneAudio: HybridOneAudioSpec {
|
|
|
818
1030
|
case Problem.artwork: code = "E_AUDIO_ARTWORK"
|
|
819
1031
|
case Problem.unknownPlayer: code = "E_AUDIO_STATE"
|
|
820
1032
|
case Problem.rate: code = "E_AUDIO_RATE"
|
|
1033
|
+
case Problem.replaced: code = "E_AUDIO_STATE"
|
|
821
1034
|
default: code = "E_AUDIO_FAILED"
|
|
822
1035
|
}
|
|
823
1036
|
return oneNativeError(code, "Audio.\(verb): \(error.localizedDescription)")
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// JAudioPlayOptions.hpp
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include <fbjni/fbjni.h>
|
|
11
|
+
#include "AudioPlayOptions.hpp"
|
|
12
|
+
|
|
13
|
+
#include <optional>
|
|
14
|
+
|
|
15
|
+
namespace margelo::nitro::one {
|
|
16
|
+
|
|
17
|
+
using namespace facebook;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The C++ JNI bridge between the C++ struct "AudioPlayOptions" and the Kotlin data class "AudioPlayOptions".
|
|
21
|
+
*/
|
|
22
|
+
struct JAudioPlayOptions final: public jni::JavaClass<JAudioPlayOptions> {
|
|
23
|
+
public:
|
|
24
|
+
static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/one/AudioPlayOptions;";
|
|
25
|
+
|
|
26
|
+
public:
|
|
27
|
+
/**
|
|
28
|
+
* Convert this Java/Kotlin-based struct to the C++ struct AudioPlayOptions by copying all values to C++.
|
|
29
|
+
*/
|
|
30
|
+
[[maybe_unused]]
|
|
31
|
+
[[nodiscard]]
|
|
32
|
+
AudioPlayOptions toCpp() const {
|
|
33
|
+
static const auto clazz = javaClassStatic();
|
|
34
|
+
static const auto fieldPositionMs = clazz->getField<jni::JDouble>("positionMs");
|
|
35
|
+
jni::local_ref<jni::JDouble> positionMs = this->getFieldValue(fieldPositionMs);
|
|
36
|
+
static const auto fieldPaused = clazz->getField<jni::JBoolean>("paused");
|
|
37
|
+
jni::local_ref<jni::JBoolean> paused = this->getFieldValue(fieldPaused);
|
|
38
|
+
return AudioPlayOptions(
|
|
39
|
+
positionMs != nullptr ? std::make_optional(positionMs->value()) : std::nullopt,
|
|
40
|
+
paused != nullptr ? std::make_optional(static_cast<bool>(paused->value())) : std::nullopt
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public:
|
|
45
|
+
/**
|
|
46
|
+
* Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java.
|
|
47
|
+
*/
|
|
48
|
+
[[maybe_unused]]
|
|
49
|
+
static jni::local_ref<JAudioPlayOptions::javaobject> fromCpp(const AudioPlayOptions& value) {
|
|
50
|
+
using JSignature = JAudioPlayOptions(jni::alias_ref<jni::JDouble>, jni::alias_ref<jni::JBoolean>);
|
|
51
|
+
static const auto clazz = javaClassStatic();
|
|
52
|
+
static const auto create = clazz->getStaticMethod<JSignature>("fromCpp");
|
|
53
|
+
return create(
|
|
54
|
+
clazz,
|
|
55
|
+
value.positionMs.has_value() ? jni::JDouble::valueOf(value.positionMs.value()) : nullptr,
|
|
56
|
+
value.paused.has_value() ? jni::JBoolean::valueOf(value.paused.value()) : nullptr
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
} // namespace margelo::nitro::one
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
#include "AudioRemoteCommandType.hpp"
|
|
14
14
|
#include "JAudioRemoteCommandType.hpp"
|
|
15
15
|
#include <optional>
|
|
16
|
+
#include <string>
|
|
16
17
|
|
|
17
18
|
namespace margelo::nitro::one {
|
|
18
19
|
|
|
@@ -37,9 +38,12 @@ namespace margelo::nitro::one {
|
|
|
37
38
|
jni::local_ref<JAudioRemoteCommandType> type = this->getFieldValue(fieldType);
|
|
38
39
|
static const auto fieldPositionMs = clazz->getField<jni::JDouble>("positionMs");
|
|
39
40
|
jni::local_ref<jni::JDouble> positionMs = this->getFieldValue(fieldPositionMs);
|
|
41
|
+
static const auto fieldPlayerId = clazz->getField<jni::JString>("playerId");
|
|
42
|
+
jni::local_ref<jni::JString> playerId = this->getFieldValue(fieldPlayerId);
|
|
40
43
|
return AudioRemoteCommandEvent(
|
|
41
44
|
type->toCpp(),
|
|
42
|
-
positionMs != nullptr ? std::make_optional(positionMs->value()) : std::nullopt
|
|
45
|
+
positionMs != nullptr ? std::make_optional(positionMs->value()) : std::nullopt,
|
|
46
|
+
playerId != nullptr ? std::make_optional(playerId->toStdString()) : std::nullopt
|
|
43
47
|
);
|
|
44
48
|
}
|
|
45
49
|
|
|
@@ -49,13 +53,14 @@ namespace margelo::nitro::one {
|
|
|
49
53
|
*/
|
|
50
54
|
[[maybe_unused]]
|
|
51
55
|
static jni::local_ref<JAudioRemoteCommandEvent::javaobject> fromCpp(const AudioRemoteCommandEvent& value) {
|
|
52
|
-
using JSignature = JAudioRemoteCommandEvent(jni::alias_ref<JAudioRemoteCommandType>, jni::alias_ref<jni::JDouble>);
|
|
56
|
+
using JSignature = JAudioRemoteCommandEvent(jni::alias_ref<JAudioRemoteCommandType>, jni::alias_ref<jni::JDouble>, jni::alias_ref<jni::JString>);
|
|
53
57
|
static const auto clazz = javaClassStatic();
|
|
54
58
|
static const auto create = clazz->getStaticMethod<JSignature>("fromCpp");
|
|
55
59
|
return create(
|
|
56
60
|
clazz,
|
|
57
61
|
JAudioRemoteCommandType::fromCpp(value.type),
|
|
58
|
-
value.positionMs.has_value() ? jni::JDouble::valueOf(value.positionMs.value()) : nullptr
|
|
62
|
+
value.positionMs.has_value() ? jni::JDouble::valueOf(value.positionMs.value()) : nullptr,
|
|
63
|
+
value.playerId.has_value() ? jni::make_jstring(value.playerId.value()) : nullptr
|
|
59
64
|
);
|
|
60
65
|
}
|
|
61
66
|
};
|