react-native-audio-api 0.11.0-nightly-b30bac9-20260114 → 0.11.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/cpp/audioapi/android/core/AndroidAudioRecorder.cpp +3 -10
- package/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp +0 -4
- package/android/src/main/java/com/swmansion/audioapi/AudioAPIModule.kt +4 -83
- package/android/src/main/java/com/swmansion/audioapi/system/CentralizedForegroundService.kt +14 -29
- package/android/src/main/java/com/swmansion/audioapi/system/ForegroundServiceManager.kt +10 -9
- package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt +10 -51
- package/android/src/main/java/com/swmansion/audioapi/system/notification/BaseNotification.kt +6 -14
- package/android/src/main/java/com/swmansion/audioapi/system/notification/NotificationRegistry.kt +79 -60
- package/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt +249 -411
- package/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotificationReceiver.kt +8 -3
- package/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotification.kt +240 -222
- package/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotificationReceiver.kt +11 -22
- package/android/src/main/java/com/swmansion/audioapi/system/notification/state/RecordingNotificationState.kt +24 -0
- package/android/src/main/res/layout/btn_round_ripple.xml +9 -0
- package/android/src/main/res/layout/notification_collapsed.xml +45 -0
- package/android/src/main/res/layout/notification_expanded.xml +44 -0
- package/android/src/oldarch/NativeAudioAPIModuleSpec.java +1 -13
- package/common/cpp/audioapi/core/utils/AudioFileWriter.cpp +1 -1
- package/ios/audioapi/ios/AudioAPIModule.mm +5 -48
- package/ios/audioapi/ios/system/notification/BaseNotification.h +0 -7
- package/ios/audioapi/ios/system/notification/NotificationRegistry.h +5 -25
- package/ios/audioapi/ios/system/notification/NotificationRegistry.mm +19 -64
- package/ios/audioapi/ios/system/notification/PlaybackNotification.mm +4 -15
- package/lib/commonjs/AudioAPIModule/AudioAPIModule.js +2 -1
- package/lib/commonjs/AudioAPIModule/AudioAPIModule.js.map +1 -1
- package/lib/commonjs/api.js +1 -29
- package/lib/commonjs/api.js.map +1 -1
- package/lib/commonjs/core/AudioDecoder.js +42 -16
- package/lib/commonjs/core/AudioDecoder.js.map +1 -1
- package/lib/commonjs/core/AudioRecorder.js +2 -1
- package/lib/commonjs/core/AudioRecorder.js.map +1 -1
- package/lib/commonjs/core/AudioStretcher.js +2 -1
- package/lib/commonjs/core/AudioStretcher.js.map +1 -1
- package/lib/commonjs/core/BaseAudioContext.js +2 -5
- package/lib/commonjs/core/BaseAudioContext.js.map +1 -1
- package/lib/commonjs/errors/AudioApiError.js +14 -0
- package/lib/commonjs/errors/AudioApiError.js.map +1 -0
- package/lib/commonjs/errors/index.js +7 -0
- package/lib/commonjs/errors/index.js.map +1 -1
- package/lib/commonjs/specs/NativeAudioAPIModule.js.map +1 -1
- package/lib/commonjs/specs/NativeAudioAPIModule.web.js +0 -9
- package/lib/commonjs/specs/NativeAudioAPIModule.web.js.map +1 -1
- package/lib/commonjs/system/notification/PlaybackNotificationManager.js +40 -85
- package/lib/commonjs/system/notification/PlaybackNotificationManager.js.map +1 -1
- package/lib/commonjs/system/notification/RecordingNotificationManager.ios.js +51 -0
- package/lib/commonjs/system/notification/RecordingNotificationManager.ios.js.map +1 -0
- package/lib/commonjs/system/notification/RecordingNotificationManager.js +30 -144
- package/lib/commonjs/system/notification/RecordingNotificationManager.js.map +1 -1
- package/lib/commonjs/system/notification/index.js +1 -9
- package/lib/commonjs/system/notification/index.js.map +1 -1
- package/lib/commonjs/utils/index.js +3 -2
- package/lib/commonjs/utils/index.js.map +1 -1
- package/lib/commonjs/utils/paths.js +18 -0
- package/lib/commonjs/utils/paths.js.map +1 -0
- package/lib/commonjs/web-core/AudioContext.js +20 -11
- package/lib/commonjs/web-core/AudioContext.js.map +1 -1
- package/lib/commonjs/web-system/notification/PlaybackNotificationManager.js +0 -1
- package/lib/commonjs/web-system/notification/PlaybackNotificationManager.js.map +1 -1
- package/lib/commonjs/web-system/notification/RecordingNotificationManager.js +1 -6
- package/lib/commonjs/web-system/notification/RecordingNotificationManager.js.map +1 -1
- package/lib/module/AudioAPIModule/AudioAPIModule.js +2 -1
- package/lib/module/AudioAPIModule/AudioAPIModule.js.map +1 -1
- package/lib/module/api.js +3 -2
- package/lib/module/api.js.map +1 -1
- package/lib/module/core/AudioDecoder.js +42 -16
- package/lib/module/core/AudioDecoder.js.map +1 -1
- package/lib/module/core/AudioRecorder.js +3 -1
- package/lib/module/core/AudioRecorder.js.map +1 -1
- package/lib/module/core/AudioStretcher.js +2 -1
- package/lib/module/core/AudioStretcher.js.map +1 -1
- package/lib/module/core/BaseAudioContext.js +2 -5
- package/lib/module/core/BaseAudioContext.js.map +1 -1
- package/lib/module/errors/AudioApiError.js +10 -0
- package/lib/module/errors/AudioApiError.js.map +1 -0
- package/lib/module/errors/index.js +1 -0
- package/lib/module/errors/index.js.map +1 -1
- package/lib/module/specs/NativeAudioAPIModule.js.map +1 -1
- package/lib/module/specs/NativeAudioAPIModule.web.js +0 -9
- package/lib/module/specs/NativeAudioAPIModule.web.js.map +1 -1
- package/lib/module/system/notification/PlaybackNotificationManager.js +40 -85
- package/lib/module/system/notification/PlaybackNotificationManager.js.map +1 -1
- package/lib/module/system/notification/RecordingNotificationManager.ios.js +47 -0
- package/lib/module/system/notification/RecordingNotificationManager.ios.js.map +1 -0
- package/lib/module/system/notification/RecordingNotificationManager.js +30 -144
- package/lib/module/system/notification/RecordingNotificationManager.js.map +1 -1
- package/lib/module/system/notification/index.js +1 -2
- package/lib/module/system/notification/index.js.map +1 -1
- package/lib/module/utils/index.js +3 -2
- package/lib/module/utils/index.js.map +1 -1
- package/lib/module/utils/paths.js +12 -0
- package/lib/module/utils/paths.js.map +1 -0
- package/lib/module/web-core/AudioContext.js +20 -11
- package/lib/module/web-core/AudioContext.js.map +1 -1
- package/lib/module/web-system/notification/PlaybackNotificationManager.js +0 -1
- package/lib/module/web-system/notification/PlaybackNotificationManager.js.map +1 -1
- package/lib/module/web-system/notification/RecordingNotificationManager.js +1 -6
- package/lib/module/web-system/notification/RecordingNotificationManager.js.map +1 -1
- package/lib/typescript/AudioAPIModule/AudioAPIModule.d.ts.map +1 -1
- package/lib/typescript/api.d.ts +3 -2
- package/lib/typescript/api.d.ts.map +1 -1
- package/lib/typescript/core/AudioDecoder.d.ts +2 -1
- package/lib/typescript/core/AudioDecoder.d.ts.map +1 -1
- package/lib/typescript/core/AudioRecorder.d.ts.map +1 -1
- package/lib/typescript/core/AudioStretcher.d.ts.map +1 -1
- package/lib/typescript/core/BaseAudioContext.d.ts +2 -2
- package/lib/typescript/core/BaseAudioContext.d.ts.map +1 -1
- package/lib/typescript/errors/AudioApiError.d.ts +5 -0
- package/lib/typescript/errors/AudioApiError.d.ts.map +1 -0
- package/lib/typescript/errors/index.d.ts +1 -0
- package/lib/typescript/errors/index.d.ts.map +1 -1
- package/lib/typescript/interfaces.d.ts +1 -1
- package/lib/typescript/interfaces.d.ts.map +1 -1
- package/lib/typescript/specs/NativeAudioAPIModule.d.ts +2 -5
- package/lib/typescript/specs/NativeAudioAPIModule.d.ts.map +1 -1
- package/lib/typescript/specs/NativeAudioAPIModule.web.d.ts +1 -4
- package/lib/typescript/specs/NativeAudioAPIModule.web.d.ts.map +1 -1
- package/lib/typescript/system/notification/PlaybackNotificationManager.d.ts +32 -9
- package/lib/typescript/system/notification/PlaybackNotificationManager.d.ts.map +1 -1
- package/lib/typescript/system/notification/RecordingNotificationManager.d.ts +26 -13
- package/lib/typescript/system/notification/RecordingNotificationManager.d.ts.map +1 -1
- package/lib/typescript/system/notification/RecordingNotificationManager.ios.d.ts +36 -0
- package/lib/typescript/system/notification/RecordingNotificationManager.ios.d.ts.map +1 -0
- package/lib/typescript/system/notification/index.d.ts +0 -1
- package/lib/typescript/system/notification/index.d.ts.map +1 -1
- package/lib/typescript/system/notification/types.d.ts +12 -22
- package/lib/typescript/system/notification/types.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +1 -0
- package/lib/typescript/types.d.ts.map +1 -1
- package/lib/typescript/utils/index.d.ts.map +1 -1
- package/lib/typescript/utils/paths.d.ts +4 -0
- package/lib/typescript/utils/paths.d.ts.map +1 -0
- package/lib/typescript/web-core/AudioContext.d.ts +8 -9
- package/lib/typescript/web-core/AudioContext.d.ts.map +1 -1
- package/lib/typescript/web-core/BaseAudioContext.d.ts +6 -7
- package/lib/typescript/web-core/BaseAudioContext.d.ts.map +1 -1
- package/lib/typescript/web-system/notification/PlaybackNotificationManager.d.ts +1 -2
- package/lib/typescript/web-system/notification/PlaybackNotificationManager.d.ts.map +1 -1
- package/lib/typescript/web-system/notification/RecordingNotificationManager.d.ts +2 -7
- package/lib/typescript/web-system/notification/RecordingNotificationManager.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/AudioAPIModule/AudioAPIModule.ts +2 -1
- package/src/api.ts +2 -8
- package/src/core/AudioDecoder.ts +91 -21
- package/src/core/AudioRecorder.ts +2 -1
- package/src/core/AudioStretcher.ts +2 -1
- package/src/core/BaseAudioContext.ts +4 -6
- package/src/errors/AudioApiError.ts +8 -0
- package/src/errors/index.ts +1 -0
- package/src/interfaces.ts +1 -1
- package/src/specs/NativeAudioAPIModule.ts +5 -15
- package/src/specs/NativeAudioAPIModule.web.ts +1 -12
- package/src/system/notification/PlaybackNotificationManager.ts +42 -117
- package/src/system/notification/RecordingNotificationManager.ios.ts +65 -0
- package/src/system/notification/RecordingNotificationManager.ts +33 -183
- package/src/system/notification/index.ts +0 -1
- package/src/system/notification/types.ts +15 -37
- package/src/types.ts +2 -0
- package/src/utils/index.ts +3 -2
- package/src/utils/paths.ts +11 -0
- package/src/web-core/AudioContext.tsx +34 -19
- package/src/web-core/BaseAudioContext.tsx +9 -7
- package/src/web-system/notification/PlaybackNotificationManager.ts +1 -7
- package/src/web-system/notification/RecordingNotificationManager.ts +1 -16
- package/android/src/main/java/com/swmansion/audioapi/core/NativeAudioPlayer.kt +0 -26
- package/android/src/main/java/com/swmansion/audioapi/core/NativeAudioRecorder.kt +0 -26
- package/android/src/main/java/com/swmansion/audioapi/system/notification/SimpleNotification.kt +0 -119
- package/lib/commonjs/system/notification/SimpleNotificationManager.js +0 -125
- package/lib/commonjs/system/notification/SimpleNotificationManager.js.map +0 -1
- package/lib/module/system/notification/SimpleNotificationManager.js +0 -121
- package/lib/module/system/notification/SimpleNotificationManager.js.map +0 -1
- package/lib/typescript/system/notification/SimpleNotificationManager.d.ts +0 -21
- package/lib/typescript/system/notification/SimpleNotificationManager.d.ts.map +0 -1
- package/src/system/notification/SimpleNotificationManager.ts +0 -175
|
@@ -3,7 +3,6 @@ package com.swmansion.audioapi.system.notification
|
|
|
3
3
|
import android.content.BroadcastReceiver
|
|
4
4
|
import android.content.Context
|
|
5
5
|
import android.content.Intent
|
|
6
|
-
import android.util.Log
|
|
7
6
|
import com.swmansion.audioapi.AudioAPIModule
|
|
8
7
|
|
|
9
8
|
/**
|
|
@@ -12,7 +11,8 @@ import com.swmansion.audioapi.AudioAPIModule
|
|
|
12
11
|
class PlaybackNotificationReceiver : BroadcastReceiver() {
|
|
13
12
|
companion object {
|
|
14
13
|
const val ACTION_NOTIFICATION_DISMISSED = "com.swmansion.audioapi.PLAYBACK_NOTIFICATION_DISMISSED"
|
|
15
|
-
|
|
14
|
+
const val ACTION_SKIP_FORWARD = "com.swmansion.audioapi.ACTION_SKIP_FORWARD"
|
|
15
|
+
const val ACTION_SKIP_BACKWARD = "com.swmansion.audioapi.ACTION_SKIP_BACKWARD"
|
|
16
16
|
|
|
17
17
|
private var audioAPIModule: AudioAPIModule? = null
|
|
18
18
|
|
|
@@ -26,8 +26,13 @@ class PlaybackNotificationReceiver : BroadcastReceiver() {
|
|
|
26
26
|
intent: Intent?,
|
|
27
27
|
) {
|
|
28
28
|
if (intent?.action == ACTION_NOTIFICATION_DISMISSED) {
|
|
29
|
-
Log.d(TAG, "Notification dismissed by user")
|
|
30
29
|
audioAPIModule?.invokeHandlerWithEventNameAndEventBody("playbackNotificationDismissed", mapOf())
|
|
30
|
+
} else if (intent?.action == ACTION_SKIP_FORWARD) {
|
|
31
|
+
val body = HashMap<String, Any>().apply { put("value", 15) }
|
|
32
|
+
audioAPIModule?.invokeHandlerWithEventNameAndEventBody("playbackNotificationSkipForward", body)
|
|
33
|
+
} else if (intent?.action == ACTION_SKIP_BACKWARD) {
|
|
34
|
+
val body = HashMap<String, Any>().apply { put("value", 15) }
|
|
35
|
+
audioAPIModule?.invokeHandlerWithEventNameAndEventBody("playbackNotificationSkipBackward", body)
|
|
31
36
|
}
|
|
32
37
|
}
|
|
33
38
|
}
|
package/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotification.kt
CHANGED
|
@@ -4,300 +4,318 @@ import android.app.Notification
|
|
|
4
4
|
import android.app.NotificationChannel
|
|
5
5
|
import android.app.NotificationManager
|
|
6
6
|
import android.app.PendingIntent
|
|
7
|
+
import android.content.ComponentCallbacks
|
|
7
8
|
import android.content.Context
|
|
8
9
|
import android.content.Intent
|
|
9
10
|
import android.content.IntentFilter
|
|
11
|
+
import android.content.res.Configuration
|
|
10
12
|
import android.graphics.Color
|
|
13
|
+
import android.graphics.drawable.Icon
|
|
11
14
|
import android.os.Build
|
|
12
15
|
import android.util.Log
|
|
16
|
+
import android.widget.RemoteViews
|
|
17
|
+
import androidx.annotation.RequiresApi
|
|
13
18
|
import androidx.core.app.NotificationCompat
|
|
19
|
+
import androidx.core.content.ContextCompat
|
|
14
20
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
15
21
|
import com.facebook.react.bridge.ReadableMap
|
|
16
22
|
import com.swmansion.audioapi.AudioAPIModule
|
|
23
|
+
import com.swmansion.audioapi.R
|
|
24
|
+
import com.swmansion.audioapi.system.notification.state.RecordingNotificationState
|
|
17
25
|
import java.lang.ref.WeakReference
|
|
18
26
|
|
|
19
|
-
/**
|
|
20
|
-
* RecordingNotification
|
|
21
|
-
*
|
|
22
|
-
* Simple notification for audio recording:
|
|
23
|
-
* - Shows recording status with red background when recording
|
|
24
|
-
* - Simple start/stop button with microphone icon
|
|
25
|
-
* - Is persistent and cannot be swiped away when recording
|
|
26
|
-
* - Notifies its dismissal via RecordingNotificationReceiver
|
|
27
|
-
*/
|
|
28
27
|
class RecordingNotification(
|
|
29
28
|
private val reactContext: WeakReference<ReactApplicationContext>,
|
|
30
29
|
private val audioAPIModule: WeakReference<AudioAPIModule>,
|
|
31
30
|
private val notificationId: Int,
|
|
32
31
|
private val channelId: String,
|
|
33
|
-
) : BaseNotification
|
|
32
|
+
) : BaseNotification,
|
|
33
|
+
ComponentCallbacks {
|
|
34
34
|
companion object {
|
|
35
35
|
private const val TAG = "RecordingNotification"
|
|
36
|
-
const val
|
|
37
|
-
const val ACTION_STOP = "com.swmansion.audioapi.RECORDING_STOP"
|
|
36
|
+
const val ID = 200
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
private var
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
private var state: RecordingNotificationState =
|
|
40
|
+
RecordingNotificationState(
|
|
41
|
+
darkTheme =
|
|
42
|
+
reactContext
|
|
43
|
+
.get()!!
|
|
44
|
+
.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES,
|
|
45
|
+
initialized = false,
|
|
46
|
+
)
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
private fun initializeNotification() {
|
|
49
49
|
val context = reactContext.get() ?: throw IllegalStateException("React context is null")
|
|
50
|
+
if (!state.initialized) {
|
|
51
|
+
context.registerComponentCallbacks(this)
|
|
52
|
+
createNotificationChannel(context)
|
|
53
|
+
state.receiver =
|
|
54
|
+
RecordingNotificationReceiver(audioAPIModule.get()!!)
|
|
55
|
+
val filter =
|
|
56
|
+
IntentFilter().apply {
|
|
57
|
+
addAction(RecordingNotificationReceiver.NOTIFICATION_RECORDING_STOPPED)
|
|
58
|
+
addAction(RecordingNotificationReceiver.NOTIFICATION_RECORDING_RESUMED)
|
|
59
|
+
}
|
|
60
|
+
ContextCompat.registerReceiver(
|
|
61
|
+
context,
|
|
62
|
+
state.receiver,
|
|
63
|
+
filter,
|
|
64
|
+
ContextCompat.RECEIVER_NOT_EXPORTED,
|
|
65
|
+
)
|
|
50
66
|
|
|
51
|
-
|
|
52
|
-
|
|
67
|
+
state.pauseIntent =
|
|
68
|
+
Intent(RecordingNotificationReceiver.NOTIFICATION_RECORDING_STOPPED).apply {
|
|
69
|
+
`package` = context.packageName
|
|
70
|
+
}
|
|
53
71
|
|
|
54
|
-
|
|
55
|
-
|
|
72
|
+
state.resumeIntent =
|
|
73
|
+
Intent(RecordingNotificationReceiver.NOTIFICATION_RECORDING_RESUMED).apply {
|
|
74
|
+
`package` = context.packageName
|
|
75
|
+
}
|
|
76
|
+
state.darkTheme = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
|
|
77
|
+
state.initialized = true
|
|
78
|
+
}
|
|
79
|
+
}
|
|
56
80
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
|
66
|
-
.setOngoing(false)
|
|
67
|
-
.setAutoCancel(false)
|
|
81
|
+
override fun show(options: ReadableMap?): Notification {
|
|
82
|
+
initializeNotification()
|
|
83
|
+
val context = reactContext.get() ?: throw IllegalStateException("React context is null")
|
|
84
|
+
if (options != state.cachedRNOptions) {
|
|
85
|
+
state.cachedRNOptions = options
|
|
86
|
+
parseMapFromRN(options)
|
|
87
|
+
}
|
|
88
|
+
val builder = getBuilder()
|
|
68
89
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
90
|
+
if (state.smallIconResourceName != null) {
|
|
91
|
+
builder.setSmallIcon(context.resources.getIdentifier(state.smallIconResourceName, "drawable", context.packageName))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (state.largeIconResourceName != null) {
|
|
95
|
+
val icon =
|
|
96
|
+
Icon.createWithResource(
|
|
75
97
|
context,
|
|
76
|
-
|
|
77
|
-
openAppIntent,
|
|
78
|
-
PendingIntent.FLAG_IMMUTABLE,
|
|
98
|
+
context.resources.getIdentifier(state.largeIconResourceName, "drawable", context.packageName),
|
|
79
99
|
)
|
|
80
|
-
|
|
100
|
+
builder.setLargeIcon(icon)
|
|
81
101
|
}
|
|
82
102
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
deleteIntent.setPackage(context.packageName)
|
|
86
|
-
val deletePendingIntent =
|
|
87
|
-
PendingIntent.getBroadcast(
|
|
88
|
-
context,
|
|
89
|
-
notificationId,
|
|
90
|
-
deleteIntent,
|
|
91
|
-
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
|
|
92
|
-
)
|
|
93
|
-
notificationBuilder?.setDeleteIntent(deletePendingIntent)
|
|
94
|
-
|
|
95
|
-
// Apply initial params if provided
|
|
96
|
-
if (params != null) {
|
|
97
|
-
update(params)
|
|
103
|
+
if (state.backgroundColor != null) {
|
|
104
|
+
builder.setColor(state.backgroundColor!!)
|
|
98
105
|
}
|
|
99
106
|
|
|
100
|
-
|
|
101
|
-
|
|
107
|
+
val collapsedView = RemoteViews(context.packageName, R.layout.notification_collapsed)
|
|
108
|
+
val expandedView = RemoteViews(context.packageName, R.layout.notification_expanded)
|
|
102
109
|
|
|
103
|
-
|
|
104
|
-
// Unregister receiver
|
|
105
|
-
unregisterReceiver()
|
|
110
|
+
val (pauseResumePendingIntent, iconId) = setupPauseResumeIntent(context)
|
|
106
111
|
|
|
107
|
-
|
|
108
|
-
title = "Audio Recording"
|
|
109
|
-
description = "Ready to record"
|
|
110
|
-
isRecording = false
|
|
111
|
-
notificationBuilder = null
|
|
112
|
-
}
|
|
112
|
+
setupRemoteView(listOf(collapsedView, expandedView), pauseResumePendingIntent, iconId)
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
builder
|
|
115
|
+
.setStyle(NotificationCompat.DecoratedCustomViewStyle())
|
|
116
|
+
.setCustomContentView(collapsedView)
|
|
117
|
+
.setCustomBigContentView(expandedView)
|
|
118
|
+
.setContentTitle(state.title)
|
|
119
|
+
.setContentText(state.contentText)
|
|
115
120
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
override fun update(options: ReadableMap?): Notification {
|
|
119
|
-
if (options == null) {
|
|
120
|
-
return buildNotification()
|
|
121
|
+
if (state.backgroundColor != null) {
|
|
122
|
+
builder.setColor(state.backgroundColor!!)
|
|
121
123
|
}
|
|
122
124
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
val control = options.getString("control")
|
|
126
|
-
val enabled = options.getBoolean("enabled")
|
|
127
|
-
when (control) {
|
|
128
|
-
"start" -> startEnabled = enabled
|
|
129
|
-
"stop" -> stopEnabled = enabled
|
|
130
|
-
}
|
|
131
|
-
updateActions()
|
|
132
|
-
return buildNotification()
|
|
133
|
-
}
|
|
125
|
+
return builder.build()
|
|
126
|
+
}
|
|
134
127
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
128
|
+
private fun setupPauseResumeIntent(context: Context): Pair<PendingIntent, Int> {
|
|
129
|
+
val pauseResumeIntent =
|
|
130
|
+
if (state.paused) {
|
|
131
|
+
state.resumeIntent
|
|
132
|
+
} else {
|
|
133
|
+
state.pauseIntent
|
|
134
|
+
}
|
|
139
135
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
136
|
+
val pauseResumePendingIntent =
|
|
137
|
+
PendingIntent.getBroadcast(
|
|
138
|
+
context,
|
|
139
|
+
0,
|
|
140
|
+
pauseResumeIntent!!,
|
|
141
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
|
|
142
|
+
)
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
144
|
+
val pauseId =
|
|
145
|
+
if (state.pauseIconResourceName != null) {
|
|
146
|
+
context.resources.getIdentifier(state.pauseIconResourceName, "drawable", context.packageName)
|
|
147
|
+
} else {
|
|
148
|
+
android.R.drawable.ic_media_pause
|
|
149
149
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (isRecording) "Recording..." else "Ready to record"
|
|
150
|
+
val resumeId =
|
|
151
|
+
if (state.resumeIconResourceName != null) {
|
|
152
|
+
context.resources.getIdentifier(state.resumeIconResourceName, "drawable", context.packageName)
|
|
153
|
+
} else {
|
|
154
|
+
android.R.drawable.ic_media_play
|
|
156
155
|
}
|
|
157
|
-
notificationBuilder
|
|
158
|
-
?.setContentTitle(title)
|
|
159
|
-
?.setContentText(statusText)
|
|
160
|
-
?.setOngoing(isRecording)
|
|
161
|
-
|
|
162
|
-
// Set red color when recording
|
|
163
|
-
if (isRecording) {
|
|
164
|
-
notificationBuilder
|
|
165
|
-
?.setColor(Color.RED)
|
|
166
|
-
?.setColorized(true)
|
|
167
|
-
} else {
|
|
168
|
-
notificationBuilder
|
|
169
|
-
?.setColorized(false)
|
|
170
|
-
}
|
|
171
156
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
return buildNotification()
|
|
157
|
+
val iconId = if (state.paused) resumeId else pauseId
|
|
158
|
+
return pauseResumePendingIntent to iconId
|
|
176
159
|
}
|
|
177
160
|
|
|
178
|
-
private fun
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
val
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
PendingIntent.getBroadcast(
|
|
196
|
-
context,
|
|
197
|
-
1001,
|
|
198
|
-
stopIntent,
|
|
199
|
-
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
|
|
200
|
-
)
|
|
201
|
-
val stopAction =
|
|
202
|
-
NotificationCompat.Action
|
|
203
|
-
.Builder(
|
|
204
|
-
android.R.drawable.ic_delete,
|
|
205
|
-
"Stop",
|
|
206
|
-
stopPendingIntent,
|
|
207
|
-
).build()
|
|
208
|
-
notificationBuilder?.addAction(stopAction)
|
|
209
|
-
} else if (!isRecording && startEnabled) {
|
|
210
|
-
// Show START button when not recording
|
|
211
|
-
val startIntent = Intent(ACTION_START)
|
|
212
|
-
startIntent.setPackage(context.packageName)
|
|
213
|
-
val startPendingIntent =
|
|
214
|
-
PendingIntent.getBroadcast(
|
|
215
|
-
context,
|
|
216
|
-
1000,
|
|
217
|
-
startIntent,
|
|
218
|
-
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
|
|
219
|
-
)
|
|
220
|
-
val startAction =
|
|
221
|
-
NotificationCompat.Action
|
|
222
|
-
.Builder(
|
|
223
|
-
android.R.drawable.ic_btn_speak_now,
|
|
224
|
-
"Record",
|
|
225
|
-
startPendingIntent,
|
|
226
|
-
).build()
|
|
227
|
-
notificationBuilder?.addAction(startAction)
|
|
161
|
+
private fun setupRemoteView(
|
|
162
|
+
views: List<RemoteViews>,
|
|
163
|
+
pauseResumePendingIntent: PendingIntent,
|
|
164
|
+
iconId: Int,
|
|
165
|
+
) {
|
|
166
|
+
val iconColor =
|
|
167
|
+
if (state.darkTheme) {
|
|
168
|
+
Color.WHITE // Dark Mode -> White Icon
|
|
169
|
+
} else {
|
|
170
|
+
Color.BLACK // Light Mode -> Black Icon
|
|
171
|
+
}
|
|
172
|
+
for (view in views) {
|
|
173
|
+
view.setTextViewText(R.id.notification_title, state.title)
|
|
174
|
+
view.setTextViewText(R.id.notification_content, state.contentText)
|
|
175
|
+
view.setImageViewResource(R.id.notification_action_btn, iconId)
|
|
176
|
+
view.setInt(R.id.notification_action_btn, "setColorFilter", iconColor)
|
|
177
|
+
view.setOnClickPendingIntent(R.id.notification_action_btn, pauseResumePendingIntent)
|
|
228
178
|
}
|
|
179
|
+
}
|
|
229
180
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
181
|
+
// not used currently, left for future reference
|
|
182
|
+
// private fun loadBitmapFromUri(
|
|
183
|
+
// context: Context,
|
|
184
|
+
// uriString: String?,
|
|
185
|
+
// ): Bitmap? =
|
|
186
|
+
// try {
|
|
187
|
+
// val uri = android.net.Uri.parse(uriString)
|
|
188
|
+
// val inputStream: InputStream
|
|
189
|
+
// if (uri.scheme == "http" || uri.scheme == "https") {
|
|
190
|
+
// // web URL
|
|
191
|
+
// val connection = java.net.URL(uriString).openConnection()
|
|
192
|
+
// connection.doInput = true
|
|
193
|
+
// connection.connect()
|
|
194
|
+
// inputStream = connection.inputStream
|
|
195
|
+
// } else {
|
|
196
|
+
// // local files
|
|
197
|
+
// inputStream = context.contentResolver.openInputStream(uri)!!
|
|
198
|
+
// }
|
|
199
|
+
// android.graphics.BitmapFactory.decodeStream(inputStream)
|
|
200
|
+
// } catch (e: Exception) {
|
|
201
|
+
// Log.e(TAG, "Failed to load bitmap from URI: $uriString", e)
|
|
202
|
+
// null
|
|
203
|
+
// }
|
|
204
|
+
|
|
205
|
+
private fun getBuilder(): NotificationCompat.Builder {
|
|
206
|
+
val context = reactContext.get() ?: throw IllegalStateException("React context is null")
|
|
207
|
+
if (state.builder == null) {
|
|
208
|
+
val openAppIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
|
|
209
|
+
val pendingIntent = PendingIntent.getActivity(context, 0, openAppIntent, PendingIntent.FLAG_IMMUTABLE)
|
|
210
|
+
|
|
211
|
+
state.builder =
|
|
212
|
+
NotificationCompat
|
|
213
|
+
.Builder(context, channelId)
|
|
214
|
+
.setOngoing(true)
|
|
215
|
+
.setContentIntent(pendingIntent)
|
|
216
|
+
}
|
|
217
|
+
if (state.smallIconResourceName == null) {
|
|
218
|
+
state.builder!!.setSmallIcon(android.R.drawable.ic_btn_speak_now)
|
|
219
|
+
}
|
|
220
|
+
return state.builder!!
|
|
240
221
|
}
|
|
241
222
|
|
|
242
|
-
private fun createNotificationChannel() {
|
|
223
|
+
private fun createNotificationChannel(context: ReactApplicationContext) {
|
|
243
224
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
244
|
-
val context = reactContext.get() ?: return
|
|
245
|
-
|
|
246
225
|
val channel =
|
|
247
226
|
NotificationChannel(
|
|
248
227
|
channelId,
|
|
249
|
-
"Audio
|
|
250
|
-
NotificationManager.
|
|
228
|
+
"Recording Audio",
|
|
229
|
+
NotificationManager.IMPORTANCE_LOW,
|
|
251
230
|
).apply {
|
|
252
|
-
description = "
|
|
253
|
-
setShowBadge(true)
|
|
231
|
+
description = "Notifications for ongoing audio recordings"
|
|
254
232
|
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
|
255
|
-
enableLights(true)
|
|
256
|
-
lightColor = Color.RED
|
|
257
|
-
enableVibration(false)
|
|
258
233
|
}
|
|
259
|
-
|
|
260
234
|
val notificationManager =
|
|
261
235
|
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
262
236
|
notificationManager.createNotificationChannel(channel)
|
|
263
|
-
|
|
264
|
-
Log.d(TAG, "Notification channel created: $channelId")
|
|
265
237
|
}
|
|
238
|
+
Log.d(TAG, "Notification channel created: $channelId")
|
|
266
239
|
}
|
|
267
240
|
|
|
268
|
-
private fun
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
241
|
+
private fun parseMapFromRN(options: ReadableMap?) {
|
|
242
|
+
state.title = if (options?.hasKey("title") == true) options.getString("title") else state.title ?: "Recording Audio"
|
|
243
|
+
state.contentText =
|
|
244
|
+
if (options?.hasKey("contentText") == true) {
|
|
245
|
+
options.getString("contentText")
|
|
246
|
+
} else {
|
|
247
|
+
state.contentText ?: "Audio recording is in progress/paused"
|
|
248
|
+
}
|
|
249
|
+
state.smallIconResourceName =
|
|
250
|
+
if (options?.hasKey("smallIconResourceName") ==
|
|
251
|
+
true
|
|
252
|
+
) {
|
|
253
|
+
options.getString("smallIconResourceName")
|
|
254
|
+
} else {
|
|
255
|
+
state.smallIconResourceName ?: null
|
|
256
|
+
}
|
|
257
|
+
state.largeIconResourceName =
|
|
258
|
+
if (options?.hasKey("largeIconResourceName") ==
|
|
259
|
+
true
|
|
260
|
+
) {
|
|
261
|
+
options.getString("largeIconResourceName")
|
|
262
|
+
} else {
|
|
263
|
+
state.largeIconResourceName ?: null
|
|
264
|
+
}
|
|
265
|
+
state.pauseIconResourceName =
|
|
266
|
+
if (options?.hasKey("pauseIconResourceName") ==
|
|
267
|
+
true
|
|
268
|
+
) {
|
|
269
|
+
options.getString("pauseIconResourceName")
|
|
282
270
|
} else {
|
|
283
|
-
|
|
271
|
+
state.pauseIconResourceName ?: null
|
|
284
272
|
}
|
|
273
|
+
state.resumeIconResourceName =
|
|
274
|
+
if (options?.hasKey("resumeIconResourceName") ==
|
|
275
|
+
true
|
|
276
|
+
) {
|
|
277
|
+
options.getString("resumeIconResourceName")
|
|
278
|
+
} else {
|
|
279
|
+
state.resumeIconResourceName ?: null
|
|
280
|
+
}
|
|
281
|
+
state.backgroundColor = if (options?.hasKey("color") == true) options.getInt("color") else state.backgroundColor ?: null
|
|
282
|
+
state.paused = if (options?.hasKey("paused") == true) options.getBoolean("paused") else false
|
|
283
|
+
}
|
|
285
284
|
|
|
286
|
-
|
|
285
|
+
override fun hide() {
|
|
286
|
+
val context = reactContext.get() ?: throw IllegalStateException("React context is null")
|
|
287
|
+
if (state.receiver != null) {
|
|
288
|
+
context.unregisterReceiver(state.receiver)
|
|
289
|
+
context.unregisterComponentCallbacks(this)
|
|
290
|
+
state.receiver = null
|
|
287
291
|
}
|
|
292
|
+
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
293
|
+
notificationManager.cancel(notificationId)
|
|
294
|
+
state.initialized = false
|
|
295
|
+
state.builder = null
|
|
288
296
|
}
|
|
289
297
|
|
|
290
|
-
|
|
291
|
-
val context = reactContext.get() ?: return
|
|
298
|
+
override fun getNotificationId(): Int = notificationId
|
|
292
299
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
+
override fun getChannelId(): String = channelId
|
|
301
|
+
|
|
302
|
+
@RequiresApi(Build.VERSION_CODES.O)
|
|
303
|
+
override fun onConfigurationChanged(newConfig: Configuration) {
|
|
304
|
+
val currentNightMode = newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
|
|
305
|
+
if (currentNightMode != state.darkTheme) {
|
|
306
|
+
// Theme changed, rebuild notification
|
|
307
|
+
state.darkTheme = currentNightMode
|
|
308
|
+
val notification = show(state.cachedRNOptions)
|
|
309
|
+
val context = reactContext.get()
|
|
310
|
+
if (context != null) {
|
|
311
|
+
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
312
|
+
notificationManager.notify(notificationId, notification)
|
|
300
313
|
}
|
|
301
314
|
}
|
|
302
315
|
}
|
|
316
|
+
|
|
317
|
+
@Deprecated("Deprecated in Java")
|
|
318
|
+
override fun onLowMemory() {
|
|
319
|
+
// left to listen for ui mode changes
|
|
320
|
+
}
|
|
303
321
|
}
|
|
@@ -6,19 +6,13 @@ import android.content.Intent
|
|
|
6
6
|
import android.util.Log
|
|
7
7
|
import com.swmansion.audioapi.AudioAPIModule
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class RecordingNotificationReceiver : BroadcastReceiver() {
|
|
9
|
+
class RecordingNotificationReceiver(
|
|
10
|
+
private val module: AudioAPIModule,
|
|
11
|
+
) : BroadcastReceiver() {
|
|
13
12
|
companion object {
|
|
14
|
-
const val
|
|
13
|
+
const val NOTIFICATION_RECORDING_STOPPED = "com.swmansion.audioapi.NOTIFICATION_RECORDING_STOPPED"
|
|
14
|
+
const val NOTIFICATION_RECORDING_RESUMED = "com.swmansion.audioapi.NOTIFICATION_RECORDING_RESUMED"
|
|
15
15
|
private const val TAG = "RecordingNotificationReceiver"
|
|
16
|
-
|
|
17
|
-
private var audioAPIModule: AudioAPIModule? = null
|
|
18
|
-
|
|
19
|
-
fun setAudioAPIModule(module: AudioAPIModule?) {
|
|
20
|
-
audioAPIModule = module
|
|
21
|
-
}
|
|
22
16
|
}
|
|
23
17
|
|
|
24
18
|
override fun onReceive(
|
|
@@ -26,19 +20,14 @@ class RecordingNotificationReceiver : BroadcastReceiver() {
|
|
|
26
20
|
intent: Intent?,
|
|
27
21
|
) {
|
|
28
22
|
when (intent?.action) {
|
|
29
|
-
|
|
30
|
-
Log.d(TAG, "Recording
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
RecordingNotification.ACTION_START -> {
|
|
35
|
-
Log.d(TAG, "Start recording action received")
|
|
36
|
-
audioAPIModule?.invokeHandlerWithEventNameAndEventBody("recordingNotificationStart", mapOf())
|
|
23
|
+
NOTIFICATION_RECORDING_STOPPED -> {
|
|
24
|
+
Log.d(TAG, "Recording stopped via notification")
|
|
25
|
+
module.invokeHandlerWithEventNameAndEventBody("recordingNotificationPause", mapOf())
|
|
37
26
|
}
|
|
38
27
|
|
|
39
|
-
|
|
40
|
-
Log.d(TAG, "
|
|
41
|
-
|
|
28
|
+
NOTIFICATION_RECORDING_RESUMED -> {
|
|
29
|
+
Log.d(TAG, "Recording resumed via notification")
|
|
30
|
+
module.invokeHandlerWithEventNameAndEventBody("recordingNotificationResume", mapOf())
|
|
42
31
|
}
|
|
43
32
|
}
|
|
44
33
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
package com.swmansion.audioapi.system.notification.state
|
|
2
|
+
|
|
3
|
+
import android.content.Intent
|
|
4
|
+
import androidx.core.app.NotificationCompat
|
|
5
|
+
import com.facebook.react.bridge.ReadableMap
|
|
6
|
+
import com.swmansion.audioapi.system.notification.RecordingNotificationReceiver
|
|
7
|
+
|
|
8
|
+
data class RecordingNotificationState(
|
|
9
|
+
var builder: NotificationCompat.Builder? = null,
|
|
10
|
+
var receiver: RecordingNotificationReceiver? = null,
|
|
11
|
+
var initialized: Boolean,
|
|
12
|
+
var pauseIntent: Intent? = null,
|
|
13
|
+
var resumeIntent: Intent? = null,
|
|
14
|
+
var title: String? = null,
|
|
15
|
+
var contentText: String? = null,
|
|
16
|
+
var paused: Boolean = false,
|
|
17
|
+
var smallIconResourceName: String? = null,
|
|
18
|
+
var largeIconResourceName: String? = null,
|
|
19
|
+
var pauseIconResourceName: String? = null,
|
|
20
|
+
var resumeIconResourceName: String? = null,
|
|
21
|
+
var backgroundColor: Int? = null,
|
|
22
|
+
var cachedRNOptions: ReadableMap? = null,
|
|
23
|
+
var darkTheme: Boolean,
|
|
24
|
+
)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
|
3
|
+
android:color="?android:attr/colorControlHighlight">
|
|
4
|
+
<item android:id="@android:id/mask">
|
|
5
|
+
<shape android:shape="oval">
|
|
6
|
+
<solid android:color="#FFFFFF" />
|
|
7
|
+
</shape>
|
|
8
|
+
</item>
|
|
9
|
+
</ripple>
|