Skip to content

Commit

Permalink
[android] Added Vorbis Comments and QuickTime metadata support
Browse files Browse the repository at this point in the history
  • Loading branch information
Guichaguri committed May 8, 2020
1 parent 1c7708e commit 4139370
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,23 @@
import com.google.android.exoplayer2.*;
import com.google.android.exoplayer2.Player.EventListener;
import com.google.android.exoplayer2.Timeline.Window;
import com.google.android.exoplayer2.extractor.mp4.MdtaMetadataEntry;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataOutput;
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.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.hls.HlsTrackMetadataEntry;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.guichaguri.trackplayer.service.MusicManager;
import com.guichaguri.trackplayer.service.Utils;
import com.guichaguri.trackplayer.service.models.Track;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
Expand Down Expand Up @@ -349,80 +354,8 @@ public void onSeekProcessed() {
// Finished seeking
}

private void handleId3Metadata(Metadata metadata) {
String title = null, url = null, artist = null, album = null, date = null, genre = null;

for(int i = 0; i < metadata.length(); i++) {
Metadata.Entry entry = metadata.get(i);

if (entry instanceof TextInformationFrame) {
// ID3 text tag
TextInformationFrame id3 = (TextInformationFrame) entry;
String id = id3.id.toUpperCase();

if (id.equals("TIT2") || id.equals("TT2")) {
title = id3.value;
} else if (id.equals("TALB") || id.equals("TOAL") || id.equals("TAL")) {
album = id3.value;
} else if (id.equals("TOPE") || id.equals("TPE1") || id.equals("TP1")) {
artist = id3.value;
} else if (id.equals("TDRC") || id.equals("TOR")) {
date = id3.value;
} else if (id.equals("TCON") || id.equals("TCO")) {
genre = id3.value;
}

} else if (entry instanceof UrlLinkFrame) {
// ID3 URL tag
UrlLinkFrame id3 = (UrlLinkFrame) entry;
String id = id3.id.toUpperCase();

if (id.equals("WOAS") || id.equals("WOAF") || id.equals("WOAR") || id.equals("WAR")) {
url = id3.url;
}

}
}

if (title != null || url != null || artist != null || album != null || date != null || genre != null) {
manager.onMetadataReceived("id3", title, url, artist, album, date, genre);
}
}

private void handleIcyMetadata(Metadata metadata) {
for (int i = 0; i < metadata.length(); i++) {
Metadata.Entry entry = metadata.get(i);

if(entry instanceof IcyHeaders) {
// ICY headers
IcyHeaders icy = (IcyHeaders)entry;

manager.onMetadataReceived("icy-headers", icy.name, icy.url, null, null, null, icy.genre);

} else if(entry instanceof IcyInfo) {
// ICY data
IcyInfo icy = (IcyInfo)entry;

String artist, title;
int index = icy.title == null ? -1 : icy.title.indexOf(" - ");

if (index != -1) {
artist = icy.title.substring(0, index);
title = icy.title.substring(index + 3);
} else {
artist = null;
title = icy.title;
}

manager.onMetadataReceived("icy", title, icy.url, artist, null, null, null);

}
}
}

@Override
public void onMetadata(@NonNull Metadata metadata) {
handleId3Metadata(metadata);
handleIcyMetadata(metadata);
SourceMetadata.handleMetadata(manager, metadata);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package com.guichaguri.trackplayer.service.player;

import com.google.android.exoplayer2.extractor.mp4.MdtaMetadataEntry;
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.guichaguri.trackplayer.service.MusicManager;

public class SourceMetadata {

/**
* Reads metadata and triggers the metadata-received event
*/
public static void handleMetadata(MusicManager manager, Metadata metadata) {
handleId3Metadata(manager, metadata);
handleIcyMetadata(manager, metadata);
handleVorbisCommentMetadata(manager, metadata);
handleQuickTimeMetadata(manager, metadata);
}

/**
* ID3 Metadata (MP3)
*
* https://en.wikipedia.org/wiki/ID3
*/
private static void handleId3Metadata(MusicManager manager, Metadata metadata) {
String title = null, url = null, artist = null, album = null, date = null, genre = null;

for(int i = 0; i < metadata.length(); i++) {
Metadata.Entry entry = metadata.get(i);

if (entry instanceof TextInformationFrame) {
// ID3 text tag
TextInformationFrame id3 = (TextInformationFrame) entry;
String id = id3.id.toUpperCase();

if (id.equals("TIT2") || id.equals("TT2")) {
title = id3.value;
} else if (id.equals("TALB") || id.equals("TOAL") || id.equals("TAL")) {
album = id3.value;
} else if (id.equals("TOPE") || id.equals("TPE1") || id.equals("TP1")) {
artist = id3.value;
} else if (id.equals("TDRC") || id.equals("TOR")) {
date = id3.value;
} else if (id.equals("TCON") || id.equals("TCO")) {
genre = id3.value;
}

} else if (entry instanceof UrlLinkFrame) {
// ID3 URL tag
UrlLinkFrame id3 = (UrlLinkFrame) entry;
String id = id3.id.toUpperCase();

if (id.equals("WOAS") || id.equals("WOAF") || id.equals("WOAR") || id.equals("WAR")) {
url = id3.url;
}

}
}

if (title != null || url != null || artist != null || album != null || date != null || genre != null) {
manager.onMetadataReceived("id3", title, url, artist, album, date, genre);
}
}

/**
* Shoutcast / Icecast metadata (ICY protocol)
*
* https://cast.readme.io/docs/icy
*/
private static void handleIcyMetadata(MusicManager manager, Metadata metadata) {
for (int i = 0; i < metadata.length(); i++) {
Metadata.Entry entry = metadata.get(i);

if(entry instanceof IcyHeaders) {
// ICY headers
IcyHeaders icy = (IcyHeaders)entry;

manager.onMetadataReceived("icy-headers", icy.name, icy.url, null, null, null, icy.genre);

} else if(entry instanceof IcyInfo) {
// ICY data
IcyInfo icy = (IcyInfo)entry;

String artist, title;
int index = icy.title == null ? -1 : icy.title.indexOf(" - ");

if (index != -1) {
artist = icy.title.substring(0, index);
title = icy.title.substring(index + 3);
} else {
artist = null;
title = icy.title;
}

manager.onMetadataReceived("icy", title, icy.url, artist, null, null, null);

}
}
}

/**
* Vorbis Comments (Vorbis, FLAC, Opus, Speex, Theora)
*
* https://xiph.org/vorbis/doc/v-comment.html
*/
private static void handleVorbisCommentMetadata(MusicManager manager, Metadata metadata) {
String title = null, url = null, artist = null, album = null, date = null, genre = null;

for (int i = 0; i < metadata.length(); i++) {
Metadata.Entry entry = metadata.get(i);

if (!(entry instanceof VorbisComment)) continue;

VorbisComment comment = (VorbisComment) entry;
String key = comment.key;

if (key.equals("TITLE")) {
title = comment.value;
} else if (key.equals("ARTIST")) {
artist = comment.value;
} else if (key.equals("ALBUM")) {
album = comment.value;
} else if (key.equals("DATE")) {
date = comment.value;
} else if (key.equals("GENRE")) {
genre = comment.value;
} else if (key.equals("URL")) {
url = comment.value;
}
}

if (title != null || url != null || artist != null || album != null || date != null || genre != null) {
manager.onMetadataReceived("vorbis-comment", title, url, artist, album, date, genre);
}
}

/**
* QuickTime MDTA metadata (mov, qt)
*
* https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/Metadata/Metadata.html
*/
private static void handleQuickTimeMetadata(MusicManager manager, Metadata metadata) {
String title = null, artist = null, album = null, date = null, genre = null;

for (int i = 0; i < metadata.length(); i++) {
Metadata.Entry entry = metadata.get(i);

if (!(entry instanceof MdtaMetadataEntry)) continue;

MdtaMetadataEntry mdta = (MdtaMetadataEntry) entry;
String key = mdta.key;

try {
if (key.equals("com.apple.quicktime.title")) {
title = new String(mdta.value, "UTF-8");
} else if (key.equals("com.apple.quicktime.artist")) {
artist = new String(mdta.value, "UTF-8");
} else if (key.equals("com.apple.quicktime.album")) {
album = new String(mdta.value, "UTF-8");
} else if (key.equals("com.apple.quicktime.creationdate")) {
date = new String(mdta.value, "UTF-8");
} else if (key.equals("com.apple.quicktime.genre")) {
genre = new String(mdta.value, "UTF-8");
}
} catch(Exception ex) {
// Ignored
}
}

if (title != null || artist != null || album != null || date != null || genre != null) {
manager.onMetadataReceived("quicktime", title, null, artist, album, date, genre);
}
}

}
4 changes: 2 additions & 2 deletions docs/Documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -444,11 +444,11 @@ Fired when the queue reaches the end.
| position | `number` | The previous track position in seconds |

#### `playback-metadata-received`
Fired when the current track receives metadata encoded in. (e.g. ID3 tags or Icy Metadata).
Fired when the current track receives metadata encoded in. (e.g. ID3 tags, Icy Metadata, Vorbis Comments or QuickTime metadata).

| Param | Type | Description |
| -------- | -------- | --------------------------------------------------- |
| source | `string` | The metadata source (`id3`, `icy` or `icy-headers`) |
| source | `string` | The metadata source (`id3`, `icy`, `icy-headers`, `vorbis-comment`, `quicktime`) |
| title | `string` | The track title. Might be null |
| url | `string` | The track url. Might be null |
| artist | `string` | The track artist. Might be null |
Expand Down

0 comments on commit 4139370

Please sign in to comment.