Open sampengilly opened 2 months ago
I think this can be controlled by the FakeMediaPeriod.TrackDataFactory
that is passed to the FakeMediaSource
constructor. It produces the samples put into the queues for playback. By default it's a single sample following the the end-of-stream signal. See here. If you leave out the end-of-stream signal you create partially buffered sources that don't progress beyond a certain point.
If you need to let them load more data at a later point in the test, you'd need to go one level down and inject your own custom FakeSampleStream
(by overriding createMediaPeriod
and createSampleStream
, see this example). FakeSampleStream
has an append
method to add more samples to the list as needed.
Hmm, it seems that creating a custom FakeMediaSource
which overrides the sample stream to omit the end of stream signal, or to introduce more sample items doesn't seem to have an effect in the test. It doesn't appear to trigger the buffering behaviour that is expected.
In this setup the runUntilPlaybackState(player, Player.STATE_BUFFERING)
call times out.
val mediaItem = MediaItem.Builder().setMediaId("TEST_ID").build()
val fakeMediaSource = object : FakeMediaSource(timelineForMediaItem(mediaItem, 10.seconds)) {
override fun createMediaPeriod(
id: MediaSource.MediaPeriodId,
trackGroupArray: TrackGroupArray,
allocator: Allocator,
mediaSourceEventDispatcher: MediaSourceEventListener.EventDispatcher,
drmSessionManager: DrmSessionManager,
drmEventDispatcher: DrmSessionEventListener.EventDispatcher,
transferListener: TransferListener?
): MediaPeriod = object : FakeMediaPeriod(
trackGroupArray,
allocator,
{ _, _ -> ImmutableList.of() },
mediaSourceEventDispatcher,
drmSessionManager,
drmEventDispatcher,
false
) {
override fun createSampleStream(
allocator: Allocator,
mediaSourceEventDispatcher: MediaSourceEventListener.EventDispatcher?,
drmSessionManager: DrmSessionManager,
drmEventDispatcher: DrmSessionEventListener.EventDispatcher,
initialFormat: Format,
fakeSampleStreamItems: MutableList<FakeSampleStream.FakeSampleStreamItem>
): FakeSampleStream = FakeSampleStream(
allocator,
mediaSourceEventDispatcher,
drmSessionManager,
drmEventDispatcher,
initialFormat,
listOf(
FakeSampleStreamItem.oneByteSample(5.seconds.inWholeMicroseconds),
FakeSampleStreamItem.oneByteSample(5.seconds.inWholeMicroseconds)
)
)
}
}
player.setMediaSource(fakeMediaSource)
player.prepare()
player.play()
TestPlayerRunHelper.playUntilPosition(player, 0, 1.seconds.inWholeMilliseconds)
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_BUFFERING)
It's not running because
Format
to the FakeMediaSource
constructor, e.g. FakeMediaSource(timelineForMediaItem(mediaItem, 10.seconds), ExoPlayerTestRunner.VIDEO_FORMAT)
FakeSampleStreamItem.oneByteSample(5.seconds.inWholeMicroseconds, C.BUFFER_FLAG_KEY_FRAME)
If I make both these changes, the test runs through as expected.
Thanks, I'll give that a try
Hmmm, I'm still not seeing the expected behaviour with those changes:
private class FakeBufferingMediaSource(
mediaItem: MediaItem
) : FakeMediaSource(timelineForMediaItem(mediaItem, 10.seconds), ExoPlayerTestRunner.VIDEO_FORMAT) {
override fun createMediaPeriod(
id: MediaSource.MediaPeriodId,
trackGroupArray: TrackGroupArray,
allocator: Allocator,
mediaSourceEventDispatcher: MediaSourceEventListener.EventDispatcher,
drmSessionManager: DrmSessionManager,
drmEventDispatcher: DrmSessionEventListener.EventDispatcher,
transferListener: TransferListener?
): MediaPeriod = object : FakeMediaPeriod(
trackGroupArray,
allocator,
{ _, _ -> ImmutableList.of() },
mediaSourceEventDispatcher,
drmSessionManager,
drmEventDispatcher,
false
) {
override fun createSampleStream(
allocator: Allocator,
mediaSourceEventDispatcher: MediaSourceEventListener.EventDispatcher?,
drmSessionManager: DrmSessionManager,
drmEventDispatcher: DrmSessionEventListener.EventDispatcher,
initialFormat: Format,
fakeSampleStreamItems: MutableList<FakeSampleStreamItem>
): FakeSampleStream = FakeSampleStream(
allocator,
mediaSourceEventDispatcher,
drmSessionManager,
drmEventDispatcher,
initialFormat,
listOf(
FakeSampleStreamItem.oneByteSample(5.seconds.inWholeMicroseconds, C.BUFFER_FLAG_KEY_FRAME),
FakeSampleStreamItem.oneByteSample(5.seconds.inWholeMicroseconds),
FakeSampleStreamItem.END_OF_STREAM_ITEM
)
)
}
}
Logs during test output (I have a listener set on the player which prints the different events)
The logs I expect to see with content that buffers (from a real remote mp3 media source)
With the changes suggested above, removing the END_OF_STREAM_ITEM
from the list causes the test to timeout (waiting for STATE_ENDED when it will never come), so clearly the sample list is having some effect here.
That sounds like working as intended I think. If you add the END_OF_STREAM_ITEM
sample, the player will just play these sample and then go the ended state. If you omit END_OF_STREAM_ITEM
., then the player will stay in a buffering state and can't make further progress (and in particular never reaching STATE_ENDED
).
As per my original comment, you can change the sample stream later with the append
method. So you can have a test setup like
// Create player and set up FakeMediaSource without END_OF_STREAM_ITEM
player.prepare();
player.play();
runUntilPlaybackState(player, Player.STATE_BUFFERING);
// Make assertions about your app here that depend on this buffering.
fakeSampleStream.append(END_OF_STREAM_ITEM); // Allow the stream to end.
runUntilPlaybackState(player, Player.STATE_ENDED);
Even using append I'm a bit lost here.
I've updated my FakeBufferingMediaSource to allow for appending sample items (including calling writeData
on the sample stream which seems to be needed for things given to the append
function to be written).
internal class FakeBufferingMediaSource(
timeline: Timeline
) : FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT) {
private lateinit var sampleStream: FakeSampleStream
fun appendNextSample(item: FakeSampleStreamItem) {
sampleStream.append(listOf(item))
sampleStream.writeData(0)
}
override fun createMediaPeriod(
id: MediaSource.MediaPeriodId,
trackGroupArray: TrackGroupArray,
allocator: Allocator,
mediaSourceEventDispatcher: MediaSourceEventListener.EventDispatcher,
drmSessionManager: DrmSessionManager,
drmEventDispatcher: DrmSessionEventListener.EventDispatcher,
transferListener: TransferListener?
): MediaPeriod = object : FakeMediaPeriod(
trackGroupArray,
allocator,
{ _, _ -> ImmutableList.of() },
mediaSourceEventDispatcher,
drmSessionManager,
drmEventDispatcher,
false
) {
override fun createSampleStream(
allocator: Allocator,
mediaSourceEventDispatcher: MediaSourceEventListener.EventDispatcher?,
drmSessionManager: DrmSessionManager,
drmEventDispatcher: DrmSessionEventListener.EventDispatcher,
initialFormat: Format,
fakeSampleStreamItems: MutableList<FakeSampleStreamItem>
): FakeSampleStream = FakeSampleStream(
allocator,
mediaSourceEventDispatcher,
drmSessionManager,
drmEventDispatcher,
initialFormat,
listOf()
).also { sampleStream = it }
}
}
In my test though, nothing at all happens until an END_OF_STREAM_ITEM is appended.
fun `play event triggered once for content that buffers midway through`() {
val mediaItem = MediaItem.Builder().setMediaId("TEST_ID").build()
val source = FakeBufferingMediaSource(
timeline = timelineForMediaItem(mediaItem, 10.seconds)
)
player.setMediaSource(source)
player.prepare()
player.play()
TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled(player)
source.appendNextSample(oneByteSample(2.seconds.inWholeMicroseconds, C.BUFFER_FLAG_KEY_FRAME))
// TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_BUFFERING)
// source.appendNextSample(oneByteSample(2.seconds.inWholeMicroseconds, C.BUFFER_FLAG_KEY_FRAME))
// TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_BUFFERING)
// source.appendNextSample(END_OF_STREAM_ITEM)
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED)
playEvents.map { it.mediaItem } shouldContainExactly listOf(mediaItem)
}
With the END_OF_STREAM_ITEM line commented out, nothing even begins to play, it times out waiting for a STATE_ENDED event that never comes but beyond that there isn't even a STATE_READY event that occurs.
If I uncomment just that line appending the END_OF_STREAM_ITEM suddenly the READY state occurs:
The other commented lines appending new samples have no effect
Sorry, I didn't get back to this conversation. I haven't tried it yet, but I think your test may be blocked on the (Default)LoadControl
requiring a minimum amount of buffered data to become 'ready'.
I tried playing around a little with the LoadControl
on the fake player, setting different parameters on it and specifying larger and larger blocks of sample data in the sample stream. Doesn't seem to have had an effect :(
I'm trying to write some unit tests and using the existing tests as a guide (like this one: https://github.com/androidx/media/blob/d833d59124d795afc146322fe488b2c0d4b9af6a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/analytics/PlaybackStatsListenerTest.java)
I'm trying to test the behaviour of a custom
Player.Listener
that should react only once for each media item that starts playing. UsingFakeMediaSource
I can emulate most of the behaviours I'm interested in covering. However one behaviour I'd like to cover is content which buffers part way through.If I set a real media item on the player from a remote mp3 url and observe the events, I get a tonne of
onPlaybackStateChanged
andonIsPlayingChanged
events as it swaps between buffering and ready.I'd like to have a test that covers this behaviour but if possible I'd like to do it using local resources rather than my current method of hitting a real mp3 on a remote server.
Is there a way to emulate this using
FakeMediaSource
or one of its subclasses? Or would I need to put a local mp3 file in my test resources and load that?