diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java index d0a07930e0..e53db4568d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java @@ -15,21 +15,29 @@ */ package com.google.android.exoplayer2; +import android.support.annotation.IntDef; import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.util.MediaClock; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Renders media read from a {@link SampleStream}. * *

Internally, a renderer's lifecycle is managed by the owning {@link ExoPlayer}. The renderer is - * transitioned through various states as the overall playback state changes. The valid state - * transitions are shown below, annotated with the methods that are called during each transition. + * transitioned through various states as the overall playback state and enabled tracks change. The + * valid state transitions are shown below, annotated with the methods that are called during each + * transition. * *

Renderer state transitions */ public interface Renderer extends PlayerMessage.Target { + /** The renderer states. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({STATE_DISABLED, STATE_ENABLED, STATE_STARTED}) + @interface State {} /** * The renderer is disabled. */ @@ -80,8 +88,10 @@ public interface Renderer extends PlayerMessage.Target { /** * Returns the current state of the renderer. * - * @return The current state (one of the {@code STATE_*} constants). + * @return The current state. One of {@link #STATE_DISABLED}, {@link #STATE_ENABLED} and {@link + * #STATE_STARTED}. */ + @State int getState(); /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java index f45a6a11c6..bda4ad98f8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java @@ -15,41 +15,35 @@ */ package com.google.android.exoplayer2.audio; -import static com.google.android.exoplayer2.audio.Ac3Util.Ac3SyncFrameInfo.STREAM_TYPE_TYPE0; -import static com.google.android.exoplayer2.audio.Ac3Util.Ac3SyncFrameInfo.STREAM_TYPE_TYPE1; -import static com.google.android.exoplayer2.audio.Ac3Util.Ac3SyncFrameInfo.STREAM_TYPE_UNDEFINED; - +import android.support.annotation.IntDef; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.audio.Ac3Util.SyncFrameInfo.StreamType; import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; /** Utility methods for parsing Dolby TrueHD and (E-)AC3 syncframes. */ public final class Ac3Util { - /** - * Holds sample format information as presented by a syncframe header. - */ - public static final class Ac3SyncFrameInfo { + /** Holds sample format information as presented by a syncframe header. */ + public static final class SyncFrameInfo { - /** - * Undefined AC3 stream type. - */ + /** AC3 stream types. See also ETSI TS 102 366 E.1.3.1.1. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({STREAM_TYPE_UNDEFINED, STREAM_TYPE_TYPE0, STREAM_TYPE_TYPE1, STREAM_TYPE_TYPE2}) + public @interface StreamType {} + /** Undefined AC3 stream type. */ public static final int STREAM_TYPE_UNDEFINED = -1; - /** - * Type 0 AC3 stream type. See ETSI TS 102 366 E.1.3.1.1. - */ + /** Type 0 AC3 stream type. */ public static final int STREAM_TYPE_TYPE0 = 0; - /** - * Type 1 AC3 stream type. See ETSI TS 102 366 E.1.3.1.1. - */ + /** Type 1 AC3 stream type. */ public static final int STREAM_TYPE_TYPE1 = 1; - /** - * Type 2 AC3 stream type. See ETSI TS 102 366 E.1.3.1.1. - */ + /** Type 2 AC3 stream type. */ public static final int STREAM_TYPE_TYPE2 = 2; /** @@ -58,10 +52,10 @@ public final class Ac3Util { */ public final String mimeType; /** - * The type of the stream if {@link #mimeType} is {@link MimeTypes#AUDIO_E_AC3}, or - * {@link #STREAM_TYPE_UNDEFINED} otherwise. + * The type of the stream if {@link #mimeType} is {@link MimeTypes#AUDIO_E_AC3}, or {@link + * #STREAM_TYPE_UNDEFINED} otherwise. */ - public final int streamType; + public final @StreamType int streamType; /** * The audio sampling rate in Hz. */ @@ -79,8 +73,13 @@ public final class Ac3Util { */ public final int sampleCount; - private Ac3SyncFrameInfo(String mimeType, int streamType, int channelCount, int sampleRate, - int frameSize, int sampleCount) { + private SyncFrameInfo( + String mimeType, + @StreamType int streamType, + int channelCount, + int sampleRate, + int frameSize, + int sampleCount) { this.mimeType = mimeType; this.streamType = streamType; this.channelCount = channelCount; @@ -212,13 +211,13 @@ public final class Ac3Util { * @param data The data to parse, positioned at the start of the syncframe. * @return The (E-)AC-3 format data parsed from the header. */ - public static Ac3SyncFrameInfo parseAc3SyncframeInfo(ParsableBitArray data) { + public static SyncFrameInfo parseAc3SyncframeInfo(ParsableBitArray data) { int initialPosition = data.getPosition(); data.skipBits(40); boolean isEac3 = data.readBits(5) == 16; data.setPosition(initialPosition); String mimeType; - int streamType = STREAM_TYPE_UNDEFINED; + @StreamType int streamType = SyncFrameInfo.STREAM_TYPE_UNDEFINED; int sampleRate; int acmod; int frameSize; @@ -228,7 +227,20 @@ public final class Ac3Util { if (isEac3) { // Syntax from ETSI TS 102 366 V1.2.1 subsections E.1.2.1 and E.1.2.2. data.skipBits(16); // syncword - streamType = data.readBits(2); + switch (data.readBits(2)) { // strmtyp + case 0: + streamType = SyncFrameInfo.STREAM_TYPE_TYPE0; + break; + case 1: + streamType = SyncFrameInfo.STREAM_TYPE_TYPE1; + break; + case 2: + streamType = SyncFrameInfo.STREAM_TYPE_TYPE2; + break; + default: + streamType = SyncFrameInfo.STREAM_TYPE_UNDEFINED; + break; + } data.skipBits(3); // substreamid frameSize = (data.readBits(11) + 1) * 2; int fscod = data.readBits(2); @@ -257,7 +269,7 @@ public final class Ac3Util { data.skipBits(8); // compr2 } } - if (streamType == STREAM_TYPE_TYPE1 && data.readBit()) { // chanmape + if (streamType == SyncFrameInfo.STREAM_TYPE_TYPE1 && data.readBit()) { // chanmape data.skipBits(16); // chanmap } if (data.readBit()) { // mixmdate @@ -273,7 +285,7 @@ public final class Ac3Util { if (lfeon && data.readBit()) { // lfemixlevcode data.skipBits(5); // lfemixlevcod } - if (streamType == STREAM_TYPE_TYPE0) { + if (streamType == SyncFrameInfo.STREAM_TYPE_TYPE0) { if (data.readBit()) { // pgmscle data.skipBits(6); //pgmscl } @@ -375,10 +387,11 @@ public final class Ac3Util { data.skipBit(); // sourcefscod } } - if (streamType == 0 && numblkscod != 3) { + if (streamType == SyncFrameInfo.STREAM_TYPE_TYPE0 && numblkscod != 3) { data.skipBit(); // convsync } - if (streamType == 2 && (numblkscod == 3 || data.readBit())) { // blkid + if (streamType == SyncFrameInfo.STREAM_TYPE_TYPE2 + && (numblkscod == 3 || data.readBit())) { // blkid data.skipBits(6); // frmsizecod } mimeType = MimeTypes.AUDIO_E_AC3; @@ -410,8 +423,8 @@ public final class Ac3Util { lfeon = data.readBit(); channelCount = CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0); } - return new Ac3SyncFrameInfo(mimeType, streamType, channelCount, sampleRate, frameSize, - sampleCount); + return new SyncFrameInfo( + mimeType, streamType, channelCount, sampleRate, frameSize, sampleCount); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index ee0fd94ffa..2ac7078e87 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -80,6 +80,10 @@ public final class DefaultAudioSink implements AudioSink { */ private static final int BUFFER_MULTIPLICATION_FACTOR = 4; + /** {@link AudioTrack} playback states. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({PLAYSTATE_STOPPED, PLAYSTATE_PAUSED, PLAYSTATE_PLAYING}) + private @interface PlayState {} /** * @see AudioTrack#PLAYSTATE_STOPPED */ @@ -965,8 +969,7 @@ public final class DefaultAudioSink implements AudioSink { startMediaTimeState = START_NOT_SET; latencyUs = 0; resetSyncParams(); - int playState = audioTrack.getPlayState(); - if (playState == PLAYSTATE_PLAYING) { + if (audioTrack.getPlayState() == PLAYSTATE_PLAYING) { audioTrack.pause(); } // AudioTrack.release can take some time, so we call it on a background thread. @@ -1426,8 +1429,8 @@ public final class DefaultAudioSink implements AudioSink { return Math.min(endPlaybackHeadPosition, stopPlaybackHeadPosition + framesSinceStop); } - int state = audioTrack.getPlayState(); - if (state == PLAYSTATE_STOPPED) { + @PlayState int playState = audioTrack.getPlayState(); + if (playState == PLAYSTATE_STOPPED) { // The audio track hasn't been started. return 0; } @@ -1437,15 +1440,16 @@ public final class DefaultAudioSink implements AudioSink { // Work around an issue with passthrough/direct AudioTracks on platform API versions 21/22 // where the playback head position jumps back to zero on paused passthrough/direct audio // tracks. See [Internal: b/19187573]. - if (state == PLAYSTATE_PAUSED && rawPlaybackHeadPosition == 0) { + if (playState == PLAYSTATE_PAUSED && rawPlaybackHeadPosition == 0) { passthroughWorkaroundPauseOffset = lastRawPlaybackHeadPosition; } rawPlaybackHeadPosition += passthroughWorkaroundPauseOffset; } if (Util.SDK_INT <= 28) { - if (rawPlaybackHeadPosition == 0 && lastRawPlaybackHeadPosition > 0 - && state == PLAYSTATE_PLAYING) { + if (rawPlaybackHeadPosition == 0 + && lastRawPlaybackHeadPosition > 0 + && playState == PLAYSTATE_PLAYING) { // If connecting a Bluetooth audio device fails, the AudioTrack may be left in a state // where its Java API is in the playing state, but the native track is stopped. When this // happens the playback head position gets stuck at zero. In this case, return the old diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java index 91bc170205..443c60fe2e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java @@ -15,12 +15,15 @@ */ package com.google.android.exoplayer2.extractor.mkv; +import android.support.annotation.IntDef; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.util.Assertions; import java.io.EOFException; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Stack; /** @@ -28,6 +31,10 @@ import java.util.Stack; */ /* package */ final class DefaultEbmlReader implements EbmlReader { + @Retention(RetentionPolicy.SOURCE) + @IntDef({ELEMENT_STATE_READ_ID, ELEMENT_STATE_READ_CONTENT_SIZE, ELEMENT_STATE_READ_CONTENT}) + private @interface ElementState {} + private static final int ELEMENT_STATE_READ_ID = 0; private static final int ELEMENT_STATE_READ_CONTENT_SIZE = 1; private static final int ELEMENT_STATE_READ_CONTENT = 2; @@ -44,7 +51,7 @@ import java.util.Stack; private final VarintReader varintReader = new VarintReader(); private EbmlReaderOutput output; - private int elementState; + private @ElementState int elementState; private int elementId; private long elementContentSize; @@ -88,23 +95,23 @@ import java.util.Stack; elementState = ELEMENT_STATE_READ_CONTENT; } - int type = output.getElementType(elementId); + @EbmlReaderOutput.ElementType int type = output.getElementType(elementId); switch (type) { - case TYPE_MASTER: + case EbmlReaderOutput.TYPE_MASTER: long elementContentPosition = input.getPosition(); long elementEndPosition = elementContentPosition + elementContentSize; masterElementsStack.add(new MasterElement(elementId, elementEndPosition)); output.startMasterElement(elementId, elementContentPosition, elementContentSize); elementState = ELEMENT_STATE_READ_ID; return true; - case TYPE_UNSIGNED_INT: + case EbmlReaderOutput.TYPE_UNSIGNED_INT: if (elementContentSize > MAX_INTEGER_ELEMENT_SIZE_BYTES) { throw new ParserException("Invalid integer size: " + elementContentSize); } output.integerElement(elementId, readInteger(input, (int) elementContentSize)); elementState = ELEMENT_STATE_READ_ID; return true; - case TYPE_FLOAT: + case EbmlReaderOutput.TYPE_FLOAT: if (elementContentSize != VALID_FLOAT32_ELEMENT_SIZE_BYTES && elementContentSize != VALID_FLOAT64_ELEMENT_SIZE_BYTES) { throw new ParserException("Invalid float size: " + elementContentSize); @@ -112,18 +119,18 @@ import java.util.Stack; output.floatElement(elementId, readFloat(input, (int) elementContentSize)); elementState = ELEMENT_STATE_READ_ID; return true; - case TYPE_STRING: + case EbmlReaderOutput.TYPE_STRING: if (elementContentSize > Integer.MAX_VALUE) { throw new ParserException("String element size: " + elementContentSize); } output.stringElement(elementId, readString(input, (int) elementContentSize)); elementState = ELEMENT_STATE_READ_ID; return true; - case TYPE_BINARY: + case EbmlReaderOutput.TYPE_BINARY: output.binaryElement(elementId, (int) elementContentSize, input); elementState = ELEMENT_STATE_READ_ID; return true; - case TYPE_UNKNOWN: + case EbmlReaderOutput.TYPE_UNKNOWN: input.skipFully((int) elementContentSize); elementState = ELEMENT_STATE_READ_ID; break; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReader.java index dc059d2cc8..9987b3c8e6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReader.java @@ -28,31 +28,6 @@ import java.io.IOException; */ /* package */ interface EbmlReader { - /** - * Type for unknown elements. - */ - int TYPE_UNKNOWN = 0; - /** - * Type for elements that contain child elements. - */ - int TYPE_MASTER = 1; - /** - * Type for integer value elements of up to 8 bytes. - */ - int TYPE_UNSIGNED_INT = 2; - /** - * Type for string elements. - */ - int TYPE_STRING = 3; - /** - * Type for binary elements. - */ - int TYPE_BINARY = 4; - /** - * Type for IEEE floating point value elements of either 4 or 8 bytes. - */ - int TYPE_FLOAT = 5; - /** * Initializes the extractor with an {@link EbmlReaderOutput}. * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReaderOutput.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReaderOutput.java index 6c97e802b9..b1cd508c8e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReaderOutput.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReaderOutput.java @@ -15,24 +15,46 @@ */ package com.google.android.exoplayer2.extractor.mkv; +import android.support.annotation.IntDef; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.extractor.ExtractorInput; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Defines EBML element IDs/types and reacts to events. */ /* package */ interface EbmlReaderOutput { + /** EBML element types. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_UNKNOWN, TYPE_MASTER, TYPE_UNSIGNED_INT, TYPE_STRING, TYPE_BINARY, TYPE_FLOAT}) + @interface ElementType {} + /** Type for unknown elements. */ + int TYPE_UNKNOWN = 0; + /** Type for elements that contain child elements. */ + int TYPE_MASTER = 1; + /** Type for integer value elements of up to 8 bytes. */ + int TYPE_UNSIGNED_INT = 2; + /** Type for string elements. */ + int TYPE_STRING = 3; + /** Type for binary elements. */ + int TYPE_BINARY = 4; + /** Type for IEEE floating point value elements of either 4 or 8 bytes. */ + int TYPE_FLOAT = 5; + /** * Maps an element ID to a corresponding type. - *

- * If {@link EbmlReader#TYPE_UNKNOWN} is returned then the element is skipped. Note that all - * children of a skipped element are also skipped. + * + *

If {@link #TYPE_UNKNOWN} is returned then the element is skipped. Note that all children of + * a skipped element are also skipped. * * @param id The element ID to map. - * @return One of the {@code TYPE_} constants defined in {@link EbmlReader}. + * @return One of {@link #TYPE_UNKNOWN}, {@link #TYPE_MASTER}, {@link #TYPE_UNSIGNED_INT}, {@link + * #TYPE_STRING}, {@link #TYPE_BINARY} and {@link #TYPE_FLOAT}. */ + @ElementType int getElementType(int id); /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index 79537e1c24..1049554f7a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -448,100 +448,6 @@ public final class MatroskaExtractor implements Extractor { return Extractor.RESULT_CONTINUE; } - /* package */ int getElementType(int id) { - switch (id) { - case ID_EBML: - case ID_SEGMENT: - case ID_SEEK_HEAD: - case ID_SEEK: - case ID_INFO: - case ID_CLUSTER: - case ID_TRACKS: - case ID_TRACK_ENTRY: - case ID_AUDIO: - case ID_VIDEO: - case ID_CONTENT_ENCODINGS: - case ID_CONTENT_ENCODING: - case ID_CONTENT_COMPRESSION: - case ID_CONTENT_ENCRYPTION: - case ID_CONTENT_ENCRYPTION_AES_SETTINGS: - case ID_CUES: - case ID_CUE_POINT: - case ID_CUE_TRACK_POSITIONS: - case ID_BLOCK_GROUP: - case ID_PROJECTION: - case ID_COLOUR: - case ID_MASTERING_METADATA: - return EbmlReader.TYPE_MASTER; - case ID_EBML_READ_VERSION: - case ID_DOC_TYPE_READ_VERSION: - case ID_SEEK_POSITION: - case ID_TIMECODE_SCALE: - case ID_TIME_CODE: - case ID_BLOCK_DURATION: - case ID_PIXEL_WIDTH: - case ID_PIXEL_HEIGHT: - case ID_DISPLAY_WIDTH: - case ID_DISPLAY_HEIGHT: - case ID_DISPLAY_UNIT: - case ID_TRACK_NUMBER: - case ID_TRACK_TYPE: - case ID_FLAG_DEFAULT: - case ID_FLAG_FORCED: - case ID_DEFAULT_DURATION: - case ID_CODEC_DELAY: - case ID_SEEK_PRE_ROLL: - case ID_CHANNELS: - case ID_AUDIO_BIT_DEPTH: - case ID_CONTENT_ENCODING_ORDER: - case ID_CONTENT_ENCODING_SCOPE: - case ID_CONTENT_COMPRESSION_ALGORITHM: - case ID_CONTENT_ENCRYPTION_ALGORITHM: - case ID_CONTENT_ENCRYPTION_AES_SETTINGS_CIPHER_MODE: - case ID_CUE_TIME: - case ID_CUE_CLUSTER_POSITION: - case ID_REFERENCE_BLOCK: - case ID_STEREO_MODE: - case ID_COLOUR_RANGE: - case ID_COLOUR_TRANSFER: - case ID_COLOUR_PRIMARIES: - case ID_MAX_CLL: - case ID_MAX_FALL: - return EbmlReader.TYPE_UNSIGNED_INT; - case ID_DOC_TYPE: - case ID_CODEC_ID: - case ID_LANGUAGE: - return EbmlReader.TYPE_STRING; - case ID_SEEK_ID: - case ID_CONTENT_COMPRESSION_SETTINGS: - case ID_CONTENT_ENCRYPTION_KEY_ID: - case ID_SIMPLE_BLOCK: - case ID_BLOCK: - case ID_CODEC_PRIVATE: - case ID_PROJECTION_PRIVATE: - return EbmlReader.TYPE_BINARY; - case ID_DURATION: - case ID_SAMPLING_FREQUENCY: - case ID_PRIMARY_R_CHROMATICITY_X: - case ID_PRIMARY_R_CHROMATICITY_Y: - case ID_PRIMARY_G_CHROMATICITY_X: - case ID_PRIMARY_G_CHROMATICITY_Y: - case ID_PRIMARY_B_CHROMATICITY_X: - case ID_PRIMARY_B_CHROMATICITY_Y: - case ID_WHITE_POINT_CHROMATICITY_X: - case ID_WHITE_POINT_CHROMATICITY_Y: - case ID_LUMNINANCE_MAX: - case ID_LUMNINANCE_MIN: - return EbmlReader.TYPE_FLOAT; - default: - return EbmlReader.TYPE_UNKNOWN; - } - } - - /* package */ boolean isLevel1Element(int id) { - return id == ID_SEGMENT_INFO || id == ID_CLUSTER || id == ID_CUES || id == ID_TRACKS; - } - /* package */ void startMasterElement(int id, long contentPosition, long contentSize) throws ParserException { switch (id) { @@ -1501,12 +1407,98 @@ public final class MatroskaExtractor implements Extractor { @Override public int getElementType(int id) { - return MatroskaExtractor.this.getElementType(id); + switch (id) { + case ID_EBML: + case ID_SEGMENT: + case ID_SEEK_HEAD: + case ID_SEEK: + case ID_INFO: + case ID_CLUSTER: + case ID_TRACKS: + case ID_TRACK_ENTRY: + case ID_AUDIO: + case ID_VIDEO: + case ID_CONTENT_ENCODINGS: + case ID_CONTENT_ENCODING: + case ID_CONTENT_COMPRESSION: + case ID_CONTENT_ENCRYPTION: + case ID_CONTENT_ENCRYPTION_AES_SETTINGS: + case ID_CUES: + case ID_CUE_POINT: + case ID_CUE_TRACK_POSITIONS: + case ID_BLOCK_GROUP: + case ID_PROJECTION: + case ID_COLOUR: + case ID_MASTERING_METADATA: + return TYPE_MASTER; + case ID_EBML_READ_VERSION: + case ID_DOC_TYPE_READ_VERSION: + case ID_SEEK_POSITION: + case ID_TIMECODE_SCALE: + case ID_TIME_CODE: + case ID_BLOCK_DURATION: + case ID_PIXEL_WIDTH: + case ID_PIXEL_HEIGHT: + case ID_DISPLAY_WIDTH: + case ID_DISPLAY_HEIGHT: + case ID_DISPLAY_UNIT: + case ID_TRACK_NUMBER: + case ID_TRACK_TYPE: + case ID_FLAG_DEFAULT: + case ID_FLAG_FORCED: + case ID_DEFAULT_DURATION: + case ID_CODEC_DELAY: + case ID_SEEK_PRE_ROLL: + case ID_CHANNELS: + case ID_AUDIO_BIT_DEPTH: + case ID_CONTENT_ENCODING_ORDER: + case ID_CONTENT_ENCODING_SCOPE: + case ID_CONTENT_COMPRESSION_ALGORITHM: + case ID_CONTENT_ENCRYPTION_ALGORITHM: + case ID_CONTENT_ENCRYPTION_AES_SETTINGS_CIPHER_MODE: + case ID_CUE_TIME: + case ID_CUE_CLUSTER_POSITION: + case ID_REFERENCE_BLOCK: + case ID_STEREO_MODE: + case ID_COLOUR_RANGE: + case ID_COLOUR_TRANSFER: + case ID_COLOUR_PRIMARIES: + case ID_MAX_CLL: + case ID_MAX_FALL: + return TYPE_UNSIGNED_INT; + case ID_DOC_TYPE: + case ID_CODEC_ID: + case ID_LANGUAGE: + return TYPE_STRING; + case ID_SEEK_ID: + case ID_CONTENT_COMPRESSION_SETTINGS: + case ID_CONTENT_ENCRYPTION_KEY_ID: + case ID_SIMPLE_BLOCK: + case ID_BLOCK: + case ID_CODEC_PRIVATE: + case ID_PROJECTION_PRIVATE: + return TYPE_BINARY; + case ID_DURATION: + case ID_SAMPLING_FREQUENCY: + case ID_PRIMARY_R_CHROMATICITY_X: + case ID_PRIMARY_R_CHROMATICITY_Y: + case ID_PRIMARY_G_CHROMATICITY_X: + case ID_PRIMARY_G_CHROMATICITY_Y: + case ID_PRIMARY_B_CHROMATICITY_X: + case ID_PRIMARY_B_CHROMATICITY_Y: + case ID_WHITE_POINT_CHROMATICITY_X: + case ID_WHITE_POINT_CHROMATICITY_Y: + case ID_LUMNINANCE_MAX: + case ID_LUMNINANCE_MIN: + return TYPE_FLOAT; + default: + return TYPE_UNKNOWN; + } } @Override public boolean isLevel1Element(int id) { - return MatroskaExtractor.this.isLevel1Element(id); + return id == ID_SEGMENT_INFO || id == ID_CLUSTER || id == ID_CUES || id == ID_TRACKS; } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java index 8383bfb8d2..4141f83370 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java @@ -19,6 +19,7 @@ import android.support.annotation.IntDef; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.audio.Ac3Util; +import com.google.android.exoplayer2.audio.Ac3Util.SyncFrameInfo; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; @@ -187,7 +188,7 @@ public final class Ac3Reader implements ElementaryStreamReader { @SuppressWarnings("ReferenceEquality") private void parseHeader() { headerScratchBits.setPosition(0); - Ac3Util.Ac3SyncFrameInfo frameInfo = Ac3Util.parseAc3SyncframeInfo(headerScratchBits); + SyncFrameInfo frameInfo = Ac3Util.parseAc3SyncframeInfo(headerScratchBits); if (format == null || frameInfo.channelCount != format.channelCount || frameInfo.sampleRate != format.sampleRate || frameInfo.mimeType != format.sampleMimeType) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java index c771188e3b..9c410355c7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java @@ -524,8 +524,8 @@ import java.util.Arrays; } @Override - public int onLoadError(ExtractingLoadable loadable, long elapsedRealtimeMs, - long loadDurationMs, IOException error) { + public @Loader.RetryAction int onLoadError( + ExtractingLoadable loadable, long elapsedRealtimeMs, long loadDurationMs, IOException error) { boolean isErrorFatal = isLoadableExceptionFatal(error); eventDispatcher.loadError( loadable.dataSpec, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java index 36e5d910c4..7c141bcf61 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java @@ -221,8 +221,8 @@ import java.util.Arrays; } @Override - public int onLoadError(SourceLoadable loadable, long elapsedRealtimeMs, long loadDurationMs, - IOException error) { + public @Loader.RetryAction int onLoadError( + SourceLoadable loadable, long elapsedRealtimeMs, long loadDurationMs, IOException error) { errorCount++; boolean cancel = treatLoadErrorsAsEndOfStream && errorCount >= minLoadableRetryCount; eventDispatcher.loadError( diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java index 7096c84c5e..ac2834c0ad 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java @@ -409,8 +409,8 @@ public class ChunkSampleStream implements SampleStream, S } @Override - public int onLoadError(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs, - IOException error) { + public @Loader.RetryAction int onLoadError( + Chunk loadable, long elapsedRealtimeMs, long loadDurationMs, IOException error) { long bytesLoaded = loadable.bytesLoaded(); boolean isMediaChunk = isMediaChunk(loadable); int lastChunkIndex = mediaChunks.size() - 1; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java index 5d120990fc..0604694831 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.trackselection; import android.content.Context; +import android.support.annotation.IntDef; import android.util.SparseArray; import android.util.SparseBooleanArray; import com.google.android.exoplayer2.C; @@ -27,6 +28,8 @@ import com.google.android.exoplayer2.RendererConfiguration; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -81,22 +84,25 @@ public abstract class MappingTrackSelector extends TrackSelector { */ public static final class MappedTrackInfo { - /** - * The renderer does not have any associated tracks. - */ + /** Levels of renderer support. Higher numerical values indicate higher levels of support. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + RENDERER_SUPPORT_NO_TRACKS, + RENDERER_SUPPORT_UNSUPPORTED_TRACKS, + RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS, + RENDERER_SUPPORT_PLAYABLE_TRACKS + }) + @interface RendererSupport {} + /** The renderer does not have any associated tracks. */ public static final int RENDERER_SUPPORT_NO_TRACKS = 0; - /** - * The renderer has associated tracks, but all are of unsupported types. - */ + /** The renderer has associated tracks, but all are of unsupported types. */ public static final int RENDERER_SUPPORT_UNSUPPORTED_TRACKS = 1; /** * The renderer has associated tracks and at least one is of a supported type, but all of the * tracks whose types are supported exceed the renderer's capabilities. */ public static final int RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS = 2; - /** - * The renderer has associated tracks and can play at least one of them. - */ + /** The renderer has associated tracks and can play at least one of them. */ public static final int RENDERER_SUPPORT_PLAYABLE_TRACKS = 3; /** @@ -145,11 +151,11 @@ public abstract class MappingTrackSelector extends TrackSelector { * Returns the extent to which a renderer can play the tracks in the track groups mapped to it. * * @param rendererIndex The renderer index. - * @return One of {@link #RENDERER_SUPPORT_PLAYABLE_TRACKS}, - * {@link #RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS}, - * {@link #RENDERER_SUPPORT_UNSUPPORTED_TRACKS} and {@link #RENDERER_SUPPORT_NO_TRACKS}. + * @return One of {@link #RENDERER_SUPPORT_PLAYABLE_TRACKS}, {@link + * #RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS}, {@link + * #RENDERER_SUPPORT_UNSUPPORTED_TRACKS} and {@link #RENDERER_SUPPORT_NO_TRACKS}. */ - public int getRendererSupport(int rendererIndex) { + public @RendererSupport int getRendererSupport(int rendererIndex) { int bestRendererSupport = RENDERER_SUPPORT_NO_TRACKS; int[][] rendererFormatSupport = formatSupport[rendererIndex]; for (int i = 0; i < rendererFormatSupport.length; i++) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java index a118f10784..009d224c6f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java @@ -20,12 +20,15 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.SystemClock; +import android.support.annotation.IntDef; import android.support.annotation.Nullable; import android.util.Log; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.concurrent.ExecutorService; /** @@ -104,20 +107,20 @@ public final class Loader implements LoaderErrorThrower { /** * Called when a load encounters an error. - *

- * Note: There is guaranteed to be a memory barrier between {@link Loadable#load()} exiting and - * this callback being called. + * + *

Note: There is guaranteed to be a memory barrier between {@link Loadable#load()} exiting + * and this callback being called. * * @param loadable The loadable whose load has encountered an error. * @param elapsedRealtimeMs {@link SystemClock#elapsedRealtime} when the error occurred. * @param loadDurationMs The duration of the load up to the point at which the error occurred. * @param error The load error. - * @return The desired retry action. One of {@link Loader#RETRY}, - * {@link Loader#RETRY_RESET_ERROR_COUNT}, {@link Loader#DONT_RETRY} and - * {@link Loader#DONT_RETRY_FATAL}. + * @return The desired retry action. One of {@link Loader#RETRY}, {@link + * Loader#RETRY_RESET_ERROR_COUNT}, {@link Loader#DONT_RETRY} and {@link + * Loader#DONT_RETRY_FATAL}. */ + @RetryAction int onLoadError(T loadable, long elapsedRealtimeMs, long loadDurationMs, IOException error); - } /** @@ -132,6 +135,11 @@ public final class Loader implements LoaderErrorThrower { } + /** Actions that can be taken in response to a load error. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({RETRY, RETRY_RESET_ERROR_COUNT, DONT_RETRY, DONT_RETRY_FATAL}) + public @interface RetryAction {} + public static final int RETRY = 0; public static final int RETRY_RESET_ERROR_COUNT = 1; public static final int DONT_RETRY = 2; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 3d08a2d117..338fce1953 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -375,7 +375,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { // We only need to update the codec if the surface has changed. if (this.surface != surface) { this.surface = surface; - int state = getState(); + @State int state = getState(); if (state == STATE_ENABLED || state == STATE_STARTED) { MediaCodec codec = getCodec(); if (Util.SDK_INT >= 23 && codec != null && surface != null diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReaderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReaderTest.java index 2fec5c7cab..16e9c5872b 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReaderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReaderTest.java @@ -174,22 +174,22 @@ public class DefaultEbmlReaderTest { private final List events = new ArrayList<>(); @Override - public int getElementType(int id) { + public @ElementType int getElementType(int id) { switch (id) { case ID_EBML: case ID_SEGMENT: - return EbmlReader.TYPE_MASTER; + return TYPE_MASTER; case ID_EBML_READ_VERSION: case ID_DOC_TYPE_READ_VERSION: - return EbmlReader.TYPE_UNSIGNED_INT; + return TYPE_UNSIGNED_INT; case ID_DOC_TYPE: - return EbmlReader.TYPE_STRING; + return TYPE_STRING; case ID_SIMPLE_BLOCK: - return EbmlReader.TYPE_BINARY; + return TYPE_BINARY; case ID_DURATION: - return EbmlReader.TYPE_FLOAT; + return TYPE_FLOAT; default: - return EbmlReader.TYPE_UNKNOWN; + return TYPE_UNKNOWN; } } diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index 8be4242bb2..a07f7a3d83 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -692,8 +692,12 @@ public final class DashMediaSource extends BaseMediaSource { } } - /* package */ int onManifestLoadError(ParsingLoadable loadable, - long elapsedRealtimeMs, long loadDurationMs, IOException error) { + /* package */ @Loader.RetryAction + int onManifestLoadError( + ParsingLoadable loadable, + long elapsedRealtimeMs, + long loadDurationMs, + IOException error) { boolean isFatal = error instanceof ParserException; manifestEventDispatcher.loadError( loadable.dataSpec, @@ -717,8 +721,12 @@ public final class DashMediaSource extends BaseMediaSource { onUtcTimestampResolved(loadable.getResult() - elapsedRealtimeMs); } - /* package */ int onUtcTimestampLoadError(ParsingLoadable loadable, long elapsedRealtimeMs, - long loadDurationMs, IOException error) { + /* package */ @Loader.RetryAction + int onUtcTimestampLoadError( + ParsingLoadable loadable, + long elapsedRealtimeMs, + long loadDurationMs, + IOException error) { manifestEventDispatcher.loadError( loadable.dataSpec, loadable.type, @@ -1114,8 +1122,11 @@ public final class DashMediaSource extends BaseMediaSource { } @Override - public int onLoadError(ParsingLoadable loadable, - long elapsedRealtimeMs, long loadDurationMs, IOException error) { + public @Loader.RetryAction int onLoadError( + ParsingLoadable loadable, + long elapsedRealtimeMs, + long loadDurationMs, + IOException error) { return onManifestLoadError(loadable, elapsedRealtimeMs, loadDurationMs, error); } @@ -1136,8 +1147,11 @@ public final class DashMediaSource extends BaseMediaSource { } @Override - public int onLoadError(ParsingLoadable loadable, long elapsedRealtimeMs, - long loadDurationMs, IOException error) { + public @Loader.RetryAction int onLoadError( + ParsingLoadable loadable, + long elapsedRealtimeMs, + long loadDurationMs, + IOException error) { return onUtcTimestampLoadError(loadable, elapsedRealtimeMs, loadDurationMs, error); } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java index f027ba5b05..44267485d6 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.source.hls; import android.os.Handler; +import android.support.annotation.IntDef; import android.util.Log; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; @@ -43,6 +44,8 @@ import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; @@ -73,6 +76,10 @@ import java.util.Arrays; private static final String TAG = "HlsSampleStreamWrapper"; + @Retention(RetentionPolicy.SOURCE) + @IntDef({PRIMARY_TYPE_NONE, PRIMARY_TYPE_TEXT, PRIMARY_TYPE_AUDIO, PRIMARY_TYPE_VIDEO}) + private @interface PrimaryTrackType {} + private static final int PRIMARY_TYPE_NONE = 0; private static final int PRIMARY_TYPE_TEXT = 1; private static final int PRIMARY_TYPE_AUDIO = 2; @@ -583,8 +590,8 @@ import java.util.Arrays; } @Override - public int onLoadError(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs, - IOException error) { + public @Loader.RetryAction int onLoadError( + Chunk loadable, long elapsedRealtimeMs, long loadDurationMs, IOException error) { long bytesLoaded = loadable.bytesLoaded(); boolean isMediaChunk = isMediaChunk(loadable); boolean cancelable = !isMediaChunk || bytesLoaded == 0; @@ -826,12 +833,12 @@ import java.util.Arrays; private void buildTracks() { // Iterate through the extractor tracks to discover the "primary" track type, and the index // of the single track of this type. - int primaryExtractorTrackType = PRIMARY_TYPE_NONE; + @PrimaryTrackType int primaryExtractorTrackType = PRIMARY_TYPE_NONE; int primaryExtractorTrackIndex = C.INDEX_UNSET; int extractorTrackCount = sampleQueues.length; for (int i = 0; i < extractorTrackCount; i++) { String sampleMimeType = sampleQueues[i].getUpstreamFormat().sampleMimeType; - int trackType; + @PrimaryTrackType int trackType; if (MimeTypes.isVideo(sampleMimeType)) { trackType = PRIMARY_TYPE_VIDEO; } else if (MimeTypes.isAudio(sampleMimeType)) { diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java index 2e565c322a..0b3b518c56 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java @@ -320,8 +320,11 @@ public final class HlsPlaylistTracker implements Loader.Callback loadable, long elapsedRealtimeMs, - long loadDurationMs, IOException error) { + public @Loader.RetryAction int onLoadError( + ParsingLoadable loadable, + long elapsedRealtimeMs, + long loadDurationMs, + IOException error) { boolean isFatal = error instanceof ParserException; eventDispatcher.loadError(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded(), error, isFatal); @@ -554,8 +557,11 @@ public final class HlsPlaylistTracker implements Loader.Callback loadable, long elapsedRealtimeMs, - long loadDurationMs, IOException error) { + public @Loader.RetryAction int onLoadError( + ParsingLoadable loadable, + long elapsedRealtimeMs, + long loadDurationMs, + IOException error) { boolean isFatal = error instanceof ParserException; eventDispatcher.loadError(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded(), error, isFatal); diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java index 6b891361a1..d07cfd116b 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -518,8 +518,11 @@ public final class SsMediaSource extends BaseMediaSource } @Override - public int onLoadError(ParsingLoadable loadable, long elapsedRealtimeMs, - long loadDurationMs, IOException error) { + public @Loader.RetryAction int onLoadError( + ParsingLoadable loadable, + long elapsedRealtimeMs, + long loadDurationMs, + IOException error) { boolean isFatal = error instanceof ParserException; manifestEventDispatcher.loadError( loadable.dataSpec,