@@ -13,6 +13,8 @@ import io.sentry.android.replay.ScreenshotRecorderConfig
1313import io.sentry.android.replay.util.gracefullyShutdown
1414import io.sentry.android.replay.util.submitSafely
1515import io.sentry.protocol.SentryId
16+ import io.sentry.rrweb.RRWebBreadcrumbEvent
17+ import io.sentry.rrweb.RRWebEvent
1618import io.sentry.rrweb.RRWebMetaEvent
1719import io.sentry.rrweb.RRWebVideoEvent
1820import io.sentry.transport.ICurrentDateProvider
@@ -28,6 +30,7 @@ import java.util.concurrent.atomic.AtomicReference
2830
2931internal abstract class BaseCaptureStrategy (
3032 private val options : SentryOptions ,
33+ private val hub : IHub ? ,
3134 private val dateProvider : ICurrentDateProvider ,
3235 protected var recorderConfig : ScreenshotRecorderConfig ,
3336 executor : ScheduledExecutorService ? = null
@@ -134,41 +137,107 @@ internal abstract class BaseCaptureStrategy(
134137 duration : Long ,
135138 replayType : ReplayType
136139 ): ReplaySegment {
140+ val endTimestamp = DateUtils .getDateTime(segmentTimestamp.time + duration)
137141 val replay = SentryReplayEvent ().apply {
138142 eventId = currentReplayId
139143 replayId = currentReplayId
140144 this .segmentId = segmentId
141- this .timestamp = DateUtils .getDateTime(segmentTimestamp.time + duration)
145+ this .timestamp = endTimestamp
142146 replayStartTimestamp = segmentTimestamp
143147 this .replayType = replayType
144148 videoFile = video
145149 }
146150
147- val recording = ReplayRecording ().apply {
151+ val recordingPayload = mutableListOf<RRWebEvent >()
152+ recordingPayload + = RRWebMetaEvent ().apply {
153+ this .timestamp = segmentTimestamp.time
154+ this .height = height
155+ this .width = width
156+ }
157+ recordingPayload + = RRWebVideoEvent ().apply {
158+ this .timestamp = segmentTimestamp.time
148159 this .segmentId = segmentId
149- payload = listOf (
150- RRWebMetaEvent ().apply {
151- this .timestamp = segmentTimestamp.time
152- this .height = height
153- this .width = width
154- },
155- RRWebVideoEvent ().apply {
156- this .timestamp = segmentTimestamp.time
157- this .segmentId = segmentId
158- this .durationMs = duration
159- this .frameCount = frameCount
160- size = video.length()
161- frameRate = recorderConfig.frameRate
162- this .height = height
163- this .width = width
164- // TODO: support non-fullscreen windows later
165- left = 0
166- top = 0
160+ this .durationMs = duration
161+ this .frameCount = frameCount
162+ size = video.length()
163+ frameRate = recorderConfig.frameRate
164+ this .height = height
165+ this .width = width
166+ // TODO: support non-fullscreen windows later
167+ left = 0
168+ top = 0
169+ }
170+
171+ hub?.configureScope { scope ->
172+ scope.breadcrumbs.forEach { breadcrumb ->
173+ if (breadcrumb.timestamp.after(segmentTimestamp) &&
174+ breadcrumb.timestamp.before(endTimestamp)
175+ ) {
176+ // TODO: rework this later when aligned with iOS and frontend
177+ var breadcrumbMessage: String? = null
178+ val breadcrumbCategory: String?
179+ val breadcrumbData = mutableMapOf<String , Any ?>()
180+ when {
181+ breadcrumb.category == " http" -> return @forEach
182+
183+ breadcrumb.category == " device.orientation" -> {
184+ breadcrumbCategory = breadcrumb.category!!
185+ breadcrumbMessage = breadcrumb.data[" position" ] as ? String ? : " "
186+ }
187+
188+ breadcrumb.type == " navigation" -> {
189+ breadcrumbCategory = " navigation"
190+ breadcrumbData[" to" ] = when {
191+ breadcrumb.data[" state" ] == " resumed" -> breadcrumb.data[" screen" ] as ? String
192+ breadcrumb.category == " app.lifecycle" -> breadcrumb.data[" state" ] as ? String
193+ " to" in breadcrumb.data -> breadcrumb.data[" to" ] as ? String
194+ else -> return @forEach
195+ } ? : return @forEach
196+ }
197+
198+ breadcrumb.category in setOf (" ui.click" , " ui.scroll" , " ui.swipe" ) -> {
199+ breadcrumbCategory = breadcrumb.category!!
200+ breadcrumbMessage = (
201+ breadcrumb.data[" view.id" ]
202+ ? : breadcrumb.data[" view.class" ]
203+ ? : breadcrumb.data[" view.tag" ]
204+ ) as ? String ? : " "
205+ }
206+
207+ breadcrumb.type == " system" -> {
208+ breadcrumbCategory = breadcrumb.type!!
209+ breadcrumbMessage = breadcrumb.data.entries.joinToString() as ? String ? : " "
210+ }
211+
212+ else -> {
213+ breadcrumbCategory = breadcrumb.category
214+ breadcrumbMessage = breadcrumb.message
215+ }
216+ }
217+ if (! breadcrumbCategory.isNullOrEmpty()) {
218+ recordingPayload + = RRWebBreadcrumbEvent ().apply {
219+ timestamp = breadcrumb.timestamp.time
220+ breadcrumbTimestamp = breadcrumb.timestamp.time / 1000.0
221+ breadcrumbType = " default"
222+ category = breadcrumbCategory
223+ message = breadcrumbMessage
224+ data = breadcrumbData
225+ }
226+ }
167227 }
168- )
228+ }
169229 }
170230
171- return ReplaySegment .Created (videoDuration = duration, replay = replay, recording = recording)
231+ val recording = ReplayRecording ().apply {
232+ this .segmentId = segmentId
233+ payload = recordingPayload.sortedBy { it.timestamp }
234+ }
235+
236+ return ReplaySegment .Created (
237+ videoDuration = duration,
238+ replay = replay,
239+ recording = recording
240+ )
172241 }
173242
174243 override fun onConfigurationChanged (recorderConfig : ScreenshotRecorderConfig ) {
0 commit comments