From 35446d7ad5e851a01bc237988e27ddb9617ef350 Mon Sep 17 00:00:00 2001 From: David Chavez Date: Tue, 15 Aug 2023 11:08:50 +0200 Subject: [PATCH] chore(metadata): Handle more metadata types (#89) --- .../kotlin_audio_example/MainActivity.kt | 5 + .../kotlinaudio/event/EventHolder.kt | 7 +- .../kotlinaudio/event/PlayerEventHolder.kt | 19 +- .../kotlinaudio/models/PlaybackMetadata.kt | 200 ------------------ .../kotlinaudio/players/BaseAudioPlayer.kt | 15 +- 5 files changed, 30 insertions(+), 216 deletions(-) delete mode 100644 kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/models/PlaybackMetadata.kt diff --git a/kotlin-audio-example/src/main/java/com/example/kotlin_audio_example/MainActivity.kt b/kotlin-audio-example/src/main/java/com/example/kotlin_audio_example/MainActivity.kt index 956177d1..7cf74eef 100644 --- a/kotlin-audio-example/src/main/java/com/example/kotlin_audio_example/MainActivity.kt +++ b/kotlin-audio-example/src/main/java/com/example/kotlin_audio_example/MainActivity.kt @@ -218,6 +218,11 @@ class MainActivity : ComponentActivity() { artwork = "https://rntp.dev/example/smooth-jazz-24-7.jpeg", artist = "New York, NY", ), + DefaultAudioItem( + "https://traffic.libsyn.com/atpfm/atp545.mp3", + title = "Chapters", + artwork = "https://random.imagecdn.app/800/800?dummy=1", + ), ) } } diff --git a/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/event/EventHolder.kt b/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/event/EventHolder.kt index f5a15c47..8252e09d 100644 --- a/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/event/EventHolder.kt +++ b/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/event/EventHolder.kt @@ -10,8 +10,11 @@ class EventHolder internal constructor(private val notificationEventHolder: Noti val onAudioFocusChanged get() = playerEventHolder.onAudioFocusChanged - val onPlaybackMetadata - get() = playerEventHolder.onPlaybackMetadata + val onCommonMetadata + get() = playerEventHolder.onCommonMetadata + + val onTimedMetadata + get() = playerEventHolder.onTimedMetadata val onPlayerActionTriggeredExternally get() = playerEventHolder.onPlayerActionTriggeredExternally diff --git a/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/event/PlayerEventHolder.kt b/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/event/PlayerEventHolder.kt index 32f1993d..259bde45 100644 --- a/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/event/PlayerEventHolder.kt +++ b/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/event/PlayerEventHolder.kt @@ -1,6 +1,8 @@ package com.doublesymmetry.kotlinaudio.event import com.doublesymmetry.kotlinaudio.models.* +import com.google.android.exoplayer2.MediaMetadata +import com.google.android.exoplayer2.metadata.Metadata import kotlinx.coroutines.MainScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -42,8 +44,11 @@ class PlayerEventHolder { private var _onAudioFocusChanged = MutableSharedFlow(1) var onAudioFocusChanged = _onAudioFocusChanged.asSharedFlow() - private var _onPlaybackMetadata = MutableSharedFlow(1) - var onPlaybackMetadata = _onPlaybackMetadata.asSharedFlow() + private var _onCommonMetadata = MutableSharedFlow(1) + var onCommonMetadata = _onCommonMetadata.asSharedFlow() + + private var _onTimedMetadata = MutableSharedFlow(1) + var onTimedMetadata = _onTimedMetadata.asSharedFlow() private var _onPlayerActionTriggeredExternally = MutableSharedFlow() @@ -92,9 +97,15 @@ class PlayerEventHolder { } } - internal fun updateOnPlaybackMetadata(metadata: PlaybackMetadata) { + internal fun updateOnCommonMetadata(metadata: MediaMetadata) { + coroutineScope.launch { + _onCommonMetadata.emit(metadata) + } + } + + internal fun updateOnTimedMetadata(metadata: Metadata) { coroutineScope.launch { - _onPlaybackMetadata.emit(metadata) + _onTimedMetadata.emit(metadata) } } diff --git a/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/models/PlaybackMetadata.kt b/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/models/PlaybackMetadata.kt deleted file mode 100644 index b459b94a..00000000 --- a/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/models/PlaybackMetadata.kt +++ /dev/null @@ -1,200 +0,0 @@ -package com.doublesymmetry.kotlinaudio.models - -import com.google.android.exoplayer2.metadata.Metadata -import com.google.android.exoplayer2.metadata.flac.VorbisComment -import com.google.android.exoplayer2.metadata.icy.IcyHeaders -import com.google.android.exoplayer2.metadata.icy.IcyInfo -import com.google.android.exoplayer2.metadata.id3.TextInformationFrame -import com.google.android.exoplayer2.metadata.id3.UrlLinkFrame -import com.google.android.exoplayer2.metadata.mp4.MdtaMetadataEntry - -data class PlaybackMetadata( - val source: String, - var title: String? = null, - var url: String? = null, - var artist: String? = null, - var album: String? = null, - var date: String? = null, - var genre: String? = null -) { - companion object { - /** - * ID3 Metadata (MP3) - * https://en.wikipedia.org/wiki/ID3 - */ - fun fromId3Metadata(metadata: Metadata): PlaybackMetadata? { - var handled = false - - var title: String? = null - var url: String? = null - var artist: String? = null - var album: String? = null - var date: String? = null - var genre: String? = null - - (0 until metadata.length()).forEach { i -> - when (val entry = metadata[i]) { - is TextInformationFrame -> { - when (entry.id.uppercase()) { - "TIT2", "TT2" -> { - handled = true - title = entry.value - } - "TALB", "TOAL", "TAL" -> { - handled = true - album = entry.value - } - "TOPE", "TPE1", "TP1" -> { - handled = true - artist = entry.value - } - "TDRC", "TOR" -> { - handled = true - date = entry.value - } - "TCON", "TCO" -> { - handled = true - genre = entry.value - } - - } - } - is UrlLinkFrame -> { - when (entry.id.uppercase()) { - "WOAS", "WOAF", "WOAR", "WAR" -> { - handled = true; - url = entry.url; - } - } - } - } - } - - - return if (handled) PlaybackMetadata("id3", title, url, artist, album, date, genre) else null - } - - /** - * Shoutcast / Icecast metadata (ICY protocol) - * https://cast.readme.io/docs/icy - */ - fun fromIcy(metadata: Metadata): PlaybackMetadata? { - for (i in 0 until metadata.length()) { - when (val entry = metadata[i]) { - is IcyHeaders -> { - return PlaybackMetadata("icy-headers", title = entry.name, url = entry.url, genre = entry.genre) - } - is IcyInfo -> { - val artist: String? - val title: String? - val index = - if (entry.title == null) -1 else entry.title!!.indexOf(" - ") - if (index != -1) { - artist = entry.title!!.substring(0, index) - title = entry.title!!.substring(index + 3) - } else { - artist = null - title = entry.title - } - - return PlaybackMetadata("icy", title = title, url = entry.url, artist = artist) - } - } - } - - return null - } - - /** - * Vorbis Comments (Vorbis, FLAC, Opus, Speex, Theora) - * https://xiph.org/vorbis/doc/v-comment.html - */ - fun fromVorbisComment(metadata: Metadata): PlaybackMetadata? { - var handled = false; - - var title: String? = null - var url: String? = null - var artist: String? = null - var album: String? = null - var date: String? = null - var genre: String? = null - - for (i in 0 until metadata.length()) { - val entry = metadata[i] - if (entry is VorbisComment) { - when (entry.key) { - "TITLE" -> { - handled = true - title = entry.value; - } - "ARTIST" -> { - handled = true - artist = entry.value; - } - "ALBUM" -> { - handled = true - album = entry.value; - } - "DATE" -> { - handled = true - date = entry.value - } - "GENRE" -> { - handled = true - genre = entry.value - } - "URL" -> { - handled = true - url = entry.value - } - } - } - } - return if (handled) PlaybackMetadata("vorbis-comment", title, url, artist, album, date, genre) else null - } - - /** - * QuickTime MDTA metadata (mov, qt) - * https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/Metadata/Metadata.html - */ - fun fromQuickTime(metadata: Metadata): PlaybackMetadata? { - var handled = false; - - var title: String? = null - var artist: String? = null - var album: String? = null - var date: String? = null - var genre: String? = null - - for (i in 0 until metadata.length()) { - val entry = metadata[i]; - if (entry is MdtaMetadataEntry) { - when (entry.key) { - "com.apple.quicktime.title" -> { - handled = true - title = entry.value.toString(); - } - "com.apple.quicktime.artist" -> { - handled = true - artist = entry.value.toString(); - } - "com.apple.quicktime.album" -> { - handled = true - album = entry.value.toString(); - } - "com.apple.quicktime.creationdate" -> { - handled = true - date = entry.value.toString(); - } - "com.apple.quicktime.genre" -> { - handled = true - genre = entry.value.toString(); - } - } - } - } - - return if (handled) PlaybackMetadata("quicktime", title = title, artist = artist, album = album, date = date, genre = genre) else null - } - } -} diff --git a/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/players/BaseAudioPlayer.kt b/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/players/BaseAudioPlayer.kt index afdbe666..8d7ce7cb 100644 --- a/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/players/BaseAudioPlayer.kt +++ b/kotlin-audio/src/main/java/com/doublesymmetry/kotlinaudio/players/BaseAudioPlayer.kt @@ -31,7 +31,6 @@ import com.doublesymmetry.kotlinaudio.models.MediaSessionCallback import com.doublesymmetry.kotlinaudio.models.MediaType import com.doublesymmetry.kotlinaudio.models.PlayWhenReadyChangeData import com.doublesymmetry.kotlinaudio.models.PlaybackError -import com.doublesymmetry.kotlinaudio.models.PlaybackMetadata import com.doublesymmetry.kotlinaudio.models.PlayerConfig import com.doublesymmetry.kotlinaudio.models.PlayerOptions import com.doublesymmetry.kotlinaudio.models.PositionChangedReason @@ -50,6 +49,7 @@ import com.google.android.exoplayer2.DefaultLoadControl.DEFAULT_MIN_BUFFER_MS import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.ForwardingPlayer import com.google.android.exoplayer2.MediaItem +import com.google.android.exoplayer2.MediaMetadata import com.google.android.exoplayer2.PlaybackException import com.google.android.exoplayer2.Player import com.google.android.exoplayer2.Player.Listener @@ -590,7 +590,6 @@ abstract class BaseAudioPlayer internal constructor( companion object { const val APPLICATION_NAME = "react-native-track-player" - const val ANDROID_NOTIFICATION_UPDATE_THROTTLE_INTERVAL = 300L } inner class PlayerListener : Listener { @@ -598,16 +597,12 @@ abstract class BaseAudioPlayer internal constructor( * Called when there is metadata associated with the current playback time. */ override fun onMetadata(metadata: Metadata) { - PlaybackMetadata.fromId3Metadata(metadata) - ?.let { playerEventHolder.updateOnPlaybackMetadata(it) } - PlaybackMetadata.fromIcy(metadata) - ?.let { playerEventHolder.updateOnPlaybackMetadata(it) } - PlaybackMetadata.fromVorbisComment(metadata) - ?.let { playerEventHolder.updateOnPlaybackMetadata(it) } - PlaybackMetadata.fromQuickTime(metadata) - ?.let { playerEventHolder.updateOnPlaybackMetadata(it) } + playerEventHolder.updateOnTimedMetadata(metadata) } + override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) { + playerEventHolder.updateOnCommonMetadata(mediaMetadata) + } /** * A position discontinuity occurs when the playing period changes, the playback position