Add unified addMetadata() method in Mp4Muxer
PiperOrigin-RevId: 610710011
This commit is contained in:
parent
0e0e1c4f1a
commit
94e0a27a81
@ -32,6 +32,7 @@ import androidx.media3.common.ColorInfo;
|
|||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
|
import androidx.media3.container.MdtaMetadataEntry;
|
||||||
import androidx.media3.container.Mp4LocationData;
|
import androidx.media3.container.Mp4LocationData;
|
||||||
import androidx.media3.container.NalUnitUtil;
|
import androidx.media3.container.NalUnitUtil;
|
||||||
import androidx.media3.muxer.FragmentedMp4Writer.SampleMetadata;
|
import androidx.media3.muxer.FragmentedMp4Writer.SampleMetadata;
|
||||||
@ -395,14 +396,13 @@ import java.util.Locale;
|
|||||||
*
|
*
|
||||||
* <p>This box contains a list of metadata keys.
|
* <p>This box contains a list of metadata keys.
|
||||||
*/
|
*/
|
||||||
public static ByteBuffer keys(List<String> keyNames) {
|
public static ByteBuffer keys(List<MdtaMetadataEntry> mdtaMetadataEntries) {
|
||||||
// This should be an adaptive size here; we don't yet care since it's usually small.
|
|
||||||
ByteBuffer contents = ByteBuffer.allocate(Mp4Utils.MAX_FIXED_LEAF_BOX_SIZE);
|
ByteBuffer contents = ByteBuffer.allocate(Mp4Utils.MAX_FIXED_LEAF_BOX_SIZE);
|
||||||
contents.putInt(0x0); // version and flags.
|
contents.putInt(0x0); // version and flags
|
||||||
contents.putInt(keyNames.size()); // num of entries
|
contents.putInt(mdtaMetadataEntries.size()); // Entry count
|
||||||
|
|
||||||
for (int i = 0; i < keyNames.size(); i++) {
|
for (int i = 0; i < mdtaMetadataEntries.size(); i++) {
|
||||||
ByteBuffer keyNameBuffer = ByteBuffer.wrap(Util.getUtf8Bytes(keyNames.get(i)));
|
ByteBuffer keyNameBuffer = ByteBuffer.wrap(Util.getUtf8Bytes(mdtaMetadataEntries.get(i).key));
|
||||||
contents.put(BoxUtils.wrapIntoBox("mdta", keyNameBuffer));
|
contents.put(BoxUtils.wrapIntoBox("mdta", keyNameBuffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -415,31 +415,18 @@ import java.util.Locale;
|
|||||||
*
|
*
|
||||||
* <p>This box contains a list of metadata values.
|
* <p>This box contains a list of metadata values.
|
||||||
*/
|
*/
|
||||||
public static ByteBuffer ilst(List<Object> values) {
|
public static ByteBuffer ilst(List<MdtaMetadataEntry> mdtaMetadataEntries) {
|
||||||
// This should be an adaptive size here; we don't yet care since it's usually small.
|
|
||||||
ByteBuffer contents = ByteBuffer.allocate(Mp4Utils.MAX_FIXED_LEAF_BOX_SIZE);
|
ByteBuffer contents = ByteBuffer.allocate(Mp4Utils.MAX_FIXED_LEAF_BOX_SIZE);
|
||||||
|
|
||||||
for (int i = 0; i < values.size(); i++) {
|
for (int i = 0; i < mdtaMetadataEntries.size(); i++) {
|
||||||
int keyId = i + 1;
|
int keyId = i + 1;
|
||||||
Object value = values.get(i);
|
MdtaMetadataEntry currentMdtaMetadataEntry = mdtaMetadataEntries.get(i);
|
||||||
|
|
||||||
ByteBuffer valueContents;
|
ByteBuffer valueContents =
|
||||||
if (value instanceof String) {
|
ByteBuffer.allocate(2 * BYTES_PER_INTEGER + currentMdtaMetadataEntry.value.length);
|
||||||
String valueString = (String) value;
|
valueContents.putInt(currentMdtaMetadataEntry.typeIndicator);
|
||||||
byte[] valueBytes = Util.getUtf8Bytes(valueString);
|
valueContents.putInt(currentMdtaMetadataEntry.localeIndicator);
|
||||||
valueContents = ByteBuffer.allocate(valueBytes.length + 8);
|
valueContents.put(currentMdtaMetadataEntry.value);
|
||||||
valueContents.putInt(1); // type code for UTF-8 string
|
|
||||||
valueContents.putInt(0); // default country / language
|
|
||||||
valueContents.put(valueBytes);
|
|
||||||
|
|
||||||
} else if (value instanceof Float) {
|
|
||||||
valueContents = ByteBuffer.allocate(12);
|
|
||||||
valueContents.putInt(23); // float32
|
|
||||||
valueContents.putInt(0); // language / country
|
|
||||||
valueContents.putFloat((float) value);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Unknown metadata type: " + value.getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
valueContents.flip();
|
valueContents.flip();
|
||||||
ByteBuffer valueBox = BoxUtils.wrapIntoBox("data", valueContents);
|
ByteBuffer valueBox = BoxUtils.wrapIntoBox("data", valueContents);
|
||||||
|
@ -15,27 +15,28 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.muxer;
|
package androidx.media3.muxer;
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkState;
|
|
||||||
import static androidx.media3.container.Mp4TimestampData.unixTimeToMp4TimeSeconds;
|
import static androidx.media3.container.Mp4TimestampData.unixTimeToMp4TimeSeconds;
|
||||||
|
|
||||||
|
import androidx.media3.common.Metadata;
|
||||||
|
import androidx.media3.container.MdtaMetadataEntry;
|
||||||
import androidx.media3.container.Mp4LocationData;
|
import androidx.media3.container.Mp4LocationData;
|
||||||
import androidx.media3.container.Mp4TimestampData;
|
import androidx.media3.container.Mp4TimestampData;
|
||||||
import java.nio.ByteBuffer;
|
import androidx.media3.container.XmpData;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.ArrayList;
|
||||||
import java.util.Map;
|
import java.util.List;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
/** Collects and provides metadata: location, FPS, XMP data, etc. */
|
/** Collects and provides metadata: location, FPS, XMP data, etc. */
|
||||||
/* package */ final class MetadataCollector {
|
/* package */ final class MetadataCollector {
|
||||||
public int orientation;
|
public int orientation;
|
||||||
public @MonotonicNonNull Mp4LocationData locationData;
|
public @MonotonicNonNull Mp4LocationData locationData;
|
||||||
public Map<String, Object> metadataPairs;
|
public List<MdtaMetadataEntry> metadataEntries;
|
||||||
public Mp4TimestampData timestampData;
|
public Mp4TimestampData timestampData;
|
||||||
public @MonotonicNonNull ByteBuffer xmpData;
|
public @MonotonicNonNull XmpData xmpData;
|
||||||
|
|
||||||
public MetadataCollector() {
|
public MetadataCollector() {
|
||||||
orientation = 0;
|
orientation = 0;
|
||||||
metadataPairs = new LinkedHashMap<>();
|
metadataEntries = new ArrayList<>();
|
||||||
long currentTimeInMp4TimeSeconds = unixTimeToMp4TimeSeconds(System.currentTimeMillis());
|
long currentTimeInMp4TimeSeconds = unixTimeToMp4TimeSeconds(System.currentTimeMillis());
|
||||||
timestampData =
|
timestampData =
|
||||||
new Mp4TimestampData(
|
new Mp4TimestampData(
|
||||||
@ -43,28 +44,21 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
/* modificationTimestampSeconds= */ currentTimeInMp4TimeSeconds);
|
/* modificationTimestampSeconds= */ currentTimeInMp4TimeSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addXmp(ByteBuffer xmpData) {
|
|
||||||
checkState(this.xmpData == null);
|
|
||||||
this.xmpData = xmpData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOrientation(int orientation) {
|
public void setOrientation(int orientation) {
|
||||||
this.orientation = orientation;
|
this.orientation = orientation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLocation(float latitude, float longitude) {
|
public void addMetadata(Metadata.Entry metadata) {
|
||||||
locationData = new Mp4LocationData(latitude, longitude);
|
if (metadata instanceof Mp4LocationData) {
|
||||||
|
locationData = (Mp4LocationData) metadata;
|
||||||
|
} else if (metadata instanceof Mp4TimestampData) {
|
||||||
|
timestampData = (Mp4TimestampData) metadata;
|
||||||
|
} else if (metadata instanceof MdtaMetadataEntry) {
|
||||||
|
metadataEntries.add((MdtaMetadataEntry) metadata);
|
||||||
|
} else if (metadata instanceof XmpData) {
|
||||||
|
xmpData = (XmpData) metadata;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unsupported metadata");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCaptureFps(float captureFps) {
|
|
||||||
metadataPairs.put("com.android.capture.fps", captureFps);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addMetadata(String key, Object value) {
|
|
||||||
metadataPairs.put(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTimestampData(Mp4TimestampData timestampData) {
|
|
||||||
this.timestampData = timestampData;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,12 +180,12 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
|
|||||||
nextTrackId, creationTimestampSeconds, modificationTimestampSeconds, videoDurationUs);
|
nextTrackId, creationTimestampSeconds, modificationTimestampSeconds, videoDurationUs);
|
||||||
ByteBuffer udtaBox = Boxes.udta(metadataCollector.locationData);
|
ByteBuffer udtaBox = Boxes.udta(metadataCollector.locationData);
|
||||||
ByteBuffer metaBox =
|
ByteBuffer metaBox =
|
||||||
metadataCollector.metadataPairs.isEmpty()
|
metadataCollector.metadataEntries.isEmpty()
|
||||||
? ByteBuffer.allocate(0)
|
? ByteBuffer.allocate(0)
|
||||||
: Boxes.meta(
|
: Boxes.meta(
|
||||||
Boxes.hdlr(/* handlerType= */ "mdta", /* handlerName= */ ""),
|
Boxes.hdlr(/* handlerType= */ "mdta", /* handlerName= */ ""),
|
||||||
Boxes.keys(Lists.newArrayList(metadataCollector.metadataPairs.keySet())),
|
Boxes.keys(Lists.newArrayList(metadataCollector.metadataEntries)),
|
||||||
Boxes.ilst(Lists.newArrayList(metadataCollector.metadataPairs.values())));
|
Boxes.ilst(Lists.newArrayList(metadataCollector.metadataEntries)));
|
||||||
|
|
||||||
ByteBuffer moovBox;
|
ByteBuffer moovBox;
|
||||||
moovBox =
|
moovBox =
|
||||||
@ -199,7 +199,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
|
|||||||
// Also add XMP if needed
|
// Also add XMP if needed
|
||||||
if (metadataCollector.xmpData != null) {
|
if (metadataCollector.xmpData != null) {
|
||||||
return BoxUtils.concatenateBuffers(
|
return BoxUtils.concatenateBuffers(
|
||||||
moovBox, Boxes.uuid(Boxes.XMP_UUID, metadataCollector.xmpData.duplicate()));
|
moovBox, Boxes.uuid(Boxes.XMP_UUID, ByteBuffer.wrap(metadataCollector.xmpData.data)));
|
||||||
} else {
|
} else {
|
||||||
// No need for another copy if there is no XMP to be appended.
|
// No need for another copy if there is no XMP to be appended.
|
||||||
return moovBox;
|
return moovBox;
|
||||||
|
@ -25,9 +25,14 @@ import androidx.annotation.FloatRange;
|
|||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
|
import androidx.media3.common.Metadata;
|
||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
|
import androidx.media3.container.MdtaMetadataEntry;
|
||||||
|
import androidx.media3.container.Mp4LocationData;
|
||||||
import androidx.media3.container.Mp4TimestampData;
|
import androidx.media3.container.Mp4TimestampData;
|
||||||
|
import androidx.media3.container.XmpData;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
@ -205,6 +210,20 @@ public final class Mp4Muxer {
|
|||||||
this.metadataCollector = metadataCollector;
|
this.metadataCollector = metadataCollector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a given {@link Metadata.Entry metadata} is supported.
|
||||||
|
*
|
||||||
|
* <p>For the list of supported metadata refer to {@link Mp4Muxer#addMetadata(Metadata.Entry)}.
|
||||||
|
*/
|
||||||
|
public static boolean isMetadataSupported(Metadata.Entry metadata) {
|
||||||
|
return metadata instanceof Mp4LocationData
|
||||||
|
|| (metadata instanceof Mp4TimestampData
|
||||||
|
&& isMp4TimestampDataSupported((Mp4TimestampData) metadata))
|
||||||
|
|| (metadata instanceof MdtaMetadataEntry
|
||||||
|
&& isMdtaMetadataEntrySupported((MdtaMetadataEntry) metadata))
|
||||||
|
|| metadata instanceof XmpData;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the orientation hint for the video playback.
|
* Sets the orientation hint for the video playback.
|
||||||
*
|
*
|
||||||
@ -215,57 +234,85 @@ public final class Mp4Muxer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the location.
|
* @deprecated Use {@link #addMetadata(Metadata.Entry)} with {@link Mp4LocationData} instead.
|
||||||
*
|
|
||||||
* @param latitude The latitude, in degrees. Its value must be in the range [-90, 90].
|
|
||||||
* @param longitude The longitude, in degrees. Its value must be in the range [-180, 180].
|
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setLocation(
|
public void setLocation(
|
||||||
@FloatRange(from = -90.0, to = 90.0) float latitude,
|
@FloatRange(from = -90.0, to = 90.0) float latitude,
|
||||||
@FloatRange(from = -180.0, to = 180.0) float longitude) {
|
@FloatRange(from = -180.0, to = 180.0) float longitude) {
|
||||||
metadataCollector.setLocation(latitude, longitude);
|
addMetadata(new Mp4LocationData(latitude, longitude));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the capture frame rate.
|
* @deprecated Use {@link #addMetadata(Metadata.Entry)} with {@link MdtaMetadataEntry} instead.
|
||||||
*
|
|
||||||
* @param captureFps The frame rate.
|
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setCaptureFps(float captureFps) {
|
public void setCaptureFps(float captureFps) {
|
||||||
metadataCollector.setCaptureFps(captureFps);
|
addMetadata(
|
||||||
|
new MdtaMetadataEntry(
|
||||||
|
MdtaMetadataEntry.KEY_ANDROID_CAPTURE_FPS,
|
||||||
|
Util.toByteArray(captureFps),
|
||||||
|
MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the timestamp data (creation time and modification time) for the output file.
|
* @deprecated Use {@link #addMetadata(Metadata.Entry)} with {@link Mp4TimestampData} instead.
|
||||||
*
|
|
||||||
* <p>If this method is not called, the file creation time and modification time will be when the
|
|
||||||
* {@link Mp4Muxer} was {@linkplain Builder#build() created}.
|
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setTimestampData(Mp4TimestampData timestampData) {
|
public void setTimestampData(Mp4TimestampData timestampData) {
|
||||||
checkArgument(
|
addMetadata(timestampData);
|
||||||
timestampData.creationTimestampSeconds <= UNSIGNED_INT_MAX_VALUE
|
|
||||||
&& timestampData.modificationTimestampSeconds <= UNSIGNED_INT_MAX_VALUE,
|
|
||||||
"Only 32-bit long timestamp is supported");
|
|
||||||
metadataCollector.setTimestampData(timestampData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds custom metadata.
|
* @deprecated Use {@link #addMetadata(Metadata.Entry)} with {@link MdtaMetadataEntry} instead.
|
||||||
*
|
|
||||||
* @param key The metadata key in {@link String} format.
|
|
||||||
* @param value The metadata value in {@link String} or {@link Float} format.
|
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void addMetadata(String key, Object value) {
|
public void addMetadata(String key, Object value) {
|
||||||
metadataCollector.addMetadata(key, value);
|
MdtaMetadataEntry mdtaMetadataEntry = null;
|
||||||
|
if (value instanceof String) {
|
||||||
|
mdtaMetadataEntry =
|
||||||
|
new MdtaMetadataEntry(
|
||||||
|
key, Util.getUtf8Bytes((String) value), MdtaMetadataEntry.TYPE_INDICATOR_STRING);
|
||||||
|
} else if (value instanceof Float) {
|
||||||
|
mdtaMetadataEntry =
|
||||||
|
new MdtaMetadataEntry(
|
||||||
|
key, Util.toByteArray((Float) value), MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unsupported metadata");
|
||||||
|
}
|
||||||
|
addMetadata(mdtaMetadataEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds xmp data.
|
* Adds metadata for the output file.
|
||||||
*
|
*
|
||||||
* @param xmp The xmp {@link ByteBuffer}.
|
* <p>List of supported {@linkplain Metadata.Entry metadata entries}:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link Mp4LocationData}
|
||||||
|
* <li>{@link Mp4TimestampData}
|
||||||
|
* <li>{@link MdtaMetadataEntry}: Only {@linkplain MdtaMetadataEntry#TYPE_INDICATOR_STRING
|
||||||
|
* string type} or {@linkplain MdtaMetadataEntry#TYPE_INDICATOR_FLOAT32 float type} value is
|
||||||
|
* supported.
|
||||||
|
* <li>{@link XmpData}
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param metadata The {@linkplain Metadata.Entry metadata}. An {@link IllegalArgumentException}
|
||||||
|
* is throw if the {@linkplain Metadata.Entry metadata} is not supported.
|
||||||
*/
|
*/
|
||||||
|
public void addMetadata(Metadata.Entry metadata) {
|
||||||
|
checkArgument(isMetadataSupported(metadata), "Unsupported metadata");
|
||||||
|
metadataCollector.addMetadata(metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #addMetadata(Metadata.Entry)} with {@link XmpData} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public void addXmp(ByteBuffer xmp) {
|
public void addXmp(ByteBuffer xmp) {
|
||||||
metadataCollector.addXmp(xmp);
|
byte[] xmpData = new byte[xmp.remaining()];
|
||||||
|
xmp.get(xmpData, 0, xmpData.length);
|
||||||
|
addMetadata(new XmpData(xmpData));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -308,4 +355,14 @@ public final class Mp4Muxer {
|
|||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
mp4Writer.close();
|
mp4Writer.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isMdtaMetadataEntrySupported(MdtaMetadataEntry mdtaMetadataEntry) {
|
||||||
|
return mdtaMetadataEntry.typeIndicator == MdtaMetadataEntry.TYPE_INDICATOR_STRING
|
||||||
|
|| mdtaMetadataEntry.typeIndicator == MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isMp4TimestampDataSupported(Mp4TimestampData timestampData) {
|
||||||
|
return timestampData.creationTimestampSeconds <= UNSIGNED_INT_MAX_VALUE
|
||||||
|
&& timestampData.modificationTimestampSeconds <= UNSIGNED_INT_MAX_VALUE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,8 @@ import android.media.MediaCodec;
|
|||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.ColorInfo;
|
import androidx.media3.common.ColorInfo;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
|
import androidx.media3.container.MdtaMetadataEntry;
|
||||||
import androidx.media3.container.Mp4LocationData;
|
import androidx.media3.container.Mp4LocationData;
|
||||||
import androidx.media3.muxer.FragmentedMp4Writer.SampleMetadata;
|
import androidx.media3.muxer.FragmentedMp4Writer.SampleMetadata;
|
||||||
import androidx.media3.test.utils.DumpFileAsserts;
|
import androidx.media3.test.utils.DumpFileAsserts;
|
||||||
@ -173,9 +175,19 @@ public class BoxesTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createKeysBox_matchesExpected() throws Exception {
|
public void createKeysBox_matchesExpected() throws Exception {
|
||||||
List<String> keyNames = ImmutableList.of("com.android.version", "com.android.capture.fps");
|
List<MdtaMetadataEntry> metadataEntries = new ArrayList<>();
|
||||||
|
metadataEntries.add(
|
||||||
|
new MdtaMetadataEntry(
|
||||||
|
"com.android.version",
|
||||||
|
Util.getUtf8Bytes("11"),
|
||||||
|
MdtaMetadataEntry.TYPE_INDICATOR_STRING));
|
||||||
|
metadataEntries.add(
|
||||||
|
new MdtaMetadataEntry(
|
||||||
|
"com.android.capture.fps",
|
||||||
|
Util.toByteArray(120.0f),
|
||||||
|
MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32));
|
||||||
|
|
||||||
ByteBuffer keysBox = Boxes.keys(keyNames);
|
ByteBuffer keysBox = Boxes.keys(metadataEntries);
|
||||||
|
|
||||||
DumpableMp4Box dumpableBox = new DumpableMp4Box(keysBox);
|
DumpableMp4Box dumpableBox = new DumpableMp4Box(keysBox);
|
||||||
DumpFileAsserts.assertOutput(context, dumpableBox, getExpectedDumpFilePath("keys_box"));
|
DumpFileAsserts.assertOutput(context, dumpableBox, getExpectedDumpFilePath("keys_box"));
|
||||||
@ -183,9 +195,19 @@ public class BoxesTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createIlstBox_matchesExpected() throws Exception {
|
public void createIlstBox_matchesExpected() throws Exception {
|
||||||
List<Object> values = ImmutableList.of("11", 120.0f);
|
List<MdtaMetadataEntry> metadataEntries = new ArrayList<>();
|
||||||
|
metadataEntries.add(
|
||||||
|
new MdtaMetadataEntry(
|
||||||
|
"com.android.version",
|
||||||
|
Util.getUtf8Bytes("11"),
|
||||||
|
MdtaMetadataEntry.TYPE_INDICATOR_STRING));
|
||||||
|
metadataEntries.add(
|
||||||
|
new MdtaMetadataEntry(
|
||||||
|
"com.android.capture.fps",
|
||||||
|
Util.toByteArray(120.0f),
|
||||||
|
MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32));
|
||||||
|
|
||||||
ByteBuffer ilstBox = Boxes.ilst(values);
|
ByteBuffer ilstBox = Boxes.ilst(metadataEntries);
|
||||||
|
|
||||||
DumpableMp4Box dumpableBox = new DumpableMp4Box(ilstBox);
|
DumpableMp4Box dumpableBox = new DumpableMp4Box(ilstBox);
|
||||||
DumpFileAsserts.assertOutput(context, dumpableBox, getExpectedDumpFilePath("ilst_box"));
|
DumpFileAsserts.assertOutput(context, dumpableBox, getExpectedDumpFilePath("ilst_box"));
|
||||||
|
@ -24,7 +24,11 @@ import static org.junit.Assert.assertThrows;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.media.MediaCodec.BufferInfo;
|
import android.media.MediaCodec.BufferInfo;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
|
import androidx.media3.container.MdtaMetadataEntry;
|
||||||
|
import androidx.media3.container.Mp4LocationData;
|
||||||
import androidx.media3.container.Mp4TimestampData;
|
import androidx.media3.container.Mp4TimestampData;
|
||||||
|
import androidx.media3.container.XmpData;
|
||||||
import androidx.media3.extractor.mp4.Mp4Extractor;
|
import androidx.media3.extractor.mp4.Mp4Extractor;
|
||||||
import androidx.media3.muxer.Mp4Muxer.TrackToken;
|
import androidx.media3.muxer.Mp4Muxer.TrackToken;
|
||||||
import androidx.media3.test.utils.DumpFileAsserts;
|
import androidx.media3.test.utils.DumpFileAsserts;
|
||||||
@ -56,7 +60,11 @@ public class Mp4MuxerEndToEndTest {
|
|||||||
try {
|
try {
|
||||||
mp4Muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
|
mp4Muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
|
||||||
mp4Muxer.setOrientation(90);
|
mp4Muxer.setOrientation(90);
|
||||||
mp4Muxer.addMetadata("key", "value");
|
mp4Muxer.addMetadata(
|
||||||
|
new MdtaMetadataEntry(
|
||||||
|
"key",
|
||||||
|
/* value= */ Util.getUtf8Bytes("value"),
|
||||||
|
MdtaMetadataEntry.TYPE_INDICATOR_STRING));
|
||||||
} finally {
|
} finally {
|
||||||
mp4Muxer.close();
|
mp4Muxer.close();
|
||||||
}
|
}
|
||||||
@ -69,7 +77,7 @@ public class Mp4MuxerEndToEndTest {
|
|||||||
public void createMp4File_withSameTracksOffset_matchesExpected() throws IOException {
|
public void createMp4File_withSameTracksOffset_matchesExpected() throws IOException {
|
||||||
String outputFilePath = temporaryFolder.newFile().getPath();
|
String outputFilePath = temporaryFolder.newFile().getPath();
|
||||||
Mp4Muxer mp4Muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
Mp4Muxer mp4Muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
||||||
mp4Muxer.setTimestampData(
|
mp4Muxer.addMetadata(
|
||||||
new Mp4TimestampData(
|
new Mp4TimestampData(
|
||||||
/* creationTimestampSeconds= */ 100_000_000L,
|
/* creationTimestampSeconds= */ 100_000_000L,
|
||||||
/* modificationTimestampSeconds= */ 500_000_000L));
|
/* modificationTimestampSeconds= */ 500_000_000L));
|
||||||
@ -112,7 +120,7 @@ public class Mp4MuxerEndToEndTest {
|
|||||||
public void createMp4File_withDifferentTracksOffset_matchesExpected() throws IOException {
|
public void createMp4File_withDifferentTracksOffset_matchesExpected() throws IOException {
|
||||||
String outputFilePath = temporaryFolder.newFile().getPath();
|
String outputFilePath = temporaryFolder.newFile().getPath();
|
||||||
Mp4Muxer mp4Muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
Mp4Muxer mp4Muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
||||||
mp4Muxer.setTimestampData(
|
mp4Muxer.addMetadata(
|
||||||
new Mp4TimestampData(
|
new Mp4TimestampData(
|
||||||
/* creationTimestampSeconds= */ 100_000_000L,
|
/* creationTimestampSeconds= */ 100_000_000L,
|
||||||
/* modificationTimestampSeconds= */ 500_000_000L));
|
/* modificationTimestampSeconds= */ 500_000_000L));
|
||||||
@ -174,7 +182,7 @@ public class Mp4MuxerEndToEndTest {
|
|||||||
public void createMp4File_withOneTrackEmpty_doesNotWriteEmptyTrack() throws Exception {
|
public void createMp4File_withOneTrackEmpty_doesNotWriteEmptyTrack() throws Exception {
|
||||||
String outputFilePath = temporaryFolder.newFile().getPath();
|
String outputFilePath = temporaryFolder.newFile().getPath();
|
||||||
Mp4Muxer mp4Muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
Mp4Muxer mp4Muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
||||||
mp4Muxer.setTimestampData(
|
mp4Muxer.addMetadata(
|
||||||
new Mp4TimestampData(
|
new Mp4TimestampData(
|
||||||
/* creationTimestampSeconds= */ 100_000_000L,
|
/* creationTimestampSeconds= */ 100_000_000L,
|
||||||
/* modificationTimestampSeconds= */ 500_000_000L));
|
/* modificationTimestampSeconds= */ 500_000_000L));
|
||||||
@ -209,18 +217,26 @@ public class Mp4MuxerEndToEndTest {
|
|||||||
Pair<ByteBuffer, BufferInfo> sampleAndSampleInfo =
|
Pair<ByteBuffer, BufferInfo> sampleAndSampleInfo =
|
||||||
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 0L);
|
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 0L);
|
||||||
byte[] xmpBytes = TestUtil.getByteArray(context, XMP_SAMPLE_DATA);
|
byte[] xmpBytes = TestUtil.getByteArray(context, XMP_SAMPLE_DATA);
|
||||||
ByteBuffer xmp = ByteBuffer.wrap(xmpBytes);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
muxer.setOrientation(90);
|
muxer.setOrientation(90);
|
||||||
muxer.setLocation(/* latitude= */ 33.0f, /* longitude= */ -120f);
|
muxer.addMetadata(new Mp4LocationData(/* latitude= */ 33.0f, /* longitude= */ -120f));
|
||||||
muxer.setCaptureFps(120.0f);
|
float captureFps = 120.0f;
|
||||||
muxer.setTimestampData(
|
muxer.addMetadata(
|
||||||
|
new MdtaMetadataEntry(
|
||||||
|
MdtaMetadataEntry.KEY_ANDROID_CAPTURE_FPS,
|
||||||
|
/* value= */ Util.toByteArray(captureFps),
|
||||||
|
MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32));
|
||||||
|
muxer.addMetadata(
|
||||||
new Mp4TimestampData(
|
new Mp4TimestampData(
|
||||||
/* creationTimestampSeconds= */ 1_000_000L,
|
/* creationTimestampSeconds= */ 1_000_000L,
|
||||||
/* modificationTimestampSeconds= */ 5_000_000L));
|
/* modificationTimestampSeconds= */ 5_000_000L));
|
||||||
muxer.addMetadata("StringKey1", "StringValue");
|
muxer.addMetadata(
|
||||||
muxer.addXmp(xmp);
|
new MdtaMetadataEntry(
|
||||||
|
"StringKey1",
|
||||||
|
/* value= */ Util.getUtf8Bytes("StringValue"),
|
||||||
|
MdtaMetadataEntry.TYPE_INDICATOR_STRING));
|
||||||
|
muxer.addMetadata(new XmpData(xmpBytes));
|
||||||
TrackToken token = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
|
TrackToken token = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
|
||||||
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
|
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -52,7 +52,7 @@ public class Mp4MuxerMetadataTest {
|
|||||||
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
muxer.setTimestampData(
|
muxer.addMetadata(
|
||||||
new Mp4TimestampData(
|
new Mp4TimestampData(
|
||||||
/* creationTimestampSeconds= */ 1_000_000L,
|
/* creationTimestampSeconds= */ 1_000_000L,
|
||||||
/* modificationTimestampSeconds= */ 5_000_000L));
|
/* modificationTimestampSeconds= */ 5_000_000L));
|
||||||
@ -77,7 +77,7 @@ public class Mp4MuxerMetadataTest {
|
|||||||
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
muxer.setTimestampData(
|
muxer.addMetadata(
|
||||||
new Mp4TimestampData(
|
new Mp4TimestampData(
|
||||||
/* creationTimestampSeconds= */ 1_000_000L,
|
/* creationTimestampSeconds= */ 1_000_000L,
|
||||||
/* modificationTimestampSeconds= */ 5_000_000L));
|
/* modificationTimestampSeconds= */ 5_000_000L));
|
||||||
@ -104,7 +104,7 @@ public class Mp4MuxerMetadataTest {
|
|||||||
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
muxer.setTimestampData(
|
muxer.addMetadata(
|
||||||
new Mp4TimestampData(
|
new Mp4TimestampData(
|
||||||
/* creationTimestampSeconds= */ 1_000_000L,
|
/* creationTimestampSeconds= */ 1_000_000L,
|
||||||
/* modificationTimestampSeconds= */ 5_000_000L));
|
/* modificationTimestampSeconds= */ 5_000_000L));
|
||||||
@ -131,7 +131,7 @@ public class Mp4MuxerMetadataTest {
|
|||||||
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
muxer.setTimestampData(
|
muxer.addMetadata(
|
||||||
new Mp4TimestampData(
|
new Mp4TimestampData(
|
||||||
/* creationTimestampSeconds= */ 1_000_000L,
|
/* creationTimestampSeconds= */ 1_000_000L,
|
||||||
/* modificationTimestampSeconds= */ 5_000_000L));
|
/* modificationTimestampSeconds= */ 5_000_000L));
|
||||||
@ -158,7 +158,7 @@ public class Mp4MuxerMetadataTest {
|
|||||||
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
muxer.setTimestampData(
|
muxer.addMetadata(
|
||||||
new Mp4TimestampData(
|
new Mp4TimestampData(
|
||||||
/* creationTimestampSeconds= */ 1_000_000L,
|
/* creationTimestampSeconds= */ 1_000_000L,
|
||||||
/* modificationTimestampSeconds= */ 5_000_000L));
|
/* modificationTimestampSeconds= */ 5_000_000L));
|
||||||
@ -184,7 +184,7 @@ public class Mp4MuxerMetadataTest {
|
|||||||
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
muxer.setTimestampData(
|
muxer.addMetadata(
|
||||||
new Mp4TimestampData(
|
new Mp4TimestampData(
|
||||||
/* creationTimestampSeconds= */ 1_000_000L,
|
/* creationTimestampSeconds= */ 1_000_000L,
|
||||||
/* modificationTimestampSeconds= */ 5_000_000L));
|
/* modificationTimestampSeconds= */ 5_000_000L));
|
||||||
@ -209,7 +209,7 @@ public class Mp4MuxerMetadataTest {
|
|||||||
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
muxer.setTimestampData(
|
muxer.addMetadata(
|
||||||
new Mp4TimestampData(
|
new Mp4TimestampData(
|
||||||
/* creationTimestampSeconds= */ 1_000_000L,
|
/* creationTimestampSeconds= */ 1_000_000L,
|
||||||
/* modificationTimestampSeconds= */ 5_000_000L));
|
/* modificationTimestampSeconds= */ 5_000_000L));
|
||||||
@ -235,7 +235,7 @@ public class Mp4MuxerMetadataTest {
|
|||||||
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
muxer.setTimestampData(
|
muxer.addMetadata(
|
||||||
new Mp4TimestampData(
|
new Mp4TimestampData(
|
||||||
/* creationTimestampSeconds= */ 1_000_000L,
|
/* creationTimestampSeconds= */ 1_000_000L,
|
||||||
/* modificationTimestampSeconds= */ 5_000_000L));
|
/* modificationTimestampSeconds= */ 5_000_000L));
|
||||||
@ -261,7 +261,7 @@ public class Mp4MuxerMetadataTest {
|
|||||||
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
muxer.setTimestampData(
|
muxer.addMetadata(
|
||||||
new Mp4TimestampData(
|
new Mp4TimestampData(
|
||||||
/* creationTimestampSeconds= */ 1_000_000L,
|
/* creationTimestampSeconds= */ 1_000_000L,
|
||||||
/* modificationTimestampSeconds= */ 5_000_000L));
|
/* modificationTimestampSeconds= */ 5_000_000L));
|
||||||
|
@ -24,11 +24,6 @@ import androidx.media3.common.Format;
|
|||||||
import androidx.media3.common.Metadata;
|
import androidx.media3.common.Metadata;
|
||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
|
||||||
import androidx.media3.container.MdtaMetadataEntry;
|
|
||||||
import androidx.media3.container.Mp4LocationData;
|
|
||||||
import androidx.media3.container.Mp4TimestampData;
|
|
||||||
import androidx.media3.container.XmpData;
|
|
||||||
import androidx.media3.muxer.Mp4Muxer;
|
import androidx.media3.muxer.Mp4Muxer;
|
||||||
import androidx.media3.muxer.Mp4Muxer.TrackToken;
|
import androidx.media3.muxer.Mp4Muxer.TrackToken;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
@ -56,14 +51,7 @@ public final class InAppMuxer implements Muxer {
|
|||||||
* <p>A {@link Metadata.Entry} can be added or removed. To modify an existing {@link
|
* <p>A {@link Metadata.Entry} can be added or removed. To modify an existing {@link
|
||||||
* Metadata.Entry}, first remove it and then add a new one.
|
* Metadata.Entry}, first remove it and then add a new one.
|
||||||
*
|
*
|
||||||
* <p>List of supported {@linkplain Metadata.Entry metadata entries}:
|
* <p>For the list of supported metadata refer to {@link Mp4Muxer#addMetadata(Metadata.Entry)}.
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
* <li>{@link Mp4LocationData}
|
|
||||||
* <li>{@link XmpData}
|
|
||||||
* <li>{@link Mp4TimestampData}
|
|
||||||
* <li>{@link MdtaMetadataEntry}
|
|
||||||
* </ul>
|
|
||||||
*/
|
*/
|
||||||
void updateMetadataEntries(Set<Metadata.Entry> metadataEntries);
|
void updateMetadataEntries(Set<Metadata.Entry> metadataEntries);
|
||||||
}
|
}
|
||||||
@ -241,17 +229,7 @@ public final class InAppMuxer implements Muxer {
|
|||||||
public void addMetadata(Metadata metadata) {
|
public void addMetadata(Metadata metadata) {
|
||||||
for (int i = 0; i < metadata.length(); i++) {
|
for (int i = 0; i < metadata.length(); i++) {
|
||||||
Metadata.Entry entry = metadata.get(i);
|
Metadata.Entry entry = metadata.get(i);
|
||||||
// Keep only supported metadata.
|
if (Mp4Muxer.isMetadataSupported(entry)) {
|
||||||
// LINT.IfChange(added_metadata)
|
|
||||||
if (entry instanceof Mp4LocationData
|
|
||||||
|| entry instanceof XmpData
|
|
||||||
|| entry instanceof Mp4TimestampData
|
|
||||||
|| (entry instanceof MdtaMetadataEntry
|
|
||||||
&& (((MdtaMetadataEntry) entry).key.equals(MdtaMetadataEntry.KEY_ANDROID_CAPTURE_FPS)
|
|
||||||
|| ((MdtaMetadataEntry) entry).typeIndicator
|
|
||||||
== MdtaMetadataEntry.TYPE_INDICATOR_STRING
|
|
||||||
|| ((MdtaMetadataEntry) entry).typeIndicator
|
|
||||||
== MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32))) {
|
|
||||||
metadataEntries.add(entry);
|
metadataEntries.add(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -282,29 +260,7 @@ public final class InAppMuxer implements Muxer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (Metadata.Entry entry : metadataEntries) {
|
for (Metadata.Entry entry : metadataEntries) {
|
||||||
// LINT.IfChange(written_metadata)
|
mp4Muxer.addMetadata(entry);
|
||||||
if (entry instanceof Mp4LocationData) {
|
|
||||||
mp4Muxer.setLocation(
|
|
||||||
((Mp4LocationData) entry).latitude, ((Mp4LocationData) entry).longitude);
|
|
||||||
} else if (entry instanceof XmpData) {
|
|
||||||
mp4Muxer.addXmp(ByteBuffer.wrap(((XmpData) entry).data));
|
|
||||||
} else if (entry instanceof Mp4TimestampData) {
|
|
||||||
mp4Muxer.setTimestampData((Mp4TimestampData) entry);
|
|
||||||
} else if (entry instanceof MdtaMetadataEntry) {
|
|
||||||
MdtaMetadataEntry mdtaMetadataEntry = (MdtaMetadataEntry) entry;
|
|
||||||
if (mdtaMetadataEntry.key.equals(MdtaMetadataEntry.KEY_ANDROID_CAPTURE_FPS)) {
|
|
||||||
byte[] captureFps = mdtaMetadataEntry.value;
|
|
||||||
mp4Muxer.setCaptureFps(ByteBuffer.wrap(captureFps).getFloat());
|
|
||||||
} else if (mdtaMetadataEntry.typeIndicator == MdtaMetadataEntry.TYPE_INDICATOR_STRING) {
|
|
||||||
mp4Muxer.addMetadata(mdtaMetadataEntry.key, Util.fromUtf8Bytes(mdtaMetadataEntry.value));
|
|
||||||
} else if (mdtaMetadataEntry.typeIndicator == MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32) {
|
|
||||||
mp4Muxer.addMetadata(mdtaMetadataEntry.key, Util.toFloat(mdtaMetadataEntry.value));
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException("Unsupported MdtaMetadataEntry " + mdtaMetadataEntry.key);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException("Unsupported Metadata.Entry " + entry.getClass().getName());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user