@@ -19,15 +19,15 @@ import androidx.compose.ui.platform.AbstractComposeView
19
19
import androidx.compose.ui.platform.ComposeView
20
20
import com.openreplay.tracker.OpenReplay
21
21
import com.openreplay.tracker.SanitizableViewGroup
22
- import kotlinx.coroutines.CoroutineScope
23
22
import kotlinx.coroutines.DelicateCoroutinesApi
24
23
import kotlinx.coroutines.Dispatchers
25
24
import kotlinx.coroutines.GlobalScope
26
25
import kotlinx.coroutines.Job
27
- import kotlinx.coroutines.cancelAndJoin
26
+ import kotlinx.coroutines.asCoroutineDispatcher
28
27
import kotlinx.coroutines.coroutineScope
29
28
import kotlinx.coroutines.delay
30
29
import kotlinx.coroutines.launch
30
+ import kotlinx.coroutines.runBlocking
31
31
import kotlinx.coroutines.withContext
32
32
import org.apache.commons.compress.archivers.tar.TarArchiveEntry
33
33
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream
@@ -37,6 +37,7 @@ import java.io.ByteArrayOutputStream
37
37
import java.io.File
38
38
import java.io.FileOutputStream
39
39
import java.lang.ref.WeakReference
40
+ import java.util.concurrent.Executors
40
41
import java.util.zip.GZIPOutputStream
41
42
import kotlin.coroutines.suspendCoroutine
42
43
@@ -51,6 +52,7 @@ object ScreenshotManager {
51
52
private var quality: Int = 10
52
53
private var minResolution: Int = 320
53
54
55
+
54
56
private var isStoping = false
55
57
fun setSettings (settings : Triple <Int , Int , Int >) {
56
58
val (_, quality, resolution) = settings
@@ -59,42 +61,36 @@ object ScreenshotManager {
59
61
}
60
62
61
63
@OptIn(DelicateCoroutinesApi ::class )
62
- fun start (context : Context , startTs : Long , scope : CoroutineScope = GlobalScope ) {
64
+ fun start (context : Context , startTs : Long ) {
63
65
uiContext = WeakReference (context)
64
66
firstTs = startTs
65
67
isStoping = false
66
68
// endless job to perform capturing
67
- screenShotJob = scope.launch {
69
+
70
+ screenShotJob = GlobalScope .launch {
68
71
val intervalMillis =
69
72
OpenReplay .options.screenshotFrequency.millis / OpenReplay .options.fps.toLong()
70
73
while (true ) {
71
- if (isStoping) {
72
- if (screenshots.isNotEmpty()) {
73
- sendScreenshots(chunkSize = screenshots.size)
74
- }
75
- screenShotJob?.cancelAndJoin()
76
- } else {
77
- delay(intervalMillis)
78
- val screenShot = withContext(Dispatchers .Main ) { captureScreenshot() }
79
- withContext(Dispatchers .IO ) {
80
- try {
81
- val byteScreenShot = compress(screenShot)
82
- // add screen shot to storage
83
- addToScreenShots(byteScreenShot)
84
- // while we are doing our job we gather and send data
85
- sendScreenshots(chunkSize = 10 )
86
- } catch (e: Exception ) {
87
- e.printStackTrace()
88
- }
74
+ delay(intervalMillis)
75
+ val screenShot = withContext(Dispatchers .Main ) { captureScreenshot() }
76
+ withContext(Dispatchers .IO ) {
77
+ try {
78
+ val byteScreenShot = compress(screenShot)
79
+ // add screen shot to storage
80
+ addToScreenShots(byteScreenShot)
81
+ // while we are doing our job we gather and send data
82
+ sendScreenshots(chunkSize = 10 )
83
+ } catch (e: Exception ) {
84
+ e.printStackTrace()
89
85
}
90
86
}
91
87
}
92
88
}
93
89
}
94
90
95
-
96
- fun stopCapturing () {
97
- isStoping = true
91
+ fun stop () {
92
+ screenShotJob?.cancel()
93
+ terminate()
98
94
}
99
95
100
96
fun addSanitizedElement (view : View ) {
@@ -112,7 +108,8 @@ object ScreenshotManager {
112
108
}
113
109
114
110
private suspend fun captureScreenshot (): Bitmap {
115
- val activity = uiContext.get() as ? Activity ? : throw IllegalStateException (" No Activity" )
111
+ val activity =
112
+ uiContext.get() as ? Activity ? : throw IllegalStateException (" No Activity" )
116
113
return suspendCoroutine { coroutine ->
117
114
activity.screenShot { shot ->
118
115
coroutine.resumeWith(Result .success(shot))
@@ -253,7 +250,8 @@ object ScreenshotManager {
253
250
254
251
private fun createCrossStripedPatternBitmap (): Bitmap {
255
252
val patternSize = 80
256
- val patternBitmap = Bitmap .createBitmap(patternSize, patternSize, Bitmap .Config .ARGB_8888 )
253
+ val patternBitmap =
254
+ Bitmap .createBitmap(patternSize, patternSize, Bitmap .Config .ARGB_8888 )
257
255
val patternCanvas = Canvas (patternBitmap)
258
256
val paint = Paint ().apply {
259
257
color = Color .DKGRAY
@@ -303,7 +301,8 @@ object ScreenshotManager {
303
301
val aspectRatio = originalBitmap.height.toFloat() / originalBitmap.width.toFloat()
304
302
val newHeight = (minResolution * aspectRatio).toInt()
305
303
306
- val updated = Bitmap .createScaledBitmap(originalBitmap, minResolution, newHeight, true )
304
+ val updated =
305
+ Bitmap .createScaledBitmap(originalBitmap, minResolution, newHeight, true )
307
306
308
307
if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .R ) {
309
308
updated.compress(Bitmap .CompressFormat .WEBP_LOSSLESS , quality, outputStream)
@@ -316,10 +315,13 @@ object ScreenshotManager {
316
315
317
316
private suspend fun sendScreenshots (chunkSize : Int ) {
318
317
coroutineScope {
319
- val sessionId = NetworkManager .sessionId ? : throw IllegalStateException (" No session" )
318
+ val sessionId =
319
+ NetworkManager .sessionId ? : throw IllegalStateException (" No session" )
320
320
if (screenshots.size < chunkSize) {
321
- DebugUtils .log(" buffering screenshots" )
321
+ DebugUtils .log(" buffering screenshots ${screenshots.size} " )
322
322
} else {
323
+ DebugUtils .log(" archiving screenshots ${screenshots.size} " )
324
+
323
325
val archiveName = " $sessionId -$lastTs .tar.gz"
324
326
// prepare data
325
327
val entries = screenshots.map { (imageFile, timestamp) ->
@@ -349,11 +351,12 @@ object ScreenshotManager {
349
351
bytes = combinedData.toByteArray(),
350
352
filename = archiveName
351
353
)
352
-
353
- try {
354
- MessageCollector .sendImagesBatch(archive)
355
- } catch (e: Exception ) {
356
- e.printStackTrace()
354
+ NetworkManager .sendImages(
355
+ projectKey = OpenReplay .projectKey!! ,
356
+ images = archive.readBytes(),
357
+ name = archive.name
358
+ ) { success ->
359
+ archive.delete()
357
360
}
358
361
}
359
362
}
@@ -376,4 +379,18 @@ object ScreenshotManager {
376
379
}
377
380
return file
378
381
}
382
+
383
+ private fun terminate () {
384
+ if (screenshots.isNotEmpty()) {
385
+ Executors .newSingleThreadExecutor()
386
+ .asCoroutineDispatcher()
387
+ .use { dispatcher ->
388
+ runBlocking {
389
+ launch(dispatcher) {
390
+ sendScreenshots(chunkSize = screenshots.size)
391
+ }
392
+ }
393
+ }
394
+ }
395
+ }
379
396
}
0 commit comments