@@ -4,6 +4,7 @@ import android.app.Application
4
4
import androidx.test.platform.app.InstrumentationRegistry
5
5
import assertk.Assert
6
6
import assertk.assertThat
7
+ import assertk.assertions.isBetween
7
8
import assertk.assertions.isCloseTo
8
9
import assertk.assertions.isEqualTo
9
10
import com.google.gson.Gson
@@ -20,14 +21,18 @@ import com.tidal.sdk.player.common.model.MediaProduct
20
21
import com.tidal.sdk.player.common.model.ProductType
21
22
import com.tidal.sdk.player.events.EventReporterModuleRoot
22
23
import com.tidal.sdk.player.events.di.DefaultEventReporterComponent
24
+ import com.tidal.sdk.player.events.model.PlaybackSession
23
25
import com.tidal.sdk.player.events.playlogtest.PlayLogTestDefaultEventReporterComponentFactory
24
26
import com.tidal.sdk.player.events.reflectionComponentFactoryF
25
27
import com.tidal.sdk.player.playbackengine.model.Event
26
28
import com.tidal.sdk.player.playbackengine.model.Event.MediaProductEnded
27
29
import com.tidal.sdk.player.repeatableflakytest.RepeatableFlakyTest
28
30
import com.tidal.sdk.player.repeatableflakytest.RepeatableFlakyTestRule
29
31
import com.tidal.sdk.player.setBodyFromFile
32
+ import kotlin.time.Duration.Companion.milliseconds
33
+ import kotlin.time.Duration.Companion.seconds
30
34
import kotlinx.coroutines.Dispatchers
35
+ import kotlinx.coroutines.delay
31
36
import kotlinx.coroutines.flow.Flow
32
37
import kotlinx.coroutines.flow.emptyFlow
33
38
import kotlinx.coroutines.flow.filter
@@ -137,7 +142,7 @@ class PlayLogTest {
137
142
argThat { ! contentEquals(" playback_session" ) },
138
143
anyOrNull(),
139
144
anyOrNull(),
140
- anyOrNull()
145
+ anyOrNull(),
141
146
)
142
147
verifyNoMoreInteractions(eventSender)
143
148
}
@@ -163,7 +168,7 @@ class PlayLogTest {
163
168
loadAndPlayUntilEnd(MediaProduct (ProductType .TRACK , " 1" , null , null ))
164
169
165
170
private fun loadAndPlayUntilEnd (mediaProduct : MediaProduct ) = runTest {
166
- responseDispatcher [
171
+ responseDispatcher[
167
172
" https://api.tidal.com/v1/tracks/1/playbackinfo?playbackmode=STREAM&assetpresentation=FULL&audioquality=LOW" .toHttpUrl(),
168
173
] = {
169
174
MockResponse ().setBodyFromFile(
@@ -201,6 +206,87 @@ class PlayLogTest {
201
206
)
202
207
}
203
208
209
+ @Test
210
+ fun loadAndPlayThenPauseThenPlayNoNulls () =
211
+ loadAndPlayThenPauseThenPlay(MediaProduct (ProductType .TRACK , " 1" , " TESTA" , " 456" ))
212
+
213
+ @Test
214
+ fun loadAndPlayThenPauseThenPlayNullSourceType () =
215
+ loadAndPlayThenPauseThenPlay(MediaProduct (ProductType .TRACK , " 1" , null , " 789" ))
216
+
217
+ @Test
218
+ fun loadAndPlayThenPauseThenPlayNullSourceId () =
219
+ loadAndPlayThenPauseThenPlay(MediaProduct (ProductType .TRACK , " 1" , " TESTB" , null ))
220
+
221
+ @Test
222
+ fun loadAndPlayThenPauseThenPlayNullSourceTypeNullSourceId () =
223
+ loadAndPlayThenPauseThenPlay(MediaProduct (ProductType .TRACK , " 1" , null , null ))
224
+
225
+ @Suppress(" LongMethod" )
226
+ private fun loadAndPlayThenPauseThenPlay (mediaProduct : MediaProduct ) = runTest {
227
+ val gson = Gson ()
228
+ responseDispatcher[
229
+ " https://api.tidal.com/v1/tracks/1/playbackinfo?playbackmode=STREAM&assetpresentation=FULL&audioquality=LOW" .toHttpUrl(),
230
+ ] = {
231
+ MockResponse ().setBodyFromFile(
232
+ " api-responses/playbackinfo/tracks/playlogtest/get_1_bts.json" ,
233
+ )
234
+ }
235
+ responseDispatcher[" https://test.audio.tidal.com/1_bts.m4a" .toHttpUrl()] = {
236
+ MockResponse ().setBodyFromFile(" raw/playlogtest/1_bts.m4a" )
237
+ }
238
+
239
+ player.playbackEngine.load(mediaProduct)
240
+ player.playbackEngine.play()
241
+ withContext(Dispatchers .Default .limitedParallelism(1 )) {
242
+ withTimeout(4 .seconds) {
243
+ player.playbackEngine.events.filter { it is Event .MediaProductTransition }.first()
244
+ }
245
+ delay(2 .seconds)
246
+ while (player.playbackEngine.assetPosition < 2 ) {
247
+ delay(10 .milliseconds)
248
+ }
249
+ player.playbackEngine.pause()
250
+ delay(1 .seconds)
251
+ player.playbackEngine.play()
252
+ withTimeout(8000 ) {
253
+ player.playbackEngine.events.filter { it is MediaProductEnded }.first()
254
+ }
255
+ }
256
+
257
+ eventReporterCoroutineScope.advanceUntilIdle()
258
+ verify(eventSender).sendEvent(
259
+ eq(" playback_session" ),
260
+ eq(ConsentCategory .NECESSARY ),
261
+ argThat {
262
+ with (gson.fromJson(this , JsonObject ::class .java)[" payload" ].asJsonObject) {
263
+ assertThat(get(" startAssetPosition" ).asDouble).isAssetPositionEqualTo(0.0 )
264
+ assertThat(get(" endAssetPosition" ).asDouble).isAssetPositionEqualTo(5.065 )
265
+ assertThat(get(" actualProductId" ).asString).isEqualTo(mediaProduct.productId)
266
+ assertThat(get(" sourceType" )?.asString).isEqualTo(mediaProduct.sourceType)
267
+ assertThat(get(" sourceId" )?.asString).isEqualTo(mediaProduct.sourceId)
268
+ with (get(" actions" ).asJsonArray) {
269
+ val pauseAction =
270
+ gson.fromJson(this [0 ], PlaybackSession .Payload .Action ::class .java)
271
+ assertThat(pauseAction.actionType)
272
+ .isEqualTo(PlaybackSession .Payload .Action .Type .PLAYBACK_STOP )
273
+ val resumeAction =
274
+ gson.fromJson(this [1 ], PlaybackSession .Payload .Action ::class .java)
275
+ assertThat(resumeAction.actionType)
276
+ .isEqualTo(PlaybackSession .Payload .Action .Type .PLAYBACK_START )
277
+ assertThat(resumeAction.assetPositionSeconds)
278
+ .isAssetPositionEqualTo(pauseAction.assetPositionSeconds)
279
+ val perfectResumeTimestamp = pauseAction.timestamp + 1_000
280
+ assertThat(resumeAction.timestamp)
281
+ .isBetween(perfectResumeTimestamp - 500 , perfectResumeTimestamp + 500 )
282
+ }
283
+ }
284
+ true
285
+ },
286
+ eq(emptyMap()),
287
+ )
288
+ }
289
+
204
290
private fun Assert<Double>.isAssetPositionEqualTo (targetPosition : Double ) = run {
205
291
isCloseTo(targetPosition, 0.5 )
206
292
}
0 commit comments