react-native-audio-api 0.6.0-rc.0 → 0.6.0-rc.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 (152) hide show
  1. package/android/CMakeLists.txt +6 -3
  2. package/android/build.gradle +1 -0
  3. package/android/src/main/java/com/swmansion/audioapi/AudioManagerModule.kt +15 -12
  4. package/android/src/main/java/com/swmansion/audioapi/system/AudioFocusListener.kt +60 -0
  5. package/android/src/main/java/com/swmansion/audioapi/system/LockScreenManager.kt +294 -0
  6. package/android/src/main/java/com/swmansion/audioapi/system/MediaNotificationManager.kt +279 -0
  7. package/android/src/main/java/com/swmansion/audioapi/system/MediaReceiver.kt +46 -0
  8. package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionCallback.kt +39 -0
  9. package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionEventEmitter.kt +84 -0
  10. package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt +144 -0
  11. package/android/src/main/res/drawable/next.xml +9 -0
  12. package/android/src/main/res/drawable/pause.xml +9 -0
  13. package/android/src/main/res/drawable/play.xml +9 -0
  14. package/android/src/main/res/drawable/previous.xml +9 -0
  15. package/android/src/main/res/drawable/skip_backward_5.xml +9 -0
  16. package/android/src/main/res/drawable/skip_forward_5.xml +9 -0
  17. package/android/src/main/res/drawable/stop.xml +9 -0
  18. package/app.plugin.js +1 -0
  19. package/common/cpp/audioapi/core/sources/AudioBufferSourceNode.cpp +1 -6
  20. package/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.cpp +8 -4
  21. package/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.h +1 -0
  22. package/common/cpp/audioapi/core/utils/AudioNodeDestructor.cpp +3 -3
  23. package/ios/audioapi/ios/AudioManagerModule.mm +9 -7
  24. package/ios/audioapi/ios/system/LockScreenManager.mm +4 -8
  25. package/ios/audioapi/ios/system/NotificationManager.h +7 -1
  26. package/ios/audioapi/ios/system/NotificationManager.mm +66 -44
  27. package/lib/commonjs/api.js +197 -0
  28. package/lib/commonjs/api.js.map +1 -0
  29. package/lib/commonjs/api.web.js +219 -0
  30. package/lib/commonjs/api.web.js.map +1 -0
  31. package/lib/commonjs/core/AnalyserNode.js +71 -0
  32. package/lib/commonjs/core/AnalyserNode.js.map +1 -0
  33. package/lib/commonjs/core/AudioBuffer.js +44 -0
  34. package/lib/commonjs/core/AudioBuffer.js.map +1 -0
  35. package/lib/commonjs/core/AudioBufferSourceNode.js +68 -0
  36. package/lib/commonjs/core/AudioBufferSourceNode.js.map +1 -0
  37. package/lib/commonjs/core/AudioContext.js +29 -0
  38. package/lib/commonjs/core/AudioContext.js.map +1 -0
  39. package/lib/commonjs/core/AudioDestinationNode.js +11 -0
  40. package/lib/commonjs/core/AudioDestinationNode.js.map +1 -0
  41. package/lib/commonjs/core/AudioNode.js +30 -0
  42. package/lib/commonjs/core/AudioNode.js.map +1 -0
  43. package/lib/commonjs/core/AudioParam.js +82 -0
  44. package/lib/commonjs/core/AudioParam.js.map +1 -0
  45. package/lib/commonjs/core/AudioScheduledSourceNode.js +38 -0
  46. package/lib/commonjs/core/AudioScheduledSourceNode.js.map +1 -0
  47. package/lib/commonjs/core/BaseAudioContext.js +80 -0
  48. package/lib/commonjs/core/BaseAudioContext.js.map +1 -0
  49. package/lib/commonjs/core/BiquadFilterNode.js +33 -0
  50. package/lib/commonjs/core/BiquadFilterNode.js.map +1 -0
  51. package/lib/commonjs/core/GainNode.js +17 -0
  52. package/lib/commonjs/core/GainNode.js.map +1 -0
  53. package/lib/commonjs/core/OfflineAudioContext.js +63 -0
  54. package/lib/commonjs/core/OfflineAudioContext.js.map +1 -0
  55. package/lib/commonjs/core/OscillatorNode.js +32 -0
  56. package/lib/commonjs/core/OscillatorNode.js.map +1 -0
  57. package/lib/commonjs/core/PeriodicWave.js +15 -0
  58. package/lib/commonjs/core/PeriodicWave.js.map +1 -0
  59. package/lib/commonjs/core/StereoPannerNode.js +17 -0
  60. package/lib/commonjs/core/StereoPannerNode.js.map +1 -0
  61. package/lib/commonjs/errors/IndexSizeError.js +14 -0
  62. package/lib/commonjs/errors/IndexSizeError.js.map +1 -0
  63. package/lib/commonjs/errors/InvalidAccessError.js +14 -0
  64. package/lib/commonjs/errors/InvalidAccessError.js.map +1 -0
  65. package/lib/commonjs/errors/InvalidStateError.js +14 -0
  66. package/lib/commonjs/errors/InvalidStateError.js.map +1 -0
  67. package/lib/commonjs/errors/NotSupportedError.js +14 -0
  68. package/lib/commonjs/errors/NotSupportedError.js.map +1 -0
  69. package/lib/commonjs/errors/RangeError.js +14 -0
  70. package/lib/commonjs/errors/RangeError.js.map +1 -0
  71. package/lib/commonjs/errors/index.js +42 -0
  72. package/lib/commonjs/errors/index.js.map +1 -0
  73. package/lib/commonjs/index.js +17 -0
  74. package/lib/commonjs/index.js.map +1 -0
  75. package/lib/commonjs/interfaces.js +6 -0
  76. package/lib/commonjs/interfaces.js.map +1 -0
  77. package/lib/commonjs/package.json +1 -0
  78. package/lib/commonjs/plugin/withAudioAPI.js +62 -0
  79. package/lib/commonjs/plugin/withAudioAPI.js.map +1 -0
  80. package/lib/commonjs/specs/NativeAudioAPIModule.js +9 -0
  81. package/lib/commonjs/specs/NativeAudioAPIModule.js.map +1 -0
  82. package/lib/commonjs/specs/NativeAudioManagerModule.js +33 -0
  83. package/lib/commonjs/specs/NativeAudioManagerModule.js.map +1 -0
  84. package/lib/commonjs/specs/index.js +27 -0
  85. package/lib/commonjs/specs/index.js.map +1 -0
  86. package/lib/commonjs/system/AudioManager.js +79 -0
  87. package/lib/commonjs/system/AudioManager.js.map +1 -0
  88. package/lib/commonjs/system/index.js +14 -0
  89. package/lib/commonjs/system/index.js.map +1 -0
  90. package/lib/commonjs/system/types.js +2 -0
  91. package/lib/commonjs/system/types.js.map +1 -0
  92. package/lib/commonjs/types.js +2 -0
  93. package/lib/commonjs/types.js.map +1 -0
  94. package/lib/commonjs/utils/index.js +10 -0
  95. package/lib/commonjs/utils/index.js.map +1 -0
  96. package/lib/commonjs/web-core/AnalyserNode.js +38 -0
  97. package/lib/commonjs/web-core/AnalyserNode.js.map +1 -0
  98. package/lib/commonjs/web-core/AudioBuffer.js +44 -0
  99. package/lib/commonjs/web-core/AudioBuffer.js.map +1 -0
  100. package/lib/commonjs/web-core/AudioBufferSourceNode.js +214 -0
  101. package/lib/commonjs/web-core/AudioBufferSourceNode.js.map +1 -0
  102. package/lib/commonjs/web-core/AudioContext.js +93 -0
  103. package/lib/commonjs/web-core/AudioContext.js.map +1 -0
  104. package/lib/commonjs/web-core/AudioDestinationNode.js +11 -0
  105. package/lib/commonjs/web-core/AudioDestinationNode.js.map +1 -0
  106. package/lib/commonjs/web-core/AudioNode.js +33 -0
  107. package/lib/commonjs/web-core/AudioNode.js.map +1 -0
  108. package/lib/commonjs/web-core/AudioParam.js +81 -0
  109. package/lib/commonjs/web-core/AudioParam.js.map +1 -0
  110. package/lib/commonjs/web-core/AudioScheduledSourceNode.js +41 -0
  111. package/lib/commonjs/web-core/AudioScheduledSourceNode.js.map +1 -0
  112. package/lib/commonjs/web-core/BaseAudioContext.js +2 -0
  113. package/lib/commonjs/web-core/BaseAudioContext.js.map +1 -0
  114. package/lib/commonjs/web-core/BiquadFilterNode.js +33 -0
  115. package/lib/commonjs/web-core/BiquadFilterNode.js.map +1 -0
  116. package/lib/commonjs/web-core/GainNode.js +17 -0
  117. package/lib/commonjs/web-core/GainNode.js.map +1 -0
  118. package/lib/commonjs/web-core/OfflineAudioContext.js +96 -0
  119. package/lib/commonjs/web-core/OfflineAudioContext.js.map +1 -0
  120. package/lib/commonjs/web-core/OscillatorNode.js +31 -0
  121. package/lib/commonjs/web-core/OscillatorNode.js.map +1 -0
  122. package/lib/commonjs/web-core/PeriodicWave.js +15 -0
  123. package/lib/commonjs/web-core/PeriodicWave.js.map +1 -0
  124. package/lib/commonjs/web-core/StereoPannerNode.js +17 -0
  125. package/lib/commonjs/web-core/StereoPannerNode.js.map +1 -0
  126. package/lib/commonjs/web-core/custom/LoadCustomWasm.js +37 -0
  127. package/lib/commonjs/web-core/custom/LoadCustomWasm.js.map +1 -0
  128. package/lib/commonjs/web-core/custom/index.js +14 -0
  129. package/lib/commonjs/web-core/custom/index.js.map +1 -0
  130. package/lib/commonjs/web-core/custom/signalsmithStretch/LICENSE.txt +21 -0
  131. package/lib/commonjs/web-core/custom/signalsmithStretch/README.md +46 -0
  132. package/lib/commonjs/web-core/custom/signalsmithStretch/SignalsmithStretch.mjs +826 -0
  133. package/lib/commonjs/web-core/custom/signalsmithStretch/SignalsmithStretch.mjs.map +1 -0
  134. package/lib/module/plugin/withAudioAPI.js +58 -0
  135. package/lib/module/plugin/withAudioAPI.js.map +1 -0
  136. package/lib/module/specs/NativeAudioManagerModule.js +5 -6
  137. package/lib/module/specs/NativeAudioManagerModule.js.map +1 -1
  138. package/lib/module/system/AudioManager.js +9 -0
  139. package/lib/module/system/AudioManager.js.map +1 -1
  140. package/lib/typescript/plugin/withAudioAPI.d.ts +9 -0
  141. package/lib/typescript/plugin/withAudioAPI.d.ts.map +1 -0
  142. package/lib/typescript/specs/NativeAudioManagerModule.d.ts +2 -1
  143. package/lib/typescript/specs/NativeAudioManagerModule.d.ts.map +1 -1
  144. package/lib/typescript/system/AudioManager.d.ts +4 -2
  145. package/lib/typescript/system/AudioManager.d.ts.map +1 -1
  146. package/lib/typescript/system/types.d.ts +16 -4
  147. package/lib/typescript/system/types.d.ts.map +1 -1
  148. package/package.json +6 -3
  149. package/src/plugin/withAudioAPI.ts +90 -0
  150. package/src/specs/NativeAudioManagerModule.ts +8 -11
  151. package/src/system/AudioManager.ts +42 -15
  152. package/src/system/types.ts +20 -4
@@ -0,0 +1,279 @@
1
+ package com.swmansion.audioapi.system
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.app.Notification
5
+ import android.app.PendingIntent
6
+ import android.app.Service
7
+ import android.content.Intent
8
+ import android.content.res.Resources
9
+ import android.os.Binder
10
+ import android.os.Build
11
+ import android.os.IBinder
12
+ import android.provider.ContactsContract
13
+ import android.support.v4.media.session.PlaybackStateCompat
14
+ import android.util.Log
15
+ import android.view.KeyEvent
16
+ import androidx.core.app.NotificationCompat
17
+ import androidx.core.app.NotificationManagerCompat
18
+ import androidx.core.content.ContextCompat
19
+ import com.facebook.react.bridge.ReactApplicationContext
20
+ import com.swmansion.audioapi.R
21
+ import java.lang.ref.WeakReference
22
+
23
+ class MediaNotificationManager(
24
+ val reactContext: ReactApplicationContext,
25
+ val notificationId: Int,
26
+ val channelId: String,
27
+ ) {
28
+ private var smallIcon: Int = R.drawable.play
29
+ private var customIcon: Int = 0
30
+
31
+ private var play: NotificationCompat.Action? = null
32
+ private var pause: NotificationCompat.Action? = null
33
+ private var stop: NotificationCompat.Action? = null
34
+ private var next: NotificationCompat.Action? = null
35
+ private var previous: NotificationCompat.Action? = null
36
+ private var skipForward: NotificationCompat.Action? = null
37
+ private var skipBackward: NotificationCompat.Action? = null
38
+
39
+ companion object {
40
+ const val REMOVE_NOTIFICATION: String = "audio_manager_remove_notification"
41
+ const val PACKAGE_NAME: String = "com.swmansion.audioapi.system"
42
+ const val MEDIA_BUTTON: String = "audio_manager_media_button"
43
+ }
44
+
45
+ @SuppressLint("RestrictedApi")
46
+ @Synchronized
47
+ fun prepareNotification(
48
+ builder: NotificationCompat.Builder,
49
+ isPlaying: Boolean,
50
+ ): Notification {
51
+ builder.mActions.clear()
52
+
53
+ if (previous != null) {
54
+ builder.addAction(previous)
55
+ }
56
+
57
+ if (skipBackward != null) {
58
+ builder.addAction(skipBackward)
59
+ }
60
+
61
+ if (play != null && !isPlaying) {
62
+ builder.addAction(play)
63
+ }
64
+
65
+ if (pause != null && isPlaying) {
66
+ builder.addAction(pause)
67
+ }
68
+
69
+ if (stop != null) {
70
+ builder.addAction(stop)
71
+ }
72
+
73
+ if (next != null) {
74
+ builder.addAction(next)
75
+ }
76
+
77
+ if (skipForward != null) {
78
+ builder.addAction(skipForward)
79
+ }
80
+
81
+ builder.setSmallIcon(if (customIcon != 0) customIcon else smallIcon)
82
+
83
+ val packageName: String = reactContext.packageName
84
+ val openApp: Intent? = reactContext.packageManager.getLaunchIntentForPackage(packageName)
85
+ try {
86
+ builder.setContentIntent(
87
+ PendingIntent.getActivity(
88
+ reactContext,
89
+ 0,
90
+ openApp,
91
+ PendingIntent.FLAG_IMMUTABLE,
92
+ ),
93
+ )
94
+ } catch (e: Exception) {
95
+ Log.w("AudioManagerModule", "Error creating content intent: ${e.message}")
96
+ }
97
+
98
+ val remove = Intent(REMOVE_NOTIFICATION)
99
+ remove.putExtra(PACKAGE_NAME, reactContext.applicationInfo.packageName)
100
+ builder.setDeleteIntent(
101
+ PendingIntent.getBroadcast(
102
+ reactContext,
103
+ 0,
104
+ remove,
105
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
106
+ ),
107
+ )
108
+
109
+ return builder.build()
110
+ }
111
+
112
+ @SuppressLint("MissingPermission")
113
+ @Synchronized
114
+ fun show(
115
+ builder: NotificationCompat.Builder?,
116
+ isPlaying: Boolean,
117
+ ) {
118
+ NotificationManagerCompat.from(reactContext).notify(
119
+ notificationId,
120
+ prepareNotification(
121
+ builder!!,
122
+ isPlaying,
123
+ ),
124
+ )
125
+ }
126
+
127
+ fun hide() {
128
+ NotificationManagerCompat.from(reactContext).cancel(notificationId)
129
+
130
+ try {
131
+ val myIntent =
132
+ Intent(
133
+ reactContext,
134
+ NotificationService::class.java,
135
+ )
136
+ reactContext.stopService(myIntent)
137
+ } catch (e: java.lang.Exception) {
138
+ Log.w("AudioManagerModule", "Error stopping service: ${e.message}")
139
+ }
140
+ }
141
+
142
+ @Synchronized
143
+ fun updateActions(mask: Long) {
144
+ play = createAction("play", "Play", mask, PlaybackStateCompat.ACTION_PLAY, play)
145
+ pause = createAction("pause", "Pause", mask, PlaybackStateCompat.ACTION_PAUSE, pause)
146
+ stop = createAction("stop", "Stop", mask, PlaybackStateCompat.ACTION_STOP, stop)
147
+ next = createAction("next", "Next", mask, PlaybackStateCompat.ACTION_SKIP_TO_NEXT, next)
148
+ previous =
149
+ createAction(
150
+ "previous",
151
+ "Previous",
152
+ mask,
153
+ PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS,
154
+ previous,
155
+ )
156
+
157
+ skipForward =
158
+ createAction(
159
+ "skip_forward_5",
160
+ "Skip Forward",
161
+ mask,
162
+ PlaybackStateCompat.ACTION_FAST_FORWARD,
163
+ skipForward,
164
+ )
165
+
166
+ skipBackward =
167
+ createAction(
168
+ "skip_backward_5",
169
+ "Skip Backward",
170
+ mask,
171
+ PlaybackStateCompat.ACTION_REWIND,
172
+ skipBackward,
173
+ )
174
+ }
175
+
176
+ private fun createAction(
177
+ iconName: String,
178
+ title: String,
179
+ mask: Long,
180
+ action: Long,
181
+ oldAction: NotificationCompat.Action?,
182
+ ): NotificationCompat.Action? {
183
+ if ((mask and action) == 0L) {
184
+ return null
185
+ }
186
+
187
+ if (oldAction != null) {
188
+ return oldAction
189
+ }
190
+
191
+ val r: Resources = reactContext.resources
192
+ val packageName: String = reactContext.packageName
193
+ val icon = r.getIdentifier(iconName, "drawable", packageName)
194
+
195
+ val keyCode = PlaybackStateCompat.toKeyCode(action)
196
+ val intent = Intent(MEDIA_BUTTON)
197
+ intent.putExtra(Intent.EXTRA_KEY_EVENT, KeyEvent(KeyEvent.ACTION_DOWN, keyCode))
198
+ intent.putExtra(ContactsContract.Directory.PACKAGE_NAME, packageName)
199
+ val i =
200
+ PendingIntent.getBroadcast(
201
+ reactContext,
202
+ keyCode,
203
+ intent,
204
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
205
+ )
206
+
207
+ return NotificationCompat.Action(icon, title, i)
208
+ }
209
+
210
+ inner class NotificationService : Service() {
211
+ private val binder = LocalBinder()
212
+ private var notification: Notification? = null
213
+
214
+ inner class LocalBinder : Binder() {
215
+ private var weakService: WeakReference<NotificationService>? = null
216
+
217
+ fun onBind(service: NotificationService) {
218
+ weakService = WeakReference(service)
219
+ }
220
+
221
+ fun getService(): NotificationService? = weakService?.get()
222
+ }
223
+
224
+ override fun onBind(intent: Intent): IBinder {
225
+ binder.onBind(this)
226
+ return binder
227
+ }
228
+
229
+ fun forceForeground() {
230
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
231
+ val intent = Intent(this, NotificationService::class.java)
232
+ ContextCompat.startForegroundService(this, intent)
233
+ notification =
234
+ MediaNotificationManager(reactContext, notificationId, channelId)
235
+ .prepareNotification(NotificationCompat.Builder(this, channelId), false)
236
+ startForeground(notificationId, notification)
237
+ }
238
+ }
239
+
240
+ override fun onCreate() {
241
+ super.onCreate()
242
+ try {
243
+ notification =
244
+ MediaNotificationManager(reactContext, notificationId, channelId)
245
+ .prepareNotification(NotificationCompat.Builder(this, channelId), false)
246
+ startForeground(notificationId, notification)
247
+ } catch (ex: Exception) {
248
+ Log.w("AudioManagerModule", "Error starting service: ${ex.message}")
249
+ }
250
+ }
251
+
252
+ override fun onStartCommand(
253
+ intent: Intent?,
254
+ flags: Int,
255
+ startId: Int,
256
+ ): Int {
257
+ onCreate()
258
+ return START_NOT_STICKY
259
+ }
260
+
261
+ override fun onTaskRemoved(rootIntent: Intent?) {
262
+ super.onTaskRemoved(rootIntent)
263
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
264
+ stopForeground(STOP_FOREGROUND_REMOVE)
265
+ }
266
+ stopSelf()
267
+ }
268
+
269
+ override fun onDestroy() {
270
+ super.onDestroy()
271
+
272
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
273
+ stopForeground(STOP_FOREGROUND_REMOVE)
274
+ }
275
+
276
+ stopSelf()
277
+ }
278
+ }
279
+ }
@@ -0,0 +1,46 @@
1
+ package com.swmansion.audioapi.system
2
+
3
+ import android.content.BroadcastReceiver
4
+ import android.content.Context
5
+ import android.content.Intent
6
+ import android.media.AudioManager
7
+ import android.view.KeyEvent
8
+ import com.facebook.react.bridge.ReactApplicationContext
9
+
10
+ class MediaReceiver(
11
+ val reactContext: ReactApplicationContext,
12
+ private val mediaSessionManager: MediaSessionManager,
13
+ ) : BroadcastReceiver() {
14
+ override fun onReceive(
15
+ context: Context?,
16
+ intent: Intent?,
17
+ ) {
18
+ val action = intent!!.action
19
+
20
+ if (MediaNotificationManager.REMOVE_NOTIFICATION == action) {
21
+ if (!checkApp(intent)) return
22
+
23
+ mediaSessionManager.mediaNotificationManager.hide()
24
+ mediaSessionManager.mediaSession.isActive = false
25
+
26
+ mediaSessionManager.eventEmitter.sendEvent("onCloseNotification", null)
27
+ } else if (MediaNotificationManager.MEDIA_BUTTON == action || Intent.ACTION_MEDIA_BUTTON == action) {
28
+ if (!intent.hasExtra(Intent.EXTRA_KEY_EVENT)) return
29
+ if (!checkApp(intent)) return
30
+
31
+ val keyEvent = intent.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT)
32
+ mediaSessionManager.mediaSession.controller.dispatchMediaButtonEvent(keyEvent)
33
+ } else if (AudioManager.ACTION_AUDIO_BECOMING_NOISY == action) {
34
+ mediaSessionManager.mediaSession.controller.transportControls
35
+ .pause()
36
+ }
37
+ }
38
+
39
+ private fun checkApp(intent: Intent): Boolean {
40
+ if (intent.hasExtra(MediaNotificationManager.PACKAGE_NAME)) {
41
+ val name = intent.getStringExtra(MediaNotificationManager.PACKAGE_NAME)
42
+ if (!reactContext.packageName.equals(name)) return false
43
+ }
44
+ return true
45
+ }
46
+ }
@@ -0,0 +1,39 @@
1
+ package com.swmansion.audioapi.system
2
+
3
+ import android.support.v4.media.session.MediaSessionCompat
4
+ import android.support.v4.media.session.PlaybackStateCompat
5
+
6
+ class MediaSessionCallback(
7
+ val eventEmitter: MediaSessionEventEmitter,
8
+ private val lockScreenManager: LockScreenManager,
9
+ ) : MediaSessionCompat.Callback() {
10
+ override fun onPlay() {
11
+ lockScreenManager.updatePlaybackState(PlaybackStateCompat.STATE_PLAYING)
12
+ eventEmitter.onPlay()
13
+ }
14
+
15
+ override fun onPause() {
16
+ lockScreenManager.updatePlaybackState(PlaybackStateCompat.STATE_PAUSED)
17
+ eventEmitter.onPause()
18
+ }
19
+
20
+ override fun onStop() {
21
+ eventEmitter.onStop()
22
+ }
23
+
24
+ override fun onSkipToNext() {
25
+ eventEmitter.onSkipToNext()
26
+ }
27
+
28
+ override fun onSkipToPrevious() {
29
+ eventEmitter.onSkipToPrevious()
30
+ }
31
+
32
+ override fun onFastForward() {
33
+ eventEmitter.onFastForward()
34
+ }
35
+
36
+ override fun onRewind() {
37
+ eventEmitter.onRewind()
38
+ }
39
+ }
@@ -0,0 +1,84 @@
1
+ package com.swmansion.audioapi.system
2
+
3
+ import android.content.Intent
4
+ import android.os.Build
5
+ import androidx.core.app.NotificationManagerCompat
6
+ import com.facebook.react.bridge.Arguments
7
+ import com.facebook.react.bridge.ReactApplicationContext
8
+ import com.facebook.react.modules.core.DeviceEventManagerModule
9
+
10
+ class MediaSessionEventEmitter(
11
+ val reactContext: ReactApplicationContext,
12
+ val notificationId: Int,
13
+ ) {
14
+ fun sendEvent(
15
+ name: String,
16
+ values: Map<String, Any>?,
17
+ ) {
18
+ val data = Arguments.createMap()
19
+
20
+ if (values != null) {
21
+ for (value in values) {
22
+ when (value.value) {
23
+ is Double, is Float -> {
24
+ data.putDouble(value.key, value.value as Double)
25
+ }
26
+
27
+ is Boolean -> {
28
+ data.putBoolean(value.key, value.value as Boolean)
29
+ }
30
+
31
+ is Int -> {
32
+ data.putInt(value.key, value.value as Int)
33
+ }
34
+ }
35
+ }
36
+ }
37
+
38
+ reactContext
39
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
40
+ .emit(name, data)
41
+ }
42
+
43
+ fun onPlay() {
44
+ sendEvent("onRemotePlay", null)
45
+ }
46
+
47
+ fun onPause() {
48
+ sendEvent("onRemotePause", null)
49
+ }
50
+
51
+ fun onStop() {
52
+ stopForegroundService()
53
+ sendEvent("onRemoteStop", null)
54
+ }
55
+
56
+ fun onSkipToNext() {
57
+ sendEvent("onRemoteNextTrack", null)
58
+ }
59
+
60
+ fun onSkipToPrevious() {
61
+ sendEvent("onRemotePreviousTrack", null)
62
+ }
63
+
64
+ fun onFastForward() {
65
+ sendEvent("onRemoteSkipForward", null)
66
+ }
67
+
68
+ fun onRewind() {
69
+ sendEvent("onRemoteSkipBackward", null)
70
+ }
71
+
72
+ fun onInterruption(values: Map<String, Any>) {
73
+ sendEvent("onInterruption", values)
74
+ }
75
+
76
+ private fun stopForegroundService() {
77
+ NotificationManagerCompat.from(reactContext).cancel(notificationId)
78
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
79
+ val myIntent =
80
+ Intent(reactContext, MediaNotificationManager.NotificationService::class.java)
81
+ reactContext.stopService(myIntent)
82
+ }
83
+ }
84
+ }
@@ -0,0 +1,144 @@
1
+ package com.swmansion.audioapi.system
2
+
3
+ import android.app.NotificationChannel
4
+ import android.app.NotificationManager
5
+ import android.content.ComponentName
6
+ import android.content.Context
7
+ import android.content.Intent
8
+ import android.content.IntentFilter
9
+ import android.content.ServiceConnection
10
+ import android.media.AudioManager
11
+ import android.os.Build
12
+ import android.os.IBinder
13
+ import android.support.v4.media.session.MediaSessionCompat
14
+ import android.util.Log
15
+ import androidx.annotation.RequiresApi
16
+ import androidx.core.app.NotificationCompat
17
+ import androidx.core.content.ContextCompat
18
+ import com.facebook.react.bridge.ReactApplicationContext
19
+ import com.facebook.react.bridge.ReadableMap
20
+
21
+ class MediaSessionManager(
22
+ val reactContext: ReactApplicationContext,
23
+ ) {
24
+ val notificationId = 100
25
+ val channelId = "react-native-audio-api"
26
+
27
+ private val audioManager: AudioManager = reactContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
28
+ val mediaSession: MediaSessionCompat = MediaSessionCompat(reactContext, "MediaSessionManager")
29
+ val mediaNotificationManager: MediaNotificationManager
30
+ private val lockScreenManager: LockScreenManager
31
+ val eventEmitter: MediaSessionEventEmitter =
32
+ MediaSessionEventEmitter(reactContext, notificationId)
33
+ private val audioFocusListener: AudioFocusListener
34
+ private val mediaReceiver: MediaReceiver =
35
+ MediaReceiver(reactContext, this)
36
+
37
+ private val connection =
38
+ object : ServiceConnection {
39
+ override fun onServiceConnected(
40
+ name: ComponentName,
41
+ service: IBinder,
42
+ ) {
43
+ Log.w("MediaSessionManager", "onServiceConnected")
44
+ val binder = service as MediaNotificationManager.NotificationService.LocalBinder
45
+ val notificationService = binder.getService()
46
+ notificationService?.forceForeground()
47
+ reactContext.unbindService(this)
48
+ }
49
+
50
+ override fun onServiceDisconnected(name: ComponentName) {
51
+ Log.w("MediaSessionManager", "Service is disconnected.")
52
+ }
53
+
54
+ override fun onBindingDied(name: ComponentName) {
55
+ Log.w("MediaSessionManager", "Binding has died.")
56
+ }
57
+
58
+ override fun onNullBinding(name: ComponentName) {
59
+ Log.w("MediaSessionManager", "Bind was null.")
60
+ }
61
+ }
62
+
63
+ init {
64
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
65
+ createChannel()
66
+ }
67
+
68
+ this.mediaNotificationManager = MediaNotificationManager(reactContext, notificationId, channelId)
69
+ this.lockScreenManager = LockScreenManager(reactContext, mediaSession, mediaNotificationManager, channelId)
70
+ this.mediaSession.setCallback(MediaSessionCallback(eventEmitter, lockScreenManager))
71
+
72
+ val filter = IntentFilter()
73
+ filter.addAction(MediaNotificationManager.REMOVE_NOTIFICATION)
74
+ filter.addAction(MediaNotificationManager.MEDIA_BUTTON)
75
+ filter.addAction(Intent.ACTION_MEDIA_BUTTON)
76
+ filter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
77
+
78
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
79
+ reactContext.registerReceiver(mediaReceiver, filter, Context.RECEIVER_EXPORTED)
80
+ } else {
81
+ ContextCompat.registerReceiver(
82
+ reactContext,
83
+ mediaReceiver,
84
+ filter,
85
+ ContextCompat.RECEIVER_NOT_EXPORTED,
86
+ )
87
+ }
88
+
89
+ this.audioFocusListener = AudioFocusListener(audioManager, eventEmitter, lockScreenManager)
90
+
91
+ val myIntent = Intent(reactContext, MediaNotificationManager.NotificationService::class.java)
92
+
93
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
94
+ try {
95
+ reactContext.bindService(myIntent, connection, Context.BIND_AUTO_CREATE)
96
+ } catch (ignored: Exception) {
97
+ ContextCompat.startForegroundService(reactContext, myIntent)
98
+ }
99
+ } else {
100
+ reactContext.startService(myIntent)
101
+ }
102
+ }
103
+
104
+ fun setLockScreenInfo(info: ReadableMap?) {
105
+ lockScreenManager.setLockScreenInfo(info)
106
+ }
107
+
108
+ fun resetLockScreenInfo() {
109
+ lockScreenManager.resetLockScreenInfo()
110
+ }
111
+
112
+ fun enableRemoteCommand(
113
+ name: String,
114
+ enabled: Boolean,
115
+ ) {
116
+ lockScreenManager.enableRemoteCommand(name, enabled)
117
+ }
118
+
119
+ fun getDevicePreferredSampleRate(): Double {
120
+ val sampleRate = this.audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE)
121
+ return sampleRate.toDouble()
122
+ }
123
+
124
+ fun observeAudioInterruptions(observe: Boolean) {
125
+ if (observe) {
126
+ audioFocusListener.requestAudioFocus()
127
+ } else {
128
+ audioFocusListener.abandonAudioFocus()
129
+ }
130
+ }
131
+
132
+ @RequiresApi(Build.VERSION_CODES.O)
133
+ private fun createChannel() {
134
+ val notificationManager =
135
+ reactContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
136
+
137
+ val mChannel =
138
+ NotificationChannel(channelId, "Audio manager", NotificationManager.IMPORTANCE_LOW)
139
+ mChannel.description = "Audio manager"
140
+ mChannel.setShowBadge(false)
141
+ mChannel.lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
142
+ notificationManager.createNotificationChannel(mChannel)
143
+ }
144
+ }
@@ -0,0 +1,9 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="24dp"
3
+ android:height="24dp"
4
+ android:viewportWidth="24.0"
5
+ android:viewportHeight="24.0">
6
+ <path
7
+ android:fillColor="#FFFFFFFF"
8
+ android:pathData="M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z"/>
9
+ </vector>
@@ -0,0 +1,9 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="24dp"
3
+ android:height="24dp"
4
+ android:viewportWidth="24.0"
5
+ android:viewportHeight="24.0">
6
+ <path
7
+ android:fillColor="#FFFFFFFF"
8
+ android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z"/>
9
+ </vector>
@@ -0,0 +1,9 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="24dp"
3
+ android:height="24dp"
4
+ android:viewportWidth="24.0"
5
+ android:viewportHeight="24.0">
6
+ <path
7
+ android:fillColor="#FFFFFFFF"
8
+ android:pathData="M8,5v14l11,-7z"/>
9
+ </vector>
@@ -0,0 +1,9 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="24dp"
3
+ android:height="24dp"
4
+ android:viewportWidth="24.0"
5
+ android:viewportHeight="24.0">
6
+ <path
7
+ android:fillColor="#FFFFFFFF"
8
+ android:pathData="M6,6h2v12L6,18zM9.5,12l8.5,6L18,6z"/>
9
+ </vector>
@@ -0,0 +1,9 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="24dp"
3
+ android:height="24dp"
4
+ android:viewportWidth="24.0"
5
+ android:viewportHeight="24.0">
6
+ <path
7
+ android:fillColor="#FFFFFFFF"
8
+ android:pathData="M12,5L12,1L7,6l5,5L12,7c3.3,0 6,2.7 6,6s-2.7,6 -6,6 -6,-2.7 -6,-6L4,13c0,4.4 3.6,8 8,8s8,-3.6 8,-8 -3.6,-8 -8,-8zM10.7,13.9l0.2,-2.2h2.4v0.7h-1.7l-0.1,0.9s0.1,0 0.1,-0.1 0.1,0 0.1,-0.1 0.1,0 0.2,0h0.2c0.2,0 0.4,0 0.5,0.1s0.3,0.2 0.4,0.3 0.2,0.3 0.3,0.5 0.1,0.4 0.1,0.6c0,0.2 0,0.4 -0.1,0.5s-0.1,0.3 -0.3,0.5 -0.3,0.2 -0.4,0.3 -0.4,0.1 -0.6,0.1c-0.2,0 -0.4,0 -0.5,-0.1s-0.3,-0.1 -0.5,-0.2 -0.2,-0.2 -0.3,-0.4 -0.1,-0.3 -0.1,-0.5h0.8c0,0.2 0.1,0.3 0.2,0.4s0.2,0.1 0.4,0.1c0.1,0 0.2,0 0.3,-0.1l0.2,-0.2s0.1,-0.2 0.1,-0.3v-0.6l-0.1,-0.2 -0.2,-0.2s-0.2,-0.1 -0.3,-0.1h-0.2s-0.1,0 -0.2,0.1 -0.1,0 -0.1,0.1 -0.1,0.1 -0.1,0.1h-0.7z"/>
9
+ </vector>
@@ -0,0 +1,9 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="24dp"
3
+ android:height="24dp"
4
+ android:viewportWidth="24.0"
5
+ android:viewportHeight="24.0">
6
+ <path
7
+ android:fillColor="#FFFFFFFF"
8
+ android:pathData="M4,13c0,4.4 3.6,8 8,8s8,-3.6 8,-8h-2c0,3.3 -2.7,6 -6,6s-6,-2.7 -6,-6 2.7,-6 6,-6v4l5,-5 -5,-5v4c-4.4,0 -8,3.6 -8,8zM10.7,13.9l0.2,-2.2h2.4v0.7h-1.7l-0.1,0.9s0.1,0 0.1,-0.1 0.1,0 0.1,-0.1 0.1,0 0.2,0h0.2c0.2,0 0.4,0 0.5,0.1s0.3,0.2 0.4,0.3 0.2,0.3 0.3,0.5 0.1,0.4 0.1,0.6c0,0.2 0,0.4 -0.1,0.5s-0.1,0.3 -0.3,0.5 -0.3,0.2 -0.5,0.3 -0.4,0.1 -0.6,0.1c-0.2,0 -0.4,0 -0.5,-0.1s-0.3,-0.1 -0.5,-0.2 -0.2,-0.2 -0.3,-0.4 -0.1,-0.3 -0.1,-0.5h0.8c0,0.2 0.1,0.3 0.2,0.4s0.2,0.1 0.4,0.1c0.1,0 0.2,0 0.3,-0.1l0.2,-0.2s0.1,-0.2 0.1,-0.3v-0.6l-0.1,-0.2 -0.2,-0.2s-0.2,-0.1 -0.3,-0.1h-0.2s-0.1,0 -0.2,0.1 -0.1,0 -0.1,0.1 -0.1,0.1 -0.1,0.1h-0.6z"/>
9
+ </vector>
@@ -0,0 +1,9 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="24dp"
3
+ android:height="24dp"
4
+ android:viewportWidth="24.0"
5
+ android:viewportHeight="24.0">
6
+ <path
7
+ android:fillColor="#FFFFFFFF"
8
+ android:pathData="M6,6h12v12H6z"/>
9
+ </vector>
package/app.plugin.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./lib/commonjs/plugin/withAudioAPI');
@@ -128,12 +128,7 @@ void AudioBufferSourceNode::start(double when, double offset, double duration) {
128
128
  }
129
129
 
130
130
  void AudioBufferSourceNode::disable() {
131
- AudioNode::disable();
132
-
133
- if (onendedCallback_) {
134
- onendedCallback_(getStopTime());
135
- }
136
-
131
+ AudioScheduledSourceNode::disable();
137
132
  alignedBus_.reset();
138
133
  }
139
134