react-native-frame-capture 1.0.1 → 1.1.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.
Files changed (50) hide show
  1. package/FrameCapture.podspec +21 -21
  2. package/LICENSE +20 -20
  3. package/README.md +159 -158
  4. package/android/build.gradle +77 -77
  5. package/android/gradle.properties +5 -5
  6. package/android/src/main/AndroidManifest.xml +20 -20
  7. package/android/src/main/java/com/framecapture/CaptureManager.kt +1013 -831
  8. package/android/src/main/java/com/framecapture/Constants.kt +205 -196
  9. package/android/src/main/java/com/framecapture/ErrorHandler.kt +165 -165
  10. package/android/src/main/java/com/framecapture/FrameCaptureModule.kt +653 -653
  11. package/android/src/main/java/com/framecapture/OverlayRenderer.kt +423 -423
  12. package/android/src/main/java/com/framecapture/PermissionHandler.kt +150 -150
  13. package/android/src/main/java/com/framecapture/ScreenCaptureService.kt +366 -366
  14. package/android/src/main/java/com/framecapture/StorageManager.kt +221 -221
  15. package/android/src/main/java/com/framecapture/capture/BitmapProcessor.kt +157 -157
  16. package/android/src/main/java/com/framecapture/capture/CaptureEventEmitter.kt +150 -120
  17. package/android/src/main/java/com/framecapture/capture/ChangeDetector.kt +191 -0
  18. package/android/src/main/java/com/framecapture/models/CaptureModels.kt +343 -302
  19. package/android/src/main/java/com/framecapture/models/EnumsAndExtensions.kt +67 -60
  20. package/android/src/main/java/com/framecapture/models/OverlayModels.kt +154 -154
  21. package/android/src/main/java/com/framecapture/service/CaptureNotificationManager.kt +286 -286
  22. package/android/src/main/java/com/framecapture/storage/StorageStrategies.kt +317 -317
  23. package/android/src/main/java/com/framecapture/utils/ValidationUtils.kt +379 -379
  24. package/ios/FrameCapture.h +5 -5
  25. package/ios/FrameCapture.mm +21 -21
  26. package/lib/module/NativeFrameCapture.js.map +1 -1
  27. package/lib/module/constants.js +45 -0
  28. package/lib/module/constants.js.map +1 -1
  29. package/lib/module/normalize.js +10 -1
  30. package/lib/module/normalize.js.map +1 -1
  31. package/lib/module/types.js +9 -0
  32. package/lib/module/types.js.map +1 -1
  33. package/lib/module/validation.js +86 -9
  34. package/lib/module/validation.js.map +1 -1
  35. package/lib/typescript/src/NativeFrameCapture.d.ts +7 -0
  36. package/lib/typescript/src/NativeFrameCapture.d.ts.map +1 -1
  37. package/lib/typescript/src/constants.d.ts +33 -0
  38. package/lib/typescript/src/constants.d.ts.map +1 -1
  39. package/lib/typescript/src/normalize.d.ts +8 -2
  40. package/lib/typescript/src/normalize.d.ts.map +1 -1
  41. package/lib/typescript/src/types.d.ts +29 -5
  42. package/lib/typescript/src/types.d.ts.map +1 -1
  43. package/lib/typescript/src/validation.d.ts.map +1 -1
  44. package/package.json +199 -196
  45. package/src/NativeFrameCapture.ts +8 -0
  46. package/src/constants.ts +45 -0
  47. package/src/normalize.ts +23 -3
  48. package/src/types.ts +30 -2
  49. package/src/validation.ts +132 -13
  50. package/plugin/build/index.js +0 -48
@@ -1,286 +1,286 @@
1
- package com.framecapture.service
2
-
3
- import android.app.Notification
4
- import android.app.NotificationChannel
5
- import android.app.NotificationManager
6
- import android.app.PendingIntent
7
- import android.app.Service
8
- import android.content.Context
9
- import android.content.Intent
10
- import android.os.Build
11
- import androidx.core.app.NotificationCompat
12
- import com.framecapture.Constants
13
- import com.framecapture.ScreenCaptureService
14
- import com.framecapture.models.NotificationOptions
15
-
16
- /**
17
- * Manages notification creation, updates, and styling for the capture service
18
- *
19
- * Handles all notification-related operations for the foreground service:
20
- * - Notification channel creation (Android 8.0+)
21
- * - Custom styling (colors, icons, priority)
22
- * - Action buttons (pause/resume/stop)
23
- * - Frame count updates with template substitution
24
- * - Paused state notifications
25
- *
26
- * Supports full customization via NotificationOptions.
27
- */
28
- class CaptureNotificationManager(
29
- private val service: Service,
30
- private val notificationOptions: NotificationOptions
31
- ) {
32
-
33
- companion object {
34
- const val NOTIFICATION_ID = Constants.NOTIFICATION_ID
35
- }
36
-
37
- /**
38
- * Resolves an icon resource name to a resource ID
39
- * Falls back to default icon if resource not found or null
40
- */
41
- private fun getIconResource(iconName: String?): Int {
42
- if (iconName == null) {
43
- return android.R.drawable.ic_menu_camera
44
- }
45
-
46
- try {
47
- val resourceId = service.applicationContext.resources.getIdentifier(
48
- iconName,
49
- Constants.RESOURCE_TYPE_DRAWABLE,
50
- service.applicationContext.packageName
51
- )
52
-
53
- if (resourceId == 0) {
54
- return android.R.drawable.ic_menu_camera
55
- }
56
-
57
- return resourceId
58
- } catch (e: Exception) {
59
- return android.R.drawable.ic_menu_camera
60
- }
61
- }
62
-
63
- /**
64
- * Parses a color hex string to a color integer
65
- * Returns null if parsing fails or input is null
66
- */
67
- private fun parseColor(colorString: String?): Int? {
68
- if (colorString == null) return null
69
-
70
- try {
71
- return android.graphics.Color.parseColor(colorString)
72
- } catch (e: Exception) {
73
- return null
74
- }
75
- }
76
-
77
- /**
78
- * Maps a priority string to a NotificationCompat priority constant
79
- * Supports "low", "default", "high" (case-insensitive)
80
- */
81
- private fun getNotificationPriority(priority: String): Int {
82
- return when (priority.lowercase()) {
83
- Constants.NOTIFICATION_PRIORITY_HIGH -> NotificationCompat.PRIORITY_HIGH
84
- Constants.NOTIFICATION_PRIORITY_DEFAULT -> NotificationCompat.PRIORITY_DEFAULT
85
- Constants.NOTIFICATION_PRIORITY_LOW -> NotificationCompat.PRIORITY_LOW
86
- else -> NotificationCompat.PRIORITY_LOW
87
- }
88
- }
89
-
90
- /**
91
- * Formats a description template by replacing {frameCount} placeholder with actual count
92
- */
93
- private fun formatDescription(template: String, frameCount: Int): String {
94
- return template.replace(Constants.NOTIFICATION_TEMPLATE_FRAME_COUNT, frameCount.toString())
95
- }
96
-
97
- /**
98
- * Generates a unique channel ID based on notification options
99
- * Returns default CHANNEL_ID for default settings, or hash-based ID for custom channels
100
- */
101
- private fun getChannelId(): String {
102
- return if (notificationOptions.channelName == Constants.CHANNEL_NAME) {
103
- Constants.CHANNEL_ID
104
- } else {
105
- "${Constants.CUSTOM_CHANNEL_ID_PREFIX}${notificationOptions.channelName.hashCode()}"
106
- }
107
- }
108
-
109
- /**
110
- * Creates notification channel for API 26+
111
- * Uses custom channel settings from notificationOptions
112
- */
113
- fun createNotificationChannel() {
114
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
115
- val channelId = getChannelId()
116
-
117
- val channel = NotificationChannel(
118
- channelId,
119
- notificationOptions.channelName,
120
- NotificationManager.IMPORTANCE_LOW
121
- ).apply {
122
- description = notificationOptions.channelDescription
123
- setShowBadge(false)
124
- }
125
-
126
- val notificationManager = service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
127
- notificationManager.createNotificationChannel(channel)
128
- }
129
- }
130
-
131
- /**
132
- * Creates a reusable notification builder with custom styling applied
133
- */
134
- private fun createNotificationBuilder(): NotificationCompat.Builder {
135
- val builder = NotificationCompat.Builder(service, getChannelId())
136
- .setSmallIcon(getIconResource(notificationOptions.smallIcon))
137
- .setPriority(getNotificationPriority(notificationOptions.priority))
138
- .setOngoing(true)
139
-
140
- // Apply color if provided
141
- parseColor(notificationOptions.color)?.let {
142
- builder.setColor(it)
143
- }
144
-
145
- // Apply large icon if provided
146
- notificationOptions.icon?.let { iconName ->
147
- try {
148
- val iconRes = getIconResource(iconName)
149
- val bitmap = android.graphics.BitmapFactory.decodeResource(service.resources, iconRes)
150
- if (bitmap != null) {
151
- builder.setLargeIcon(bitmap)
152
- }
153
- } catch (e: Exception) {
154
- // Silently fail - large icon is optional
155
- }
156
- }
157
-
158
- return builder
159
- }
160
-
161
- /**
162
- * Creates a PendingIntent for service actions
163
- */
164
- private fun createActionPendingIntent(action: String, requestCode: Int): PendingIntent {
165
- val intent = Intent(service, ScreenCaptureService::class.java).apply {
166
- this.action = action
167
- }
168
-
169
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
170
- PendingIntent.getService(
171
- service,
172
- requestCode,
173
- intent,
174
- PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
175
- )
176
- } else {
177
- PendingIntent.getService(
178
- service,
179
- requestCode,
180
- intent,
181
- PendingIntent.FLAG_UPDATE_CURRENT
182
- )
183
- }
184
- }
185
-
186
- /**
187
- * Creates the initial foreground service notification
188
- */
189
- fun createNotification(): Notification {
190
- val builder = createNotificationBuilder()
191
- .setContentTitle(notificationOptions.title)
192
- .setContentText(notificationOptions.description)
193
-
194
- // Add pause action if enabled
195
- if (notificationOptions.showPauseAction) {
196
- builder.addAction(
197
- android.R.drawable.ic_media_pause,
198
- Constants.NOTIFICATION_ACTION_PAUSE,
199
- createActionPendingIntent(Constants.ACTION_PAUSE_CAPTURE, Constants.NOTIFICATION_REQUEST_CODE_PAUSE_RESUME)
200
- )
201
- }
202
-
203
- // Add stop action if enabled
204
- if (notificationOptions.showStopAction) {
205
- builder.addAction(
206
- android.R.drawable.ic_menu_close_clear_cancel,
207
- Constants.NOTIFICATION_ACTION_STOP,
208
- createActionPendingIntent(Constants.ACTION_STOP_CAPTURE, Constants.NOTIFICATION_REQUEST_CODE_STOP)
209
- )
210
- }
211
-
212
- return builder.build()
213
- }
214
-
215
- /**
216
- * Updates the notification with current frame count progress
217
- */
218
- fun updateNotification(frameCount: Int) {
219
- try {
220
- val builder = createNotificationBuilder()
221
- .setContentTitle(notificationOptions.title)
222
- .setContentText(formatDescription(notificationOptions.description, frameCount))
223
-
224
- // Add pause action if enabled
225
- if (notificationOptions.showPauseAction) {
226
- builder.addAction(
227
- android.R.drawable.ic_media_pause,
228
- Constants.NOTIFICATION_ACTION_PAUSE,
229
- createActionPendingIntent(Constants.ACTION_PAUSE_CAPTURE, Constants.NOTIFICATION_REQUEST_CODE_PAUSE_RESUME)
230
- )
231
- }
232
-
233
- // Add stop action if enabled
234
- if (notificationOptions.showStopAction) {
235
- builder.addAction(
236
- android.R.drawable.ic_menu_close_clear_cancel,
237
- Constants.NOTIFICATION_ACTION_STOP,
238
- createActionPendingIntent(Constants.ACTION_STOP_CAPTURE, Constants.NOTIFICATION_REQUEST_CODE_STOP)
239
- )
240
- }
241
-
242
- val notification = builder.build()
243
- val notificationManager = service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
244
- notificationManager.notify(NOTIFICATION_ID, notification)
245
-
246
- } catch (e: Exception) {
247
- // Silently fail - notification update is not critical
248
- }
249
- }
250
-
251
- /**
252
- * Updates the notification to show paused state
253
- */
254
- fun updateNotificationPaused(frameCount: Int) {
255
- try {
256
- val builder = createNotificationBuilder()
257
- .setContentTitle(notificationOptions.pausedTitle)
258
- .setContentText(formatDescription(notificationOptions.pausedDescription, frameCount))
259
-
260
- // Add resume action if enabled
261
- if (notificationOptions.showResumeAction) {
262
- builder.addAction(
263
- android.R.drawable.ic_media_play,
264
- Constants.NOTIFICATION_ACTION_RESUME,
265
- createActionPendingIntent(Constants.ACTION_RESUME_CAPTURE, Constants.NOTIFICATION_REQUEST_CODE_PAUSE_RESUME)
266
- )
267
- }
268
-
269
- // Add stop action if enabled
270
- if (notificationOptions.showStopAction) {
271
- builder.addAction(
272
- android.R.drawable.ic_menu_close_clear_cancel,
273
- Constants.NOTIFICATION_ACTION_STOP,
274
- createActionPendingIntent(Constants.ACTION_STOP_CAPTURE, Constants.NOTIFICATION_REQUEST_CODE_STOP)
275
- )
276
- }
277
-
278
- val notification = builder.build()
279
- val notificationManager = service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
280
- notificationManager.notify(NOTIFICATION_ID, notification)
281
-
282
- } catch (e: Exception) {
283
- // Silently fail - notification update is not critical
284
- }
285
- }
286
- }
1
+ package com.framecapture.service
2
+
3
+ import android.app.Notification
4
+ import android.app.NotificationChannel
5
+ import android.app.NotificationManager
6
+ import android.app.PendingIntent
7
+ import android.app.Service
8
+ import android.content.Context
9
+ import android.content.Intent
10
+ import android.os.Build
11
+ import androidx.core.app.NotificationCompat
12
+ import com.framecapture.Constants
13
+ import com.framecapture.ScreenCaptureService
14
+ import com.framecapture.models.NotificationOptions
15
+
16
+ /**
17
+ * Manages notification creation, updates, and styling for the capture service
18
+ *
19
+ * Handles all notification-related operations for the foreground service:
20
+ * - Notification channel creation (Android 8.0+)
21
+ * - Custom styling (colors, icons, priority)
22
+ * - Action buttons (pause/resume/stop)
23
+ * - Frame count updates with template substitution
24
+ * - Paused state notifications
25
+ *
26
+ * Supports full customization via NotificationOptions.
27
+ */
28
+ class CaptureNotificationManager(
29
+ private val service: Service,
30
+ private val notificationOptions: NotificationOptions
31
+ ) {
32
+
33
+ companion object {
34
+ const val NOTIFICATION_ID = Constants.NOTIFICATION_ID
35
+ }
36
+
37
+ /**
38
+ * Resolves an icon resource name to a resource ID
39
+ * Falls back to default icon if resource not found or null
40
+ */
41
+ private fun getIconResource(iconName: String?): Int {
42
+ if (iconName == null) {
43
+ return android.R.drawable.ic_menu_camera
44
+ }
45
+
46
+ try {
47
+ val resourceId = service.applicationContext.resources.getIdentifier(
48
+ iconName,
49
+ Constants.RESOURCE_TYPE_DRAWABLE,
50
+ service.applicationContext.packageName
51
+ )
52
+
53
+ if (resourceId == 0) {
54
+ return android.R.drawable.ic_menu_camera
55
+ }
56
+
57
+ return resourceId
58
+ } catch (e: Exception) {
59
+ return android.R.drawable.ic_menu_camera
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Parses a color hex string to a color integer
65
+ * Returns null if parsing fails or input is null
66
+ */
67
+ private fun parseColor(colorString: String?): Int? {
68
+ if (colorString == null) return null
69
+
70
+ try {
71
+ return android.graphics.Color.parseColor(colorString)
72
+ } catch (e: Exception) {
73
+ return null
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Maps a priority string to a NotificationCompat priority constant
79
+ * Supports "low", "default", "high" (case-insensitive)
80
+ */
81
+ private fun getNotificationPriority(priority: String): Int {
82
+ return when (priority.lowercase()) {
83
+ Constants.NOTIFICATION_PRIORITY_HIGH -> NotificationCompat.PRIORITY_HIGH
84
+ Constants.NOTIFICATION_PRIORITY_DEFAULT -> NotificationCompat.PRIORITY_DEFAULT
85
+ Constants.NOTIFICATION_PRIORITY_LOW -> NotificationCompat.PRIORITY_LOW
86
+ else -> NotificationCompat.PRIORITY_LOW
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Formats a description template by replacing {frameCount} placeholder with actual count
92
+ */
93
+ private fun formatDescription(template: String, frameCount: Int): String {
94
+ return template.replace(Constants.NOTIFICATION_TEMPLATE_FRAME_COUNT, frameCount.toString())
95
+ }
96
+
97
+ /**
98
+ * Generates a unique channel ID based on notification options
99
+ * Returns default CHANNEL_ID for default settings, or hash-based ID for custom channels
100
+ */
101
+ private fun getChannelId(): String {
102
+ return if (notificationOptions.channelName == Constants.CHANNEL_NAME) {
103
+ Constants.CHANNEL_ID
104
+ } else {
105
+ "${Constants.CUSTOM_CHANNEL_ID_PREFIX}${notificationOptions.channelName.hashCode()}"
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Creates notification channel for API 26+
111
+ * Uses custom channel settings from notificationOptions
112
+ */
113
+ fun createNotificationChannel() {
114
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
115
+ val channelId = getChannelId()
116
+
117
+ val channel = NotificationChannel(
118
+ channelId,
119
+ notificationOptions.channelName,
120
+ NotificationManager.IMPORTANCE_LOW
121
+ ).apply {
122
+ description = notificationOptions.channelDescription
123
+ setShowBadge(false)
124
+ }
125
+
126
+ val notificationManager = service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
127
+ notificationManager.createNotificationChannel(channel)
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Creates a reusable notification builder with custom styling applied
133
+ */
134
+ private fun createNotificationBuilder(): NotificationCompat.Builder {
135
+ val builder = NotificationCompat.Builder(service, getChannelId())
136
+ .setSmallIcon(getIconResource(notificationOptions.smallIcon))
137
+ .setPriority(getNotificationPriority(notificationOptions.priority))
138
+ .setOngoing(true)
139
+
140
+ // Apply color if provided
141
+ parseColor(notificationOptions.color)?.let {
142
+ builder.setColor(it)
143
+ }
144
+
145
+ // Apply large icon if provided
146
+ notificationOptions.icon?.let { iconName ->
147
+ try {
148
+ val iconRes = getIconResource(iconName)
149
+ val bitmap = android.graphics.BitmapFactory.decodeResource(service.resources, iconRes)
150
+ if (bitmap != null) {
151
+ builder.setLargeIcon(bitmap)
152
+ }
153
+ } catch (e: Exception) {
154
+ // Silently fail - large icon is optional
155
+ }
156
+ }
157
+
158
+ return builder
159
+ }
160
+
161
+ /**
162
+ * Creates a PendingIntent for service actions
163
+ */
164
+ private fun createActionPendingIntent(action: String, requestCode: Int): PendingIntent {
165
+ val intent = Intent(service, ScreenCaptureService::class.java).apply {
166
+ this.action = action
167
+ }
168
+
169
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
170
+ PendingIntent.getService(
171
+ service,
172
+ requestCode,
173
+ intent,
174
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
175
+ )
176
+ } else {
177
+ PendingIntent.getService(
178
+ service,
179
+ requestCode,
180
+ intent,
181
+ PendingIntent.FLAG_UPDATE_CURRENT
182
+ )
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Creates the initial foreground service notification
188
+ */
189
+ fun createNotification(): Notification {
190
+ val builder = createNotificationBuilder()
191
+ .setContentTitle(notificationOptions.title)
192
+ .setContentText(notificationOptions.description)
193
+
194
+ // Add pause action if enabled
195
+ if (notificationOptions.showPauseAction) {
196
+ builder.addAction(
197
+ android.R.drawable.ic_media_pause,
198
+ Constants.NOTIFICATION_ACTION_PAUSE,
199
+ createActionPendingIntent(Constants.ACTION_PAUSE_CAPTURE, Constants.NOTIFICATION_REQUEST_CODE_PAUSE_RESUME)
200
+ )
201
+ }
202
+
203
+ // Add stop action if enabled
204
+ if (notificationOptions.showStopAction) {
205
+ builder.addAction(
206
+ android.R.drawable.ic_menu_close_clear_cancel,
207
+ Constants.NOTIFICATION_ACTION_STOP,
208
+ createActionPendingIntent(Constants.ACTION_STOP_CAPTURE, Constants.NOTIFICATION_REQUEST_CODE_STOP)
209
+ )
210
+ }
211
+
212
+ return builder.build()
213
+ }
214
+
215
+ /**
216
+ * Updates the notification with current frame count progress
217
+ */
218
+ fun updateNotification(frameCount: Int) {
219
+ try {
220
+ val builder = createNotificationBuilder()
221
+ .setContentTitle(notificationOptions.title)
222
+ .setContentText(formatDescription(notificationOptions.description, frameCount))
223
+
224
+ // Add pause action if enabled
225
+ if (notificationOptions.showPauseAction) {
226
+ builder.addAction(
227
+ android.R.drawable.ic_media_pause,
228
+ Constants.NOTIFICATION_ACTION_PAUSE,
229
+ createActionPendingIntent(Constants.ACTION_PAUSE_CAPTURE, Constants.NOTIFICATION_REQUEST_CODE_PAUSE_RESUME)
230
+ )
231
+ }
232
+
233
+ // Add stop action if enabled
234
+ if (notificationOptions.showStopAction) {
235
+ builder.addAction(
236
+ android.R.drawable.ic_menu_close_clear_cancel,
237
+ Constants.NOTIFICATION_ACTION_STOP,
238
+ createActionPendingIntent(Constants.ACTION_STOP_CAPTURE, Constants.NOTIFICATION_REQUEST_CODE_STOP)
239
+ )
240
+ }
241
+
242
+ val notification = builder.build()
243
+ val notificationManager = service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
244
+ notificationManager.notify(NOTIFICATION_ID, notification)
245
+
246
+ } catch (e: Exception) {
247
+ // Silently fail - notification update is not critical
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Updates the notification to show paused state
253
+ */
254
+ fun updateNotificationPaused(frameCount: Int) {
255
+ try {
256
+ val builder = createNotificationBuilder()
257
+ .setContentTitle(notificationOptions.pausedTitle)
258
+ .setContentText(formatDescription(notificationOptions.pausedDescription, frameCount))
259
+
260
+ // Add resume action if enabled
261
+ if (notificationOptions.showResumeAction) {
262
+ builder.addAction(
263
+ android.R.drawable.ic_media_play,
264
+ Constants.NOTIFICATION_ACTION_RESUME,
265
+ createActionPendingIntent(Constants.ACTION_RESUME_CAPTURE, Constants.NOTIFICATION_REQUEST_CODE_PAUSE_RESUME)
266
+ )
267
+ }
268
+
269
+ // Add stop action if enabled
270
+ if (notificationOptions.showStopAction) {
271
+ builder.addAction(
272
+ android.R.drawable.ic_menu_close_clear_cancel,
273
+ Constants.NOTIFICATION_ACTION_STOP,
274
+ createActionPendingIntent(Constants.ACTION_STOP_CAPTURE, Constants.NOTIFICATION_REQUEST_CODE_STOP)
275
+ )
276
+ }
277
+
278
+ val notification = builder.build()
279
+ val notificationManager = service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
280
+ notificationManager.notify(NOTIFICATION_ID, notification)
281
+
282
+ } catch (e: Exception) {
283
+ // Silently fail - notification update is not critical
284
+ }
285
+ }
286
+ }