Commit 42d4afe7 by olly Committed by Oliver Woodman

Make Format.Builder, peakBitrate and averageBitrate public

- Deprecate old Format.createXXX methods
- Deprecate most Format.copyXXX methods
- Stop using deprecated Format.copyXXX methods in the library

Note: Replacing library usages of Format.createXXX method
will be done in follow up CLs. These changes aren't purely
mechanical because we need to decide which out of peakBitrate
and averageBitrate to set in each case where currently a
single bitrate is provided.

Issue: #2863
PiperOrigin-RevId: 296450935
parent 4c05201a
...@@ -32,6 +32,10 @@ ...@@ -32,6 +32,10 @@
* Add a sample count parameter to `MediaCodecRenderer.processOutputBuffer` * Add a sample count parameter to `MediaCodecRenderer.processOutputBuffer`
and `AudioSink.handleBuffer` to allow batching multiple encoded frames and `AudioSink.handleBuffer` to allow batching multiple encoded frames
in one buffer. in one buffer.
* Add a `Format.Builder` and deprecate all `Format.create*` methods and most
`Format.copyWith*` methods.
* Split `Format.bitrate` into `Format.averageBitrate` and `Format.peakBitrate`
([#2863](https://github.com/google/ExoPlayer/issues/2863)).
* Text: * Text:
* Parse `<ruby>` and `<rt>` tags in WebVTT subtitles (rendering is coming * Parse `<ruby>` and `<rt>` tags in WebVTT subtitles (rendering is coming
later). later).
......
...@@ -110,12 +110,13 @@ public final class Format implements Parcelable { ...@@ -110,12 +110,13 @@ public final class Format implements Parcelable {
/** /**
* Builds {@link Format} instances. * Builds {@link Format} instances.
* *
* <p>Use Format#buildUpon() to obtain a builder representing an existing {@link Format}.
*
* <p>When building formats, populate all fields whose values are known and relevant to the type * <p>When building formats, populate all fields whose values are known and relevant to the type
* of format being constructed. See the {@link Format} Javadoc for information about which fields * of format being constructed. See the {@link Format} Javadoc for information about which fields
* should be set for different types of format. * should be set for different types of format.
*/ */
// TODO: Make public. public static final class Builder {
/* package */ static final class Builder {
@Nullable private String id; @Nullable private String id;
@Nullable private String label; @Nullable private String label;
...@@ -243,6 +244,18 @@ public final class Format implements Parcelable { ...@@ -243,6 +244,18 @@ public final class Format implements Parcelable {
} }
/** /**
* Sets {@link Format#id} to {@link Integer#toString() Integer.toString(id)}. The default value
* is {@code null}.
*
* @param id The {@link Format#id}.
* @return The builder.
*/
public Builder setId(int id) {
this.id = Integer.toString(id);
return this;
}
/**
* Sets {@link Format#label}. The default value is {@code null}. * Sets {@link Format#label}. The default value is {@code null}.
* *
* @param label The {@link Format#label}. * @param label The {@link Format#label}.
...@@ -373,7 +386,7 @@ public final class Format implements Parcelable { ...@@ -373,7 +386,7 @@ public final class Format implements Parcelable {
* @param initializationData The {@link Format#initializationData}. * @param initializationData The {@link Format#initializationData}.
* @return The builder. * @return The builder.
*/ */
public Builder setInitializationData(List<byte[]> initializationData) { public Builder setInitializationData(@Nullable List<byte[]> initializationData) {
this.initializationData = initializationData; this.initializationData = initializationData;
return this; return this;
} }
...@@ -645,8 +658,7 @@ public final class Format implements Parcelable { ...@@ -645,8 +658,7 @@ public final class Format implements Parcelable {
* the average bitrate if defined by the container. * the average bitrate if defined by the container.
* </ul> * </ul>
*/ */
// TODO: Make public. public final int averageBitrate;
private final int averageBitrate;
/** /**
* The peak bitrate in bits per second, or {@link #NO_VALUE} if unknown or not applicable. This * The peak bitrate in bits per second, or {@link #NO_VALUE} if unknown or not applicable. This
* field may be populated from the following sources, depending on media type and the type of the * field may be populated from the following sources, depending on media type and the type of the
...@@ -662,8 +674,7 @@ public final class Format implements Parcelable { ...@@ -662,8 +674,7 @@ public final class Format implements Parcelable {
* the peak bitrate if defined by the container. * the peak bitrate if defined by the container.
* </ul> * </ul>
*/ */
// TODO: Make public. public final int peakBitrate;
private final int peakBitrate;
/** /**
* The bitrate in bits per second. This is the peak bitrate if known, or else the average bitrate * The bitrate in bits per second. This is the peak bitrate if known, or else the average bitrate
* if known, or else {@link Format#NO_VALUE}. Equivalent to: {@code peakBitrate != NO_VALUE ? * if known, or else {@link Format#NO_VALUE}. Equivalent to: {@code peakBitrate != NO_VALUE ?
...@@ -777,6 +788,8 @@ public final class Format implements Parcelable { ...@@ -777,6 +788,8 @@ public final class Format implements Parcelable {
// Video. // Video.
/** @deprecated Use {@link Format.Builder}. */
@Deprecated
public static Format createVideoContainerFormat( public static Format createVideoContainerFormat(
@Nullable String id, @Nullable String id,
@Nullable String label, @Nullable String label,
...@@ -791,39 +804,26 @@ public final class Format implements Parcelable { ...@@ -791,39 +804,26 @@ public final class Format implements Parcelable {
@Nullable List<byte[]> initializationData, @Nullable List<byte[]> initializationData,
@C.SelectionFlags int selectionFlags, @C.SelectionFlags int selectionFlags,
@C.RoleFlags int roleFlags) { @C.RoleFlags int roleFlags) {
return new Format( return new Builder()
id, .setId(id)
label, .setLabel(label)
/* language= */ null, .setSelectionFlags(selectionFlags)
selectionFlags, .setRoleFlags(roleFlags)
roleFlags, .setAverageBitrate(bitrate)
/* averageBitrate= */ bitrate, .setPeakBitrate(bitrate)
/* peakBitrate= */ bitrate, .setCodecs(codecs)
codecs, .setMetadata(metadata)
metadata, .setContainerMimeType(containerMimeType)
containerMimeType, .setSampleMimeType(sampleMimeType)
sampleMimeType, .setInitializationData(initializationData)
/* maxInputSize= */ NO_VALUE, .setWidth(width)
initializationData, .setHeight(height)
/* drmInitData= */ null, .setFrameRate(frameRate)
OFFSET_SAMPLE_RELATIVE, .build();
width,
height,
frameRate,
/* rotationDegrees= */ NO_VALUE,
/* pixelWidthHeightRatio= */ NO_VALUE,
/* projectionData= */ null,
/* stereoMode= */ NO_VALUE,
/* colorInfo= */ null,
/* channelCount= */ NO_VALUE,
/* sampleRate= */ NO_VALUE,
/* pcmEncoding= */ NO_VALUE,
/* encoderDelay= */ NO_VALUE,
/* encoderPadding= */ NO_VALUE,
/* accessibilityChannel= */ NO_VALUE,
/* exoMediaCryptoType= */ null);
} }
/** @deprecated Use {@link Format.Builder}. */
@Deprecated
public static Format createVideoSampleFormat( public static Format createVideoSampleFormat(
@Nullable String id, @Nullable String id,
@Nullable String sampleMimeType, @Nullable String sampleMimeType,
...@@ -835,21 +835,23 @@ public final class Format implements Parcelable { ...@@ -835,21 +835,23 @@ public final class Format implements Parcelable {
float frameRate, float frameRate,
@Nullable List<byte[]> initializationData, @Nullable List<byte[]> initializationData,
@Nullable DrmInitData drmInitData) { @Nullable DrmInitData drmInitData) {
return createVideoSampleFormat( return new Builder()
id, .setId(id)
sampleMimeType, .setAverageBitrate(bitrate)
codecs, .setPeakBitrate(bitrate)
bitrate, .setCodecs(codecs)
maxInputSize, .setSampleMimeType(sampleMimeType)
width, .setMaxInputSize(maxInputSize)
height, .setInitializationData(initializationData)
frameRate, .setDrmInitData(drmInitData)
initializationData, .setWidth(width)
/* rotationDegrees= */ NO_VALUE, .setHeight(height)
/* pixelWidthHeightRatio= */ NO_VALUE, .setFrameRate(frameRate)
drmInitData); .build();
} }
/** @deprecated Use {@link Format.Builder}. */
@Deprecated
public static Format createVideoSampleFormat( public static Format createVideoSampleFormat(
@Nullable String id, @Nullable String id,
@Nullable String sampleMimeType, @Nullable String sampleMimeType,
...@@ -863,24 +865,25 @@ public final class Format implements Parcelable { ...@@ -863,24 +865,25 @@ public final class Format implements Parcelable {
int rotationDegrees, int rotationDegrees,
float pixelWidthHeightRatio, float pixelWidthHeightRatio,
@Nullable DrmInitData drmInitData) { @Nullable DrmInitData drmInitData) {
return createVideoSampleFormat( return new Builder()
id, .setId(id)
sampleMimeType, .setAverageBitrate(bitrate)
codecs, .setPeakBitrate(bitrate)
bitrate, .setCodecs(codecs)
maxInputSize, .setSampleMimeType(sampleMimeType)
width, .setMaxInputSize(maxInputSize)
height, .setInitializationData(initializationData)
frameRate, .setDrmInitData(drmInitData)
initializationData, .setWidth(width)
rotationDegrees, .setHeight(height)
pixelWidthHeightRatio, .setFrameRate(frameRate)
/* projectionData= */ null, .setRotationDegrees(rotationDegrees)
/* stereoMode= */ NO_VALUE, .setPixelWidthHeightRatio(pixelWidthHeightRatio)
/* colorInfo= */ null, .build();
drmInitData);
} }
/** @deprecated Use {@link Format.Builder}. */
@Deprecated
public static Format createVideoSampleFormat( public static Format createVideoSampleFormat(
@Nullable String id, @Nullable String id,
@Nullable String sampleMimeType, @Nullable String sampleMimeType,
...@@ -897,41 +900,30 @@ public final class Format implements Parcelable { ...@@ -897,41 +900,30 @@ public final class Format implements Parcelable {
@C.StereoMode int stereoMode, @C.StereoMode int stereoMode,
@Nullable ColorInfo colorInfo, @Nullable ColorInfo colorInfo,
@Nullable DrmInitData drmInitData) { @Nullable DrmInitData drmInitData) {
return new Format( return new Builder()
id, .setId(id)
/* label= */ null, .setAverageBitrate(bitrate)
/* language= */ null, .setPeakBitrate(bitrate)
/* selectionFlags= */ 0, .setCodecs(codecs)
/* roleFlags= */ 0, .setSampleMimeType(sampleMimeType)
/* averageBitrate= */ bitrate, .setMaxInputSize(maxInputSize)
/* peakBitrate= */ bitrate, .setInitializationData(initializationData)
codecs, .setDrmInitData(drmInitData)
/* metadata= */ null, .setWidth(width)
/* containerMimeType= */ null, .setHeight(height)
sampleMimeType, .setFrameRate(frameRate)
maxInputSize, .setRotationDegrees(rotationDegrees)
initializationData, .setPixelWidthHeightRatio(pixelWidthHeightRatio)
drmInitData, .setProjectionData(projectionData)
OFFSET_SAMPLE_RELATIVE, .setStereoMode(stereoMode)
width, .setColorInfo(colorInfo)
height, .build();
frameRate,
rotationDegrees,
pixelWidthHeightRatio,
projectionData,
stereoMode,
colorInfo,
/* channelCount= */ NO_VALUE,
/* sampleRate= */ NO_VALUE,
/* pcmEncoding= */ NO_VALUE,
/* encoderDelay= */ NO_VALUE,
/* encoderPadding= */ NO_VALUE,
/* accessibilityChannel= */ NO_VALUE,
/* exoMediaCryptoType= */ null);
} }
// Audio. // Audio.
/** @deprecated Use {@link Format.Builder}. */
@Deprecated
public static Format createAudioContainerFormat( public static Format createAudioContainerFormat(
@Nullable String id, @Nullable String id,
@Nullable String label, @Nullable String label,
...@@ -946,39 +938,26 @@ public final class Format implements Parcelable { ...@@ -946,39 +938,26 @@ public final class Format implements Parcelable {
@C.SelectionFlags int selectionFlags, @C.SelectionFlags int selectionFlags,
@C.RoleFlags int roleFlags, @C.RoleFlags int roleFlags,
@Nullable String language) { @Nullable String language) {
return new Format( return new Builder()
id, .setId(id)
label, .setLabel(label)
language, .setLanguage(language)
selectionFlags, .setSelectionFlags(selectionFlags)
roleFlags, .setRoleFlags(roleFlags)
/* averageBitrate= */ bitrate, .setAverageBitrate(bitrate)
/* peakBitrate= */ bitrate, .setPeakBitrate(bitrate)
codecs, .setCodecs(codecs)
metadata, .setMetadata(metadata)
containerMimeType, .setContainerMimeType(containerMimeType)
sampleMimeType, .setSampleMimeType(sampleMimeType)
/* maxInputSize= */ NO_VALUE, .setInitializationData(initializationData)
initializationData, .setChannelCount(channelCount)
/* drmInitData= */ null, .setSampleRate(sampleRate)
OFFSET_SAMPLE_RELATIVE, .build();
/* width= */ NO_VALUE,
/* height= */ NO_VALUE,
/* frameRate= */ NO_VALUE,
/* rotationDegrees= */ NO_VALUE,
/* pixelWidthHeightRatio= */ NO_VALUE,
/* projectionData= */ null,
/* stereoMode= */ NO_VALUE,
/* colorInfo= */ null,
channelCount,
sampleRate,
/* pcmEncoding= */ NO_VALUE,
/* encoderDelay= */ NO_VALUE,
/* encoderPadding= */ NO_VALUE,
/* accessibilityChannel= */ NO_VALUE,
/* exoMediaCryptoType= */ null);
} }
/** @deprecated Use {@link Format.Builder}. */
@Deprecated
public static Format createAudioSampleFormat( public static Format createAudioSampleFormat(
@Nullable String id, @Nullable String id,
@Nullable String sampleMimeType, @Nullable String sampleMimeType,
...@@ -991,21 +970,24 @@ public final class Format implements Parcelable { ...@@ -991,21 +970,24 @@ public final class Format implements Parcelable {
@Nullable DrmInitData drmInitData, @Nullable DrmInitData drmInitData,
@C.SelectionFlags int selectionFlags, @C.SelectionFlags int selectionFlags,
@Nullable String language) { @Nullable String language) {
return createAudioSampleFormat( return new Builder()
id, .setId(id)
sampleMimeType, .setLanguage(language)
codecs, .setSelectionFlags(selectionFlags)
bitrate, .setAverageBitrate(bitrate)
maxInputSize, .setPeakBitrate(bitrate)
channelCount, .setCodecs(codecs)
sampleRate, .setSampleMimeType(sampleMimeType)
/* pcmEncoding= */ NO_VALUE, .setMaxInputSize(maxInputSize)
initializationData, .setInitializationData(initializationData)
drmInitData, .setDrmInitData(drmInitData)
selectionFlags, .setChannelCount(channelCount)
language); .setSampleRate(sampleRate)
.build();
} }
/** @deprecated Use {@link Format.Builder}. */
@Deprecated
public static Format createAudioSampleFormat( public static Format createAudioSampleFormat(
@Nullable String id, @Nullable String id,
@Nullable String sampleMimeType, @Nullable String sampleMimeType,
...@@ -1019,24 +1001,25 @@ public final class Format implements Parcelable { ...@@ -1019,24 +1001,25 @@ public final class Format implements Parcelable {
@Nullable DrmInitData drmInitData, @Nullable DrmInitData drmInitData,
@C.SelectionFlags int selectionFlags, @C.SelectionFlags int selectionFlags,
@Nullable String language) { @Nullable String language) {
return createAudioSampleFormat( return new Builder()
id, .setId(id)
sampleMimeType, .setLanguage(language)
codecs, .setSelectionFlags(selectionFlags)
bitrate, .setAverageBitrate(bitrate)
maxInputSize, .setPeakBitrate(bitrate)
channelCount, .setCodecs(codecs)
sampleRate, .setSampleMimeType(sampleMimeType)
pcmEncoding, .setMaxInputSize(maxInputSize)
/* encoderDelay= */ NO_VALUE, .setInitializationData(initializationData)
/* encoderPadding= */ NO_VALUE, .setDrmInitData(drmInitData)
initializationData, .setChannelCount(channelCount)
drmInitData, .setSampleRate(sampleRate)
selectionFlags, .setPcmEncoding(pcmEncoding)
language, .build();
/* metadata= */ null);
} }
/** @deprecated Use {@link Format.Builder}. */
@Deprecated
public static Format createAudioSampleFormat( public static Format createAudioSampleFormat(
@Nullable String id, @Nullable String id,
@Nullable String sampleMimeType, @Nullable String sampleMimeType,
...@@ -1053,41 +1036,30 @@ public final class Format implements Parcelable { ...@@ -1053,41 +1036,30 @@ public final class Format implements Parcelable {
@C.SelectionFlags int selectionFlags, @C.SelectionFlags int selectionFlags,
@Nullable String language, @Nullable String language,
@Nullable Metadata metadata) { @Nullable Metadata metadata) {
return new Format( return new Builder()
id, .setId(id)
/* label= */ null, .setLanguage(language)
language, .setSelectionFlags(selectionFlags)
selectionFlags, .setAverageBitrate(bitrate)
/* roleFlags= */ 0, .setPeakBitrate(bitrate)
/* averageBitrate= */ bitrate, .setCodecs(codecs)
/* peakBitrate= */ bitrate, .setMetadata(metadata)
codecs, .setSampleMimeType(sampleMimeType)
metadata, .setMaxInputSize(maxInputSize)
/* containerMimeType= */ null, .setInitializationData(initializationData)
sampleMimeType, .setDrmInitData(drmInitData)
maxInputSize, .setChannelCount(channelCount)
initializationData, .setSampleRate(sampleRate)
drmInitData, .setPcmEncoding(pcmEncoding)
OFFSET_SAMPLE_RELATIVE, .setEncoderDelay(encoderDelay)
/* width= */ NO_VALUE, .setEncoderPadding(encoderPadding)
/* height= */ NO_VALUE, .build();
/* frameRate= */ NO_VALUE,
/* rotationDegrees= */ NO_VALUE,
/* pixelWidthHeightRatio= */ NO_VALUE,
/* projectionData= */ null,
/* stereoMode= */ NO_VALUE,
/* colorInfo= */ null,
channelCount,
sampleRate,
pcmEncoding,
encoderDelay,
encoderPadding,
/* accessibilityChannel= */ NO_VALUE,
/* exoMediaCryptoType= */ null);
} }
// Text. // Text.
/** @deprecated Use {@link Format.Builder}. */
@Deprecated
public static Format createTextContainerFormat( public static Format createTextContainerFormat(
@Nullable String id, @Nullable String id,
@Nullable String label, @Nullable String label,
...@@ -1098,19 +1070,22 @@ public final class Format implements Parcelable { ...@@ -1098,19 +1070,22 @@ public final class Format implements Parcelable {
@C.SelectionFlags int selectionFlags, @C.SelectionFlags int selectionFlags,
@C.RoleFlags int roleFlags, @C.RoleFlags int roleFlags,
@Nullable String language) { @Nullable String language) {
return createTextContainerFormat( return new Builder()
id, .setId(id)
label, .setLabel(label)
containerMimeType, .setLanguage(language)
sampleMimeType, .setSelectionFlags(selectionFlags)
codecs, .setRoleFlags(roleFlags)
bitrate, .setAverageBitrate(bitrate)
selectionFlags, .setPeakBitrate(bitrate)
roleFlags, .setCodecs(codecs)
language, .setContainerMimeType(containerMimeType)
/* accessibilityChannel= */ NO_VALUE); .setSampleMimeType(sampleMimeType)
.build();
} }
/** @deprecated Use {@link Format.Builder}. */
@Deprecated
public static Format createTextContainerFormat( public static Format createTextContainerFormat(
@Nullable String id, @Nullable String id,
@Nullable String label, @Nullable String label,
...@@ -1122,54 +1097,38 @@ public final class Format implements Parcelable { ...@@ -1122,54 +1097,38 @@ public final class Format implements Parcelable {
@C.RoleFlags int roleFlags, @C.RoleFlags int roleFlags,
@Nullable String language, @Nullable String language,
int accessibilityChannel) { int accessibilityChannel) {
return new Format( return new Builder()
id, .setId(id)
label, .setLabel(label)
language, .setLanguage(language)
selectionFlags, .setSelectionFlags(selectionFlags)
roleFlags, .setRoleFlags(roleFlags)
/* averageBitrate= */ bitrate, .setAverageBitrate(bitrate)
/* peakBitrate= */ bitrate, .setPeakBitrate(bitrate)
codecs, .setCodecs(codecs)
/* metadata= */ null, .setContainerMimeType(containerMimeType)
containerMimeType, .setSampleMimeType(sampleMimeType)
sampleMimeType, .setAccessibilityChannel(accessibilityChannel)
/* maxInputSize= */ NO_VALUE, .build();
/* initializationData= */ null,
/* drmInitData= */ null,
OFFSET_SAMPLE_RELATIVE,
/* width= */ NO_VALUE,
/* height= */ NO_VALUE,
/* frameRate= */ NO_VALUE,
/* rotationDegrees= */ NO_VALUE,
/* pixelWidthHeightRatio= */ NO_VALUE,
/* projectionData= */ null,
/* stereoMode= */ NO_VALUE,
/* colorInfo= */ null,
/* channelCount= */ NO_VALUE,
/* sampleRate= */ NO_VALUE,
/* pcmEncoding= */ NO_VALUE,
/* encoderDelay= */ NO_VALUE,
/* encoderPadding= */ NO_VALUE,
accessibilityChannel,
/* exoMediaCryptoType= */ null);
} }
/** @deprecated Use {@link Format.Builder}. */
@Deprecated
public static Format createTextSampleFormat( public static Format createTextSampleFormat(
@Nullable String id, @Nullable String id,
@Nullable String sampleMimeType, @Nullable String sampleMimeType,
@C.SelectionFlags int selectionFlags, @C.SelectionFlags int selectionFlags,
@Nullable String language) { @Nullable String language) {
return createTextSampleFormat( return new Builder()
id, .setId(id)
sampleMimeType, .setLanguage(language)
selectionFlags, .setSelectionFlags(selectionFlags)
language, .setSampleMimeType(sampleMimeType)
NO_VALUE, .build();
OFFSET_SAMPLE_RELATIVE,
Collections.emptyList());
} }
/** @deprecated Use {@link Format.Builder}. */
@Deprecated
public static Format createTextSampleFormat( public static Format createTextSampleFormat(
@Nullable String id, @Nullable String id,
@Nullable String sampleMimeType, @Nullable String sampleMimeType,
...@@ -1178,82 +1137,40 @@ public final class Format implements Parcelable { ...@@ -1178,82 +1137,40 @@ public final class Format implements Parcelable {
int accessibilityChannel, int accessibilityChannel,
long subsampleOffsetUs, long subsampleOffsetUs,
@Nullable List<byte[]> initializationData) { @Nullable List<byte[]> initializationData) {
return new Format( return new Builder()
id, .setId(id)
/* label= */ null, .setLanguage(language)
language, .setSelectionFlags(selectionFlags)
selectionFlags, .setSampleMimeType(sampleMimeType)
/* roleFlags= */ 0, .setInitializationData(initializationData)
/* averageBitrate= */ NO_VALUE, .setSubsampleOffsetUs(subsampleOffsetUs)
/* peakBitrate= */ NO_VALUE, .setAccessibilityChannel(accessibilityChannel)
/* codecs= */ null, .build();
/* metadata= */ null,
/* containerMimeType= */ null,
sampleMimeType,
/* maxInputSize= */ NO_VALUE,
initializationData,
/* drmInitData= */ null,
subsampleOffsetUs,
/* width= */ NO_VALUE,
/* height= */ NO_VALUE,
/* frameRate= */ NO_VALUE,
/* rotationDegrees= */ NO_VALUE,
/* pixelWidthHeightRatio= */ NO_VALUE,
/* projectionData= */ null,
/* stereoMode= */ NO_VALUE,
/* colorInfo= */ null,
/* channelCount= */ NO_VALUE,
/* sampleRate= */ NO_VALUE,
/* pcmEncoding= */ NO_VALUE,
/* encoderDelay= */ NO_VALUE,
/* encoderPadding= */ NO_VALUE,
accessibilityChannel,
/* exoMediaCryptoType= */ null);
} }
// Image. // Image.
/** @deprecated Use {@link Format.Builder}. */
@Deprecated
public static Format createImageSampleFormat( public static Format createImageSampleFormat(
@Nullable String id, @Nullable String id,
@Nullable String sampleMimeType, @Nullable String sampleMimeType,
@C.SelectionFlags int selectionFlags, @C.SelectionFlags int selectionFlags,
@Nullable List<byte[]> initializationData, @Nullable List<byte[]> initializationData,
@Nullable String language) { @Nullable String language) {
return new Format( return new Builder()
id, .setId(id)
/* label= */ null, .setLanguage(language)
language, .setSelectionFlags(selectionFlags)
selectionFlags, .setSampleMimeType(sampleMimeType)
/* roleFlags= */ 0, .setInitializationData(initializationData)
/* averageBitrate= */ NO_VALUE, .build();
/* peakBitrate= */ NO_VALUE,
/* codecs= */ null,
/* metadata=*/ null,
/* containerMimeType= */ null,
sampleMimeType,
/* maxInputSize= */ NO_VALUE,
initializationData,
/* drmInitData= */ null,
OFFSET_SAMPLE_RELATIVE,
/* width= */ NO_VALUE,
/* height= */ NO_VALUE,
/* frameRate= */ NO_VALUE,
/* rotationDegrees= */ NO_VALUE,
/* pixelWidthHeightRatio= */ NO_VALUE,
/* projectionData= */ null,
/* stereoMode= */ NO_VALUE,
/* colorInfo= */ null,
/* channelCount= */ NO_VALUE,
/* sampleRate= */ NO_VALUE,
/* pcmEncoding= */ NO_VALUE,
/* encoderDelay= */ NO_VALUE,
/* encoderPadding= */ NO_VALUE,
/* accessibilityChannel= */ NO_VALUE,
/* exoMediaCryptoType= */ null);
} }
// Generic. // Generic.
/** @deprecated Use {@link Format.Builder}. */
@Deprecated
public static Format createContainerFormat( public static Format createContainerFormat(
@Nullable String id, @Nullable String id,
@Nullable String label, @Nullable String label,
...@@ -1264,71 +1181,24 @@ public final class Format implements Parcelable { ...@@ -1264,71 +1181,24 @@ public final class Format implements Parcelable {
@C.SelectionFlags int selectionFlags, @C.SelectionFlags int selectionFlags,
@C.RoleFlags int roleFlags, @C.RoleFlags int roleFlags,
@Nullable String language) { @Nullable String language) {
return new Format( return new Builder()
id, .setId(id)
label, .setLabel(label)
language, .setLanguage(language)
selectionFlags, .setSelectionFlags(selectionFlags)
roleFlags, .setRoleFlags(roleFlags)
/* averageBitrate= */ bitrate, .setAverageBitrate(bitrate)
/* peakBitrate= */ bitrate, .setPeakBitrate(bitrate)
codecs, .setCodecs(codecs)
/* metadata= */ null, .setContainerMimeType(containerMimeType)
containerMimeType, .setSampleMimeType(sampleMimeType)
sampleMimeType, .build();
/* maxInputSize= */ NO_VALUE,
/* initializationData= */ null,
/* drmInitData= */ null,
OFFSET_SAMPLE_RELATIVE,
/* width= */ NO_VALUE,
/* height= */ NO_VALUE,
/* frameRate= */ NO_VALUE,
/* rotationDegrees= */ NO_VALUE,
/* pixelWidthHeightRatio= */ NO_VALUE,
/* projectionData= */ null,
/* stereoMode= */ NO_VALUE,
/* colorInfo= */ null,
/* channelCount= */ NO_VALUE,
/* sampleRate= */ NO_VALUE,
/* pcmEncoding= */ NO_VALUE,
/* encoderDelay= */ NO_VALUE,
/* encoderPadding= */ NO_VALUE,
/* accessibilityChannel= */ NO_VALUE,
/* exoMediaCryptoType= */ null);
} }
/** @deprecated Use {@link Format.Builder}. */
@Deprecated
public static Format createSampleFormat(@Nullable String id, @Nullable String sampleMimeType) { public static Format createSampleFormat(@Nullable String id, @Nullable String sampleMimeType) {
return new Format( return new Builder().setId(id).setSampleMimeType(sampleMimeType).build();
id,
/* label= */ null,
/* language= */ null,
/* selectionFlags= */ 0,
/* roleFlags= */ 0,
/* averageBitrate= */ NO_VALUE,
/* peakBitrate= */ NO_VALUE,
/* codecs= */ null,
/* metadata= */ null,
/* containerMimeType= */ null,
sampleMimeType,
/* maxInputSize= */ NO_VALUE,
/* initializationData= */ null,
/* drmInitData= */ null,
OFFSET_SAMPLE_RELATIVE,
/* width= */ NO_VALUE,
/* height= */ NO_VALUE,
/* frameRate= */ NO_VALUE,
/* rotationDegrees= */ NO_VALUE,
/* pixelWidthHeightRatio= */ NO_VALUE,
/* projectionData= */ null,
/* stereoMode= */ NO_VALUE,
/* colorInfo= */ null,
/* channelCount= */ NO_VALUE,
/* sampleRate= */ NO_VALUE,
/* pcmEncoding= */ NO_VALUE,
/* encoderDelay= */ NO_VALUE,
/* encoderPadding= */ NO_VALUE,
/* accessibilityChannel= */ NO_VALUE,
/* exoMediaCryptoType= */ null);
} }
/* package */ Format( /* package */ Format(
...@@ -1454,29 +1324,25 @@ public final class Format implements Parcelable { ...@@ -1454,29 +1324,25 @@ public final class Format implements Parcelable {
exoMediaCryptoType = null; exoMediaCryptoType = null;
} }
// TODO: Make public.
/** Returns a {@link Format.Builder} initialized with the values of this instance. */ /** Returns a {@link Format.Builder} initialized with the values of this instance. */
/* package */ Builder buildUpon() { public Builder buildUpon() {
return new Builder(this); return new Builder(this);
} }
// TODO: Deprecate. /** @deprecated Use {@link #buildUpon()} and {@link Builder#setMaxInputSize(int)}. */
// /** @deprecated Use {@link #buildUpon()} and {@link Builder#setMaxInputSize(int)}. */ @Deprecated
// @Deprecated
public Format copyWithMaxInputSize(int maxInputSize) { public Format copyWithMaxInputSize(int maxInputSize) {
return buildUpon().setMaxInputSize(maxInputSize).build(); return buildUpon().setMaxInputSize(maxInputSize).build();
} }
// TODO: Deprecate. /** @deprecated Use {@link #buildUpon()} and {@link Builder#setSubsampleOffsetUs(long)}. */
// /** @deprecated Use {@link #buildUpon()} and {@link Builder#setSubsampleOffsetUs(long)}. */ @Deprecated
// @Deprecated
public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) { public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) {
return buildUpon().setSubsampleOffsetUs(subsampleOffsetUs).build(); return buildUpon().setSubsampleOffsetUs(subsampleOffsetUs).build();
} }
// TODO: Deprecate. /** @deprecated Use {@link #buildUpon()} and {@link Builder#setLabel(String)} . */
// /** @deprecated Use {@link #buildUpon()} and {@link Builder#setLabel(String)} . */ @Deprecated
// @Deprecated
public Format copyWithLabel(@Nullable String label) { public Format copyWithLabel(@Nullable String label) {
return buildUpon().setLabel(label).build(); return buildUpon().setLabel(label).build();
} }
...@@ -1514,8 +1380,14 @@ public final class Format implements Parcelable { ...@@ -1514,8 +1380,14 @@ public final class Format implements Parcelable {
.build(); .build();
} }
@SuppressWarnings("ReferenceEquality") /** @deprecated Use {@link #withManifestFormatInfo(Format)}. */
@Deprecated
public Format copyWithManifestFormatInfo(Format manifestFormat) { public Format copyWithManifestFormatInfo(Format manifestFormat) {
return withManifestFormatInfo(manifestFormat);
}
@SuppressWarnings("ReferenceEquality")
public Format withManifestFormatInfo(Format manifestFormat) {
if (this == manifestFormat) { if (this == manifestFormat) {
// No need to copy from ourselves. // No need to copy from ourselves.
return this; return this;
...@@ -1581,57 +1453,47 @@ public final class Format implements Parcelable { ...@@ -1581,57 +1453,47 @@ public final class Format implements Parcelable {
.build(); .build();
} }
// TODO: Deprecate. /**
// /** * @deprecated Use {@link #buildUpon()}, {@link Builder#setEncoderDelay(int)} and {@link
// * @deprecated Use {@link #buildUpon()}, {@link Builder#setEncoderDelay(int)} and {@link * Builder#setEncoderPadding(int)}.
// * Builder#setEncoderPadding(int)}. */
// */ @Deprecated
// @Deprecated
public Format copyWithGaplessInfo(int encoderDelay, int encoderPadding) { public Format copyWithGaplessInfo(int encoderDelay, int encoderPadding) {
return buildUpon().setEncoderDelay(encoderDelay).setEncoderPadding(encoderPadding).build(); return buildUpon().setEncoderDelay(encoderDelay).setEncoderPadding(encoderPadding).build();
} }
// TODO: Deprecate. /** @deprecated Use {@link #buildUpon()} and {@link Builder#setFrameRate(float)}. */
// /** @deprecated Use {@link #buildUpon()} and {@link Builder#setFrameRate(float)}. */ @Deprecated
// @Deprecated
public Format copyWithFrameRate(float frameRate) { public Format copyWithFrameRate(float frameRate) {
return buildUpon().setFrameRate(frameRate).build(); return buildUpon().setFrameRate(frameRate).build();
} }
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setDrmInitData(DrmInitData)}. */
@Deprecated
public Format copyWithDrmInitData(@Nullable DrmInitData drmInitData) { public Format copyWithDrmInitData(@Nullable DrmInitData drmInitData) {
return copyWithAdjustments(drmInitData, metadata); return buildUpon().setDrmInitData(drmInitData).build();
} }
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setMetadata(Metadata)}. */
@Deprecated
public Format copyWithMetadata(@Nullable Metadata metadata) { public Format copyWithMetadata(@Nullable Metadata metadata) {
return copyWithAdjustments(drmInitData, metadata); return buildUpon().setMetadata(metadata).build();
} }
// TODO: Inline into HlsSampleStreamWrapper and remove. /**
@SuppressWarnings("ReferenceEquality") * @deprecated Use {@link #buildUpon()} and {@link Builder#setAverageBitrate(int)} and {@link
public Format copyWithAdjustments( * Builder#setPeakBitrate(int)}.
@Nullable DrmInitData drmInitData, @Nullable Metadata metadata) { */
if (drmInitData == this.drmInitData && metadata == this.metadata) { @Deprecated
return this;
}
return buildUpon().setDrmInitData(drmInitData).setMetadata(metadata).build();
}
// TODO: Deprecate.
// /**
// * @deprecated Use {@link #buildUpon()} and {@link Builder#setAverageBitrate(int)} and {@link
// * Builder#setPeakBitrate(int)}.
// */
// @Deprecated
public Format copyWithBitrate(int bitrate) { public Format copyWithBitrate(int bitrate) {
return buildUpon().setAverageBitrate(bitrate).setPeakBitrate(bitrate).build(); return buildUpon().setAverageBitrate(bitrate).setPeakBitrate(bitrate).build();
} }
// TODO: Deprecate. /**
// /** * @deprecated Use {@link #buildUpon()}, {@link Builder#setWidth(int)} and {@link
// * @deprecated Use {@link #buildUpon()}, {@link Builder#setWidth(int)} and {@link * Builder#setHeight(int)}.
// * Builder#setHeight(int)}. */
// */ @Deprecated
// @Deprecated
public Format copyWithVideoSize(int width, int height) { public Format copyWithVideoSize(int width, int height) {
return buildUpon().setWidth(width).setHeight(height).build(); return buildUpon().setWidth(width).setHeight(height).build();
} }
......
...@@ -32,7 +32,11 @@ import java.util.Map; ...@@ -32,7 +32,11 @@ import java.util.Map;
*/ */
public final class DataSpec { public final class DataSpec {
/** Builds {@link DataSpec} instances. */ /**
* Builds {@link DataSpec} instances.
*
* <p>Use DataSpec#buildUpon() to obtain a builder representing an existing {@link DataSpec}.
*/
public static final class Builder { public static final class Builder {
@Nullable private Uri uri; @Nullable private Uri uri;
......
...@@ -699,7 +699,8 @@ public final class PlaybackStatsListener ...@@ -699,7 +699,8 @@ public final class PlaybackStatsListener
*/ */
public void onVideoSizeChanged(EventTime eventTime, int width, int height) { public void onVideoSizeChanged(EventTime eventTime, int width, int height) {
if (currentVideoFormat != null && currentVideoFormat.height == Format.NO_VALUE) { if (currentVideoFormat != null && currentVideoFormat.height == Format.NO_VALUE) {
Format formatWithHeight = currentVideoFormat.copyWithVideoSize(width, height); Format formatWithHeight =
currentVideoFormat.buildUpon().setWidth(width).setHeight(height).build();
maybeUpdateVideoFormat(eventTime, formatWithHeight); maybeUpdateVideoFormat(eventTime, formatWithHeight);
} }
} }
......
...@@ -317,7 +317,12 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb ...@@ -317,7 +317,12 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
// Clear gapless playback metadata if the start/end points don't match the media. // Clear gapless playback metadata if the start/end points don't match the media.
int encoderDelay = startUs != 0 ? 0 : format.encoderDelay; int encoderDelay = startUs != 0 ? 0 : format.encoderDelay;
int encoderPadding = endUs != C.TIME_END_OF_SOURCE ? 0 : format.encoderPadding; int encoderPadding = endUs != C.TIME_END_OF_SOURCE ? 0 : format.encoderPadding;
formatHolder.format = format.copyWithGaplessInfo(encoderDelay, encoderPadding); formatHolder.format =
format
.buildUpon()
.setEncoderDelay(encoderDelay)
.setEncoderPadding(encoderPadding)
.build();
} }
return C.RESULT_FORMAT_READ; return C.RESULT_FORMAT_READ;
} }
......
...@@ -734,10 +734,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -734,10 +734,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
? new Metadata(icyHeaders) ? new Metadata(icyHeaders)
: metadata.copyWithAppendedEntries(icyHeaders)); : metadata.copyWithAppendedEntries(icyHeaders));
} }
// Update the track format with the bitrate from the ICY header only if it declares neither
// an average or peak bitrate of its own.
if (isAudio if (isAudio
&& trackFormat.bitrate == Format.NO_VALUE && trackFormat.averageBitrate == Format.NO_VALUE
&& trackFormat.peakBitrate == Format.NO_VALUE
&& icyHeaders.bitrate != Format.NO_VALUE) { && icyHeaders.bitrate != Format.NO_VALUE) {
trackFormat = trackFormat.copyWithBitrate(icyHeaders.bitrate); trackFormat = trackFormat.buildUpon().setAverageBitrate(icyHeaders.bitrate).build();
} }
} }
trackArray[i] = new TrackGroup(trackFormat); trackArray[i] = new TrackGroup(trackFormat);
......
...@@ -197,8 +197,8 @@ public final class ChunkExtractorWrapper implements ExtractorOutput { ...@@ -197,8 +197,8 @@ public final class ChunkExtractorWrapper implements ExtractorOutput {
@Override @Override
public void format(Format format) { public void format(Format format) {
sampleFormat = manifestFormat != null ? format.copyWithManifestFormatInfo(manifestFormat) sampleFormat =
: format; manifestFormat != null ? format.withManifestFormatInfo(manifestFormat) : format;
castNonNull(trackOutput).format(sampleFormat); castNonNull(trackOutput).format(sampleFormat);
} }
......
...@@ -98,7 +98,7 @@ public final class DashUtil { ...@@ -98,7 +98,7 @@ public final class DashUtil {
Format sampleFormat = DashUtil.loadSampleFormat(dataSource, primaryTrackType, representation); Format sampleFormat = DashUtil.loadSampleFormat(dataSource, primaryTrackType, representation);
return sampleFormat == null return sampleFormat == null
? manifestFormat.drmInitData ? manifestFormat.drmInitData
: sampleFormat.copyWithManifestFormatInfo(manifestFormat).drmInitData; : sampleFormat.withManifestFormatInfo(manifestFormat).drmInitData;
} }
/** /**
......
...@@ -284,25 +284,22 @@ import com.google.android.exoplayer2.util.ParsableByteArray; ...@@ -284,25 +284,22 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
private MetadataUtil() {} private MetadataUtil() {}
/** /** Updates a {@link Format.Builder} to include metadata from the provided sources. */
* Returns a {@link Format} that is the same as the input format but includes information from the public static void setFormatMetadata(
* specified sources of metadata.
*/
public static Format getFormatWithMetadata(
int trackType, int trackType,
Format format,
@Nullable Metadata udtaMetadata, @Nullable Metadata udtaMetadata,
@Nullable Metadata mdtaMetadata, @Nullable Metadata mdtaMetadata,
GaplessInfoHolder gaplessInfoHolder) { GaplessInfoHolder gaplessInfoHolder,
Format.Builder formatBuilder) {
if (trackType == C.TRACK_TYPE_AUDIO) { if (trackType == C.TRACK_TYPE_AUDIO) {
if (gaplessInfoHolder.hasGaplessInfo()) { if (gaplessInfoHolder.hasGaplessInfo()) {
format = formatBuilder
format.copyWithGaplessInfo( .setEncoderDelay(gaplessInfoHolder.encoderDelay)
gaplessInfoHolder.encoderDelay, gaplessInfoHolder.encoderPadding); .setEncoderPadding(gaplessInfoHolder.encoderPadding);
} }
// We assume all udta metadata is associated with the audio track. // We assume all udta metadata is associated with the audio track.
if (udtaMetadata != null) { if (udtaMetadata != null) {
format = format.copyWithMetadata(udtaMetadata); formatBuilder.setMetadata(udtaMetadata);
} }
} else if (trackType == C.TRACK_TYPE_VIDEO && mdtaMetadata != null) { } else if (trackType == C.TRACK_TYPE_VIDEO && mdtaMetadata != null) {
// Populate only metadata keys that are known to be specific to video. // Populate only metadata keys that are known to be specific to video.
...@@ -311,12 +308,11 @@ import com.google.android.exoplayer2.util.ParsableByteArray; ...@@ -311,12 +308,11 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
if (entry instanceof MdtaMetadataEntry) { if (entry instanceof MdtaMetadataEntry) {
MdtaMetadataEntry mdtaMetadataEntry = (MdtaMetadataEntry) entry; MdtaMetadataEntry mdtaMetadataEntry = (MdtaMetadataEntry) entry;
if (MDTA_KEY_ANDROID_CAPTURE_FPS.equals(mdtaMetadataEntry.key)) { if (MDTA_KEY_ANDROID_CAPTURE_FPS.equals(mdtaMetadataEntry.key)) {
format = format.copyWithMetadata(new Metadata(mdtaMetadataEntry)); formatBuilder.setMetadata(new Metadata(mdtaMetadataEntry));
} }
} }
} }
} }
return format;
} }
/** /**
......
...@@ -423,17 +423,17 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -423,17 +423,17 @@ public final class Mp4Extractor implements Extractor, SeekMap {
// Each sample has up to three bytes of overhead for the start code that replaces its length. // Each sample has up to three bytes of overhead for the start code that replaces its length.
// Allow ten source samples per output sample, like the platform extractor. // Allow ten source samples per output sample, like the platform extractor.
int maxInputSize = trackSampleTable.maximumSize + 3 * 10; int maxInputSize = trackSampleTable.maximumSize + 3 * 10;
Format format = track.format.copyWithMaxInputSize(maxInputSize); Format.Builder formatBuilder = track.format.buildUpon();
formatBuilder.setMaxInputSize(maxInputSize);
if (track.type == C.TRACK_TYPE_VIDEO if (track.type == C.TRACK_TYPE_VIDEO
&& trackDurationUs > 0 && trackDurationUs > 0
&& trackSampleTable.sampleCount > 1) { && trackSampleTable.sampleCount > 1) {
float frameRate = trackSampleTable.sampleCount / (trackDurationUs / 1000000f); float frameRate = trackSampleTable.sampleCount / (trackDurationUs / 1000000f);
format = format.copyWithFrameRate(frameRate); formatBuilder.setFrameRate(frameRate);
} }
format = MetadataUtil.setFormatMetadata(
MetadataUtil.getFormatWithMetadata( track.type, udtaMetadata, mdtaMetadata, gaplessInfoHolder, formatBuilder);
track.type, format, udtaMetadata, mdtaMetadata, gaplessInfoHolder); mp4Track.trackOutput.format(formatBuilder.build());
mp4Track.trackOutput.format(format);
if (track.type == C.TRACK_TYPE_VIDEO && firstVideoTrackIndex == C.INDEX_UNSET) { if (track.type == C.TRACK_TYPE_VIDEO && firstVideoTrackIndex == C.INDEX_UNSET) {
firstVideoTrackIndex = tracks.size(); firstVideoTrackIndex = tracks.size();
......
...@@ -568,7 +568,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -568,7 +568,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
chunkIndex < mediaChunks.size() chunkIndex < mediaChunks.size()
? mediaChunks.get(chunkIndex).trackFormat ? mediaChunks.get(chunkIndex).trackFormat
: Assertions.checkNotNull(upstreamTrackFormat); : Assertions.checkNotNull(upstreamTrackFormat);
format = format.copyWithManifestFormatInfo(trackFormat); format = format.withManifestFormatInfo(trackFormat);
} }
formatHolder.format = format; formatHolder.format = format;
} }
...@@ -1160,7 +1160,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -1160,7 +1160,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
if (i == primaryExtractorTrackIndex) { if (i == primaryExtractorTrackIndex) {
Format[] formats = new Format[chunkSourceTrackCount]; Format[] formats = new Format[chunkSourceTrackCount];
if (chunkSourceTrackCount == 1) { if (chunkSourceTrackCount == 1) {
formats[0] = sampleFormat.copyWithManifestFormatInfo(chunkSourceTrackGroup.getFormat(0)); formats[0] = sampleFormat.withManifestFormatInfo(chunkSourceTrackGroup.getFormat(0));
} else { } else {
for (int j = 0; j < chunkSourceTrackCount; j++) { for (int j = 0; j < chunkSourceTrackCount; j++) {
formats[j] = deriveFormat(chunkSourceTrackGroup.getFormat(j), sampleFormat, true); formats[j] = deriveFormat(chunkSourceTrackGroup.getFormat(j), sampleFormat, true);
...@@ -1346,6 +1346,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -1346,6 +1346,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
invalidateUpstreamFormatAdjustment(); invalidateUpstreamFormatAdjustment();
} }
@SuppressWarnings("ReferenceEquality")
@Override @Override
public Format getAdjustedUpstreamFormat(Format format) { public Format getAdjustedUpstreamFormat(Format format) {
@Nullable @Nullable
...@@ -1357,8 +1358,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -1357,8 +1358,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
drmInitData = overridingDrmInitData; drmInitData = overridingDrmInitData;
} }
} }
return super.getAdjustedUpstreamFormat( @Nullable Metadata metadata = getAdjustedMetadata(format.metadata);
format.copyWithAdjustments(drmInitData, getAdjustedMetadata(format.metadata))); if (drmInitData != format.drmInitData || metadata != format.metadata) {
format = format.buildUpon().setDrmInitData(drmInitData).setMetadata(metadata).build();
}
return super.getAdjustedUpstreamFormat(format);
} }
/** /**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment