diff --git a/app/src/main/kotlin/org/akanework/gramophone/logic/utils/SemanticLyrics.kt b/app/src/main/kotlin/org/akanework/gramophone/logic/utils/SemanticLyrics.kt index 9f54c7a0d0..c921d3a0e3 100644 --- a/app/src/main/kotlin/org/akanework/gramophone/logic/utils/SemanticLyrics.kt +++ b/app/src/main/kotlin/org/akanework/gramophone/logic/utils/SemanticLyrics.kt @@ -638,19 +638,83 @@ private val tt = "http://www.w3.org/ns/ttml" private val ttm = "http://www.w3.org/ns/ttml#metadata" private val itunes = "http://itunes.apple.com/lyric-ttml-extensions" private val itunesInternal = "http://music.apple.com/lyric-ttml-internal" +private fun XmlPullParser.skipToEndOfTag() { + if (eventType != XmlPullParser.START_TAG) + throw XmlPullParserException("expected start tag in skipToEndOfTag()") + while (next() != XmlPullParser.END_TAG) { + // we have a child tag! + if (eventType == XmlPullParser.START_TAG) + skipToEndOfTag() + else if (eventType != XmlPullParser.TEXT) + throw XmlPullParserException("expected start tag or text in skipToEndOfTag()") + // else: we have some text, boring + } +} +private fun XmlPullParser.nextAndThrowIfNotEnd() { + if (next() != XmlPullParser.END_TAG) + throw XmlPullParserException("expected end tag in nextAndThrowIfNotEnd()") +} +private fun XmlPullParser.nextAndThrowIfNotText() { + if (next() != XmlPullParser.TEXT) + throw XmlPullParserException("expected end tag in nextAndThrowIfNotText()") +} fun parseTtml(lyricText: String, trimEnabled: Boolean): SemanticLyrics? { val parser = Xml.newPullParser() parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true) parser.setInput(StringReader(lyricText)) try { - parser.next() + parser.nextTag() parser.require(XmlPullParser.START_TAG, tt, "tt") } catch (_: XmlPullParserException) { return null // not ttml } val lang = parser.getAttributeValue("http://www.w3.org/XML/1998/namespace", "lang") val timing = parser.getAttributeValue(itunesInternal, "timing") - while (parser.next() != XmlPullParser.END_TAG) { + parser.nextTag() + parser.require(XmlPullParser.START_TAG, tt, "head") + // TODO parse and reject based on https://www.w3.org/TR/2018/REC-ttml2-20181108/#feature-profile-version-2 to be compliant + // TODO https://www.w3.org/TR/2018/REC-ttml2-20181108/#timing-attribute-dur + while (parser.nextTag() != XmlPullParser.END_TAG) { + if (parser.name == "metadata") { + while (parser.nextTag() != XmlPullParser.END_TAG) { + if (parser.namespace == ttm && parser.name == "agent") { + val id = parser.getAttributeValue("http://www.w3.org/XML/1998/namespace", "id") + val type = parser.getAttributeValue(ttm, "lang") + // TODO do something with this information + parser.nextAndThrowIfNotEnd() + } + if (parser.name == "iTunesMetadata") { + while (parser.nextTag() != XmlPullParser.END_TAG) { + if (parser.name == "songwriters") { + while (parser.nextTag() != XmlPullParser.END_TAG) { + if (parser.name == "songwriter") { + parser.nextAndThrowIfNotText() + val songwriter = parser.text + // TODO do something with this information + parser.nextAndThrowIfNotEnd() + } else { + throw XmlPullParserException("expected , got " + + "<${(parser.prefix?.plus(":") ?: "") + parser.name}> " + + "in in ") + } + } + } else { + throw XmlPullParserException("unknown element " + + "<${(parser.prefix?.plus(":") ?: "") + parser.name}> in " + + "") + } + } + parser.nextAndThrowIfNotEnd() + } + } + } else // probably or + parser.skipToEndOfTag() + } + parser.require(XmlPullParser.END_TAG, tt, "head") + parser.next() + parser.require(XmlPullParser.START_TAG, tt, "body") + while (parser.nextTag() != XmlPullParser.END_TAG) { + parser.skipToEndOfTag() // TODO parse } return null } diff --git a/app/src/main/kotlin/org/akanework/gramophone/ui/components/FullBottomSheet.kt b/app/src/main/kotlin/org/akanework/gramophone/ui/components/FullBottomSheet.kt index dca66e858d..29b4cd1602 100644 --- a/app/src/main/kotlin/org/akanework/gramophone/ui/components/FullBottomSheet.kt +++ b/app/src/main/kotlin/org/akanework/gramophone/ui/components/FullBottomSheet.kt @@ -157,7 +157,7 @@ class FullBottomSheet } override fun onStopTrackingTouch(seekBar: SeekBar?) { - val mediaId = instance?.currentMediaItem?.mediaId + val mediaId = instance?.currentMediaItem if (mediaId != null) { if (seekBar != null) { instance?.seekTo((seekBar.progress.toLong())) @@ -174,7 +174,7 @@ class FullBottomSheet } override fun onStopTrackingTouch(slider: Slider) { - val mediaId = instance?.currentMediaItem?.mediaId + val mediaId = instance?.currentMediaItem if (mediaId != null) { instance?.seekTo((slider.value.toLong())) bottomSheetFullLyricView.updateLyricPositionFromPlaybackPos() diff --git a/app/src/main/kotlin/org/akanework/gramophone/ui/components/LegacyLyricsAdapter.kt b/app/src/main/kotlin/org/akanework/gramophone/ui/components/LegacyLyricsAdapter.kt index 4a188b2f52..4a67efc824 100644 --- a/app/src/main/kotlin/org/akanework/gramophone/ui/components/LegacyLyricsAdapter.kt +++ b/app/src/main/kotlin/org/akanework/gramophone/ui/components/LegacyLyricsAdapter.kt @@ -13,16 +13,19 @@ import android.view.View.VISIBLE import android.view.ViewGroup import android.view.animation.PathInterpolator import android.widget.TextView +import androidx.annotation.OptIn import androidx.core.animation.doOnEnd import androidx.core.graphics.TypefaceCompat import androidx.core.view.HapticFeedbackConstantsCompat import androidx.core.view.ViewCompat import androidx.core.view.doOnLayout +import androidx.media3.common.util.UnstableApi import androidx.preference.PreferenceManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.card.MaterialCardView import org.akanework.gramophone.R +import org.akanework.gramophone.logic.GramophonePlaybackService import org.akanework.gramophone.logic.dpToPx import org.akanework.gramophone.logic.getBooleanStrict import org.akanework.gramophone.logic.ui.CustomSmoothScroller @@ -55,6 +58,7 @@ class LegacyLyricsAdapter( private var currentTranslationPos = -1 private var isBoldEnabled = false private var isLyricCentered = false + private var forceNoAnimation = false private val sizeFactor = 1f private val defaultSizeFactor = .97f @@ -198,6 +202,7 @@ class LegacyLyricsAdapter( fun updateLyricStatus() { isBoldEnabled = prefs.getBooleanStrict("lyric_bold", false) isLyricCentered = prefs.getBooleanStrict("lyric_center", false) + forceNoAnimation = prefs.getBooleanStrict("lyric_no_animation", false) } override fun getItemCount(): Int = lyricList.size @@ -310,7 +315,7 @@ class LegacyLyricsAdapter( } fun smoothScrollTo(position: Int, noAnimation: Boolean = false) { - val smoothScroller = createSmoothScroller(noAnimation) + val smoothScroller = createSmoothScroller(noAnimation || forceNoAnimation) smoothScroller.targetPosition = position recyclerView!!.layoutManager!!.startSmoothScroll( smoothScroller @@ -339,9 +344,12 @@ class LegacyLyricsAdapter( } } + // https://github.com/androidx/media/issues/1578 + @OptIn(UnstableApi::class) private fun updateNewIndex(): Int { val filteredList = lyricList.filterIndexed { _, lyric -> - (lyric.timeStamp ?: 0) <= (instance?.currentPosition ?: 0) + (lyric.timeStamp ?: 0) <= (GramophonePlaybackService.instanceForWidgetAndLyricsOnly + ?.endedWorkaroundPlayer?.currentPosition ?: instance?.currentPosition ?: 0) } return if (filteredList.isNotEmpty()) { diff --git a/libphonograph b/libphonograph index 8dc120bec4..68a6ade078 160000 --- a/libphonograph +++ b/libphonograph @@ -1 +1 @@ -Subproject commit 8dc120bec48dc7b986dcd554e8818b098d0ccdd1 +Subproject commit 68a6ade07806b1f3a8f8ef96507e7ec96fdbdb0f