package org.terst.feedreader.ui /** * Recognising a YouTube placeholder link, and building a page its embed will actually play in. * * Split out of ReaderWebView for the usual reason: this is string work with no WebView, no * gestures and no Compose in it, so it can be exercised without a device -- which matters here * because the bug it fixes is invisible until a video is tapped. * * **Why the embed needs a page at all.** The player dialog used to call * `loadUrl("https://www.youtube-nocookie.com/embed/")`, loading the embed as the WebView's * own top-level document. YouTube refuses that: the player comes up showing "Video player * configuration error -- Error 153", which is reported as "YouTube play is misconfigured". * Confirmed by loading the same URL three ways in a real browser on an Android user agent: * * | how the embed is loaded | result | * |------------------------------------------|-----------------------------| * | top-level, as the app did | Error 153 | * | top-level with `&origin=` | Error 153 | * | in an `""" + """""" } /** * The id with anything outside YouTube's own alphabet removed. * * The ids reaching here are already matched against `[a-zA-Z0-9_-]{11}` upstream, so this * changes nothing today. It exists because the value is spliced straight into an HTML * attribute, and that should be safe on its own terms rather than because of a check in a * different file that a later edit might move. */ private fun scrub(videoId: String): String = VIDEO_ID_CHARS.replace(videoId, "") /** The path of [url], or "/" when it has none. */ private fun pathOf(url: String): String { val afterScheme = url.substringAfter("://", missingDelimiterValue = "") val afterAuthority = afterScheme.substringAfter('/', missingDelimiterValue = "") val path = "/" + afterAuthority.substringBefore('?').substringBefore('#') return path } /** The host of [url], lowercased, or null when it is not a URL with one. */ private fun hostOf(url: String): String? { val afterScheme = url.substringAfter("://", missingDelimiterValue = "") if (afterScheme.isEmpty()) return null val authority = afterScheme.substringBefore('/').substringBefore('?').substringBefore('#') val host = authority.substringAfterLast('@').substringBefore(':') return host.lowercase().ifEmpty { null } }