package link.roadio.riff.player.library import link.roadio.cuetree.Life import link.roadio.cuetree.MediaId import link.roadio.cuetree.Node import link.roadio.cuetree.NodeKind import link.roadio.cuetree.ROOT_IDENTITY_ATTRIBUTE import link.roadio.cuetree.Section import link.roadio.cuetree.Snapshot import link.roadio.cuetree.SourceInfo import link.roadio.cuetree.Span import link.roadio.cuetree.TableObservation import link.roadio.riff.ui.TocChapter import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test class CuetreePlaybackBindingsTest { private fun media( starts: List, reference: String? = null, ): Node = Node( NodeKind.MEDIA, MediaId(10), MediaId(1), SourceInfo("book.m4b"), life = Life(1), tables = listOf( TableObservation( "mp4:0", emptyMap(), starts.mapIndexed { index, start -> Section(reference, Span(start), if (index == 0) mapOf("title" to listOf("Opening")) else emptyMap()) }, ), ), ) @Test fun consumesIndexedStartsAndTitlesWithoutInventingZero() { assertEquals(listOf(TocChapter(1000, "Opening"), TocChapter(5000)), indexedChapters(media(listOf(1_000_000, 5_000_000)))) } @Test fun rejectsSingletonExternalAndNonChronologicalTables() { assertTrue(indexedChapters(media(listOf(1000))).isEmpty()) assertTrue(indexedChapters(media(listOf(1000, 2000), "other.mp3")).isEmpty()) assertTrue(indexedChapters(media(listOf(2000, 1000))).isEmpty()) assertTrue(indexedChapters(media(listOf(1000, 1001))).isEmpty()) } @Test fun clippingUsesOccurrenceRelativeCoordinatesAndExcludesEnd() { val source = media(listOf(0, 1_000_000, 2_000_000, 3_000_000)) val fragment = Node(NodeKind.FRAGMENT, MediaId(20), source.id, span = Span(1_000_000, 3_000_000), life = Life(1)) assertEquals(listOf(TocChapter(0), TocChapter(1000)), indexedChapters(source, fragment)) } @Test fun copiesRetainTheirOwnTocInBothBrowseProjections() { val root = Node( NodeKind.DIRECTORY, MediaId(1), source = SourceInfo("Books", provider = "saf", locator = "content://root"), attributes = mapOf(ROOT_IDENTITY_ATTRIBUTE to listOf("root")), life = Life(1), ) val first = media(listOf(0, 10_000_000)) val second = media(listOf(0, 20_000_000)).copy(source = SourceInfo("copy.m4b")) val model = CuetreeBrowserTree.build( Snapshot(1, "test", listOf(root, first, second)), PlayerLibraryProjection(), emptySet(), hash = { bytes -> bytes.contentHashCode().toLong() }, ) val mediaId = link.roadio.riff.indexer.core.media .MediaId(10, 0, false) assertTrue(model.music.tocChaptersByBindingId.isEmpty()) model.music.lookup(mediaId) model.audiobook.lookup(mediaId) val chapters = model.music.tocChaptersByBindingId assertEquals(2, chapters.size) assertEquals(setOf(10_000L, 20_000L), chapters.values.map { it.last().startMs }.toSet()) assertEquals(chapters, model.audiobook.tocChaptersByBindingId) assertEquals(chapters.mapValues { (_, values) -> values.map { it.startMs } }, model.music.tocMarkersByBindingId) } @Test fun playbackLookupAndBrowseUseTheSameBindingsAndBookAliases() { val root = Node( NodeKind.DIRECTORY, MediaId(1), source = SourceInfo("Books"), life = Life(1), attributes = mapOf(ROOT_IDENTITY_ATTRIBUTE to listOf("root")), ) val original = media(listOf(0, 10_000_000)).copy( id = MediaId(10, 0), identityFlags = link.roadio.cuetree.Flags.SHRT, attributes = mapOf("automaticBook" to listOf("1")), ) val collider = media(listOf(0, 20_000_000)).copy(id = MediaId(10, 1), identityFlags = 0, source = SourceInfo("music.mp3")) val snapshot = Snapshot(1, "test", listOf(root, original, collider)) val hash: (ByteArray) -> Long = { it.contentHashCode().toLong() } val projection = PlayerLibraryProjection() val catalog = CuetreeTrackCatalog(snapshot, projection, hash) val browser = CuetreeBrowserTree.build(snapshot, projection, emptySet(), hash = hash).music val alias = link.roadio.riff.indexer.core.media .MediaId(10, 0, false) val other = link.roadio.riff.indexer.core.media .MediaId(10, 1, true) val early = catalog.lookup(alias) assertEquals(browser.lookup(alias).sources, early.sources) assertEquals(browser.lookup(alias).chapters, early.chapters) assertEquals(browser.lookup(alias).row, early.row) assertTrue(early.row?.audiobook == true) assertTrue(alias in browser.effectiveBookMediaIds) assertTrue(alias in browser.audiobookMediaIds) assertTrue(other !in browser.effectiveBookMediaIds) assertTrue(catalog.lookup(other).row?.audiobook == false) } }