diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e816ccf51e..a00c337946 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -90,6 +90,9 @@ ([#710](https://github.com/androidx/media/pull/710)). * Leanback extension: * Cast Extension: + * Sanitize creation of a `Timeline` to not crash the app when loading + media fails on the cast device + ([#708](https://github.com/androidx/media/issues/708)). * Test Utilities: * Remove deprecated symbols: * Demo app: diff --git a/libraries/cast/src/main/java/androidx/media3/cast/CastTimelineTracker.java b/libraries/cast/src/main/java/androidx/media3/cast/CastTimelineTracker.java index c955387ff4..a052eb595d 100644 --- a/libraries/cast/src/main/java/androidx/media3/cast/CastTimelineTracker.java +++ b/libraries/cast/src/main/java/androidx/media3/cast/CastTimelineTracker.java @@ -17,7 +17,6 @@ package androidx.media3.cast; import static androidx.media3.cast.CastTimeline.ItemData.UNKNOWN_CONTENT_ID; import static androidx.media3.common.util.Assertions.checkNotNull; -import static androidx.media3.common.util.Assertions.checkStateNotNull; import android.util.SparseArray; import androidx.annotation.Nullable; @@ -105,12 +104,12 @@ import java.util.List; // TODO: Reset state when the app instance changes [Internal ref: b/129672468]. MediaStatus mediaStatus = remoteMediaClient.getMediaStatus(); - if (mediaStatus == null) { + if (mediaStatus == null || mediaStatus.getMediaInfo() == null) { return CastTimeline.EMPTY_CAST_TIMELINE; } int currentItemId = mediaStatus.getCurrentItemId(); - String currentContentId = checkStateNotNull(mediaStatus.getMediaInfo()).getContentId(); + String currentContentId = checkNotNull(mediaStatus.getMediaInfo()).getContentId(); MediaItem mediaItem = mediaItemsByContentId.get(currentContentId); updateItemData( currentItemId, diff --git a/libraries/cast/src/test/java/androidx/media3/cast/CastTimelineTrackerTest.java b/libraries/cast/src/test/java/androidx/media3/cast/CastTimelineTrackerTest.java index a8a1930056..8cd775b425 100644 --- a/libraries/cast/src/test/java/androidx/media3/cast/CastTimelineTrackerTest.java +++ b/libraries/cast/src/test/java/androidx/media3/cast/CastTimelineTrackerTest.java @@ -277,6 +277,30 @@ public class CastTimelineTrackerTest { assertThat(castTimelineTracker.mediaItemsByContentId).hasSize(1); } + @Test + public void getCastTimeline_mediaStatusIsNull_returnsEmptyTimeline() { + RemoteMediaClient mockRemoteMediaClient = mock(RemoteMediaClient.class); + MediaQueue mediaQueue = mock(MediaQueue.class); + when(mockRemoteMediaClient.getMediaQueue()).thenReturn(mediaQueue); + when(mediaQueue.getItemIds()).thenReturn(new int[0]); + when(mockRemoteMediaClient.getMediaStatus()).thenReturn(null); + + assertThat(castTimelineTracker.getCastTimeline(mockRemoteMediaClient).isEmpty()).isTrue(); + } + + @Test + public void getCastTimeline_mediaInfoIsNull_returnsEmptyTimeline() { + RemoteMediaClient mockRemoteMediaClient = mock(RemoteMediaClient.class); + MediaQueue mediaQueue = mock(MediaQueue.class); + when(mockRemoteMediaClient.getMediaQueue()).thenReturn(mediaQueue); + when(mediaQueue.getItemIds()).thenReturn(new int[0]); + MediaStatus mediaStatus = mock(MediaStatus.class); + when(mockRemoteMediaClient.getMediaStatus()).thenReturn(mediaStatus); + when(mediaStatus.getMediaInfo()).thenReturn(null); + + assertThat(castTimelineTracker.getCastTimeline(mockRemoteMediaClient).isEmpty()).isTrue(); + } + private MediaItem createMediaItem(int uid) { return new MediaItem.Builder() .setUri("http://www.google.com/" + uid)