Skip to content

Commit

Permalink
chore(metadata): maintain compatibility with new audio players (#2097)
Browse files Browse the repository at this point in the history
  • Loading branch information
dcvz authored Aug 15, 2023
1 parent fc0eb05 commit dfd6566
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 24 deletions.
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ repositories {
}

dependencies {
implementation 'com.github.doublesymmetry:kotlinaudio:v2.0.0-rc11'
implementation 'com.github.doublesymmetry:kotlinaudio:v2.0.0-rc12'
// used when building against local maven
// implementation "com.github.doublesymmetry:kotlin-audio:1.2.2"

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package com.doublesymmetry.trackplayer.model

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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.doublesymmetry.trackplayer.extensions.NumberExt.Companion.toMilliseco
import com.doublesymmetry.trackplayer.extensions.NumberExt.Companion.toSeconds
import com.doublesymmetry.trackplayer.extensions.asLibState
import com.doublesymmetry.trackplayer.extensions.find
import com.doublesymmetry.trackplayer.model.PlaybackMetadata
import com.doublesymmetry.trackplayer.model.Track
import com.doublesymmetry.trackplayer.model.TrackAudioItem
import com.doublesymmetry.trackplayer.module.MusicEvents
Expand Down Expand Up @@ -660,16 +661,24 @@ class MusicService : HeadlessJsTaskService() {
}

scope.launch {
event.onPlaybackMetadata.collect {
Bundle().apply {
putString("source", it.source)
putString("title", it.title)
putString("url", it.url)
putString("artist", it.artist)
putString("album", it.album)
putString("date", it.date)
putString("genre", it.genre)
emit(MusicEvents.PLAYBACK_METADATA, this)
event.onTimedMetadata.collect {
// TODO: Handle the different types of metadata and publish to new events
val metadata = PlaybackMetadata.fromId3Metadata(it)
?: PlaybackMetadata.fromIcy(it)
?: PlaybackMetadata.fromVorbisComment(it)
?: PlaybackMetadata.fromQuickTime(it)

if (metadata != null) {
Bundle().apply {
putString("source", metadata.source)
putString("title", metadata.title)
putString("url", metadata.url)
putString("artist", metadata.artist)
putString("album", metadata.album)
putString("date", metadata.date)
putString("genre", metadata.genre)
emit(MusicEvents.PLAYBACK_METADATA, this)
}
}
}
}
Expand Down
10 changes: 0 additions & 10 deletions example/ios/example.xcworkspace/contents.xcworkspacedata

This file was deleted.

4 changes: 4 additions & 0 deletions example/src/services/PlaybackService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ export async function PlaybackService() {
console.log('Event.PlaybackState', event);
});

TrackPlayer.addEventListener(Event.PlaybackMetadataReceived, (event) => {
console.log('Event.PlaybackMetadataReceived', event);
});

TrackPlayer.addEventListener(
Event.PlaybackMetadataReceived,
async ({ title, artist }) => {
Expand Down
6 changes: 4 additions & 2 deletions ios/RNTrackPlayer/RNTrackPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class RNTrackPlayer: RCTEventEmitter, AudioSessionControllerDelegate {
EventEmitter.shared.register(eventEmitter: self)
audioSessionController.delegate = self
player.playWhenReady = false;
player.event.receiveMetadata.addListener(self, handleAudioPlayerMetadataReceived)
player.event.receiveTimedMetadata.addListener(self, handleAudioPlayerTimedMetadataReceived)
player.event.stateChange.addListener(self, handleAudioPlayerStateChange)
player.event.fail.addListener(self, handleAudioPlayerFailed)
player.event.currentItem.addListener(self, handleAudioPlayerCurrentItemChange)
Expand Down Expand Up @@ -799,7 +799,9 @@ public class RNTrackPlayer: RCTEventEmitter, AudioSessionControllerDelegate {
}
}

func handleAudioPlayerMetadataReceived(metadata: [AVTimedMetadataGroup]) {
func handleAudioPlayerTimedMetadataReceived(metadata: [AVTimedMetadataGroup]) {
// TODO: Handle the different types of metadata and publish to new events

// SwiftAudioEx was updated to return the array of timed metadata
// Until we have support for that in RNTP, we take the first item to keep existing behaviour.
let metadata = metadata.first?.items ?? []
Expand Down
2 changes: 1 addition & 1 deletion react-native-track-player.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ Pod::Spec.new do |s|
s.swift_version = "4.2"

s.dependency "React-Core"
s.dependency "SwiftAudioEx", "1.0.0-rc.5"
s.dependency "SwiftAudioEx", "1.0.0-rc.6"
end

0 comments on commit dfd6566

Please sign in to comment.