diff --git a/app/src/main/java/com/nononsenseapps/feeder/model/html/HtmlLinearizer.kt b/app/src/main/java/com/nononsenseapps/feeder/model/html/HtmlLinearizer.kt index ca6596164..da1480052 100644 --- a/app/src/main/java/com/nononsenseapps/feeder/model/html/HtmlLinearizer.kt +++ b/app/src/main/java/com/nononsenseapps/feeder/model/html/HtmlLinearizer.kt @@ -3,6 +3,7 @@ package com.nononsenseapps.feeder.model.html import android.util.Log import com.nononsenseapps.feeder.ui.compose.text.ancestors import com.nononsenseapps.feeder.ui.compose.text.stripHtml +import com.nononsenseapps.feeder.ui.text.getVideo import com.nononsenseapps.feeder.util.asUTF8Sequence import com.nononsenseapps.feeder.util.logDebug import org.jsoup.Jsoup @@ -399,7 +400,7 @@ class HtmlLinearizer { add( LinearImage( - candidates = imageCandidates, + sources = imageCandidates, caption = caption, link = link, ), @@ -417,7 +418,7 @@ class HtmlLinearizer { .takeIf { it.isNotBlank() } add( LinearImage( - candidates = candidates, + sources = candidates, // Parse a LinearText with annotations from element.attr("alt") caption = captionText?.let { @@ -555,16 +556,70 @@ class HtmlLinearizer { } } - "iframe" -> { - // TODO - } - "rt", "rp" -> { // Ruby text elements. Not supported. } + "audio" -> { + val sources = + element.getElementsByTag("source").asSequence() + .mapNotNull { source -> + source.attr("abs:src").takeIf { it.isNotBlank() }?.let { src -> + LinearAudioSource( + uri = src, + mimeType = source.attr("type").ifBlank { null }, + ) + } + }.toList() + .takeIf { it.isNotEmpty() } + + if (sources != null) { + add(LinearAudio(sources)) + } + } + + "iframe" -> { + getVideo(element.attr("abs:src").ifBlank { null })?.let { video -> + add( + LinearVideo( + sources = + listOf( + LinearVideoSource( + uri = video.src, + link = video.link, + imageThumbnail = video.imageUrl, + widthPx = video.width, + heightPx = video.height, + mimeType = null, + ), + ), + ), + ) + } + } + "video" -> { - // not implemented yet. + val width = element.attr("width").toIntOrNull() + val height = element.attr("height").toIntOrNull() + val sources = + element.getElementsByTag("source").asSequence() + .mapNotNull { source -> + source.attr("abs:src").takeIf { it.isNotBlank() }?.let { src -> + LinearVideoSource( + uri = src, + link = src, + imageThumbnail = null, + mimeType = source.attr("type").ifBlank { null }, + widthPx = width, + heightPx = height, + ) + } + }.toList() + .takeIf { it.isNotEmpty() } + + if (sources != null) { + add(LinearVideo(sources)) + } } else -> { @@ -633,7 +688,7 @@ class HtmlLinearizer { private fun getImageSource( baseUrl: String, element: Element, - ): List { + ): List { val absSrc: String = element.attr("abs:src") val dataImgUrl: String = element.attr("data-img-url").ifBlank { element.attr("data-src") } val srcSet: String = element.attr("srcset").ifBlank { element.attr("data-responsive") } @@ -659,7 +714,7 @@ class HtmlLinearizer { ?.firstOrNull() ?: "" - val result = mutableListOf() + val result = mutableListOf() try { srcSet.splitToSequence(", ") @@ -671,7 +726,7 @@ class HtmlLinearizer { } if (candidate.size == 1) { result.add( - LinearImageCandidate( + LinearImageSource( imgUri = StringUtil.resolve(baseUrl, candidate.first()), pixelDensity = null, heightPx = null, @@ -689,7 +744,7 @@ class HtmlLinearizer { } result.add( - LinearImageCandidate( + LinearImageSource( imgUri = StringUtil.resolve(baseUrl, candidate.first()), pixelDensity = null, heightPx = null, @@ -707,7 +762,7 @@ class HtmlLinearizer { } result.add( - LinearImageCandidate( + LinearImageSource( imgUri = StringUtil.resolve(baseUrl, candidate.first()), pixelDensity = density, heightPx = null, @@ -727,7 +782,7 @@ class HtmlLinearizer { val url = StringUtil.resolve(baseUrl, it) if (width != null && height != null) { result.add( - LinearImageCandidate( + LinearImageSource( imgUri = url, pixelDensity = null, screenWidth = null, @@ -737,7 +792,7 @@ class HtmlLinearizer { ) } else { result.add( - LinearImageCandidate( + LinearImageSource( imgUri = url, pixelDensity = null, heightPx = null, @@ -752,7 +807,7 @@ class HtmlLinearizer { val url = StringUtil.resolve(baseUrl, it) if (width != null && height != null) { result.add( - LinearImageCandidate( + LinearImageSource( imgUri = url, pixelDensity = null, screenWidth = null, @@ -762,7 +817,7 @@ class HtmlLinearizer { ) } else { result.add( - LinearImageCandidate( + LinearImageSource( imgUri = url, pixelDensity = null, screenWidth = null, @@ -776,7 +831,7 @@ class HtmlLinearizer { backgroundImage.takeIf { it.isNotBlank() }?.let { val url = StringUtil.resolve(baseUrl, it) result.add( - LinearImageCandidate( + LinearImageSource( imgUri = url, pixelDensity = null, screenWidth = null, @@ -791,7 +846,7 @@ class HtmlLinearizer { return result } - private fun Element.descendantImageCandidates(baseUrl: String): List? { + private fun Element.descendantImageCandidates(baseUrl: String): List? { // Arstechnica is weird and has images inside divs inside figures return sequence { yieldAll(getElementsByTag("img")) @@ -803,7 +858,7 @@ class HtmlLinearizer { .takeIf { it.isNotEmpty() } } - private fun Element.ancestorImageCandidates(baseUrl: String): List? { + private fun Element.ancestorImageCandidates(baseUrl: String): List? { // Arstechnica is weird and places image details in list items which themselves contain the figure return ancestors { it.hasAttr("data-src") || it.hasAttr("data-responsive") diff --git a/app/src/main/java/com/nononsenseapps/feeder/model/html/LinearStuff.kt b/app/src/main/java/com/nononsenseapps/feeder/model/html/LinearStuff.kt index 5f2c7613b..c550571de 100644 --- a/app/src/main/java/com/nononsenseapps/feeder/model/html/LinearStuff.kt +++ b/app/src/main/java/com/nononsenseapps/feeder/model/html/LinearStuff.kt @@ -279,15 +279,62 @@ val LinearTextBlockStyle.shouldSoftWrap: Boolean * Represents an image element */ data class LinearImage( - val candidates: List, + val sources: List, val caption: LinearText?, val link: String?, ) : LinearElement -data class LinearImageCandidate( +data class LinearImageSource( val imgUri: String, val widthPx: Int?, val heightPx: Int?, val pixelDensity: Float?, val screenWidth: Int?, ) + +/** + * Represents a video element + */ +data class LinearVideo( + val sources: List, +) : LinearElement { + init { + require(sources.isNotEmpty()) { "At least one source must be provided" } + } + + val imageThumbnail: String? by lazy { + sources.firstOrNull { it.imageThumbnail != null }?.imageThumbnail + } + + val firstSource: LinearVideoSource + get() = sources.first() +} + +data class LinearVideoSource( + val uri: String, + // This might be different from the uri, for example for youtube videos where uri is the embed uri + val link: String, + val imageThumbnail: String?, + val widthPx: Int?, + val heightPx: Int?, + val mimeType: String?, +) + +/** + * Represents an audio element + */ +data class LinearAudio( + val sources: List, +) : LinearElement { + init { + require(sources.isNotEmpty()) { "At least one source must be provided" } + } + + val firstSource: LinearAudioSource + get() = sources.first() +} + +data class LinearAudioSource( + val uri: String, + val mimeType: String?, +) diff --git a/app/src/main/java/com/nononsenseapps/feeder/ui/compose/html/LinearArticleContent.kt b/app/src/main/java/com/nononsenseapps/feeder/ui/compose/html/LinearArticleContent.kt index eafe56431..cb060f698 100644 --- a/app/src/main/java/com/nononsenseapps/feeder/ui/compose/html/LinearArticleContent.kt +++ b/app/src/main/java/com/nononsenseapps/feeder/ui/compose/html/LinearArticleContent.kt @@ -21,6 +21,7 @@ import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.selection.DisableSelection import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ErrorOutline +import androidx.compose.material.icons.outlined.PlayCircleOutline import androidx.compose.material.icons.outlined.Terrain import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme @@ -39,6 +40,7 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.TextStyle @@ -55,12 +57,14 @@ import coil.request.ImageRequest import coil.size.Precision import coil.size.Scale import coil.size.Size +import com.nononsenseapps.feeder.R import com.nononsenseapps.feeder.model.html.Coordinate import com.nononsenseapps.feeder.model.html.LinearArticle +import com.nononsenseapps.feeder.model.html.LinearAudio import com.nononsenseapps.feeder.model.html.LinearBlockQuote import com.nononsenseapps.feeder.model.html.LinearElement import com.nononsenseapps.feeder.model.html.LinearImage -import com.nononsenseapps.feeder.model.html.LinearImageCandidate +import com.nononsenseapps.feeder.model.html.LinearImageSource import com.nononsenseapps.feeder.model.html.LinearList import com.nononsenseapps.feeder.model.html.LinearListItem import com.nononsenseapps.feeder.model.html.LinearTable @@ -85,6 +89,7 @@ import com.nononsenseapps.feeder.model.html.LinearTextAnnotationSubscript import com.nononsenseapps.feeder.model.html.LinearTextAnnotationSuperscript import com.nononsenseapps.feeder.model.html.LinearTextAnnotationUnderline import com.nononsenseapps.feeder.model.html.LinearTextBlockStyle +import com.nononsenseapps.feeder.model.html.LinearVideo import com.nononsenseapps.feeder.ui.compose.coil.RestrainedFillWidthScaling import com.nononsenseapps.feeder.ui.compose.coil.RestrainedFitScaling import com.nononsenseapps.feeder.ui.compose.coil.rememberTintedVectorPainter @@ -206,6 +211,144 @@ fun LinearElementContent( allowHorizontalScroll = allowHorizontalScroll, modifier = modifier, ) + + is LinearAudio -> + LinearAudioContent( + linearAudio = linearElement, + onLinkClick = onLinkClick, + modifier = modifier, + ) + + is LinearVideo -> + LinearVideoContent( + linearVideo = linearElement, + onLinkClick = onLinkClick, + modifier = modifier, + ) + } +} + +@Composable +fun LinearAudioContent( + linearAudio: LinearAudio, + modifier: Modifier = Modifier, + onLinkClick: (String) -> Unit, +) { + Column( + modifier = + Modifier + .then(modifier), + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + DisableSelection { + ProvideScaledText( + style = + MaterialTheme.typography.bodyLarge.merge( + LinkTextStyle(), + ), + ) { + Text( + text = stringResource(R.string.touch_to_play_audio), + modifier = + Modifier.clickable { + onLinkClick(linearAudio.firstSource.uri) + }, + ) + } + } + } +} + +@Composable +fun LinearVideoContent( + linearVideo: LinearVideo, + modifier: Modifier = Modifier, + onLinkClick: (String) -> Unit, +) { + Column( + modifier = + Modifier + .then(modifier), + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + DisableSelection { + if (linearVideo.imageThumbnail != null) { + BoxWithConstraints( + contentAlignment = Alignment.Center, + modifier = + Modifier + .clip(RectangleShape) + .clickable { + linearVideo.firstSource.link.let(onLinkClick) + } + .fillMaxWidth(), + ) { + val maxImageWidth by rememberMaxImageWidth() + val pixelDensity = LocalDensity.current.density + + val imageWidth: Int = + remember(linearVideo.firstSource) { + when { + linearVideo.firstSource.widthPx != null -> linearVideo.firstSource.widthPx!! + else -> maxImageWidth + } + } + val imageHeight: Int? = null + val dimens = LocalDimens.current + + val contentScale = + remember(pixelDensity, dimens.hasImageAspectRatioInReader) { + if (dimens.hasImageAspectRatioInReader) { + RestrainedFitScaling(pixelDensity) + } else { + RestrainedFillWidthScaling(pixelDensity) + } + } + + AsyncImage( + model = + ImageRequest.Builder(LocalContext.current) + .data(linearVideo.imageThumbnail) + .scale(Scale.FIT) + // DO NOT use the actualSize parameter here + .size(Size(imageWidth, imageHeight ?: imageWidth)) + // If image is larger than requested size, scale down + // But if image is smaller, don't scale up + // Note that this is the pixels, not how it is scaled inside the ImageView + .precision(Precision.INEXACT) + .build(), + contentDescription = stringResource(R.string.touch_to_play_video), + placeholder = + rememberTintedVectorPainter( + Icons.Outlined.PlayCircleOutline, + ), + error = rememberTintedVectorPainter(Icons.Outlined.ErrorOutline), + contentScale = contentScale, + modifier = + Modifier + .widthIn(max = maxWidth) + .fillMaxWidth(), + ) + } + } + + ProvideScaledText( + style = + MaterialTheme.typography.bodyLarge.merge( + LinkTextStyle(), + ), + ) { + Text( + text = stringResource(R.string.touch_to_play_video), + modifier = + Modifier.clickable { + onLinkClick(linearVideo.firstSource.link) + }, + ) + } + } } } @@ -259,7 +402,7 @@ fun LinearImageContent( modifier: Modifier = Modifier, onLinkClick: (String) -> Unit, ) { - if (linearImage.candidates.isEmpty()) { + if (linearImage.sources.isEmpty()) { return } @@ -379,8 +522,8 @@ fun LinearImageContent( private fun LinearImage.getBestImageForMaxSize( pixelDensity: Float, maxWidth: Int, -): LinearImageCandidate? = - candidates.minByOrNull { candidate -> +): LinearImageSource? = + sources.minByOrNull { candidate -> val candidateSize = when { candidate.pixelDensity != null -> candidate.pixelDensity / pixelDensity @@ -698,6 +841,8 @@ val LinearElement.lazyListContentType: String is LinearText -> "LinearText" is LinearTable -> "LinearTable" is LinearBlockQuote -> "LinearBlockQuote" + is LinearAudio -> "LinearAudio" + is LinearVideo -> "LinearVideo" } @Composable @@ -964,9 +1109,9 @@ private fun PreviewLinearUnorderedListContent() { private fun PreviewLinearImageContent() { val linearImage = LinearImage( - candidates = + sources = listOf( - LinearImageCandidate( + LinearImageSource( imgUri = "https://example.com/image.jpg", widthPx = 200, heightPx = 200, @@ -1064,9 +1209,9 @@ private fun PreviewNestedTableContent() { content = listOf( LinearImage( - candidates = + sources = listOf( - LinearImageCandidate( + LinearImageSource( imgUri = "https://example.com/image.jpg", widthPx = null, heightPx = null, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cf27100ff..d4c166951 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -261,4 +261,5 @@ Close menu Skip duplicate articles Articles with links or titles identical to existing articles are ignored + Touch to play audio diff --git a/app/src/test/java/com/nononsenseapps/feeder/model/html/HtmlLinearizerTest.kt b/app/src/test/java/com/nononsenseapps/feeder/model/html/HtmlLinearizerTest.kt index 0ae6acc6f..d1d709ee6 100644 --- a/app/src/test/java/com/nononsenseapps/feeder/model/html/HtmlLinearizerTest.kt +++ b/app/src/test/java/com/nononsenseapps/feeder/model/html/HtmlLinearizerTest.kt @@ -192,9 +192,9 @@ class HtmlLinearizerTest { assertEquals(1, result.size, "Expected one item: $result") assertEquals( LinearImage( - candidates = + sources = listOf( - LinearImageCandidate(imgUri = "https://example.com/image.jpg", widthPx = null, heightPx = null, pixelDensity = null, screenWidth = null), + LinearImageSource(imgUri = "https://example.com/image.jpg", widthPx = null, heightPx = null, pixelDensity = null, screenWidth = null), ), caption = LinearText("Alt text", LinearTextBlockStyle.TEXT), link = null, @@ -213,9 +213,9 @@ class HtmlLinearizerTest { assertEquals(1, result.size, "Expected one item: $result") assertEquals( LinearImage( - candidates = + sources = listOf( - LinearImageCandidate(imgUri = "https://example.com/image.jpg", widthPx = null, heightPx = null, pixelDensity = null, screenWidth = null), + LinearImageSource(imgUri = "https://example.com/image.jpg", widthPx = null, heightPx = null, pixelDensity = null, screenWidth = null), ), caption = LinearText("Bold text", LinearTextBlockStyle.TEXT), link = null, @@ -234,9 +234,9 @@ class HtmlLinearizerTest { assertEquals(1, result.size, "Expected one item: $result") assertEquals( LinearImage( - candidates = + sources = listOf( - LinearImageCandidate(imgUri = "https://example.com/image.jpg", widthPx = null, heightPx = null, pixelDensity = null, screenWidth = null), + LinearImageSource(imgUri = "https://example.com/image.jpg", widthPx = null, heightPx = null, pixelDensity = null, screenWidth = null), ), caption = LinearText("Alt text", LinearTextBlockStyle.TEXT), link = "https://example.com/link", @@ -255,9 +255,9 @@ class HtmlLinearizerTest { assertEquals(1, result.size, "Expected one item: $result") assertEquals( LinearImage( - candidates = + sources = listOf( - LinearImageCandidate(imgUri = "https://example.com/image.jpg", widthPx = 100, heightPx = 200, pixelDensity = null, screenWidth = null), + LinearImageSource(imgUri = "https://example.com/image.jpg", widthPx = 100, heightPx = 200, pixelDensity = null, screenWidth = null), ), caption = null, link = null, @@ -281,12 +281,12 @@ class HtmlLinearizerTest { assertEquals(1, result.size, "Expected one item: $result") assertEquals( LinearImage( - candidates = + sources = listOf( - LinearImageCandidate(imgUri = "https://example.com/image.jpg", widthPx = null, heightPx = null, screenWidth = null, pixelDensity = 1f), - LinearImageCandidate(imgUri = "https://example.com/image-2x.jpg", widthPx = null, heightPx = null, screenWidth = null, pixelDensity = 2f), - LinearImageCandidate(imgUri = "https://example.com/image-700w.jpg", widthPx = null, heightPx = null, screenWidth = 700, pixelDensity = null), - LinearImageCandidate(imgUri = "https://example.com/image-fallback.jpg", widthPx = null, heightPx = null, screenWidth = null, pixelDensity = null), + LinearImageSource(imgUri = "https://example.com/image.jpg", widthPx = null, heightPx = null, screenWidth = null, pixelDensity = 1f), + LinearImageSource(imgUri = "https://example.com/image-2x.jpg", widthPx = null, heightPx = null, screenWidth = null, pixelDensity = 2f), + LinearImageSource(imgUri = "https://example.com/image-700w.jpg", widthPx = null, heightPx = null, screenWidth = 700, pixelDensity = null), + LinearImageSource(imgUri = "https://example.com/image-fallback.jpg", widthPx = null, heightPx = null, screenWidth = null, pixelDensity = null), ), caption = null, link = null, @@ -305,9 +305,9 @@ class HtmlLinearizerTest { assertEquals(1, result.size, "Expected one item: $result") assertEquals( LinearImage( - candidates = + sources = listOf( - LinearImageCandidate(imgUri = "https://example.com/image.jpg", widthPx = null, heightPx = null, pixelDensity = null, screenWidth = null), + LinearImageSource(imgUri = "https://example.com/image.jpg", widthPx = null, heightPx = null, pixelDensity = null, screenWidth = null), ), caption = null, link = null, @@ -326,9 +326,9 @@ class HtmlLinearizerTest { assertEquals(1, result.size, "Expected one item: $result") assertEquals( LinearImage( - candidates = + sources = listOf( - LinearImageCandidate(imgUri = "https://example.com/image.jpg", widthPx = null, heightPx = null, pixelDensity = null, screenWidth = null), + LinearImageSource(imgUri = "https://example.com/image.jpg", widthPx = null, heightPx = null, pixelDensity = null, screenWidth = null), ), caption = null, link = null, @@ -347,9 +347,9 @@ class HtmlLinearizerTest { assertEquals(1, result.size, "Expected one item: $result") assertEquals( LinearImage( - candidates = + sources = listOf( - LinearImageCandidate(imgUri = "https://example.com/image.jpg", widthPx = null, heightPx = null, pixelDensity = null, screenWidth = null), + LinearImageSource(imgUri = "https://example.com/image.jpg", widthPx = null, heightPx = null, pixelDensity = null, screenWidth = null), ), caption = LinearText("Alt text", LinearTextBlockStyle.TEXT, LinearTextAnnotation(LinearTextAnnotationBold, 4, 4)), link = null, @@ -373,9 +373,9 @@ class HtmlLinearizerTest { assertEquals(1, result.size, "Expected one item: $result") assertEquals( LinearImage( - candidates = + sources = listOf( - LinearImageCandidate(imgUri = "https://example.com/image.jpg", widthPx = null, heightPx = null, pixelDensity = null, screenWidth = null), + LinearImageSource(imgUri = "https://example.com/image.jpg", widthPx = null, heightPx = null, pixelDensity = null, screenWidth = null), ), caption = LinearText("Alt text", LinearTextBlockStyle.TEXT, LinearTextAnnotation(data = LinearTextAnnotationLink("https://example.com/link"), start = 0, end = 7)), link = "https://example.com/link", @@ -418,10 +418,10 @@ class HtmlLinearizerTest { assertEquals(1, result.size, "Expected one item: $result") assertEquals( LinearImage( - candidates = + sources = listOf( - LinearImageCandidate(imgUri = "https://example.com/image.jpg", widthPx = null, heightPx = null, pixelDensity = 1f, screenWidth = null), - LinearImageCandidate(imgUri = "https://example.com/image-2x.jpg", widthPx = null, heightPx = null, pixelDensity = 2f, screenWidth = null), + LinearImageSource(imgUri = "https://example.com/image.jpg", widthPx = null, heightPx = null, pixelDensity = 1f, screenWidth = null), + LinearImageSource(imgUri = "https://example.com/image-2x.jpg", widthPx = null, heightPx = null, pixelDensity = 2f, screenWidth = null), ), caption = null, link = null, @@ -446,11 +446,11 @@ class HtmlLinearizerTest { assertEquals(1, result.size, "Expected one item: $result") assertEquals( LinearImage( - candidates = + sources = listOf( - LinearImageCandidate(imgUri = "https://example.com/image.jpg", widthPx = null, heightPx = null, pixelDensity = 1f, screenWidth = null), - LinearImageCandidate(imgUri = "https://example.com/image-2x.jpg", widthPx = null, heightPx = null, pixelDensity = 2f, screenWidth = null), - LinearImageCandidate(imgUri = "https://example.com/image-3x.jpg", widthPx = null, heightPx = null, pixelDensity = null, screenWidth = null), + LinearImageSource(imgUri = "https://example.com/image.jpg", widthPx = null, heightPx = null, pixelDensity = 1f, screenWidth = null), + LinearImageSource(imgUri = "https://example.com/image-2x.jpg", widthPx = null, heightPx = null, pixelDensity = 2f, screenWidth = null), + LinearImageSource(imgUri = "https://example.com/image-3x.jpg", widthPx = null, heightPx = null, pixelDensity = null, screenWidth = null), ), caption = null, link = null, @@ -501,6 +501,103 @@ class HtmlLinearizerTest { ) } + @Test + fun `audio with no sources is ignored`() { + val html = "" + val baseUrl = "https://example.com" + + val result = linearizer.linearize(html, baseUrl).elements + + assertEquals(emptyList(), result) + } + + @Test + fun `audio with two sources`() { + val html = + """ + + + """.trimIndent() + val baseUrl = "https://example.com" + + val result = linearizer.linearize(html, baseUrl).elements + + assertEquals(1, result.size, "Expected one item: $result") + assertEquals( + LinearAudio( + sources = + listOf( + LinearAudioSource("https://example.com/audio.mp3", "audio/mpeg"), + LinearAudioSource("https://example.com/audio.ogg", "audio/ogg"), + ), + ), + result[0], + ) + } + + @Test + fun `video with no sources is ignored`() { + val html = "" + val baseUrl = "https://example.com" + + val result = linearizer.linearize(html, baseUrl).elements + + assertEquals(emptyList(), result) + } + + @Test + fun `video with two sources`() { + val html = + """ + + + """.trimIndent() + val baseUrl = "https://example.com" + + val result = linearizer.linearize(html, baseUrl).elements + + assertEquals(1, result.size, "Expected one item: $result") + assertEquals( + LinearVideo( + sources = + listOf( + LinearVideoSource("https://example.com/video.mp4", "https://example.com/video.mp4", null, null, null, "video/mp4"), + LinearVideoSource("https://example.com/video.webm", "https://example.com/video.webm", null, null, null, "video/webm"), + ), + ), + result[0], + ) + } + + @Test + fun `iframe with youtube video`() { + val html = "" + val baseUrl = "https://example.com" + + val result = linearizer.linearize(html, baseUrl).elements + + assertEquals(1, result.size, "Expected one item: $result") + assertEquals( + LinearVideo( + sources = + listOf( + LinearVideoSource( + "https://www.youtube.com/embed/cjxnVO9RpaQ", + "https://www.youtube.com/watch?v=cjxnVO9RpaQ", + "http://img.youtube.com/vi/cjxnVO9RpaQ/hqdefault.jpg", + 480, + 360, + null, + ), + ), + ), + result[0], + ) + } + @Test fun `table block 2x2`() { val html = "
12
34
" @@ -800,8 +897,8 @@ class HtmlLinearizerTest { // Image url ends with jpeg val image = it.content[0] as LinearImage assertTrue("Expected jpeg image: $image") { - image.candidates[0].imgUri.startsWith("https://cdn.arstechnica.net/wp-content/uploads/2024/05/") - image.candidates[0].imgUri.endsWith(".jpeg") + image.sources[0].imgUri.startsWith("https://cdn.arstechnica.net/wp-content/uploads/2024/05/") + image.sources[0].imgUri.endsWith(".jpeg") } } } @@ -912,8 +1009,8 @@ class HtmlLinearizerTest { // Image url ends with jpeg val image = it.content[0] as LinearImage assertTrue("Expected jpeg image: $image") { - image.candidates[0].imgUri.startsWith("https://cdn.arstechnica.net/wp-content/uploads/2024/05/") - image.candidates[0].imgUri.endsWith(".jpeg") + image.sources[0].imgUri.startsWith("https://cdn.arstechnica.net/wp-content/uploads/2024/05/") + image.sources[0].imgUri.endsWith(".jpeg") } } }