summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordaniel oeh <daniel.oeh@gmail.com>2014-09-13 20:37:30 +0200
committerdaniel oeh <daniel.oeh@gmail.com>2014-09-13 20:37:30 +0200
commit1ebb209bc6b6e8bd28a9aca1811aed605b015ded (patch)
tree914a3e6d8efd13d9fe95012d55ff7b495d4b8565 /src
parent4b1b271ca91600d4293a56ea7e0d3682afa88d71 (diff)
downloadAntennaPod-1ebb209bc6b6e8bd28a9aca1811aed605b015ded.zip
Added support for reading MP4 chapters
Diffstat (limited to 'src')
-rw-r--r--src/de/danoeh/antennapod/feed/MP4Chapter.java27
-rw-r--r--src/de/danoeh/antennapod/storage/DBReader.java3
-rw-r--r--src/de/danoeh/antennapod/util/ChapterUtils.java32
-rw-r--r--src/wseemann/media/FFmpegChapter.java29
-rw-r--r--src/wseemann/media/FFmpegMediaMetadataRetriever.java595
5 files changed, 686 insertions, 0 deletions
diff --git a/src/de/danoeh/antennapod/feed/MP4Chapter.java b/src/de/danoeh/antennapod/feed/MP4Chapter.java
new file mode 100644
index 000000000..a5e1df393
--- /dev/null
+++ b/src/de/danoeh/antennapod/feed/MP4Chapter.java
@@ -0,0 +1,27 @@
+package de.danoeh.antennapod.feed;
+
+import wseemann.media.FFmpegChapter;
+
+/**
+ * Represents a chapter contained in a MP4 file.
+ */
+public class MP4Chapter extends Chapter {
+ public static final int CHAPTERTYPE_MP4CHAPTER = 4;
+
+ /**
+ * Construct a MP4Chapter from an FFmpegChapter.
+ */
+ public MP4Chapter(FFmpegChapter ch) {
+ this.start = ch.getStart();
+ this.title = ch.getTitle();
+ }
+
+ public MP4Chapter(long start, String title, FeedItem item, String link) {
+ super(start, title, item, link);
+ }
+
+ @Override
+ public int getChapterType() {
+ return CHAPTERTYPE_MP4CHAPTER;
+ }
+}
diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java
index e49ea4f83..0924c30ec 100644
--- a/src/de/danoeh/antennapod/storage/DBReader.java
+++ b/src/de/danoeh/antennapod/storage/DBReader.java
@@ -262,6 +262,9 @@ public final class DBReader {
chapter = new VorbisCommentChapter(start,
title, item, link);
break;
+ case MP4Chapter.CHAPTERTYPE_MP4CHAPTER:
+ chapter = new MP4Chapter(start, title, item, link);
+ break;
}
if (chapter != null) {
chapter.setId(chapterCursor
diff --git a/src/de/danoeh/antennapod/util/ChapterUtils.java b/src/de/danoeh/antennapod/util/ChapterUtils.java
index 9e1c50674..4a953703a 100644
--- a/src/de/danoeh/antennapod/util/ChapterUtils.java
+++ b/src/de/danoeh/antennapod/util/ChapterUtils.java
@@ -3,17 +3,22 @@ package de.danoeh.antennapod.util;
import android.util.Log;
import de.danoeh.antennapod.BuildConfig;
import de.danoeh.antennapod.feed.Chapter;
+import de.danoeh.antennapod.feed.MP4Chapter;
import de.danoeh.antennapod.util.comparator.ChapterStartTimeComparator;
import de.danoeh.antennapod.util.id3reader.ChapterReader;
import de.danoeh.antennapod.util.id3reader.ID3ReaderException;
import de.danoeh.antennapod.util.playback.Playable;
import de.danoeh.antennapod.util.vorbiscommentreader.VorbisCommentChapterReader;
import de.danoeh.antennapod.util.vorbiscommentreader.VorbisCommentReaderException;
+import wseemann.media.FFmpegChapter;
+import wseemann.media.FFmpegMediaMetadataRetriever;
+
import org.apache.commons.io.IOUtils;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -190,6 +195,30 @@ public class ChapterUtils {
}
}
+ private static void readMP4ChaptersFromFileUrl(Playable p) {
+ if (!FFmpegMediaMetadataRetriever.LIB_AVAILABLE) {
+ if (BuildConfig.DEBUG) Log.d(TAG, "FFmpegMediaMetadataRetriever not available on this architecture");
+ return;
+ }
+ if (BuildConfig.DEBUG) Log.d(TAG, "Trying to read mp4 chapters from file " + p.getEpisodeTitle());
+
+ FFmpegMediaMetadataRetriever retriever = new FFmpegMediaMetadataRetriever();
+ retriever.setDataSource(p.getLocalMediaUrl());
+ FFmpegChapter[] res = retriever.getChapters();
+ retriever.release();
+ if (res != null) {
+ List<Chapter> chapters = new ArrayList<Chapter>();
+ for (FFmpegChapter fFmpegChapter : res) {
+ chapters.add(new MP4Chapter(fFmpegChapter));
+ }
+ Collections.sort(chapters, new ChapterStartTimeComparator());
+ processChapters(chapters, p);
+ p.setChapters(chapters);
+ } else {
+ if (BuildConfig.DEBUG) Log.d(TAG, "No mp4 chapters found in " + p.getEpisodeTitle());
+ }
+ }
+
/** Makes sure that chapter does a title and an item attribute. */
private static void processChapters(List<Chapter> chapters, Playable p) {
for (int i = 0; i < chapters.size(); i++) {
@@ -254,6 +283,9 @@ public class ChapterUtils {
if (media.getChapters() == null) {
ChapterUtils.readOggChaptersFromPlayableFileUrl(media);
}
+ if (media.getChapters() == null) {
+ ChapterUtils.readMP4ChaptersFromFileUrl(media);
+ }
} else {
Log.e(TAG, "Could not load chapters from file url: local file not available");
}
diff --git a/src/wseemann/media/FFmpegChapter.java b/src/wseemann/media/FFmpegChapter.java
new file mode 100644
index 000000000..2a359c386
--- /dev/null
+++ b/src/wseemann/media/FFmpegChapter.java
@@ -0,0 +1,29 @@
+package wseemann.media;
+
+/**
+ * Represents a chapter mark returned by FFmpegMediaMetadataRetriever.
+ * */
+public class FFmpegChapter
+{
+ private int id;
+ private String title;
+ private long start;
+
+ public FFmpegChapter(int id, String title, long start) {
+ this.id = id;
+ this.title = title;
+ this.start = start;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public long getStart() {
+ return start;
+ }
+}
diff --git a/src/wseemann/media/FFmpegMediaMetadataRetriever.java b/src/wseemann/media/FFmpegMediaMetadataRetriever.java
new file mode 100644
index 000000000..89fba915c
--- /dev/null
+++ b/src/wseemann/media/FFmpegMediaMetadataRetriever.java
@@ -0,0 +1,595 @@
+/*
+ * FFmpegMediaMetadataRetriever: A unified interface for retrieving frame
+ * and meta data from an input media file.
+ *
+ * Copyright 2014 William Seemann
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *
+ * Changes by Daniel Oeh:
+ * - Rewrite of the 'static' section
+ * - Addition of 'getChapters' method
+ *
+ */
+
+package wseemann.media;
+
+import android.annotation.SuppressLint;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * FFmpegMediaMetadataRetriever class provides a unified interface for retrieving
+ * frame and meta data from an input media file.
+ */
+public class FFmpegMediaMetadataRetriever
+{
+ private final static String TAG = "FFmpegMediaMetadataRetriever";
+
+ public static boolean LIB_AVAILABLE = false;
+
+ /**
+ * User defined bitmap configuration. A bitmap configuration describes how pixels are
+ * stored. This affects the quality (color depth) as well as the ability to display
+ * transparent/translucent colors.
+ */
+ public static Bitmap.Config IN_PREFERRED_CONFIG;
+
+ @SuppressLint("SdCardPath")
+ private static final String LIBRARY_PATH = "/data/data/";
+
+ private static final String [] JNI_LIBRARIES = {
+ "avutil",
+ "swscale",
+ "avcodec",
+ "avformat",
+ "ffmpeg_mediametadataretriever_jni"
+ };
+
+ static {
+ /*
+ StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
+
+ StringBuffer path = null;
+ File file = null;
+ boolean foundLibs = false;
+
+ for (int j = 0; j < stackTraceElements.length; j++) {
+ String libraryPath = stackTraceElements[j].getClassName();
+
+ String [] packageFragments = libraryPath.trim().split("\\.");
+
+ path = new StringBuffer(LIBRARY_PATH);
+
+ for (int i = 0; i < packageFragments.length; i++) {
+ if (i > 0) {
+ path.append(".");
+ }
+
+ path.append(packageFragments[i]);
+ try {
+ //System.load(path.toString() + "/lib/" + JNI_LIBRARIES[0]);
+ file = new File(path.toString() + "/lib/" + JNI_LIBRARIES[0]);
+ if (file.exists()) {
+ path.append("/lib/");
+ foundLibs = true;
+ break;
+ }
+ } catch (UnsatisfiedLinkError ex) {
+ }
+ }
+
+ if (foundLibs) {
+ break;
+ }
+ }
+
+ // Since libraries for some architectures have been excluded from the source in order to save
+ // space, this class might not work on all devices.
+ if (!foundLibs) {
+ Log.e(TAG, TAG + " libraries not found. Did you forget to add them to your libs folder?");
+ //throw new UnsatisfiedLinkError();
+ LIB_AVAILABLE = false;
+ } else {
+ LIB_AVAILABLE = true;
+ for (int i = 0; i < JNI_LIBRARIES.length; i++) {
+ System.load(path.toString() + JNI_LIBRARIES[i]);
+ }
+
+ native_init();
+ }*/
+ try {
+ for (int i = 0; i < JNI_LIBRARIES.length; i++) {
+ System.loadLibrary(JNI_LIBRARIES[i]);
+ }
+ LIB_AVAILABLE = true;
+ native_init();
+ } catch (UnsatisfiedLinkError e) {
+ Log.e(TAG, "Library not found");
+ LIB_AVAILABLE = false;
+ }
+ }
+
+ // The field below is accessed by native methods
+ private int mNativeContext;
+
+ public FFmpegMediaMetadataRetriever() {
+ native_setup();
+ }
+
+ /**
+ * Sets the data source (file pathname) to use. Call this
+ * method before the rest of the methods in this class. This method may be
+ * time-consuming.
+ *
+ * @param path The path of the input media file.
+ * @throws IllegalArgumentException If the path is invalid.
+ */
+ public native void setDataSource(String path) throws IllegalArgumentException;
+
+ /**
+ * Sets the data source (URI) to use. Call this
+ * method before the rest of the methods in this class. This method may be
+ * time-consuming.
+ *
+ * @param uri The URI of the input media.
+ * @param headers the headers to be sent together with the request for the data
+ * @throws IllegalArgumentException If the URI is invalid.
+ */
+ public void setDataSource(String uri, Map<String, String> headers)
+ throws IllegalArgumentException {
+ int i = 0;
+ String[] keys = new String[headers.size()];
+ String[] values = new String[headers.size()];
+ for (Map.Entry<String, String> entry: headers.entrySet()) {
+ keys[i] = entry.getKey();
+ values[i] = entry.getValue();
+ ++i;
+ }
+ _setDataSource(uri, keys, values);
+ }
+
+ private native void _setDataSource(
+ String uri, String[] keys, String[] values)
+ throws IllegalArgumentException;
+
+ /**
+ * Sets the data source (FileDescriptor) to use. It is the caller's
+ * responsibility to close the file descriptor. It is safe to do so as soon
+ * as this call returns. Call this method before the rest of the methods in
+ * this class. This method may be time-consuming.
+ *
+ * @param fd the FileDescriptor for the file you want to play
+ * @param offset the offset into the file where the data to be played starts,
+ * in bytes. It must be non-negative
+ * @param length the length in bytes of the data to be played. It must be
+ * non-negative.
+ * @throws IllegalArgumentException if the arguments are invalid
+ */
+ public native void setDataSource(FileDescriptor fd, long offset, long length)
+ throws IllegalArgumentException;
+
+ /**
+ * Sets the data source (FileDescriptor) to use. It is the caller's
+ * responsibility to close the file descriptor. It is safe to do so as soon
+ * as this call returns. Call this method before the rest of the methods in
+ * this class. This method may be time-consuming.
+ *
+ * @param fd the FileDescriptor for the file you want to play
+ * @throws IllegalArgumentException if the FileDescriptor is invalid
+ */
+ public void setDataSource(FileDescriptor fd)
+ throws IllegalArgumentException {
+ // intentionally less than LONG_MAX
+ setDataSource(fd, 0, 0x7ffffffffffffffL);
+ }
+
+ /**
+ * Sets the data source as a content Uri. Call this method before
+ * the rest of the methods in this class. This method may be time-consuming.
+ *
+ * @param context the Context to use when resolving the Uri
+ * @param uri the Content URI of the data you want to play
+ * @throws IllegalArgumentException if the Uri is invalid
+ * @throws SecurityException if the Uri cannot be used due to lack of
+ * permission.
+ */
+ public void setDataSource(Context context, Uri uri)
+ throws IllegalArgumentException, SecurityException {
+ if (uri == null) {
+ throw new IllegalArgumentException();
+ }
+
+ String scheme = uri.getScheme();
+ if(scheme == null || scheme.equals("file")) {
+ setDataSource(uri.getPath());
+ return;
+ }
+
+ AssetFileDescriptor fd = null;
+ try {
+ ContentResolver resolver = context.getContentResolver();
+ try {
+ fd = resolver.openAssetFileDescriptor(uri, "r");
+ } catch(FileNotFoundException e) {
+ throw new IllegalArgumentException();
+ }
+ if (fd == null) {
+ throw new IllegalArgumentException();
+ }
+ FileDescriptor descriptor = fd.getFileDescriptor();
+ if (!descriptor.valid()) {
+ throw new IllegalArgumentException();
+ }
+ // Note: using getDeclaredLength so that our behavior is the same
+ // as previous versions when the content provider is returning
+ // a full file.
+ if (fd.getDeclaredLength() < 0) {
+ setDataSource(descriptor);
+ } else {
+ setDataSource(descriptor, fd.getStartOffset(), fd.getDeclaredLength());
+ }
+ return;
+ } catch (SecurityException ex) {
+ } finally {
+ try {
+ if (fd != null) {
+ fd.close();
+ }
+ } catch(IOException ioEx) {
+ }
+ }
+ setDataSource(uri.toString());
+ }
+
+ /**
+ * Call this method after setDataSource(). This method retrieves the
+ * meta data value associated with the keyCode.
+ *
+ * The keyCode currently supported is listed below as METADATA_XXX
+ * constants. With any other value, it returns a null pointer.
+ *
+ * @param keyCode One of the constants listed below at the end of the class.
+ * @return The meta data value associate with the given keyCode on success;
+ * null on failure.
+ */
+ public native String extractMetadata(String key);
+
+ /**
+ * Call this method after setDataSource(). This method finds a
+ * representative frame close to the given time position by considering
+ * the given option if possible, and returns it as a bitmap. This is
+ * useful for generating a thumbnail for an input data source or just
+ * obtain and display a frame at the given time position.
+ *
+ * @param timeUs The time position where the frame will be retrieved.
+ * When retrieving the frame at the given time position, there is no
+ * guarantee that the data source has a frame located at the position.
+ * When this happens, a frame nearby will be returned. If timeUs is
+ * negative, time position and option will ignored, and any frame
+ * that the implementation considers as representative may be returned.
+ *
+ * @param option a hint on how the frame is found. Use
+ * {@link #OPTION_PREVIOUS_SYNC} if one wants to retrieve a sync frame
+ * that has a timestamp earlier than or the same as timeUs. Use
+ * {@link #OPTION_NEXT_SYNC} if one wants to retrieve a sync frame
+ * that has a timestamp later than or the same as timeUs. Use
+ * {@link #OPTION_CLOSEST_SYNC} if one wants to retrieve a sync frame
+ * that has a timestamp closest to or the same as timeUs. Use
+ * {@link #OPTION_CLOSEST} if one wants to retrieve a frame that may
+ * or may not be a sync frame but is closest to or the same as timeUs.
+ * {@link #OPTION_CLOSEST} often has larger performance overhead compared
+ * to the other options if there is no sync frame located at timeUs.
+ *
+ * @return A Bitmap containing a representative video frame, which
+ * can be null, if such a frame cannot be retrieved.
+ */
+ public Bitmap getFrameAtTime(long timeUs, int option) {
+ if (option < OPTION_PREVIOUS_SYNC ||
+ option > OPTION_CLOSEST) {
+ throw new IllegalArgumentException("Unsupported option: " + option);
+ }
+
+ Bitmap b = null;
+
+ BitmapFactory.Options bitmapOptionsCache = new BitmapFactory.Options();
+ bitmapOptionsCache.inPreferredConfig = getInPreferredConfig();
+ bitmapOptionsCache.inDither = false;
+
+ byte [] picture = _getFrameAtTime(timeUs, option);
+
+ if (picture != null) {
+ b = BitmapFactory.decodeByteArray(picture, 0, picture.length, bitmapOptionsCache);
+ }
+
+ return b;
+ }
+
+ /**
+ * Call this method after setDataSource(). This method finds a
+ * representative frame close to the given time position if possible,
+ * and returns it as a bitmap. This is useful for generating a thumbnail
+ * for an input data source. Call this method if one does not care
+ * how the frame is found as long as it is close to the given time;
+ * otherwise, please call {@link #getFrameAtTime(long, int)}.
+ *
+ * @param timeUs The time position where the frame will be retrieved.
+ * When retrieving the frame at the given time position, there is no
+ * guarentee that the data source has a frame located at the position.
+ * When this happens, a frame nearby will be returned. If timeUs is
+ * negative, time position and option will ignored, and any frame
+ * that the implementation considers as representative may be returned.
+ *
+ * @return A Bitmap containing a representative video frame, which
+ * can be null, if such a frame cannot be retrieved.
+ *
+ * @see #getFrameAtTime(long, int)
+ */
+ public Bitmap getFrameAtTime(long timeUs) {
+ Bitmap b = null;
+
+ BitmapFactory.Options bitmapOptionsCache = new BitmapFactory.Options();
+ bitmapOptionsCache.inPreferredConfig = getInPreferredConfig();
+ bitmapOptionsCache.inDither = false;
+
+ byte [] picture = _getFrameAtTime(timeUs, OPTION_CLOSEST_SYNC);
+
+ if (picture != null) {
+ b = BitmapFactory.decodeByteArray(picture, 0, picture.length, bitmapOptionsCache);
+ }
+
+ return b;
+ }
+
+ /**
+ * Call this method after setDataSource(). This method finds a
+ * representative frame at any time position if possible,
+ * and returns it as a bitmap. This is useful for generating a thumbnail
+ * for an input data source. Call this method if one does not
+ * care about where the frame is located; otherwise, please call
+ * {@link #getFrameAtTime(long)} or {@link #getFrameAtTime(long, int)}
+ *
+ * @return A Bitmap containing a representative video frame, which
+ * can be null, if such a frame cannot be retrieved.
+ *
+ * @see #getFrameAtTime(long)
+ * @see #getFrameAtTime(long, int)
+ */
+ public Bitmap getFrameAtTime() {
+ return getFrameAtTime(-1, OPTION_CLOSEST_SYNC);
+ }
+
+ /**
+ * Call this method after setDataSource(). This method finds any
+ * chapter marks that are contained in the media file.
+ *
+ * @return An array of FFmpegChapter objects or null if no chapters
+ * could be found.
+ * */
+ public native FFmpegChapter[] getChapters();
+
+ private native byte [] _getFrameAtTime(long timeUs, int option);
+
+ /**
+ * Call this method after setDataSource(). This method finds the optional
+ * graphic or album/cover art associated associated with the data source. If
+ * there are more than one pictures, (any) one of them is returned.
+ *
+ * @return null if no such graphic is found.
+ */
+ public native byte[] getEmbeddedPicture();
+
+ /**
+ * Call it when one is done with the object. This method releases the memory
+ * allocated internally.
+ */
+ public native void release();
+ private native void native_setup();
+ private static native void native_init();
+
+ private native final void native_finalize();
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ native_finalize();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private Bitmap.Config getInPreferredConfig() {
+ if (IN_PREFERRED_CONFIG != null) {
+ return IN_PREFERRED_CONFIG;
+ }
+
+ return Bitmap.Config.RGB_565;
+ }
+
+ /**
+ * Option used in method {@link #getFrameAtTime(long, int)} to get a
+ * frame at a specified location.
+ *
+ * @see #getFrameAtTime(long, int)
+ */
+ /* Do not change these option values without updating their counterparts
+ * in jni/metadata/ffmpeg_mediametadataretriever.h!
+ */
+ /**
+ * This option is used with {@link #getFrameAtTime(long, int)} to retrieve
+ * a sync (or key) frame associated with a data source that is located
+ * right before or at the given time.
+ *
+ * @see #getFrameAtTime(long, int)
+ */
+ public static final int OPTION_PREVIOUS_SYNC = 0x00;
+ /**
+ * This option is used with {@link #getFrameAtTime(long, int)} to retrieve
+ * a sync (or key) frame associated with a data source that is located
+ * right after or at the given time.
+ *
+ * @see #getFrameAtTime(long, int)
+ */
+ public static final int OPTION_NEXT_SYNC = 0x01;
+ /**
+ * This option is used with {@link #getFrameAtTime(long, int)} to retrieve
+ * a sync (or key) frame associated with a data source that is located
+ * closest to (in time) or at the given time.
+ *
+ * @see #getFrameAtTime(long, int)
+ */
+ public static final int OPTION_CLOSEST_SYNC = 0x02;
+ /**
+ * This option is used with {@link #getFrameAtTime(long, int)} to retrieve
+ * a frame (not necessarily a key frame) associated with a data source that
+ * is located closest to or at the given time.
+ *
+ * @see #getFrameAtTime(long, int)
+ */
+ public static final int OPTION_CLOSEST = 0x03;
+
+ /**
+ * The metadata key to retrieve the name of the set this work belongs to.
+ */
+ public static final String METADATA_KEY_ALBUM = "album";
+ /**
+ * The metadata key to retrieve the main creator of the set/album, if different
+ * from artist. e.g. "Various Artists" for compilation albums.
+ */
+ public static final String METADATA_KEY_ALBUM_ARTIST = "album_artist";
+ /**
+ * The metadata key to retrieve the main creator of the work.
+ */
+ public static final String METADATA_KEY_ARTIST = "artist";
+ /**
+ * The metadata key to retrieve the any additional description of the file.
+ */
+ public static final String METADATA_KEY_COMMENT = "comment";
+ /**
+ * The metadata key to retrieve the who composed the work, if different from artist.
+ */
+ public static final String METADATA_KEY_COMPOSER = "composer";
+ /**
+ * The metadata key to retrieve the name of copyright holder.
+ */
+ public static final String METADATA_KEY_COPYRIGHT = "copyright";
+ /**
+ * The metadata key to retrieve the date when the file was created, preferably in ISO 8601.
+ */
+ public static final String METADATA_KEY_CREATION_TIME = "creation_time";
+ /**
+ * The metadata key to retrieve the date when the work was created, preferably in ISO 8601.
+ */
+ public static final String METADATA_KEY_DATE = "date";
+ /**
+ * The metadata key to retrieve the number of a subset, e.g. disc in a multi-disc collection.
+ */
+ public static final String METADATA_KEY_DISC = "disc";
+ /**
+ * The metadata key to retrieve the name/settings of the software/hardware that produced the file.
+ */
+ public static final String METADATA_KEY_ENCODER = "encoder";
+ /**
+ * The metadata key to retrieve the person/group who created the file.
+ */
+ public static final String METADATA_KEY_ENCODED_BY = "encoded_by";
+ /**
+ * The metadata key to retrieve the original name of the file.
+ */
+ public static final String METADATA_KEY_FILENAME = "filename";
+ /**
+ * The metadata key to retrieve the genre of the work.
+ */
+ public static final String METADATA_KEY_GENRE = "genre";
+ /**
+ * The metadata key to retrieve the main language in which the work is performed, preferably
+ * in ISO 639-2 format. Multiple languages can be specified by separating them with commas.
+ */
+ public static final String METADATA_KEY_LANGUAGE = "language";
+ /**
+ * The metadata key to retrieve the artist who performed the work, if different from artist.
+ * E.g for "Also sprach Zarathustra", artist would be "Richard Strauss" and performer "London
+ * Philharmonic Orchestra".
+ */
+ public static final String METADATA_KEY_PERFORMER = "performer";
+ /**
+ * The metadata key to retrieve the name of the label/publisher.
+ */
+ public static final String METADATA_KEY_PUBLISHER = "publisher";
+ /**
+ * The metadata key to retrieve the name of the service in broadcasting (channel name).
+ */
+ public static final String METADATA_KEY_SERVICE_NAME = "service_name";
+ /**
+ * The metadata key to retrieve the name of the service provider in broadcasting.
+ */
+ public static final String METADATA_KEY_SERVICE_PROVIDER = "service_provider";
+ /**
+ * The metadata key to retrieve the name of the work.
+ */
+ public static final String METADATA_KEY_TITLE = "title";
+ /**
+ * The metadata key to retrieve the number of this work in the set, can be in form current/total.
+ */
+ public static final String METADATA_KEY_TRACK = "track";
+ /**
+ * The metadata key to retrieve the total bitrate of the bitrate variant that the current stream
+ * is part of.
+ */
+ public static final String METADATA_KEY_VARIANT_BITRATE = "bitrate";
+ /**
+ * The metadata key to retrieve the duration of the work in milliseconds.
+ */
+ public static final String METADATA_KEY_DURATION = "duration";
+ /**
+ * The metadata key to retrieve the audio codec of the work.
+ */
+ public static final String METADATA_KEY_AUDIO_CODEC = "audio_codec";
+ /**
+ * The metadata key to retrieve the video codec of the work.
+ */
+ public static final String METADATA_KEY_VIDEO_CODEC = "video_codec";
+ /**
+ * This key retrieves the video rotation angle in degrees, if available.
+ * The video rotation angle may be 0, 90, 180, or 270 degrees.
+ */
+ public static final String METADATA_KEY_VIDEO_ROTATION = "rotate";
+ /**
+ * The metadata key to retrieve the main creator of the work.
+ */
+ public static final String METADATA_KEY_ICY_METADATA = "icy_metadata";
+ /**
+ * The metadata key to retrieve the main creator of the work.
+ */
+ //private static final String METADATA_KEY_ICY_ARTIST = "icy_artist";
+ /**
+ * The metadata key to retrieve the name of the work.
+ */
+ //private static final String METADATA_KEY_ICY_TITLE = "icy_title";
+ /**
+ * This metadata key retrieves the average framerate (in frames/sec), if available.
+ */
+ public static final String METADATA_KEY_FRAMERATE = "framerate";
+}