@@ -5,37 +5,24 @@ import android.content.Context
55import android.content.res.Configuration
66import android.graphics.Bitmap
77import android.os.Build
8- import io.sentry.DateUtils
98import io.sentry.Hint
109import io.sentry.IHub
1110import io.sentry.Integration
1211import io.sentry.ReplayController
13- import io.sentry.ReplayRecording
1412import io.sentry.SentryEvent
1513import io.sentry.SentryIntegrationPackageStorage
1614import io.sentry.SentryLevel.DEBUG
1715import io.sentry.SentryLevel.INFO
1816import io.sentry.SentryOptions
19- import io.sentry.SentryReplayEvent
20- import io.sentry.SentryReplayEvent.ReplayType
21- import io.sentry.SentryReplayEvent.ReplayType.BUFFER
22- import io.sentry.SentryReplayEvent.ReplayType.SESSION
2317import io.sentry.android.replay.capture.BufferCaptureStrategy
2418import io.sentry.android.replay.capture.CaptureStrategy
2519import io.sentry.android.replay.capture.SessionCaptureStrategy
26- import io.sentry.android.replay.util.gracefullyShutdown
2720import io.sentry.android.replay.util.sample
28- import io.sentry.android.replay.util.submitSafely
2921import io.sentry.protocol.SentryId
30- import io.sentry.rrweb.RRWebMetaEvent
31- import io.sentry.rrweb.RRWebVideoEvent
3222import io.sentry.transport.ICurrentDateProvider
33- import io.sentry.util.FileUtils
3423import io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion
3524import java.io.Closeable
36- import java.io.File
3725import java.security.SecureRandom
38- import java.util.Date
3926import java.util.concurrent.atomic.AtomicBoolean
4027
4128class ReplayIntegration (
@@ -49,7 +36,6 @@ class ReplayIntegration(
4936 private val random by lazy { SecureRandom () }
5037
5138 // TODO: probably not everything has to be thread-safe here
52- private val isFullSession = AtomicBoolean (false )
5339 private val isEnabled = AtomicBoolean (false )
5440 private val isRecording = AtomicBoolean (false )
5541 private var captureStrategy: CaptureStrategy ? = null
@@ -71,22 +57,9 @@ class ReplayIntegration(
7157 return
7258 }
7359
74- isFullSession.set(random.sample(options.experimental.sessionReplay.sessionSampleRate))
75- if (! isFullSession.get() &&
76- ! options.experimental.sessionReplay.isSessionReplayForErrorsEnabled
77- ) {
78- options.logger.log(INFO , " Session replay is disabled, full session was not sampled and errorSampleRate is not specified" )
79- return
80- }
81-
8260 this .hub = hub
8361 recorder = WindowRecorder (options, this )
8462 isEnabled.set(true )
85- captureStrategy = if (isFullSession.get()) {
86- SessionCaptureStrategy (options, hub, dateProvider)
87- } else {
88- BufferCaptureStrategy (options, hub, dateProvider, random)
89- }
9063
9164 try {
9265 context.registerComponentCallbacks(this )
@@ -115,7 +88,19 @@ class ReplayIntegration(
11588 return
11689 }
11790
91+ val isFullSession = random.sample(options.experimental.sessionReplay.sessionSampleRate)
92+ if (! isFullSession && ! options.experimental.sessionReplay.isSessionReplayForErrorsEnabled) {
93+ options.logger.log(INFO , " Session replay is not started, full session was not sampled and errorSampleRate is not specified" )
94+ return
95+ }
96+
11897 recorderConfig = ScreenshotRecorderConfig .from(context, options.experimental.sessionReplay)
98+ captureStrategy = if (isFullSession) {
99+ SessionCaptureStrategy (options, hub, dateProvider, recorderConfig)
100+ } else {
101+ BufferCaptureStrategy (options, hub, dateProvider, recorderConfig, random)
102+ }
103+
119104 captureStrategy?.start()
120105 recorder?.startRecording(recorderConfig)
121106 }
@@ -139,8 +124,12 @@ class ReplayIntegration(
139124 return
140125 }
141126
142- captureStrategy?.sendReplayForEvent(event, hint)
143- isFullSession.set(true )
127+ if (SentryId .EMPTY_ID .equals(captureStrategy?.currentReplayId?.get())) {
128+ options.logger.log(DEBUG , " Replay id is not set, not capturing for event %s" , event.eventId)
129+ return
130+ }
131+
132+ captureStrategy?.sendReplayForEvent(event, hint, onSegmentSent = { captureStrategy?.currentSegment?.getAndIncrement()})
144133 captureStrategy = captureStrategy?.convert()
145134 }
146135
@@ -158,72 +147,14 @@ class ReplayIntegration(
158147 return
159148 }
160149
161- val now = dateProvider.currentTimeMillis
162- val currentSegmentTimestamp = segmentTimestamp.get()
163- val segmentId = currentSegment.get()
164- val duration = now - currentSegmentTimestamp.time
165- val replayId = currentReplayId.get()
166- val replayCacheDir = cache?.replayCacheDir
167- val height = recorderConfig.recordingHeight
168- val width = recorderConfig.recordingWidth
169- replayExecutor.submitSafely(options, " $TAG .stop" ) {
170- // we don't flush the segment, but we still wanna clean up the folder for buffer mode
171- if (isFullSession.get()) {
172- createAndCaptureSegment(duration, currentSegmentTimestamp, replayId, segmentId, height, width)
173- }
174- FileUtils .deleteRecursively(replayCacheDir)
175- }
176-
177150 recorder?.stopRecording()
178- cache?.close()
179- currentSegment.set(0 )
180- replayStartTimestamp.set(0 )
181- segmentTimestamp.set(null )
182- currentReplayId.set(SentryId .EMPTY_ID )
183- hub?.configureScope { it.replayId = SentryId .EMPTY_ID }
151+ captureStrategy?.stop()
184152 isRecording.set(false )
153+ captureStrategy = null
185154 }
186155
187156 override fun onScreenshotRecorded (bitmap : Bitmap ) {
188- // have to do it before submitting, otherwise if the queue is busy, the timestamp won't be
189- // reflecting the exact time of when it was captured
190- val frameTimestamp = dateProvider.currentTimeMillis
191- val height = recorderConfig.recordingHeight
192- val width = recorderConfig.recordingWidth
193- replayExecutor.submitSafely(options, " $TAG .add_frame" ) {
194- cache?.addFrame(bitmap, frameTimestamp)
195-
196- val now = dateProvider.currentTimeMillis
197- if (isFullSession.get() &&
198- (now - segmentTimestamp.get().time >= options.experimental.sessionReplay.sessionSegmentDuration)
199- ) {
200- val currentSegmentTimestamp = segmentTimestamp.get()
201- val segmentId = currentSegment.get()
202- val replayId = currentReplayId.get()
203-
204- val videoDuration =
205- createAndCaptureSegment(
206- options.experimental.sessionReplay.sessionSegmentDuration,
207- currentSegmentTimestamp,
208- replayId,
209- segmentId,
210- height,
211- width
212- )
213- if (videoDuration != null ) {
214- currentSegment.getAndIncrement()
215- // set next segment timestamp as close to the previous one as possible to avoid gaps
216- segmentTimestamp.set(DateUtils .getDateTime(currentSegmentTimestamp.time + videoDuration))
217- }
218- } else if (isFullSession.get() &&
219- (now - replayStartTimestamp.get() >= options.experimental.sessionReplay.sessionDuration)
220- ) {
221- stop()
222- options.logger.log(INFO , " Session replay deadline exceeded (1h), stopping recording" )
223- } else if (! isFullSession.get()) {
224- cache?.rotate(now - options.experimental.sessionReplay.errorReplayDuration)
225- }
226- }
157+ captureStrategy?.onScreenshotRecorded(bitmap)
227158 }
228159
229160 override fun close () {
@@ -247,26 +178,10 @@ class ReplayIntegration(
247178
248179 recorder?.stopRecording()
249180
250- // TODO: support buffer mode and breadcrumb/rrweb_event
251- if (isFullSession.get()) {
252- val now = dateProvider.currentTimeMillis
253- val currentSegmentTimestamp = segmentTimestamp.get()
254- val segmentId = currentSegment.get()
255- val duration = now - currentSegmentTimestamp.time
256- val replayId = currentReplayId.get()
257- val height = recorderConfig.recordingHeight
258- val width = recorderConfig.recordingWidth
259- replayExecutor.submitSafely(options, " $TAG .onConfigurationChanged" ) {
260- val videoDuration =
261- createAndCaptureSegment(duration, currentSegmentTimestamp, replayId, segmentId, height, width)
262- if (videoDuration != null ) {
263- currentSegment.getAndIncrement()
264- }
265- }
266- }
267-
268181 // refresh config based on new device configuration
269182 recorderConfig = ScreenshotRecorderConfig .from(context, options.experimental.sessionReplay)
183+ captureStrategy?.onConfigurationChanged(recorderConfig)
184+
270185 recorder?.startRecording(recorderConfig)
271186 }
272187
0 commit comments