mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
commit
71f72c5953
68 changed files with 1183 additions and 475 deletions
|
|
@ -1,5 +1,24 @@
|
||||||
# Release notes #
|
# Release notes #
|
||||||
|
|
||||||
|
### 2.9.3 ###
|
||||||
|
|
||||||
|
* Captions: Support PNG subtitles in SMPTE-TT
|
||||||
|
([#1583](https://github.com/google/ExoPlayer/issues/1583)).
|
||||||
|
* MPEG-TS: Use random access indicators to minimize the need for
|
||||||
|
`FLAG_ALLOW_NON_IDR_KEYFRAMES`.
|
||||||
|
* Downloading: Reduce time taken to remove downloads
|
||||||
|
([#5136](https://github.com/google/ExoPlayer/issues/5136)).
|
||||||
|
* MP3:
|
||||||
|
* Use the true bitrate for constant-bitrate MP3 seeking.
|
||||||
|
* Fix issue where streams would play twice on some Samsung devices
|
||||||
|
([#4519](https://github.com/google/ExoPlayer/issues/4519)).
|
||||||
|
* Fix regression where some audio formats were incorrectly marked as being
|
||||||
|
unplayable due to under-reporting of platform decoder capabilities
|
||||||
|
([#5145](https://github.com/google/ExoPlayer/issues/5145)).
|
||||||
|
* Fix decode-only frame skipping on Nvidia Shield TV devices.
|
||||||
|
* Workaround for MiTV (dangal) issue when swapping output surface
|
||||||
|
([#5169](https://github.com/google/ExoPlayer/issues/5169)).
|
||||||
|
|
||||||
### 2.9.2 ###
|
### 2.9.2 ###
|
||||||
|
|
||||||
* HLS:
|
* HLS:
|
||||||
|
|
@ -47,10 +66,10 @@
|
||||||
* DASH: Parse ProgramInformation element if present in the manifest.
|
* DASH: Parse ProgramInformation element if present in the manifest.
|
||||||
* HLS:
|
* HLS:
|
||||||
* Add constructor to `DefaultHlsExtractorFactory` for adding TS payload
|
* Add constructor to `DefaultHlsExtractorFactory` for adding TS payload
|
||||||
reader factory flags.
|
reader factory flags
|
||||||
|
([#4861](https://github.com/google/ExoPlayer/issues/4861)).
|
||||||
* Fix bug in segment sniffing
|
* Fix bug in segment sniffing
|
||||||
([#5039](https://github.com/google/ExoPlayer/issues/5039)).
|
([#5039](https://github.com/google/ExoPlayer/issues/5039)).
|
||||||
([#4861](https://github.com/google/ExoPlayer/issues/4861)).
|
|
||||||
* SubRip: Add support for alignment tags, and remove tags from the displayed
|
* SubRip: Add support for alignment tags, and remove tags from the displayed
|
||||||
captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)).
|
captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)).
|
||||||
* Fix issue with blind seeking to windows with non-zero offset in a
|
* Fix issue with blind seeking to windows with non-zero offset in a
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
project.ext {
|
project.ext {
|
||||||
// ExoPlayer version and version code.
|
// ExoPlayer version and version code.
|
||||||
releaseVersion = '2.9.2'
|
releaseVersion = '2.9.3'
|
||||||
releaseVersionCode = 2009002
|
releaseVersionCode = 2009003
|
||||||
// Important: ExoPlayer specifies a minSdkVersion of 14 because various
|
// Important: ExoPlayer specifies a minSdkVersion of 14 because various
|
||||||
// components provided by the library may be of use on older devices.
|
// components provided by the library may be of use on older devices.
|
||||||
// However, please note that the core media playback functionality provided
|
// However, please note that the core media playback functionality provided
|
||||||
|
|
|
||||||
|
|
@ -283,20 +283,29 @@ public final class CastPlayer extends BasePlayer {
|
||||||
// Player implementation.
|
// Player implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Nullable
|
||||||
public AudioComponent getAudioComponent() {
|
public AudioComponent getAudioComponent() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Nullable
|
||||||
public VideoComponent getVideoComponent() {
|
public VideoComponent getVideoComponent() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Nullable
|
||||||
public TextComponent getTextComponent() {
|
public TextComponent getTextComponent() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public MetadataComponent getMetadataComponent() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Looper getApplicationLooper() {
|
public Looper getApplicationLooper() {
|
||||||
return Looper.getMainLooper();
|
return Looper.getMainLooper();
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,12 @@ public final class ImaAdsMediaSource extends BaseMediaSource implements SourceIn
|
||||||
adUiViewGroup, eventHandler, eventListener);
|
adUiViewGroup, eventHandler, eventListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Object getTag() {
|
||||||
|
return adsMediaSource.getTag();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSourceInternal(
|
public void prepareSourceInternal(
|
||||||
final ExoPlayer player,
|
final ExoPlayer player,
|
||||||
|
|
|
||||||
|
|
@ -64,13 +64,6 @@ public final class TimelineQueueEditor
|
||||||
* {@link MediaSessionConnector}.
|
* {@link MediaSessionConnector}.
|
||||||
*/
|
*/
|
||||||
public interface QueueDataAdapter {
|
public interface QueueDataAdapter {
|
||||||
/**
|
|
||||||
* Gets the {@link MediaDescriptionCompat} for a {@code position}.
|
|
||||||
*
|
|
||||||
* @param position The position in the queue for which to provide a description.
|
|
||||||
* @return A {@link MediaDescriptionCompat}.
|
|
||||||
*/
|
|
||||||
MediaDescriptionCompat getMediaDescription(int position);
|
|
||||||
/**
|
/**
|
||||||
* Adds a {@link MediaDescriptionCompat} at the given {@code position}.
|
* Adds a {@link MediaDescriptionCompat} at the given {@code position}.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -144,20 +144,29 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Nullable
|
||||||
public AudioComponent getAudioComponent() {
|
public AudioComponent getAudioComponent() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Nullable
|
||||||
public VideoComponent getVideoComponent() {
|
public VideoComponent getVideoComponent() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Nullable
|
||||||
public TextComponent getTextComponent() {
|
public TextComponent getTextComponent() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public MetadataComponent getMetadataComponent() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Looper getPlaybackLooper() {
|
public Looper getPlaybackLooper() {
|
||||||
return internalPlayer.getPlaybackLooper();
|
return internalPlayer.getPlaybackLooper();
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,11 @@ public final class ExoPlayerLibraryInfo {
|
||||||
|
|
||||||
/** The version of the library expressed as a string, for example "1.2.3". */
|
/** The version of the library expressed as a string, for example "1.2.3". */
|
||||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
|
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
|
||||||
public static final String VERSION = "2.9.2";
|
public static final String VERSION = "2.9.3";
|
||||||
|
|
||||||
/** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */
|
/** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */
|
||||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
||||||
public static final String VERSION_SLASHY = "ExoPlayerLib/2.9.2";
|
public static final String VERSION_SLASHY = "ExoPlayerLib/2.9.3";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The version of the library expressed as an integer, for example 1002003.
|
* The version of the library expressed as an integer, for example 1002003.
|
||||||
|
|
@ -43,7 +43,7 @@ public final class ExoPlayerLibraryInfo {
|
||||||
* integer version 123045006 (123-045-006).
|
* integer version 123045006 (123-045-006).
|
||||||
*/
|
*/
|
||||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
||||||
public static final int VERSION_INT = 2009002;
|
public static final int VERSION_INT = 2009003;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
|
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import com.google.android.exoplayer2.C.VideoScalingMode;
|
||||||
import com.google.android.exoplayer2.audio.AudioAttributes;
|
import com.google.android.exoplayer2.audio.AudioAttributes;
|
||||||
import com.google.android.exoplayer2.audio.AudioListener;
|
import com.google.android.exoplayer2.audio.AudioListener;
|
||||||
import com.google.android.exoplayer2.audio.AuxEffectInfo;
|
import com.google.android.exoplayer2.audio.AuxEffectInfo;
|
||||||
|
import com.google.android.exoplayer2.metadata.MetadataOutput;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
import com.google.android.exoplayer2.text.TextOutput;
|
import com.google.android.exoplayer2.text.TextOutput;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||||
|
|
@ -299,6 +300,24 @@ public interface Player {
|
||||||
void removeTextOutput(TextOutput listener);
|
void removeTextOutput(TextOutput listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The metadata component of a {@link Player}. */
|
||||||
|
interface MetadataComponent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a {@link MetadataOutput} to receive metadata.
|
||||||
|
*
|
||||||
|
* @param output The output to register.
|
||||||
|
*/
|
||||||
|
void addMetadataOutput(MetadataOutput output);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a {@link MetadataOutput}.
|
||||||
|
*
|
||||||
|
* @param output The output to remove.
|
||||||
|
*/
|
||||||
|
void removeMetadataOutput(MetadataOutput output);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listener of changes in player state. All methods have no-op default implementations to allow
|
* Listener of changes in player state. All methods have no-op default implementations to allow
|
||||||
* selective overrides.
|
* selective overrides.
|
||||||
|
|
@ -533,6 +552,12 @@ public interface Player {
|
||||||
@Nullable
|
@Nullable
|
||||||
TextComponent getTextComponent();
|
TextComponent getTextComponent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the component of this player for metadata output, or null if metadata is not supported.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
MetadataComponent getMetadataComponent();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the {@link Looper} associated with the application thread that's used to access the
|
* Returns the {@link Looper} associated with the application thread that's used to access the
|
||||||
* player and on which player events are received.
|
* player and on which player events are received.
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,11 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
*/
|
*/
|
||||||
@TargetApi(16)
|
@TargetApi(16)
|
||||||
public class SimpleExoPlayer extends BasePlayer
|
public class SimpleExoPlayer extends BasePlayer
|
||||||
implements ExoPlayer, Player.AudioComponent, Player.VideoComponent, Player.TextComponent {
|
implements ExoPlayer,
|
||||||
|
Player.AudioComponent,
|
||||||
|
Player.VideoComponent,
|
||||||
|
Player.TextComponent,
|
||||||
|
Player.MetadataComponent {
|
||||||
|
|
||||||
/** @deprecated Use {@link com.google.android.exoplayer2.video.VideoListener}. */
|
/** @deprecated Use {@link com.google.android.exoplayer2.video.VideoListener}. */
|
||||||
@Deprecated
|
@Deprecated
|
||||||
|
|
@ -243,20 +247,29 @@ public class SimpleExoPlayer extends BasePlayer
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Nullable
|
||||||
public AudioComponent getAudioComponent() {
|
public AudioComponent getAudioComponent() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Nullable
|
||||||
public VideoComponent getVideoComponent() {
|
public VideoComponent getVideoComponent() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Nullable
|
||||||
public TextComponent getTextComponent() {
|
public TextComponent getTextComponent() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public MetadataComponent getMetadataComponent() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the video scaling mode.
|
* Sets the video scaling mode.
|
||||||
*
|
*
|
||||||
|
|
@ -713,20 +726,12 @@ public class SimpleExoPlayer extends BasePlayer
|
||||||
removeTextOutput(output);
|
removeTextOutput(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Adds a {@link MetadataOutput} to receive metadata.
|
|
||||||
*
|
|
||||||
* @param listener The output to register.
|
|
||||||
*/
|
|
||||||
public void addMetadataOutput(MetadataOutput listener) {
|
public void addMetadataOutput(MetadataOutput listener) {
|
||||||
metadataOutputs.add(listener);
|
metadataOutputs.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Removes a {@link MetadataOutput}.
|
|
||||||
*
|
|
||||||
* @param listener The output to remove.
|
|
||||||
*/
|
|
||||||
public void removeMetadataOutput(MetadataOutput listener) {
|
public void removeMetadataOutput(MetadataOutput listener) {
|
||||||
metadataOutputs.remove(listener);
|
metadataOutputs.remove(listener);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,8 @@ import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listener of audio {@link Renderer} events.
|
* Listener of audio {@link Renderer} events. All methods have no-op default implementations to
|
||||||
|
* allow selective overrides.
|
||||||
*/
|
*/
|
||||||
public interface AudioRendererEventListener {
|
public interface AudioRendererEventListener {
|
||||||
|
|
||||||
|
|
@ -35,14 +36,14 @@ public interface AudioRendererEventListener {
|
||||||
* @param counters {@link DecoderCounters} that will be updated by the renderer for as long as it
|
* @param counters {@link DecoderCounters} that will be updated by the renderer for as long as it
|
||||||
* remains enabled.
|
* remains enabled.
|
||||||
*/
|
*/
|
||||||
void onAudioEnabled(DecoderCounters counters);
|
default void onAudioEnabled(DecoderCounters counters) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the audio session is set.
|
* Called when the audio session is set.
|
||||||
*
|
*
|
||||||
* @param audioSessionId The audio session id.
|
* @param audioSessionId The audio session id.
|
||||||
*/
|
*/
|
||||||
void onAudioSessionId(int audioSessionId);
|
default void onAudioSessionId(int audioSessionId) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a decoder is created.
|
* Called when a decoder is created.
|
||||||
|
|
@ -52,15 +53,15 @@ public interface AudioRendererEventListener {
|
||||||
* finished.
|
* finished.
|
||||||
* @param initializationDurationMs The time taken to initialize the decoder in milliseconds.
|
* @param initializationDurationMs The time taken to initialize the decoder in milliseconds.
|
||||||
*/
|
*/
|
||||||
void onAudioDecoderInitialized(String decoderName, long initializedTimestampMs,
|
default void onAudioDecoderInitialized(
|
||||||
long initializationDurationMs);
|
String decoderName, long initializedTimestampMs, long initializationDurationMs) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the format of the media being consumed by the renderer changes.
|
* Called when the format of the media being consumed by the renderer changes.
|
||||||
*
|
*
|
||||||
* @param format The new format.
|
* @param format The new format.
|
||||||
*/
|
*/
|
||||||
void onAudioInputFormatChanged(Format format);
|
default void onAudioInputFormatChanged(Format format) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when an {@link AudioSink} underrun occurs.
|
* Called when an {@link AudioSink} underrun occurs.
|
||||||
|
|
@ -71,14 +72,15 @@ public interface AudioRendererEventListener {
|
||||||
* as the buffered media can have a variable bitrate so the duration may be unknown.
|
* as the buffered media can have a variable bitrate so the duration may be unknown.
|
||||||
* @param elapsedSinceLastFeedMs The time since the {@link AudioSink} was last fed data.
|
* @param elapsedSinceLastFeedMs The time since the {@link AudioSink} was last fed data.
|
||||||
*/
|
*/
|
||||||
void onAudioSinkUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs);
|
default void onAudioSinkUnderrun(
|
||||||
|
int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the renderer is disabled.
|
* Called when the renderer is disabled.
|
||||||
*
|
*
|
||||||
* @param counters {@link DecoderCounters} that were updated by the renderer.
|
* @param counters {@link DecoderCounters} that were updated by the renderer.
|
||||||
*/
|
*/
|
||||||
void onAudioDisabled(DecoderCounters counters);
|
default void onAudioDisabled(DecoderCounters counters) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatches events to a {@link AudioRendererEventListener}.
|
* Dispatches events to a {@link AudioRendererEventListener}.
|
||||||
|
|
|
||||||
|
|
@ -34,16 +34,26 @@ public final class MpegAudioHeader {
|
||||||
private static final String[] MIME_TYPE_BY_LAYER =
|
private static final String[] MIME_TYPE_BY_LAYER =
|
||||||
new String[] {MimeTypes.AUDIO_MPEG_L1, MimeTypes.AUDIO_MPEG_L2, MimeTypes.AUDIO_MPEG};
|
new String[] {MimeTypes.AUDIO_MPEG_L1, MimeTypes.AUDIO_MPEG_L2, MimeTypes.AUDIO_MPEG};
|
||||||
private static final int[] SAMPLING_RATE_V1 = {44100, 48000, 32000};
|
private static final int[] SAMPLING_RATE_V1 = {44100, 48000, 32000};
|
||||||
private static final int[] BITRATE_V1_L1 =
|
private static final int[] BITRATE_V1_L1 = {
|
||||||
{32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448};
|
32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000, 384000,
|
||||||
private static final int[] BITRATE_V2_L1 =
|
416000, 448000
|
||||||
{32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256};
|
};
|
||||||
private static final int[] BITRATE_V1_L2 =
|
private static final int[] BITRATE_V2_L1 = {
|
||||||
{32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384};
|
32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000, 192000,
|
||||||
private static final int[] BITRATE_V1_L3 =
|
224000, 256000
|
||||||
{32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320};
|
};
|
||||||
private static final int[] BITRATE_V2 =
|
private static final int[] BITRATE_V1_L2 = {
|
||||||
{8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160};
|
32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000,
|
||||||
|
320000, 384000
|
||||||
|
};
|
||||||
|
private static final int[] BITRATE_V1_L3 = {
|
||||||
|
32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000,
|
||||||
|
320000
|
||||||
|
};
|
||||||
|
private static final int[] BITRATE_V2 = {
|
||||||
|
8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000,
|
||||||
|
160000
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the size of the frame associated with {@code header}, or {@link C#LENGTH_UNSET} if it
|
* Returns the size of the frame associated with {@code header}, or {@link C#LENGTH_UNSET} if it
|
||||||
|
|
@ -89,7 +99,7 @@ public final class MpegAudioHeader {
|
||||||
if (layer == 3) {
|
if (layer == 3) {
|
||||||
// Layer I (layer == 3)
|
// Layer I (layer == 3)
|
||||||
bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1];
|
bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1];
|
||||||
return (12000 * bitrate / samplingRate + padding) * 4;
|
return (12 * bitrate / samplingRate + padding) * 4;
|
||||||
} else {
|
} else {
|
||||||
// Layer II (layer == 2) or III (layer == 1)
|
// Layer II (layer == 2) or III (layer == 1)
|
||||||
if (version == 3) {
|
if (version == 3) {
|
||||||
|
|
@ -102,10 +112,10 @@ public final class MpegAudioHeader {
|
||||||
|
|
||||||
if (version == 3) {
|
if (version == 3) {
|
||||||
// Version 1
|
// Version 1
|
||||||
return 144000 * bitrate / samplingRate + padding;
|
return 144 * bitrate / samplingRate + padding;
|
||||||
} else {
|
} else {
|
||||||
// Version 2 or 2.5
|
// Version 2 or 2.5
|
||||||
return (layer == 1 ? 72000 : 144000) * bitrate / samplingRate + padding;
|
return (layer == 1 ? 72 : 144) * bitrate / samplingRate + padding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,7 +169,7 @@ public final class MpegAudioHeader {
|
||||||
if (layer == 3) {
|
if (layer == 3) {
|
||||||
// Layer I (layer == 3)
|
// Layer I (layer == 3)
|
||||||
bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1];
|
bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1];
|
||||||
frameSize = (12000 * bitrate / sampleRate + padding) * 4;
|
frameSize = (12 * bitrate / sampleRate + padding) * 4;
|
||||||
samplesPerFrame = 384;
|
samplesPerFrame = 384;
|
||||||
} else {
|
} else {
|
||||||
// Layer II (layer == 2) or III (layer == 1)
|
// Layer II (layer == 2) or III (layer == 1)
|
||||||
|
|
@ -167,19 +177,22 @@ public final class MpegAudioHeader {
|
||||||
// Version 1
|
// Version 1
|
||||||
bitrate = layer == 2 ? BITRATE_V1_L2[bitrateIndex - 1] : BITRATE_V1_L3[bitrateIndex - 1];
|
bitrate = layer == 2 ? BITRATE_V1_L2[bitrateIndex - 1] : BITRATE_V1_L3[bitrateIndex - 1];
|
||||||
samplesPerFrame = 1152;
|
samplesPerFrame = 1152;
|
||||||
frameSize = 144000 * bitrate / sampleRate + padding;
|
frameSize = 144 * bitrate / sampleRate + padding;
|
||||||
} else {
|
} else {
|
||||||
// Version 2 or 2.5.
|
// Version 2 or 2.5.
|
||||||
bitrate = BITRATE_V2[bitrateIndex - 1];
|
bitrate = BITRATE_V2[bitrateIndex - 1];
|
||||||
samplesPerFrame = layer == 1 ? 576 : 1152;
|
samplesPerFrame = layer == 1 ? 576 : 1152;
|
||||||
frameSize = (layer == 1 ? 72000 : 144000) * bitrate / sampleRate + padding;
|
frameSize = (layer == 1 ? 72 : 144) * bitrate / sampleRate + padding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate the bitrate in the same way Mp3Extractor calculates sample timestamps so that
|
||||||
|
// seeking to a given timestamp and playing from the start up to that timestamp give the same
|
||||||
|
// results for CBR streams. See also [internal: b/120390268].
|
||||||
|
bitrate = 8 * frameSize * sampleRate / samplesPerFrame;
|
||||||
String mimeType = MIME_TYPE_BY_LAYER[3 - layer];
|
String mimeType = MIME_TYPE_BY_LAYER[3 - layer];
|
||||||
int channels = ((headerData >> 6) & 3) == 3 ? 1 : 2;
|
int channels = ((headerData >> 6) & 3) == 3 ? 1 : 2;
|
||||||
header.setValues(version, mimeType, frameSize, sampleRate, channels, bitrate * 1000,
|
header.setValues(version, mimeType, frameSize, sampleRate, channels, bitrate, samplesPerFrame);
|
||||||
samplesPerFrame);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -198,8 +211,14 @@ public final class MpegAudioHeader {
|
||||||
/** Number of samples stored in the frame. */
|
/** Number of samples stored in the frame. */
|
||||||
public int samplesPerFrame;
|
public int samplesPerFrame;
|
||||||
|
|
||||||
private void setValues(int version, String mimeType, int frameSize, int sampleRate, int channels,
|
private void setValues(
|
||||||
int bitrate, int samplesPerFrame) {
|
int version,
|
||||||
|
String mimeType,
|
||||||
|
int frameSize,
|
||||||
|
int sampleRate,
|
||||||
|
int channels,
|
||||||
|
int bitrate,
|
||||||
|
int samplesPerFrame) {
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.mimeType = mimeType;
|
this.mimeType = mimeType;
|
||||||
this.frameSize = frameSize;
|
this.frameSize = frameSize;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.extractor.ts;
|
package com.google.android.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.extractor.ts.TsPayloadReader.FLAG_DATA_ALIGNMENT_INDICATOR;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.audio.Ac3Util;
|
import com.google.android.exoplayer2.audio.Ac3Util;
|
||||||
import com.google.android.exoplayer2.extractor.Extractor;
|
import com.google.android.exoplayer2.extractor.Extractor;
|
||||||
|
|
@ -140,7 +142,7 @@ public final class Ac3Extractor implements Extractor {
|
||||||
|
|
||||||
if (!startedPacket) {
|
if (!startedPacket) {
|
||||||
// Pass data to the reader as though it's contained within a single infinitely long packet.
|
// Pass data to the reader as though it's contained within a single infinitely long packet.
|
||||||
reader.packetStarted(firstSampleTimestampUs, true);
|
reader.packetStarted(firstSampleTimestampUs, FLAG_DATA_ALIGNMENT_INDICATOR);
|
||||||
startedPacket = true;
|
startedPacket = true;
|
||||||
}
|
}
|
||||||
// TODO: Make it possible for the reader to consume the dataSource directly, so that it becomes
|
// TODO: Make it possible for the reader to consume the dataSource directly, so that it becomes
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ public final class Ac3Reader implements ElementaryStreamReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
|
public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) {
|
||||||
timeUs = pesTimeUs;
|
timeUs = pesTimeUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.extractor.ts;
|
package com.google.android.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.extractor.ts.TsPayloadReader.FLAG_DATA_ALIGNMENT_INDICATOR;
|
||||||
|
|
||||||
import android.support.annotation.IntDef;
|
import android.support.annotation.IntDef;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
|
|
@ -202,7 +204,7 @@ public final class AdtsExtractor implements Extractor {
|
||||||
|
|
||||||
if (!startedPacket) {
|
if (!startedPacket) {
|
||||||
// Pass data to the reader as though it's contained within a single infinitely long packet.
|
// Pass data to the reader as though it's contained within a single infinitely long packet.
|
||||||
reader.packetStarted(firstSampleTimestampUs, true);
|
reader.packetStarted(firstSampleTimestampUs, FLAG_DATA_ALIGNMENT_INDICATOR);
|
||||||
startedPacket = true;
|
startedPacket = true;
|
||||||
}
|
}
|
||||||
// TODO: Make it possible for reader to consume the dataSource directly, so that it becomes
|
// TODO: Make it possible for reader to consume the dataSource directly, so that it becomes
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@ public final class AdtsReader implements ElementaryStreamReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
|
public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) {
|
||||||
timeUs = pesTimeUs;
|
timeUs = pesTimeUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ public final class DtsReader implements ElementaryStreamReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
|
public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) {
|
||||||
timeUs = pesTimeUs;
|
timeUs = pesTimeUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.extractor.ts;
|
package com.google.android.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.extractor.ts.TsPayloadReader.FLAG_DATA_ALIGNMENT_INDICATOR;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
|
|
@ -73,8 +75,8 @@ public final class DvbSubtitleReader implements ElementaryStreamReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
|
public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) {
|
||||||
if (!dataAlignmentIndicator) {
|
if ((flags & FLAG_DATA_ALIGNMENT_INDICATOR) == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
writingSample = true;
|
writingSample = true;
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,9 @@ public interface ElementaryStreamReader {
|
||||||
* Called when a packet starts.
|
* Called when a packet starts.
|
||||||
*
|
*
|
||||||
* @param pesTimeUs The timestamp associated with the packet.
|
* @param pesTimeUs The timestamp associated with the packet.
|
||||||
* @param dataAlignmentIndicator The data alignment indicator associated with the packet.
|
* @param flags See {@link TsPayloadReader.Flags}.
|
||||||
*/
|
*/
|
||||||
void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator);
|
void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consumes (possibly partial) data from the current packet.
|
* Consumes (possibly partial) data from the current packet.
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,8 @@ public final class H262Reader implements ElementaryStreamReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
|
public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) {
|
||||||
|
// TODO (Internal b/32267012): Consider using random access indicator.
|
||||||
this.pesTimeUs = pesTimeUs;
|
this.pesTimeUs = pesTimeUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.extractor.ts;
|
package com.google.android.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.extractor.ts.TsPayloadReader.FLAG_RANDOM_ACCESS_INDICATOR;
|
||||||
|
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
|
|
@ -56,9 +58,12 @@ public final class H264Reader implements ElementaryStreamReader {
|
||||||
// State that should not be reset on seek.
|
// State that should not be reset on seek.
|
||||||
private boolean hasOutputFormat;
|
private boolean hasOutputFormat;
|
||||||
|
|
||||||
// Per packet state that gets reset at the start of each packet.
|
// Per PES packet state that gets reset at the start of each PES packet.
|
||||||
private long pesTimeUs;
|
private long pesTimeUs;
|
||||||
|
|
||||||
|
// State inherited from the TS packet header.
|
||||||
|
private boolean randomAccessIndicator;
|
||||||
|
|
||||||
// Scratch variables to avoid allocations.
|
// Scratch variables to avoid allocations.
|
||||||
private final ParsableByteArray seiWrapper;
|
private final ParsableByteArray seiWrapper;
|
||||||
|
|
||||||
|
|
@ -88,6 +93,7 @@ public final class H264Reader implements ElementaryStreamReader {
|
||||||
sei.reset();
|
sei.reset();
|
||||||
sampleReader.reset();
|
sampleReader.reset();
|
||||||
totalBytesWritten = 0;
|
totalBytesWritten = 0;
|
||||||
|
randomAccessIndicator = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -100,8 +106,9 @@ public final class H264Reader implements ElementaryStreamReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
|
public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) {
|
||||||
this.pesTimeUs = pesTimeUs;
|
this.pesTimeUs = pesTimeUs;
|
||||||
|
randomAccessIndicator |= (flags & FLAG_RANDOM_ACCESS_INDICATOR) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -220,12 +227,17 @@ public final class H264Reader implements ElementaryStreamReader {
|
||||||
seiWrapper.setPosition(4); // NAL prefix and nal_unit() header.
|
seiWrapper.setPosition(4); // NAL prefix and nal_unit() header.
|
||||||
seiReader.consume(pesTimeUs, seiWrapper);
|
seiReader.consume(pesTimeUs, seiWrapper);
|
||||||
}
|
}
|
||||||
sampleReader.endNalUnit(position, offset);
|
boolean sampleIsKeyFrame =
|
||||||
|
sampleReader.endNalUnit(position, offset, hasOutputFormat, randomAccessIndicator);
|
||||||
|
if (sampleIsKeyFrame) {
|
||||||
|
// This is either an IDR frame or the first I-frame since the random access indicator, so mark
|
||||||
|
// it as a keyframe. Clear the flag so that subsequent non-IDR I-frames are not marked as
|
||||||
|
// keyframes until we see another random access indicator.
|
||||||
|
randomAccessIndicator = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Consumes a stream of NAL units and outputs samples. */
|
||||||
* Consumes a stream of NAL units and outputs samples.
|
|
||||||
*/
|
|
||||||
private static final class SampleReader {
|
private static final class SampleReader {
|
||||||
|
|
||||||
private static final int DEFAULT_BUFFER_SIZE = 128;
|
private static final int DEFAULT_BUFFER_SIZE = 128;
|
||||||
|
|
@ -430,11 +442,12 @@ public final class H264Reader implements ElementaryStreamReader {
|
||||||
isFilling = false;
|
isFilling = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void endNalUnit(long position, int offset) {
|
public boolean endNalUnit(
|
||||||
|
long position, int offset, boolean hasOutputFormat, boolean randomAccessIndicator) {
|
||||||
if (nalUnitType == NAL_UNIT_TYPE_AUD
|
if (nalUnitType == NAL_UNIT_TYPE_AUD
|
||||||
|| (detectAccessUnits && sliceHeader.isFirstVclNalUnitOfPicture(previousSliceHeader))) {
|
|| (detectAccessUnits && sliceHeader.isFirstVclNalUnitOfPicture(previousSliceHeader))) {
|
||||||
// If the NAL unit ending is the start of a new sample, output the previous one.
|
// If the NAL unit ending is the start of a new sample, output the previous one.
|
||||||
if (readingSample) {
|
if (hasOutputFormat && readingSample) {
|
||||||
int nalUnitLength = (int) (position - nalUnitStartPosition);
|
int nalUnitLength = (int) (position - nalUnitStartPosition);
|
||||||
outputSample(offset + nalUnitLength);
|
outputSample(offset + nalUnitLength);
|
||||||
}
|
}
|
||||||
|
|
@ -443,8 +456,12 @@ public final class H264Reader implements ElementaryStreamReader {
|
||||||
sampleIsKeyframe = false;
|
sampleIsKeyframe = false;
|
||||||
readingSample = true;
|
readingSample = true;
|
||||||
}
|
}
|
||||||
sampleIsKeyframe |= nalUnitType == NAL_UNIT_TYPE_IDR || (allowNonIdrKeyframes
|
boolean treatIFrameAsKeyframe =
|
||||||
&& nalUnitType == NAL_UNIT_TYPE_NON_IDR && sliceHeader.isISlice());
|
allowNonIdrKeyframes ? sliceHeader.isISlice() : randomAccessIndicator;
|
||||||
|
sampleIsKeyframe |=
|
||||||
|
nalUnitType == NAL_UNIT_TYPE_IDR
|
||||||
|
|| (treatIFrameAsKeyframe && nalUnitType == NAL_UNIT_TYPE_NON_IDR);
|
||||||
|
return sampleIsKeyframe;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void outputSample(int offset) {
|
private void outputSample(int offset) {
|
||||||
|
|
@ -486,10 +503,21 @@ public final class H264Reader implements ElementaryStreamReader {
|
||||||
hasSliceType = true;
|
hasSliceType = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAll(SpsData spsData, int nalRefIdc, int sliceType, int frameNum,
|
public void setAll(
|
||||||
int picParameterSetId, boolean fieldPicFlag, boolean bottomFieldFlagPresent,
|
SpsData spsData,
|
||||||
boolean bottomFieldFlag, boolean idrPicFlag, int idrPicId, int picOrderCntLsb,
|
int nalRefIdc,
|
||||||
int deltaPicOrderCntBottom, int deltaPicOrderCnt0, int deltaPicOrderCnt1) {
|
int sliceType,
|
||||||
|
int frameNum,
|
||||||
|
int picParameterSetId,
|
||||||
|
boolean fieldPicFlag,
|
||||||
|
boolean bottomFieldFlagPresent,
|
||||||
|
boolean bottomFieldFlag,
|
||||||
|
boolean idrPicFlag,
|
||||||
|
int idrPicId,
|
||||||
|
int picOrderCntLsb,
|
||||||
|
int deltaPicOrderCntBottom,
|
||||||
|
int deltaPicOrderCnt0,
|
||||||
|
int deltaPicOrderCnt1) {
|
||||||
this.spsData = spsData;
|
this.spsData = spsData;
|
||||||
this.nalRefIdc = nalRefIdc;
|
this.nalRefIdc = nalRefIdc;
|
||||||
this.sliceType = sliceType;
|
this.sliceType = sliceType;
|
||||||
|
|
@ -514,23 +542,26 @@ public final class H264Reader implements ElementaryStreamReader {
|
||||||
|
|
||||||
private boolean isFirstVclNalUnitOfPicture(SliceHeaderData other) {
|
private boolean isFirstVclNalUnitOfPicture(SliceHeaderData other) {
|
||||||
// See ISO 14496-10 subsection 7.4.1.2.4.
|
// See ISO 14496-10 subsection 7.4.1.2.4.
|
||||||
return isComplete && (!other.isComplete || frameNum != other.frameNum
|
return isComplete
|
||||||
|| picParameterSetId != other.picParameterSetId || fieldPicFlag != other.fieldPicFlag
|
&& (!other.isComplete
|
||||||
|| (bottomFieldFlagPresent && other.bottomFieldFlagPresent
|
|| frameNum != other.frameNum
|
||||||
&& bottomFieldFlag != other.bottomFieldFlag)
|
|| picParameterSetId != other.picParameterSetId
|
||||||
|| (nalRefIdc != other.nalRefIdc && (nalRefIdc == 0 || other.nalRefIdc == 0))
|
|| fieldPicFlag != other.fieldPicFlag
|
||||||
|| (spsData.picOrderCountType == 0 && other.spsData.picOrderCountType == 0
|
|| (bottomFieldFlagPresent
|
||||||
&& (picOrderCntLsb != other.picOrderCntLsb
|
&& other.bottomFieldFlagPresent
|
||||||
|| deltaPicOrderCntBottom != other.deltaPicOrderCntBottom))
|
&& bottomFieldFlag != other.bottomFieldFlag)
|
||||||
|| (spsData.picOrderCountType == 1 && other.spsData.picOrderCountType == 1
|
|| (nalRefIdc != other.nalRefIdc && (nalRefIdc == 0 || other.nalRefIdc == 0))
|
||||||
&& (deltaPicOrderCnt0 != other.deltaPicOrderCnt0
|
|| (spsData.picOrderCountType == 0
|
||||||
|| deltaPicOrderCnt1 != other.deltaPicOrderCnt1))
|
&& other.spsData.picOrderCountType == 0
|
||||||
|| idrPicFlag != other.idrPicFlag
|
&& (picOrderCntLsb != other.picOrderCntLsb
|
||||||
|| (idrPicFlag && other.idrPicFlag && idrPicId != other.idrPicId));
|
|| deltaPicOrderCntBottom != other.deltaPicOrderCntBottom))
|
||||||
|
|| (spsData.picOrderCountType == 1
|
||||||
|
&& other.spsData.picOrderCountType == 1
|
||||||
|
&& (deltaPicOrderCnt0 != other.deltaPicOrderCnt0
|
||||||
|
|| deltaPicOrderCnt1 != other.deltaPicOrderCnt1))
|
||||||
|
|| idrPicFlag != other.idrPicFlag
|
||||||
|
|| (idrPicFlag && other.idrPicFlag && idrPicId != other.idrPicId));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,8 @@ public final class H265Reader implements ElementaryStreamReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
|
public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) {
|
||||||
|
// TODO (Internal b/32267012): Consider using random access indicator.
|
||||||
this.pesTimeUs = pesTimeUs;
|
this.pesTimeUs = pesTimeUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.extractor.ts;
|
package com.google.android.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.extractor.ts.TsPayloadReader.FLAG_DATA_ALIGNMENT_INDICATOR;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
|
|
@ -63,8 +65,8 @@ public final class Id3Reader implements ElementaryStreamReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
|
public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) {
|
||||||
if (!dataAlignmentIndicator) {
|
if ((flags & FLAG_DATA_ALIGNMENT_INDICATOR) == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
writingSample = true;
|
writingSample = true;
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ public final class LatmReader implements ElementaryStreamReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
|
public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) {
|
||||||
timeUs = pesTimeUs;
|
timeUs = pesTimeUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ public final class MpegAudioReader implements ElementaryStreamReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
|
public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) {
|
||||||
timeUs = pesTimeUs;
|
timeUs = pesTimeUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,9 +78,8 @@ public final class PesReader implements TsPayloadReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void consume(ParsableByteArray data, boolean payloadUnitStartIndicator)
|
public final void consume(ParsableByteArray data, @Flags int flags) throws ParserException {
|
||||||
throws ParserException {
|
if ((flags & FLAG_PAYLOAD_UNIT_START_INDICATOR) != 0) {
|
||||||
if (payloadUnitStartIndicator) {
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case STATE_FINDING_HEADER:
|
case STATE_FINDING_HEADER:
|
||||||
case STATE_READING_HEADER:
|
case STATE_READING_HEADER:
|
||||||
|
|
@ -122,7 +121,8 @@ public final class PesReader implements TsPayloadReader {
|
||||||
if (continueRead(data, pesScratch.data, readLength)
|
if (continueRead(data, pesScratch.data, readLength)
|
||||||
&& continueRead(data, null, extendedHeaderLength)) {
|
&& continueRead(data, null, extendedHeaderLength)) {
|
||||||
parseHeaderExtension();
|
parseHeaderExtension();
|
||||||
reader.packetStarted(timeUs, dataAlignmentIndicator);
|
flags |= dataAlignmentIndicator ? FLAG_DATA_ALIGNMENT_INDICATOR : 0;
|
||||||
|
reader.packetStarted(timeUs, flags);
|
||||||
setState(STATE_READING_BODY);
|
setState(STATE_READING_BODY);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -343,7 +343,7 @@ public final class PsExtractor implements Extractor {
|
||||||
data.readBytes(pesScratch.data, 0, extendedHeaderLength);
|
data.readBytes(pesScratch.data, 0, extendedHeaderLength);
|
||||||
pesScratch.setPosition(0);
|
pesScratch.setPosition(0);
|
||||||
parseHeaderExtension();
|
parseHeaderExtension();
|
||||||
pesPayloadReader.packetStarted(timeUs, true);
|
pesPayloadReader.packetStarted(timeUs, TsPayloadReader.FLAG_DATA_ALIGNMENT_INDICATOR);
|
||||||
pesPayloadReader.consume(data);
|
pesPayloadReader.consume(data);
|
||||||
// We always have complete PES packets with program stream.
|
// We always have complete PES packets with program stream.
|
||||||
pesPayloadReader.packetFinished();
|
pesPayloadReader.packetFinished();
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,8 @@ public final class SectionReader implements TsPayloadReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) {
|
public void consume(ParsableByteArray data, @Flags int flags) {
|
||||||
|
boolean payloadUnitStartIndicator = (flags & FLAG_PAYLOAD_UNIT_START_INDICATOR) != 0;
|
||||||
int payloadStartPosition = C.POSITION_UNSET;
|
int payloadStartPosition = C.POSITION_UNSET;
|
||||||
if (payloadUnitStartIndicator) {
|
if (payloadUnitStartIndicator) {
|
||||||
int payloadStartOffset = data.readUnsignedByte();
|
int payloadStartOffset = data.readUnsignedByte();
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.extractor.ts;
|
package com.google.android.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.extractor.ts.TsPayloadReader.FLAG_PAYLOAD_UNIT_START_INDICATOR;
|
||||||
|
|
||||||
import android.support.annotation.IntDef;
|
import android.support.annotation.IntDef;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.util.SparseBooleanArray;
|
import android.util.SparseBooleanArray;
|
||||||
|
|
@ -279,6 +281,8 @@ public final class TsExtractor implements Extractor {
|
||||||
return RESULT_CONTINUE;
|
return RESULT_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TsPayloadReader.Flags int packetHeaderFlags = 0;
|
||||||
|
|
||||||
// Note: See ISO/IEC 13818-1, section 2.4.3.2 for details of the header format.
|
// Note: See ISO/IEC 13818-1, section 2.4.3.2 for details of the header format.
|
||||||
int tsPacketHeader = tsPacketBuffer.readInt();
|
int tsPacketHeader = tsPacketBuffer.readInt();
|
||||||
if ((tsPacketHeader & 0x800000) != 0) { // transport_error_indicator
|
if ((tsPacketHeader & 0x800000) != 0) { // transport_error_indicator
|
||||||
|
|
@ -286,7 +290,7 @@ public final class TsExtractor implements Extractor {
|
||||||
tsPacketBuffer.setPosition(endOfPacket);
|
tsPacketBuffer.setPosition(endOfPacket);
|
||||||
return RESULT_CONTINUE;
|
return RESULT_CONTINUE;
|
||||||
}
|
}
|
||||||
boolean payloadUnitStartIndicator = (tsPacketHeader & 0x400000) != 0;
|
packetHeaderFlags |= (tsPacketHeader & 0x400000) != 0 ? FLAG_PAYLOAD_UNIT_START_INDICATOR : 0;
|
||||||
// Ignoring transport_priority (tsPacketHeader & 0x200000)
|
// Ignoring transport_priority (tsPacketHeader & 0x200000)
|
||||||
int pid = (tsPacketHeader & 0x1FFF00) >> 8;
|
int pid = (tsPacketHeader & 0x1FFF00) >> 8;
|
||||||
// Ignoring transport_scrambling_control (tsPacketHeader & 0xC0)
|
// Ignoring transport_scrambling_control (tsPacketHeader & 0xC0)
|
||||||
|
|
@ -317,14 +321,20 @@ public final class TsExtractor implements Extractor {
|
||||||
// Skip the adaptation field.
|
// Skip the adaptation field.
|
||||||
if (adaptationFieldExists) {
|
if (adaptationFieldExists) {
|
||||||
int adaptationFieldLength = tsPacketBuffer.readUnsignedByte();
|
int adaptationFieldLength = tsPacketBuffer.readUnsignedByte();
|
||||||
tsPacketBuffer.skipBytes(adaptationFieldLength);
|
int adaptationFieldFlags = tsPacketBuffer.readUnsignedByte();
|
||||||
|
|
||||||
|
packetHeaderFlags |=
|
||||||
|
(adaptationFieldFlags & 0x40) != 0 // random_access_indicator.
|
||||||
|
? TsPayloadReader.FLAG_RANDOM_ACCESS_INDICATOR
|
||||||
|
: 0;
|
||||||
|
tsPacketBuffer.skipBytes(adaptationFieldLength - 1 /* flags */);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the payload.
|
// Read the payload.
|
||||||
boolean wereTracksEnded = tracksEnded;
|
boolean wereTracksEnded = tracksEnded;
|
||||||
if (shouldConsumePacketPayload(pid)) {
|
if (shouldConsumePacketPayload(pid)) {
|
||||||
tsPacketBuffer.setLimit(endOfPacket);
|
tsPacketBuffer.setLimit(endOfPacket);
|
||||||
payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator);
|
payloadReader.consume(tsPacketBuffer, packetHeaderFlags);
|
||||||
tsPacketBuffer.setLimit(limit);
|
tsPacketBuffer.setLimit(limit);
|
||||||
}
|
}
|
||||||
if (mode != MODE_HLS && !wereTracksEnded && tracksEnded && inputLength != C.LENGTH_UNSET) {
|
if (mode != MODE_HLS && !wereTracksEnded && tracksEnded && inputLength != C.LENGTH_UNSET) {
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,16 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.extractor.ts;
|
package com.google.android.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
|
import android.support.annotation.IntDef;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import com.google.android.exoplayer2.ParserException;
|
import com.google.android.exoplayer2.ParserException;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
import com.google.android.exoplayer2.util.TimestampAdjuster;
|
import com.google.android.exoplayer2.util.TimestampAdjuster;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
@ -174,6 +178,29 @@ public interface TsPayloadReader {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contextual flags indicating the presence of indicators in the TS packet or PES packet headers.
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef(
|
||||||
|
flag = true,
|
||||||
|
value = {
|
||||||
|
FLAG_PAYLOAD_UNIT_START_INDICATOR,
|
||||||
|
FLAG_RANDOM_ACCESS_INDICATOR,
|
||||||
|
FLAG_DATA_ALIGNMENT_INDICATOR
|
||||||
|
})
|
||||||
|
@interface Flags {}
|
||||||
|
|
||||||
|
/** Indicates the presence of the payload_unit_start_indicator in the TS packet header. */
|
||||||
|
int FLAG_PAYLOAD_UNIT_START_INDICATOR = 1;
|
||||||
|
/**
|
||||||
|
* Indicates the presence of the random_access_indicator in the TS packet header adaptation field.
|
||||||
|
*/
|
||||||
|
int FLAG_RANDOM_ACCESS_INDICATOR = 1 << 1;
|
||||||
|
/** Indicates the presence of the data_alignment_indicator in the PES header. */
|
||||||
|
int FLAG_DATA_ALIGNMENT_INDICATOR = 1 << 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the payload reader.
|
* Initializes the payload reader.
|
||||||
*
|
*
|
||||||
|
|
@ -187,10 +214,10 @@ public interface TsPayloadReader {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies the reader that a seek has occurred.
|
* Notifies the reader that a seek has occurred.
|
||||||
* <p>
|
*
|
||||||
* Following a call to this method, the data passed to the next invocation of
|
* <p>Following a call to this method, the data passed to the next invocation of {@link #consume}
|
||||||
* {@link #consume(ParsableByteArray, boolean)} will not be a continuation of the data that was
|
* will not be a continuation of the data that was previously passed. Hence the reader should
|
||||||
* previously passed. Hence the reader should reset any internal state.
|
* reset any internal state.
|
||||||
*/
|
*/
|
||||||
void seek();
|
void seek();
|
||||||
|
|
||||||
|
|
@ -198,9 +225,8 @@ public interface TsPayloadReader {
|
||||||
* Consumes the payload of a TS packet.
|
* Consumes the payload of a TS packet.
|
||||||
*
|
*
|
||||||
* @param data The TS packet. The position will be set to the start of the payload.
|
* @param data The TS packet. The position will be set to the start of the payload.
|
||||||
* @param payloadUnitStartIndicator Whether payloadUnitStartIndicator was set on the TS packet.
|
* @param flags See {@link Flags}.
|
||||||
* @throws ParserException If the payload could not be parsed.
|
* @throws ParserException If the payload could not be parsed.
|
||||||
*/
|
*/
|
||||||
void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) throws ParserException;
|
void consume(ParsableByteArray data, @Flags int flags) throws ParserException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -248,9 +248,15 @@ public final class MediaCodecInfo {
|
||||||
// If we don't know any better, we assume that the profile and level are supported.
|
// If we don't know any better, we assume that the profile and level are supported.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
int profile = codecProfileAndLevel.first;
|
||||||
|
int level = codecProfileAndLevel.second;
|
||||||
|
if (!isVideo && profile != CodecProfileLevel.AACObjectXHE) {
|
||||||
|
// Some devices/builds under-report audio capabilities, so assume support except for xHE-AAC
|
||||||
|
// which is not widely supported. See https://github.com/google/ExoPlayer/issues/5145.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
for (CodecProfileLevel capabilities : getProfileLevels()) {
|
for (CodecProfileLevel capabilities : getProfileLevels()) {
|
||||||
if (capabilities.profile == codecProfileAndLevel.first
|
if (capabilities.profile == profile && capabilities.level >= level) {
|
||||||
&& capabilities.level >= codecProfileAndLevel.second) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1622,7 +1622,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
*/
|
*/
|
||||||
private static boolean codecNeedsEosFlushWorkaround(String name) {
|
private static boolean codecNeedsEosFlushWorkaround(String name) {
|
||||||
return (Util.SDK_INT <= 23 && "OMX.google.vorbis.decoder".equals(name))
|
return (Util.SDK_INT <= 23 && "OMX.google.vorbis.decoder".equals(name))
|
||||||
|| (Util.SDK_INT <= 19 && "hb2000".equals(Util.DEVICE)
|
|| (Util.SDK_INT <= 19
|
||||||
|
&& ("hb2000".equals(Util.DEVICE) || "stvm8".equals(Util.DEVICE))
|
||||||
&& ("OMX.amlogic.avc.decoder.awesome".equals(name)
|
&& ("OMX.amlogic.avc.decoder.awesome".equals(name)
|
||||||
|| "OMX.amlogic.avc.decoder.awesome.secure".equals(name)));
|
|| "OMX.amlogic.avc.decoder.awesome.secure".equals(name)));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -318,7 +318,21 @@ public final class MediaCodecUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Work around https://github.com/google/ExoPlayer/issues/4519.
|
// Work around https://github.com/google/ExoPlayer/issues/4519.
|
||||||
if ("OMX.SEC.mp3.dec".equals(name) && "SM-T530".equals(Util.MODEL)) {
|
if ("OMX.SEC.mp3.dec".equals(name)
|
||||||
|
&& (Util.MODEL.startsWith("GT-I9152")
|
||||||
|
|| Util.MODEL.startsWith("GT-I9515")
|
||||||
|
|| Util.MODEL.startsWith("GT-P5220")
|
||||||
|
|| Util.MODEL.startsWith("GT-S7580")
|
||||||
|
|| Util.MODEL.startsWith("SM-G350")
|
||||||
|
|| Util.MODEL.startsWith("SM-G386")
|
||||||
|
|| Util.MODEL.startsWith("SM-T231")
|
||||||
|
|| Util.MODEL.startsWith("SM-T530"))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ("OMX.brcm.audio.mp3.decoder".equals(name)
|
||||||
|
&& (Util.MODEL.startsWith("GT-I9152")
|
||||||
|
|| Util.MODEL.startsWith("GT-S7580")
|
||||||
|
|| Util.MODEL.startsWith("SM-G350"))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -216,6 +216,12 @@ public final class ClippingMediaSource extends CompositeMediaSource<Void> {
|
||||||
window = new Timeline.Window();
|
window = new Timeline.Window();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Object getTag() {
|
||||||
|
return mediaSource.getTag();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSourceInternal(
|
public void prepareSourceInternal(
|
||||||
ExoPlayer player,
|
ExoPlayer player,
|
||||||
|
|
|
||||||
|
|
@ -453,6 +453,12 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Object getTag() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final synchronized void prepareSourceInternal(
|
public final synchronized void prepareSourceInternal(
|
||||||
ExoPlayer player,
|
ExoPlayer player,
|
||||||
|
|
@ -820,7 +826,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
|
||||||
|
|
||||||
public MediaSourceHolder(MediaSource mediaSource) {
|
public MediaSourceHolder(MediaSource mediaSource) {
|
||||||
this.mediaSource = mediaSource;
|
this.mediaSource = mediaSource;
|
||||||
this.timeline = new DeferredTimeline();
|
this.timeline = DeferredTimeline.createWithDummyTimeline(mediaSource.getTag());
|
||||||
this.activeMediaPeriods = new ArrayList<>();
|
this.activeMediaPeriods = new ArrayList<>();
|
||||||
this.uid = new Object();
|
this.uid = new Object();
|
||||||
}
|
}
|
||||||
|
|
@ -945,10 +951,18 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
|
||||||
private static final class DeferredTimeline extends ForwardingTimeline {
|
private static final class DeferredTimeline extends ForwardingTimeline {
|
||||||
|
|
||||||
private static final Object DUMMY_ID = new Object();
|
private static final Object DUMMY_ID = new Object();
|
||||||
private static final DummyTimeline DUMMY_TIMELINE = new DummyTimeline();
|
|
||||||
|
|
||||||
private final Object replacedId;
|
private final Object replacedId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an instance with a dummy timeline using the provided window tag.
|
||||||
|
*
|
||||||
|
* @param windowTag A window tag.
|
||||||
|
*/
|
||||||
|
public static DeferredTimeline createWithDummyTimeline(@Nullable Object windowTag) {
|
||||||
|
return new DeferredTimeline(new DummyTimeline(windowTag), DUMMY_ID);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an instance with a real timeline, replacing the provided period ID with the already
|
* Returns an instance with a real timeline, replacing the provided period ID with the already
|
||||||
* assigned dummy period ID.
|
* assigned dummy period ID.
|
||||||
|
|
@ -962,11 +976,6 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
|
||||||
return new DeferredTimeline(timeline, firstPeriodUid);
|
return new DeferredTimeline(timeline, firstPeriodUid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates deferred timeline exposing a {@link DummyTimeline}. */
|
|
||||||
public DeferredTimeline() {
|
|
||||||
this(DUMMY_TIMELINE, DUMMY_ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DeferredTimeline(Timeline timeline, Object replacedId) {
|
private DeferredTimeline(Timeline timeline, Object replacedId) {
|
||||||
super(timeline);
|
super(timeline);
|
||||||
this.replacedId = replacedId;
|
this.replacedId = replacedId;
|
||||||
|
|
@ -1010,6 +1019,12 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
|
||||||
/** Dummy placeholder timeline with one dynamic window with a period of indeterminate duration. */
|
/** Dummy placeholder timeline with one dynamic window with a period of indeterminate duration. */
|
||||||
private static final class DummyTimeline extends Timeline {
|
private static final class DummyTimeline extends Timeline {
|
||||||
|
|
||||||
|
@Nullable private final Object tag;
|
||||||
|
|
||||||
|
public DummyTimeline(@Nullable Object tag) {
|
||||||
|
this.tag = tag;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getWindowCount() {
|
public int getWindowCount() {
|
||||||
return 1;
|
return 1;
|
||||||
|
|
@ -1019,7 +1034,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
|
||||||
public Window getWindow(
|
public Window getWindow(
|
||||||
int windowIndex, Window window, boolean setTag, long defaultPositionProjectionUs) {
|
int windowIndex, Window window, boolean setTag, long defaultPositionProjectionUs) {
|
||||||
return window.set(
|
return window.set(
|
||||||
/* tag= */ null,
|
tag,
|
||||||
/* presentationStartTimeMs= */ C.TIME_UNSET,
|
/* presentationStartTimeMs= */ C.TIME_UNSET,
|
||||||
/* windowStartTimeMs= */ C.TIME_UNSET,
|
/* windowStartTimeMs= */ C.TIME_UNSET,
|
||||||
/* isSeekable= */ false,
|
/* isSeekable= */ false,
|
||||||
|
|
@ -1069,6 +1084,12 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Object getTag() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void releaseSourceInternal() {
|
protected void releaseSourceInternal() {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
|
|
|
||||||
|
|
@ -358,6 +358,12 @@ public final class ExtractorMediaSource extends BaseMediaSource
|
||||||
this.tag = tag;
|
this.tag = tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Object getTag() {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSourceInternal(
|
public void prepareSourceInternal(
|
||||||
ExoPlayer player,
|
ExoPlayer player,
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,12 @@ public final class LoopingMediaSource extends CompositeMediaSource<Void> {
|
||||||
mediaPeriodToChildMediaPeriodId = new HashMap<>();
|
mediaPeriodToChildMediaPeriodId = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Object getTag() {
|
||||||
|
return childSource.getTag();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSourceInternal(
|
public void prepareSourceInternal(
|
||||||
ExoPlayer player,
|
ExoPlayer player,
|
||||||
|
|
|
||||||
|
|
@ -220,6 +220,12 @@ public interface MediaSource {
|
||||||
*/
|
*/
|
||||||
void removeEventListener(MediaSourceEventListener eventListener);
|
void removeEventListener(MediaSourceEventListener eventListener);
|
||||||
|
|
||||||
|
/** Returns the tag set on the media source, or null if none was set. */
|
||||||
|
@Nullable
|
||||||
|
default Object getTag() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/** @deprecated Will be removed in the next release. */
|
/** @deprecated Will be removed in the next release. */
|
||||||
@Deprecated
|
@Deprecated
|
||||||
void prepareSource(
|
void prepareSource(
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,12 @@ public final class MergingMediaSource extends CompositeMediaSource<Integer> {
|
||||||
timelines = new Timeline[mediaSources.length];
|
timelines = new Timeline[mediaSources.length];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Object getTag() {
|
||||||
|
return mediaSources.length > 0 ? mediaSources[0].getTag() : null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSourceInternal(
|
public void prepareSourceInternal(
|
||||||
ExoPlayer player,
|
ExoPlayer player,
|
||||||
|
|
|
||||||
|
|
@ -185,6 +185,7 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
|
||||||
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
||||||
private final boolean treatLoadErrorsAsEndOfStream;
|
private final boolean treatLoadErrorsAsEndOfStream;
|
||||||
private final Timeline timeline;
|
private final Timeline timeline;
|
||||||
|
@Nullable private final Object tag;
|
||||||
|
|
||||||
private @Nullable TransferListener transferListener;
|
private @Nullable TransferListener transferListener;
|
||||||
|
|
||||||
|
|
@ -287,6 +288,7 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
|
||||||
this.durationUs = durationUs;
|
this.durationUs = durationUs;
|
||||||
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
|
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
|
||||||
this.treatLoadErrorsAsEndOfStream = treatLoadErrorsAsEndOfStream;
|
this.treatLoadErrorsAsEndOfStream = treatLoadErrorsAsEndOfStream;
|
||||||
|
this.tag = tag;
|
||||||
dataSpec =
|
dataSpec =
|
||||||
new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP | DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH);
|
new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP | DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH);
|
||||||
timeline =
|
timeline =
|
||||||
|
|
@ -295,6 +297,12 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
|
||||||
|
|
||||||
// MediaSource implementation.
|
// MediaSource implementation.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Object getTag() {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSourceInternal(
|
public void prepareSourceInternal(
|
||||||
ExoPlayer player,
|
ExoPlayer player,
|
||||||
|
|
|
||||||
|
|
@ -319,6 +319,12 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
||||||
adsLoader.setSupportedContentTypes(adMediaSourceFactory.getSupportedTypes());
|
adsLoader.setSupportedContentTypes(adMediaSourceFactory.getSupportedTypes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Object getTag() {
|
||||||
|
return contentMediaSource.getTag();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSourceInternal(
|
public void prepareSourceInternal(
|
||||||
final ExoPlayer player,
|
final ExoPlayer player,
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
private static final String ATTR_END = "end";
|
private static final String ATTR_END = "end";
|
||||||
private static final String ATTR_STYLE = "style";
|
private static final String ATTR_STYLE = "style";
|
||||||
private static final String ATTR_REGION = "region";
|
private static final String ATTR_REGION = "region";
|
||||||
|
private static final String ATTR_IMAGE = "backgroundImage";
|
||||||
|
|
||||||
private static final Pattern CLOCK_TIME =
|
private static final Pattern CLOCK_TIME =
|
||||||
Pattern.compile("^([0-9][0-9]+):([0-9][0-9]):([0-9][0-9])"
|
Pattern.compile("^([0-9][0-9]+):([0-9][0-9]):([0-9][0-9])"
|
||||||
|
|
@ -77,6 +78,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
private static final Pattern FONT_SIZE = Pattern.compile("^(([0-9]*.)?[0-9]+)(px|em|%)$");
|
private static final Pattern FONT_SIZE = Pattern.compile("^(([0-9]*.)?[0-9]+)(px|em|%)$");
|
||||||
private static final Pattern PERCENTAGE_COORDINATES =
|
private static final Pattern PERCENTAGE_COORDINATES =
|
||||||
Pattern.compile("^(\\d+\\.?\\d*?)% (\\d+\\.?\\d*?)%$");
|
Pattern.compile("^(\\d+\\.?\\d*?)% (\\d+\\.?\\d*?)%$");
|
||||||
|
private static final Pattern PIXEL_COORDINATES =
|
||||||
|
Pattern.compile("^(\\d+\\.?\\d*?)px (\\d+\\.?\\d*?)px$");
|
||||||
private static final Pattern CELL_RESOLUTION = Pattern.compile("^(\\d+) (\\d+)$");
|
private static final Pattern CELL_RESOLUTION = Pattern.compile("^(\\d+) (\\d+)$");
|
||||||
|
|
||||||
private static final int DEFAULT_FRAME_RATE = 30;
|
private static final int DEFAULT_FRAME_RATE = 30;
|
||||||
|
|
@ -105,6 +108,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
XmlPullParser xmlParser = xmlParserFactory.newPullParser();
|
XmlPullParser xmlParser = xmlParserFactory.newPullParser();
|
||||||
Map<String, TtmlStyle> globalStyles = new HashMap<>();
|
Map<String, TtmlStyle> globalStyles = new HashMap<>();
|
||||||
Map<String, TtmlRegion> regionMap = new HashMap<>();
|
Map<String, TtmlRegion> regionMap = new HashMap<>();
|
||||||
|
Map<String, String> imageMap = new HashMap<>();
|
||||||
regionMap.put(TtmlNode.ANONYMOUS_REGION_ID, new TtmlRegion(null));
|
regionMap.put(TtmlNode.ANONYMOUS_REGION_ID, new TtmlRegion(null));
|
||||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes, 0, length);
|
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes, 0, length);
|
||||||
xmlParser.setInput(inputStream, null);
|
xmlParser.setInput(inputStream, null);
|
||||||
|
|
@ -114,6 +118,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
int eventType = xmlParser.getEventType();
|
int eventType = xmlParser.getEventType();
|
||||||
FrameAndTickRate frameAndTickRate = DEFAULT_FRAME_AND_TICK_RATE;
|
FrameAndTickRate frameAndTickRate = DEFAULT_FRAME_AND_TICK_RATE;
|
||||||
CellResolution cellResolution = DEFAULT_CELL_RESOLUTION;
|
CellResolution cellResolution = DEFAULT_CELL_RESOLUTION;
|
||||||
|
TtsExtent ttsExtent = null;
|
||||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||||
TtmlNode parent = nodeStack.peek();
|
TtmlNode parent = nodeStack.peek();
|
||||||
if (unsupportedNodeDepth == 0) {
|
if (unsupportedNodeDepth == 0) {
|
||||||
|
|
@ -122,12 +127,13 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
if (TtmlNode.TAG_TT.equals(name)) {
|
if (TtmlNode.TAG_TT.equals(name)) {
|
||||||
frameAndTickRate = parseFrameAndTickRates(xmlParser);
|
frameAndTickRate = parseFrameAndTickRates(xmlParser);
|
||||||
cellResolution = parseCellResolution(xmlParser, DEFAULT_CELL_RESOLUTION);
|
cellResolution = parseCellResolution(xmlParser, DEFAULT_CELL_RESOLUTION);
|
||||||
|
ttsExtent = parseTtsExtent(xmlParser);
|
||||||
}
|
}
|
||||||
if (!isSupportedTag(name)) {
|
if (!isSupportedTag(name)) {
|
||||||
Log.i(TAG, "Ignoring unsupported tag: " + xmlParser.getName());
|
Log.i(TAG, "Ignoring unsupported tag: " + xmlParser.getName());
|
||||||
unsupportedNodeDepth++;
|
unsupportedNodeDepth++;
|
||||||
} else if (TtmlNode.TAG_HEAD.equals(name)) {
|
} else if (TtmlNode.TAG_HEAD.equals(name)) {
|
||||||
parseHeader(xmlParser, globalStyles, regionMap, cellResolution);
|
parseHeader(xmlParser, globalStyles, cellResolution, ttsExtent, regionMap, imageMap);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
TtmlNode node = parseNode(xmlParser, parent, regionMap, frameAndTickRate);
|
TtmlNode node = parseNode(xmlParser, parent, regionMap, frameAndTickRate);
|
||||||
|
|
@ -145,7 +151,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
parent.addChild(TtmlNode.buildTextNode(xmlParser.getText()));
|
parent.addChild(TtmlNode.buildTextNode(xmlParser.getText()));
|
||||||
} else if (eventType == XmlPullParser.END_TAG) {
|
} else if (eventType == XmlPullParser.END_TAG) {
|
||||||
if (xmlParser.getName().equals(TtmlNode.TAG_TT)) {
|
if (xmlParser.getName().equals(TtmlNode.TAG_TT)) {
|
||||||
ttmlSubtitle = new TtmlSubtitle(nodeStack.peek(), globalStyles, regionMap);
|
ttmlSubtitle = new TtmlSubtitle(nodeStack.peek(), globalStyles, regionMap, imageMap);
|
||||||
}
|
}
|
||||||
nodeStack.pop();
|
nodeStack.pop();
|
||||||
}
|
}
|
||||||
|
|
@ -226,11 +232,34 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TtsExtent parseTtsExtent(XmlPullParser xmlParser) {
|
||||||
|
String ttsExtent = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT);
|
||||||
|
if (ttsExtent == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matcher extentMatcher = PIXEL_COORDINATES.matcher(ttsExtent);
|
||||||
|
if (!extentMatcher.matches()) {
|
||||||
|
Log.w(TAG, "Ignoring non-pixel tts extent: " + ttsExtent);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
int width = Integer.parseInt(extentMatcher.group(1));
|
||||||
|
int height = Integer.parseInt(extentMatcher.group(2));
|
||||||
|
return new TtsExtent(width, height);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Log.w(TAG, "Ignoring malformed tts extent: " + ttsExtent);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Map<String, TtmlStyle> parseHeader(
|
private Map<String, TtmlStyle> parseHeader(
|
||||||
XmlPullParser xmlParser,
|
XmlPullParser xmlParser,
|
||||||
Map<String, TtmlStyle> globalStyles,
|
Map<String, TtmlStyle> globalStyles,
|
||||||
|
CellResolution cellResolution,
|
||||||
|
TtsExtent ttsExtent,
|
||||||
Map<String, TtmlRegion> globalRegions,
|
Map<String, TtmlRegion> globalRegions,
|
||||||
CellResolution cellResolution)
|
Map<String, String> imageMap)
|
||||||
throws IOException, XmlPullParserException {
|
throws IOException, XmlPullParserException {
|
||||||
do {
|
do {
|
||||||
xmlParser.next();
|
xmlParser.next();
|
||||||
|
|
@ -246,23 +275,41 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
globalStyles.put(style.getId(), style);
|
globalStyles.put(style.getId(), style);
|
||||||
}
|
}
|
||||||
} else if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_REGION)) {
|
} else if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_REGION)) {
|
||||||
TtmlRegion ttmlRegion = parseRegionAttributes(xmlParser, cellResolution);
|
TtmlRegion ttmlRegion = parseRegionAttributes(xmlParser, cellResolution, ttsExtent);
|
||||||
if (ttmlRegion != null) {
|
if (ttmlRegion != null) {
|
||||||
globalRegions.put(ttmlRegion.id, ttmlRegion);
|
globalRegions.put(ttmlRegion.id, ttmlRegion);
|
||||||
}
|
}
|
||||||
|
} else if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_METADATA)) {
|
||||||
|
parseMetadata(xmlParser, imageMap);
|
||||||
}
|
}
|
||||||
} while (!XmlPullParserUtil.isEndTag(xmlParser, TtmlNode.TAG_HEAD));
|
} while (!XmlPullParserUtil.isEndTag(xmlParser, TtmlNode.TAG_HEAD));
|
||||||
return globalStyles;
|
return globalStyles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void parseMetadata(XmlPullParser xmlParser, Map<String, String> imageMap)
|
||||||
|
throws IOException, XmlPullParserException {
|
||||||
|
do {
|
||||||
|
xmlParser.next();
|
||||||
|
if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_IMAGE)) {
|
||||||
|
String id = XmlPullParserUtil.getAttributeValue(xmlParser, "id");
|
||||||
|
if (id != null) {
|
||||||
|
String encodedBitmapData = xmlParser.nextText();
|
||||||
|
imageMap.put(id, encodedBitmapData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (!XmlPullParserUtil.isEndTag(xmlParser, TtmlNode.TAG_METADATA));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a region declaration.
|
* Parses a region declaration.
|
||||||
*
|
*
|
||||||
* <p>If the region defines an origin and extent, it is required that they're defined as
|
* <p>Supports both percentage and pixel defined regions. In case of pixel defined regions the
|
||||||
* percentages of the viewport. Region declarations that define origin and extent in other formats
|
* passed {@code ttsExtent} is used as a reference window to convert the pixel values to
|
||||||
* are unsupported, and null is returned.
|
* fractions. In case of missing tts:extent the pixel defined regions can't be parsed, and null is
|
||||||
|
* returned.
|
||||||
*/
|
*/
|
||||||
private TtmlRegion parseRegionAttributes(XmlPullParser xmlParser, CellResolution cellResolution) {
|
private TtmlRegion parseRegionAttributes(
|
||||||
|
XmlPullParser xmlParser, CellResolution cellResolution, TtsExtent ttsExtent) {
|
||||||
String regionId = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_ID);
|
String regionId = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_ID);
|
||||||
if (regionId == null) {
|
if (regionId == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -270,13 +317,30 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
|
|
||||||
float position;
|
float position;
|
||||||
float line;
|
float line;
|
||||||
|
|
||||||
String regionOrigin = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_ORIGIN);
|
String regionOrigin = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_ORIGIN);
|
||||||
if (regionOrigin != null) {
|
if (regionOrigin != null) {
|
||||||
Matcher originMatcher = PERCENTAGE_COORDINATES.matcher(regionOrigin);
|
Matcher originPercentageMatcher = PERCENTAGE_COORDINATES.matcher(regionOrigin);
|
||||||
if (originMatcher.matches()) {
|
Matcher originPixelMatcher = PIXEL_COORDINATES.matcher(regionOrigin);
|
||||||
|
if (originPercentageMatcher.matches()) {
|
||||||
try {
|
try {
|
||||||
position = Float.parseFloat(originMatcher.group(1)) / 100f;
|
position = Float.parseFloat(originPercentageMatcher.group(1)) / 100f;
|
||||||
line = Float.parseFloat(originMatcher.group(2)) / 100f;
|
line = Float.parseFloat(originPercentageMatcher.group(2)) / 100f;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Log.w(TAG, "Ignoring region with malformed origin: " + regionOrigin);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else if (originPixelMatcher.matches()) {
|
||||||
|
if (ttsExtent == null) {
|
||||||
|
Log.w(TAG, "Ignoring region with missing tts:extent: " + regionOrigin);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
int width = Integer.parseInt(originPixelMatcher.group(1));
|
||||||
|
int height = Integer.parseInt(originPixelMatcher.group(2));
|
||||||
|
// Convert pixel values to fractions.
|
||||||
|
position = width / (float) ttsExtent.width;
|
||||||
|
line = height / (float) ttsExtent.height;
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
Log.w(TAG, "Ignoring region with malformed origin: " + regionOrigin);
|
Log.w(TAG, "Ignoring region with malformed origin: " + regionOrigin);
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -299,11 +363,27 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
float height;
|
float height;
|
||||||
String regionExtent = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT);
|
String regionExtent = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT);
|
||||||
if (regionExtent != null) {
|
if (regionExtent != null) {
|
||||||
Matcher extentMatcher = PERCENTAGE_COORDINATES.matcher(regionExtent);
|
Matcher extentPercentageMatcher = PERCENTAGE_COORDINATES.matcher(regionExtent);
|
||||||
if (extentMatcher.matches()) {
|
Matcher extentPixelMatcher = PIXEL_COORDINATES.matcher(regionExtent);
|
||||||
|
if (extentPercentageMatcher.matches()) {
|
||||||
try {
|
try {
|
||||||
width = Float.parseFloat(extentMatcher.group(1)) / 100f;
|
width = Float.parseFloat(extentPercentageMatcher.group(1)) / 100f;
|
||||||
height = Float.parseFloat(extentMatcher.group(2)) / 100f;
|
height = Float.parseFloat(extentPercentageMatcher.group(2)) / 100f;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Log.w(TAG, "Ignoring region with malformed extent: " + regionOrigin);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else if (extentPixelMatcher.matches()) {
|
||||||
|
if (ttsExtent == null) {
|
||||||
|
Log.w(TAG, "Ignoring region with missing tts:extent: " + regionOrigin);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
int extentWidth = Integer.parseInt(extentPixelMatcher.group(1));
|
||||||
|
int extentHeight = Integer.parseInt(extentPixelMatcher.group(2));
|
||||||
|
// Convert pixel values to fractions.
|
||||||
|
width = extentWidth / (float) ttsExtent.width;
|
||||||
|
height = extentHeight / (float) ttsExtent.height;
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
Log.w(TAG, "Ignoring region with malformed extent: " + regionOrigin);
|
Log.w(TAG, "Ignoring region with malformed extent: " + regionOrigin);
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -457,6 +537,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
long startTime = C.TIME_UNSET;
|
long startTime = C.TIME_UNSET;
|
||||||
long endTime = C.TIME_UNSET;
|
long endTime = C.TIME_UNSET;
|
||||||
String regionId = TtmlNode.ANONYMOUS_REGION_ID;
|
String regionId = TtmlNode.ANONYMOUS_REGION_ID;
|
||||||
|
String imageId = null;
|
||||||
String[] styleIds = null;
|
String[] styleIds = null;
|
||||||
int attributeCount = parser.getAttributeCount();
|
int attributeCount = parser.getAttributeCount();
|
||||||
TtmlStyle style = parseStyleAttributes(parser, null);
|
TtmlStyle style = parseStyleAttributes(parser, null);
|
||||||
|
|
@ -487,6 +568,13 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
regionId = value;
|
regionId = value;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case ATTR_IMAGE:
|
||||||
|
// Parse URI reference only if refers to an element in the same document (it must start
|
||||||
|
// with '#'). Resolving URIs from external sources is not supported.
|
||||||
|
if (value.startsWith("#")) {
|
||||||
|
imageId = value.substring(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
break;
|
break;
|
||||||
|
|
@ -509,7 +597,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
endTime = parent.endTimeUs;
|
endTime = parent.endTimeUs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return TtmlNode.buildNode(parser.getName(), startTime, endTime, style, styleIds, regionId);
|
return TtmlNode.buildNode(
|
||||||
|
parser.getName(), startTime, endTime, style, styleIds, regionId, imageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isSupportedTag(String tag) {
|
private static boolean isSupportedTag(String tag) {
|
||||||
|
|
@ -525,9 +614,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
|| tag.equals(TtmlNode.TAG_LAYOUT)
|
|| tag.equals(TtmlNode.TAG_LAYOUT)
|
||||||
|| tag.equals(TtmlNode.TAG_REGION)
|
|| tag.equals(TtmlNode.TAG_REGION)
|
||||||
|| tag.equals(TtmlNode.TAG_METADATA)
|
|| tag.equals(TtmlNode.TAG_METADATA)
|
||||||
|| tag.equals(TtmlNode.TAG_SMPTE_IMAGE)
|
|| tag.equals(TtmlNode.TAG_IMAGE)
|
||||||
|| tag.equals(TtmlNode.TAG_SMPTE_DATA)
|
|| tag.equals(TtmlNode.TAG_DATA)
|
||||||
|| tag.equals(TtmlNode.TAG_SMPTE_INFORMATION);
|
|| tag.equals(TtmlNode.TAG_INFORMATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void parseFontSize(String expression, TtmlStyle out) throws
|
private static void parseFontSize(String expression, TtmlStyle out) throws
|
||||||
|
|
@ -651,4 +740,15 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||||
this.rows = rows;
|
this.rows = rows;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Represents the tts:extent for a TTML file. */
|
||||||
|
private static final class TtsExtent {
|
||||||
|
final int width;
|
||||||
|
final int height;
|
||||||
|
|
||||||
|
TtsExtent(int width, int height) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,12 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.text.ttml;
|
package com.google.android.exoplayer2.text.ttml;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.util.Base64;
|
||||||
|
import android.util.Pair;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.text.Cue;
|
import com.google.android.exoplayer2.text.Cue;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
|
@ -44,9 +49,9 @@ import java.util.TreeSet;
|
||||||
public static final String TAG_LAYOUT = "layout";
|
public static final String TAG_LAYOUT = "layout";
|
||||||
public static final String TAG_REGION = "region";
|
public static final String TAG_REGION = "region";
|
||||||
public static final String TAG_METADATA = "metadata";
|
public static final String TAG_METADATA = "metadata";
|
||||||
public static final String TAG_SMPTE_IMAGE = "smpte:image";
|
public static final String TAG_IMAGE = "image";
|
||||||
public static final String TAG_SMPTE_DATA = "smpte:data";
|
public static final String TAG_DATA = "data";
|
||||||
public static final String TAG_SMPTE_INFORMATION = "smpte:information";
|
public static final String TAG_INFORMATION = "information";
|
||||||
|
|
||||||
public static final String ANONYMOUS_REGION_ID = "";
|
public static final String ANONYMOUS_REGION_ID = "";
|
||||||
public static final String ATTR_ID = "id";
|
public static final String ATTR_ID = "id";
|
||||||
|
|
@ -75,34 +80,57 @@ import java.util.TreeSet;
|
||||||
public static final String START = "start";
|
public static final String START = "start";
|
||||||
public static final String END = "end";
|
public static final String END = "end";
|
||||||
|
|
||||||
public final String tag;
|
@Nullable public final String tag;
|
||||||
public final String text;
|
@Nullable public final String text;
|
||||||
public final boolean isTextNode;
|
public final boolean isTextNode;
|
||||||
public final long startTimeUs;
|
public final long startTimeUs;
|
||||||
public final long endTimeUs;
|
public final long endTimeUs;
|
||||||
public final TtmlStyle style;
|
@Nullable public final TtmlStyle style;
|
||||||
|
@Nullable private final String[] styleIds;
|
||||||
public final String regionId;
|
public final String regionId;
|
||||||
|
@Nullable public final String imageId;
|
||||||
|
|
||||||
private final String[] styleIds;
|
|
||||||
private final HashMap<String, Integer> nodeStartsByRegion;
|
private final HashMap<String, Integer> nodeStartsByRegion;
|
||||||
private final HashMap<String, Integer> nodeEndsByRegion;
|
private final HashMap<String, Integer> nodeEndsByRegion;
|
||||||
|
|
||||||
private List<TtmlNode> children;
|
private List<TtmlNode> children;
|
||||||
|
|
||||||
public static TtmlNode buildTextNode(String text) {
|
public static TtmlNode buildTextNode(String text) {
|
||||||
return new TtmlNode(null, TtmlRenderUtil.applyTextElementSpacePolicy(text), C.TIME_UNSET,
|
return new TtmlNode(
|
||||||
C.TIME_UNSET, null, null, ANONYMOUS_REGION_ID);
|
/* tag= */ null,
|
||||||
|
TtmlRenderUtil.applyTextElementSpacePolicy(text),
|
||||||
|
/* startTimeUs= */ C.TIME_UNSET,
|
||||||
|
/* endTimeUs= */ C.TIME_UNSET,
|
||||||
|
/* style= */ null,
|
||||||
|
/* styleIds= */ null,
|
||||||
|
ANONYMOUS_REGION_ID,
|
||||||
|
/* imageId= */ null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TtmlNode buildNode(String tag, long startTimeUs, long endTimeUs,
|
public static TtmlNode buildNode(
|
||||||
TtmlStyle style, String[] styleIds, String regionId) {
|
@Nullable String tag,
|
||||||
return new TtmlNode(tag, null, startTimeUs, endTimeUs, style, styleIds, regionId);
|
long startTimeUs,
|
||||||
|
long endTimeUs,
|
||||||
|
@Nullable TtmlStyle style,
|
||||||
|
@Nullable String[] styleIds,
|
||||||
|
String regionId,
|
||||||
|
@Nullable String imageId) {
|
||||||
|
return new TtmlNode(
|
||||||
|
tag, /* text= */ null, startTimeUs, endTimeUs, style, styleIds, regionId, imageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TtmlNode(String tag, String text, long startTimeUs, long endTimeUs,
|
private TtmlNode(
|
||||||
TtmlStyle style, String[] styleIds, String regionId) {
|
@Nullable String tag,
|
||||||
|
@Nullable String text,
|
||||||
|
long startTimeUs,
|
||||||
|
long endTimeUs,
|
||||||
|
@Nullable TtmlStyle style,
|
||||||
|
@Nullable String[] styleIds,
|
||||||
|
String regionId,
|
||||||
|
@Nullable String imageId) {
|
||||||
this.tag = tag;
|
this.tag = tag;
|
||||||
this.text = text;
|
this.text = text;
|
||||||
|
this.imageId = imageId;
|
||||||
this.style = style;
|
this.style = style;
|
||||||
this.styleIds = styleIds;
|
this.styleIds = styleIds;
|
||||||
this.isTextNode = text != null;
|
this.isTextNode = text != null;
|
||||||
|
|
@ -151,7 +179,8 @@ import java.util.TreeSet;
|
||||||
|
|
||||||
private void getEventTimes(TreeSet<Long> out, boolean descendsPNode) {
|
private void getEventTimes(TreeSet<Long> out, boolean descendsPNode) {
|
||||||
boolean isPNode = TAG_P.equals(tag);
|
boolean isPNode = TAG_P.equals(tag);
|
||||||
if (descendsPNode || isPNode) {
|
boolean isDivNode = TAG_DIV.equals(tag);
|
||||||
|
if (descendsPNode || isPNode || (isDivNode && imageId != null)) {
|
||||||
if (startTimeUs != C.TIME_UNSET) {
|
if (startTimeUs != C.TIME_UNSET) {
|
||||||
out.add(startTimeUs);
|
out.add(startTimeUs);
|
||||||
}
|
}
|
||||||
|
|
@ -171,13 +200,46 @@ import java.util.TreeSet;
|
||||||
return styleIds;
|
return styleIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Cue> getCues(long timeUs, Map<String, TtmlStyle> globalStyles,
|
public List<Cue> getCues(
|
||||||
Map<String, TtmlRegion> regionMap) {
|
long timeUs,
|
||||||
TreeMap<String, SpannableStringBuilder> regionOutputs = new TreeMap<>();
|
Map<String, TtmlStyle> globalStyles,
|
||||||
traverseForText(timeUs, false, regionId, regionOutputs);
|
Map<String, TtmlRegion> regionMap,
|
||||||
traverseForStyle(timeUs, globalStyles, regionOutputs);
|
Map<String, String> imageMap) {
|
||||||
|
|
||||||
|
List<Pair<String, String>> regionImageOutputs = new ArrayList<>();
|
||||||
|
traverseForImage(timeUs, regionId, regionImageOutputs);
|
||||||
|
|
||||||
|
TreeMap<String, SpannableStringBuilder> regionTextOutputs = new TreeMap<>();
|
||||||
|
traverseForText(timeUs, false, regionId, regionTextOutputs);
|
||||||
|
traverseForStyle(timeUs, globalStyles, regionTextOutputs);
|
||||||
|
|
||||||
List<Cue> cues = new ArrayList<>();
|
List<Cue> cues = new ArrayList<>();
|
||||||
for (Entry<String, SpannableStringBuilder> entry : regionOutputs.entrySet()) {
|
|
||||||
|
// Create image based cues.
|
||||||
|
for (Pair<String, String> regionImagePair : regionImageOutputs) {
|
||||||
|
String encodedBitmapData = imageMap.get(regionImagePair.second);
|
||||||
|
if (encodedBitmapData == null) {
|
||||||
|
// Image reference points to an invalid image. Do nothing.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] bitmapData = Base64.decode(encodedBitmapData, Base64.DEFAULT);
|
||||||
|
Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapData, /* offset= */ 0, bitmapData.length);
|
||||||
|
TtmlRegion region = regionMap.get(regionImagePair.first);
|
||||||
|
|
||||||
|
cues.add(
|
||||||
|
new Cue(
|
||||||
|
bitmap,
|
||||||
|
region.position,
|
||||||
|
Cue.ANCHOR_TYPE_MIDDLE,
|
||||||
|
region.line,
|
||||||
|
region.lineAnchor,
|
||||||
|
region.width,
|
||||||
|
/* height= */ Cue.DIMEN_UNSET));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create text based cues.
|
||||||
|
for (Entry<String, SpannableStringBuilder> entry : regionTextOutputs.entrySet()) {
|
||||||
TtmlRegion region = regionMap.get(entry.getKey());
|
TtmlRegion region = regionMap.get(entry.getKey());
|
||||||
cues.add(
|
cues.add(
|
||||||
new Cue(
|
new Cue(
|
||||||
|
|
@ -192,9 +254,22 @@ import java.util.TreeSet;
|
||||||
region.textSizeType,
|
region.textSizeType,
|
||||||
region.textSize));
|
region.textSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
return cues;
|
return cues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void traverseForImage(
|
||||||
|
long timeUs, String inheritedRegion, List<Pair<String, String>> regionImageList) {
|
||||||
|
String resolvedRegionId = ANONYMOUS_REGION_ID.equals(regionId) ? inheritedRegion : regionId;
|
||||||
|
if (isActive(timeUs) && TAG_DIV.equals(tag) && imageId != null) {
|
||||||
|
regionImageList.add(new Pair<>(resolvedRegionId, imageId));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < getChildCount(); ++i) {
|
||||||
|
getChild(i).traverseForImage(timeUs, resolvedRegionId, regionImageList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void traverseForText(
|
private void traverseForText(
|
||||||
long timeUs,
|
long timeUs,
|
||||||
boolean descendsPNode,
|
boolean descendsPNode,
|
||||||
|
|
|
||||||
|
|
@ -32,11 +32,16 @@ import java.util.Map;
|
||||||
private final long[] eventTimesUs;
|
private final long[] eventTimesUs;
|
||||||
private final Map<String, TtmlStyle> globalStyles;
|
private final Map<String, TtmlStyle> globalStyles;
|
||||||
private final Map<String, TtmlRegion> regionMap;
|
private final Map<String, TtmlRegion> regionMap;
|
||||||
|
private final Map<String, String> imageMap;
|
||||||
|
|
||||||
public TtmlSubtitle(TtmlNode root, Map<String, TtmlStyle> globalStyles,
|
public TtmlSubtitle(
|
||||||
Map<String, TtmlRegion> regionMap) {
|
TtmlNode root,
|
||||||
|
Map<String, TtmlStyle> globalStyles,
|
||||||
|
Map<String, TtmlRegion> regionMap,
|
||||||
|
Map<String, String> imageMap) {
|
||||||
this.root = root;
|
this.root = root;
|
||||||
this.regionMap = regionMap;
|
this.regionMap = regionMap;
|
||||||
|
this.imageMap = imageMap;
|
||||||
this.globalStyles =
|
this.globalStyles =
|
||||||
globalStyles != null ? Collections.unmodifiableMap(globalStyles) : Collections.emptyMap();
|
globalStyles != null ? Collections.unmodifiableMap(globalStyles) : Collections.emptyMap();
|
||||||
this.eventTimesUs = root.getEventTimesUs();
|
this.eventTimesUs = root.getEventTimesUs();
|
||||||
|
|
@ -65,7 +70,7 @@ import java.util.Map;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Cue> getCues(long timeUs) {
|
public List<Cue> getCues(long timeUs) {
|
||||||
return root.getCues(timeUs, globalStyles, regionMap);
|
return root.getCues(timeUs, globalStyles, regionMap, imageMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @VisibleForTesting */
|
/* @VisibleForTesting */
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ public interface Cache {
|
||||||
* Releases the cache. This method must be called when the cache is no longer required. The cache
|
* Releases the cache. This method must be called when the cache is no longer required. The cache
|
||||||
* must not be used after calling this method.
|
* must not be used after calling this method.
|
||||||
*/
|
*/
|
||||||
void release() throws CacheException;
|
void release();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a listener to listen for changes to a given key.
|
* Registers a listener to listen for changes to a given key.
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
package com.google.android.exoplayer2.upstream.cache;
|
package com.google.android.exoplayer2.upstream.cache;
|
||||||
|
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
|
import android.util.SparseBooleanArray;
|
||||||
import com.google.android.exoplayer2.upstream.cache.Cache.CacheException;
|
import com.google.android.exoplayer2.upstream.cache.Cache.CacheException;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.AtomicFile;
|
import com.google.android.exoplayer2.util.AtomicFile;
|
||||||
|
|
@ -41,6 +42,7 @@ import javax.crypto.CipherOutputStream;
|
||||||
import javax.crypto.NoSuchPaddingException;
|
import javax.crypto.NoSuchPaddingException;
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||||
|
|
||||||
/** Maintains the index of cached content. */
|
/** Maintains the index of cached content. */
|
||||||
/*package*/ class CachedContentIndex {
|
/*package*/ class CachedContentIndex {
|
||||||
|
|
@ -52,7 +54,30 @@ import javax.crypto.spec.SecretKeySpec;
|
||||||
private static final int FLAG_ENCRYPTED_INDEX = 1;
|
private static final int FLAG_ENCRYPTED_INDEX = 1;
|
||||||
|
|
||||||
private final HashMap<String, CachedContent> keyToContent;
|
private final HashMap<String, CachedContent> keyToContent;
|
||||||
private final SparseArray<String> idToKey;
|
/**
|
||||||
|
* Maps assigned ids to their corresponding keys. Also contains (id -> null) entries for ids that
|
||||||
|
* have been removed from the index since it was last stored. This prevents reuse of these ids,
|
||||||
|
* which is necessary to avoid clashes that could otherwise occur as a result of the sequence:
|
||||||
|
*
|
||||||
|
* <p>[1] (key1, id1) is removed from the in-memory index ... the index is not stored to disk ...
|
||||||
|
* [2] id1 is reused for a different key2 ... the index is not stored to disk ... [3] A file for
|
||||||
|
* key2 is partially written using a path corresponding to id1 ... the process is killed before
|
||||||
|
* the index is stored to disk ... [4] The index is read from disk, causing the partially written
|
||||||
|
* file to be incorrectly associated to key1
|
||||||
|
*
|
||||||
|
* <p>By avoiding id reuse in step [2], a new id2 will be used instead. Step [4] will then delete
|
||||||
|
* the partially written file because the index does not contain an entry for id2.
|
||||||
|
*
|
||||||
|
* <p>When the index is next stored (id -> null) entries are removed, making the ids eligible for
|
||||||
|
* reuse.
|
||||||
|
*/
|
||||||
|
private final SparseArray<@NullableType String> idToKey;
|
||||||
|
/**
|
||||||
|
* Tracks ids for which (id -> null) entries are present in idToKey, so that they can be removed
|
||||||
|
* efficiently when the index is next stored.
|
||||||
|
*/
|
||||||
|
private final SparseBooleanArray removedIds;
|
||||||
|
|
||||||
private final AtomicFile atomicFile;
|
private final AtomicFile atomicFile;
|
||||||
private final Cipher cipher;
|
private final Cipher cipher;
|
||||||
private final SecretKeySpec secretKeySpec;
|
private final SecretKeySpec secretKeySpec;
|
||||||
|
|
@ -104,6 +129,7 @@ import javax.crypto.spec.SecretKeySpec;
|
||||||
}
|
}
|
||||||
keyToContent = new HashMap<>();
|
keyToContent = new HashMap<>();
|
||||||
idToKey = new SparseArray<>();
|
idToKey = new SparseArray<>();
|
||||||
|
removedIds = new SparseBooleanArray();
|
||||||
atomicFile = new AtomicFile(new File(cacheDir, FILE_NAME));
|
atomicFile = new AtomicFile(new File(cacheDir, FILE_NAME));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,6 +150,12 @@ import javax.crypto.spec.SecretKeySpec;
|
||||||
}
|
}
|
||||||
writeFile();
|
writeFile();
|
||||||
changed = false;
|
changed = false;
|
||||||
|
// Make ids that were removed since the index was last stored eligible for re-use.
|
||||||
|
int removedIdCount = removedIds.size();
|
||||||
|
for (int i = 0; i < removedIdCount; i++) {
|
||||||
|
idToKey.remove(removedIds.keyAt(i));
|
||||||
|
}
|
||||||
|
removedIds.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -168,8 +200,11 @@ import javax.crypto.spec.SecretKeySpec;
|
||||||
CachedContent cachedContent = keyToContent.get(key);
|
CachedContent cachedContent = keyToContent.get(key);
|
||||||
if (cachedContent != null && cachedContent.isEmpty() && !cachedContent.isLocked()) {
|
if (cachedContent != null && cachedContent.isEmpty() && !cachedContent.isLocked()) {
|
||||||
keyToContent.remove(key);
|
keyToContent.remove(key);
|
||||||
idToKey.remove(cachedContent.id);
|
|
||||||
changed = true;
|
changed = true;
|
||||||
|
// Keep an entry in idToKey to stop the id from being reused until the index is next stored.
|
||||||
|
idToKey.put(cachedContent.id, /* value= */ null);
|
||||||
|
// Track that the entry should be removed from idToKey when the index is next stored.
|
||||||
|
removedIds.put(cachedContent.id, /* value= */ true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -146,13 +146,16 @@ public final class SimpleCache implements Cache {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void release() throws CacheException {
|
public synchronized void release() {
|
||||||
if (released) {
|
if (released) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
listeners.clear();
|
listeners.clear();
|
||||||
|
removeStaleSpans();
|
||||||
try {
|
try {
|
||||||
removeStaleSpansAndCachedContents();
|
index.store();
|
||||||
|
} catch (CacheException e) {
|
||||||
|
Log.e(TAG, "Storing index file failed", e);
|
||||||
} finally {
|
} finally {
|
||||||
unlockFolder(cacheDir);
|
unlockFolder(cacheDir);
|
||||||
released = true;
|
released = true;
|
||||||
|
|
@ -265,7 +268,7 @@ public final class SimpleCache implements Cache {
|
||||||
if (!cacheDir.exists()) {
|
if (!cacheDir.exists()) {
|
||||||
// For some reason the cache directory doesn't exist. Make a best effort to create it.
|
// For some reason the cache directory doesn't exist. Make a best effort to create it.
|
||||||
cacheDir.mkdirs();
|
cacheDir.mkdirs();
|
||||||
removeStaleSpansAndCachedContents();
|
removeStaleSpans();
|
||||||
}
|
}
|
||||||
evictor.onStartFile(this, key, position, maxLength);
|
evictor.onStartFile(this, key, position, maxLength);
|
||||||
return SimpleCacheSpan.getCacheFile(
|
return SimpleCacheSpan.getCacheFile(
|
||||||
|
|
@ -311,9 +314,9 @@ public final class SimpleCache implements Cache {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void removeSpan(CacheSpan span) throws CacheException {
|
public synchronized void removeSpan(CacheSpan span) {
|
||||||
Assertions.checkState(!released);
|
Assertions.checkState(!released);
|
||||||
removeSpan(span, true);
|
removeSpanInternal(span);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -379,7 +382,7 @@ public final class SimpleCache implements Cache {
|
||||||
if (span.isCached && !span.file.exists()) {
|
if (span.isCached && !span.file.exists()) {
|
||||||
// The file has been deleted from under us. It's likely that other files will have been
|
// The file has been deleted from under us. It's likely that other files will have been
|
||||||
// deleted too, so scan the whole in-memory representation.
|
// deleted too, so scan the whole in-memory representation.
|
||||||
removeStaleSpansAndCachedContents();
|
removeStaleSpans();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
return span;
|
return span;
|
||||||
|
|
@ -431,27 +434,21 @@ public final class SimpleCache implements Cache {
|
||||||
notifySpanAdded(span);
|
notifySpanAdded(span);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeSpan(CacheSpan span, boolean removeEmptyCachedContent) throws CacheException {
|
private void removeSpanInternal(CacheSpan span) {
|
||||||
CachedContent cachedContent = index.get(span.key);
|
CachedContent cachedContent = index.get(span.key);
|
||||||
if (cachedContent == null || !cachedContent.removeSpan(span)) {
|
if (cachedContent == null || !cachedContent.removeSpan(span)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
totalSpace -= span.length;
|
totalSpace -= span.length;
|
||||||
try {
|
index.maybeRemove(cachedContent.key);
|
||||||
if (removeEmptyCachedContent) {
|
notifySpanRemoved(span);
|
||||||
index.maybeRemove(cachedContent.key);
|
|
||||||
index.store();
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
notifySpanRemoved(span);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scans all of the cached spans in the in-memory representation, removing any for which files no
|
* Scans all of the cached spans in the in-memory representation, removing any for which files no
|
||||||
* longer exist.
|
* longer exist.
|
||||||
*/
|
*/
|
||||||
private void removeStaleSpansAndCachedContents() throws CacheException {
|
private void removeStaleSpans() {
|
||||||
ArrayList<CacheSpan> spansToBeRemoved = new ArrayList<>();
|
ArrayList<CacheSpan> spansToBeRemoved = new ArrayList<>();
|
||||||
for (CachedContent cachedContent : index.getAll()) {
|
for (CachedContent cachedContent : index.getAll()) {
|
||||||
for (CacheSpan span : cachedContent.getSpans()) {
|
for (CacheSpan span : cachedContent.getSpans()) {
|
||||||
|
|
@ -461,11 +458,8 @@ public final class SimpleCache implements Cache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int i = 0; i < spansToBeRemoved.size(); i++) {
|
for (int i = 0; i < spansToBeRemoved.size(); i++) {
|
||||||
// Remove span but not CachedContent to prevent multiple index.store() calls.
|
removeSpanInternal(spansToBeRemoved.get(i));
|
||||||
removeSpan(spansToBeRemoved.get(i), false);
|
|
||||||
}
|
}
|
||||||
index.removeEmpty();
|
|
||||||
index.store();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifySpanRemoved(CacheSpan span) {
|
private void notifySpanRemoved(CacheSpan span) {
|
||||||
|
|
|
||||||
|
|
@ -1436,11 +1436,12 @@ public final class Util {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps a {@link C} {@code TRACK_TYPE_*} constant to the corresponding {@link C}
|
* Maps a {@link C} {@code TRACK_TYPE_*} constant to the corresponding {@link C} {@code
|
||||||
* {@code DEFAULT_*_BUFFER_SIZE} constant.
|
* DEFAULT_*_BUFFER_SIZE} constant.
|
||||||
*
|
*
|
||||||
* @param trackType The track type.
|
* @param trackType The track type.
|
||||||
* @return The corresponding default buffer size in bytes.
|
* @return The corresponding default buffer size in bytes.
|
||||||
|
* @throws IllegalArgumentException If the track type is an unrecognized or custom track type.
|
||||||
*/
|
*/
|
||||||
public static int getDefaultBufferSize(int trackType) {
|
public static int getDefaultBufferSize(int trackType) {
|
||||||
switch (trackType) {
|
switch (trackType) {
|
||||||
|
|
@ -1456,8 +1457,10 @@ public final class Util {
|
||||||
return C.DEFAULT_METADATA_BUFFER_SIZE;
|
return C.DEFAULT_METADATA_BUFFER_SIZE;
|
||||||
case C.TRACK_TYPE_CAMERA_MOTION:
|
case C.TRACK_TYPE_CAMERA_MOTION:
|
||||||
return C.DEFAULT_CAMERA_MOTION_BUFFER_SIZE;
|
return C.DEFAULT_CAMERA_MOTION_BUFFER_SIZE;
|
||||||
|
case C.TRACK_TYPE_NONE:
|
||||||
|
return 0;
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException();
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
private final EventDispatcher eventDispatcher;
|
private final EventDispatcher eventDispatcher;
|
||||||
private final long allowedJoiningTimeMs;
|
private final long allowedJoiningTimeMs;
|
||||||
private final int maxDroppedFramesToNotify;
|
private final int maxDroppedFramesToNotify;
|
||||||
private final boolean deviceNeedsAutoFrcWorkaround;
|
private final boolean deviceNeedsNoPostProcessWorkaround;
|
||||||
private final long[] pendingOutputStreamOffsetsUs;
|
private final long[] pendingOutputStreamOffsetsUs;
|
||||||
private final long[] pendingOutputStreamSwitchTimesUs;
|
private final long[] pendingOutputStreamSwitchTimesUs;
|
||||||
|
|
||||||
|
|
@ -226,7 +226,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
this.context = context.getApplicationContext();
|
this.context = context.getApplicationContext();
|
||||||
frameReleaseTimeHelper = new VideoFrameReleaseTimeHelper(this.context);
|
frameReleaseTimeHelper = new VideoFrameReleaseTimeHelper(this.context);
|
||||||
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
|
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
|
||||||
deviceNeedsAutoFrcWorkaround = deviceNeedsAutoFrcWorkaround();
|
deviceNeedsNoPostProcessWorkaround = deviceNeedsNoPostProcessWorkaround();
|
||||||
pendingOutputStreamOffsetsUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT];
|
pendingOutputStreamOffsetsUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT];
|
||||||
pendingOutputStreamSwitchTimesUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT];
|
pendingOutputStreamSwitchTimesUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT];
|
||||||
outputStreamOffsetUs = C.TIME_UNSET;
|
outputStreamOffsetUs = C.TIME_UNSET;
|
||||||
|
|
@ -471,7 +471,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
format,
|
format,
|
||||||
codecMaxValues,
|
codecMaxValues,
|
||||||
codecOperatingRate,
|
codecOperatingRate,
|
||||||
deviceNeedsAutoFrcWorkaround,
|
deviceNeedsNoPostProcessWorkaround,
|
||||||
tunnelingAudioSessionId);
|
tunnelingAudioSessionId);
|
||||||
if (surface == null) {
|
if (surface == null) {
|
||||||
Assertions.checkState(shouldUseDummySurface(codecInfo));
|
Assertions.checkState(shouldUseDummySurface(codecInfo));
|
||||||
|
|
@ -1027,8 +1027,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
* @param codecMaxValues Codec max values that should be used when configuring the decoder.
|
* @param codecMaxValues Codec max values that should be used when configuring the decoder.
|
||||||
* @param codecOperatingRate The codec operating rate, or {@link #CODEC_OPERATING_RATE_UNSET} if
|
* @param codecOperatingRate The codec operating rate, or {@link #CODEC_OPERATING_RATE_UNSET} if
|
||||||
* no codec operating rate should be set.
|
* no codec operating rate should be set.
|
||||||
* @param deviceNeedsAutoFrcWorkaround Whether the device is known to enable frame-rate conversion
|
* @param deviceNeedsNoPostProcessWorkaround Whether the device is known to do post processing by
|
||||||
* logic that negatively impacts ExoPlayer.
|
* default that isn't compatible with ExoPlayer.
|
||||||
* @param tunnelingAudioSessionId The audio session id to use for tunneling, or {@link
|
* @param tunnelingAudioSessionId The audio session id to use for tunneling, or {@link
|
||||||
* C#AUDIO_SESSION_ID_UNSET} if tunneling should not be enabled.
|
* C#AUDIO_SESSION_ID_UNSET} if tunneling should not be enabled.
|
||||||
* @return The framework {@link MediaFormat} that should be used to configure the decoder.
|
* @return The framework {@link MediaFormat} that should be used to configure the decoder.
|
||||||
|
|
@ -1038,7 +1038,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
Format format,
|
Format format,
|
||||||
CodecMaxValues codecMaxValues,
|
CodecMaxValues codecMaxValues,
|
||||||
float codecOperatingRate,
|
float codecOperatingRate,
|
||||||
boolean deviceNeedsAutoFrcWorkaround,
|
boolean deviceNeedsNoPostProcessWorkaround,
|
||||||
int tunnelingAudioSessionId) {
|
int tunnelingAudioSessionId) {
|
||||||
MediaFormat mediaFormat = new MediaFormat();
|
MediaFormat mediaFormat = new MediaFormat();
|
||||||
// Set format parameters that should always be set.
|
// Set format parameters that should always be set.
|
||||||
|
|
@ -1062,7 +1062,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
mediaFormat.setFloat(MediaFormat.KEY_OPERATING_RATE, codecOperatingRate);
|
mediaFormat.setFloat(MediaFormat.KEY_OPERATING_RATE, codecOperatingRate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (deviceNeedsAutoFrcWorkaround) {
|
if (deviceNeedsNoPostProcessWorkaround) {
|
||||||
|
mediaFormat.setInteger("no-post-process", 1);
|
||||||
mediaFormat.setInteger("auto-frc", 0);
|
mediaFormat.setInteger("auto-frc", 0);
|
||||||
}
|
}
|
||||||
if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) {
|
if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) {
|
||||||
|
|
@ -1256,21 +1257,21 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the device is known to enable frame-rate conversion logic that negatively
|
* Returns whether the device is known to do post processing by default that isn't compatible with
|
||||||
* impacts ExoPlayer.
|
* ExoPlayer.
|
||||||
* <p>
|
|
||||||
* If true is returned then we explicitly disable the feature.
|
|
||||||
*
|
*
|
||||||
* @return True if the device is known to enable frame-rate conversion logic that negatively
|
* @return Whether the device is known to do post processing by default that isn't compatible with
|
||||||
* impacts ExoPlayer. False otherwise.
|
* ExoPlayer.
|
||||||
*/
|
*/
|
||||||
private static boolean deviceNeedsAutoFrcWorkaround() {
|
private static boolean deviceNeedsNoPostProcessWorkaround() {
|
||||||
// nVidia Shield prior to M tries to adjust the playback rate to better map the frame-rate of
|
// Nvidia devices prior to M try to adjust the playback rate to better map the frame-rate of
|
||||||
// content to the refresh rate of the display. For example playback of 23.976fps content is
|
// content to the refresh rate of the display. For example playback of 23.976fps content is
|
||||||
// adjusted to play at 1.001x speed when the output display is 60Hz. Unfortunately the
|
// adjusted to play at 1.001x speed when the output display is 60Hz. Unfortunately the
|
||||||
// implementation causes ExoPlayer's reported playback position to drift out of sync. Captions
|
// implementation causes ExoPlayer's reported playback position to drift out of sync. Captions
|
||||||
// also lose sync [Internal: b/26453592].
|
// also lose sync [Internal: b/26453592]. Even after M, the devices may apply post processing
|
||||||
return Util.SDK_INT <= 22 && "foster".equals(Util.DEVICE) && "NVIDIA".equals(Util.MANUFACTURER);
|
// operations that can modify frame output timestamps, which is incompatible with ExoPlayer's
|
||||||
|
// logic for skipping decode-only frames.
|
||||||
|
return "NVIDIA".equals(Util.MANUFACTURER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -1296,163 +1297,171 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
* incorrectly.
|
* incorrectly.
|
||||||
*/
|
*/
|
||||||
protected boolean codecNeedsSetOutputSurfaceWorkaround(String name) {
|
protected boolean codecNeedsSetOutputSurfaceWorkaround(String name) {
|
||||||
if (Util.SDK_INT >= 27 || name.startsWith("OMX.google")) {
|
if (name.startsWith("OMX.google")) {
|
||||||
// Devices running API level 27 or later should also be unaffected. Google OMX decoders are
|
// Google OMX decoders are not known to have this issue on any API level.
|
||||||
// not known to have this issue on any API level.
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Work around:
|
|
||||||
// https://github.com/google/ExoPlayer/issues/3236,
|
|
||||||
// https://github.com/google/ExoPlayer/issues/3355,
|
|
||||||
// https://github.com/google/ExoPlayer/issues/3439,
|
|
||||||
// https://github.com/google/ExoPlayer/issues/3724,
|
|
||||||
// https://github.com/google/ExoPlayer/issues/3835,
|
|
||||||
// https://github.com/google/ExoPlayer/issues/4006,
|
|
||||||
// https://github.com/google/ExoPlayer/issues/4084,
|
|
||||||
// https://github.com/google/ExoPlayer/issues/4104,
|
|
||||||
// https://github.com/google/ExoPlayer/issues/4134,
|
|
||||||
// https://github.com/google/ExoPlayer/issues/4315,
|
|
||||||
// https://github.com/google/ExoPlayer/issues/4419,
|
|
||||||
// https://github.com/google/ExoPlayer/issues/4460,
|
|
||||||
// https://github.com/google/ExoPlayer/issues/4468.
|
|
||||||
synchronized (MediaCodecVideoRenderer.class) {
|
synchronized (MediaCodecVideoRenderer.class) {
|
||||||
if (!evaluatedDeviceNeedsSetOutputSurfaceWorkaround) {
|
if (!evaluatedDeviceNeedsSetOutputSurfaceWorkaround) {
|
||||||
switch (Util.DEVICE) {
|
if (Util.SDK_INT <= 27 && "dangal".equals(Util.DEVICE)) {
|
||||||
case "1601":
|
// Dangal is affected on API level 27: https://github.com/google/ExoPlayer/issues/5169.
|
||||||
case "1713":
|
deviceNeedsSetOutputSurfaceWorkaround = true;
|
||||||
case "1714":
|
} else if (Util.SDK_INT >= 27) {
|
||||||
case "A10-70F":
|
// In general, devices running API level 27 or later should be unaffected. Do nothing.
|
||||||
case "A1601":
|
} else {
|
||||||
case "A2016a40":
|
// Enable the workaround on a per-device basis. Works around:
|
||||||
case "A7000-a":
|
// https://github.com/google/ExoPlayer/issues/3236,
|
||||||
case "A7000plus":
|
// https://github.com/google/ExoPlayer/issues/3355,
|
||||||
case "A7010a48":
|
// https://github.com/google/ExoPlayer/issues/3439,
|
||||||
case "A7020a48":
|
// https://github.com/google/ExoPlayer/issues/3724,
|
||||||
case "AquaPowerM":
|
// https://github.com/google/ExoPlayer/issues/3835,
|
||||||
case "ASUS_X00AD_2":
|
// https://github.com/google/ExoPlayer/issues/4006,
|
||||||
case "Aura_Note_2":
|
// https://github.com/google/ExoPlayer/issues/4084,
|
||||||
case "BLACK-1X":
|
// https://github.com/google/ExoPlayer/issues/4104,
|
||||||
case "BRAVIA_ATV2":
|
// https://github.com/google/ExoPlayer/issues/4134,
|
||||||
case "C1":
|
// https://github.com/google/ExoPlayer/issues/4315,
|
||||||
case "ComioS1":
|
// https://github.com/google/ExoPlayer/issues/4419,
|
||||||
case "CP8676_I02":
|
// https://github.com/google/ExoPlayer/issues/4460,
|
||||||
case "CPH1609":
|
// https://github.com/google/ExoPlayer/issues/4468.
|
||||||
case "CPY83_I00":
|
switch (Util.DEVICE) {
|
||||||
case "cv1":
|
case "1601":
|
||||||
case "cv3":
|
case "1713":
|
||||||
case "deb":
|
case "1714":
|
||||||
case "E5643":
|
case "A10-70F":
|
||||||
case "ELUGA_A3_Pro":
|
case "A1601":
|
||||||
case "ELUGA_Note":
|
case "A2016a40":
|
||||||
case "ELUGA_Prim":
|
case "A7000-a":
|
||||||
case "ELUGA_Ray_X":
|
case "A7000plus":
|
||||||
case "EverStar_S":
|
case "A7010a48":
|
||||||
case "F3111":
|
case "A7020a48":
|
||||||
case "F3113":
|
case "AquaPowerM":
|
||||||
case "F3116":
|
case "ASUS_X00AD_2":
|
||||||
case "F3211":
|
case "Aura_Note_2":
|
||||||
case "F3213":
|
case "BLACK-1X":
|
||||||
case "F3215":
|
case "BRAVIA_ATV2":
|
||||||
case "F3311":
|
case "BRAVIA_ATV3_4K":
|
||||||
case "flo":
|
case "C1":
|
||||||
case "GiONEE_CBL7513":
|
case "ComioS1":
|
||||||
case "GiONEE_GBL7319":
|
case "CP8676_I02":
|
||||||
case "GIONEE_GBL7360":
|
case "CPH1609":
|
||||||
case "GIONEE_SWW1609":
|
case "CPY83_I00":
|
||||||
case "GIONEE_SWW1627":
|
case "cv1":
|
||||||
case "GIONEE_SWW1631":
|
case "cv3":
|
||||||
case "GIONEE_WBL5708":
|
case "deb":
|
||||||
case "GIONEE_WBL7365":
|
case "E5643":
|
||||||
case "GIONEE_WBL7519":
|
case "ELUGA_A3_Pro":
|
||||||
case "griffin":
|
case "ELUGA_Note":
|
||||||
case "htc_e56ml_dtul":
|
case "ELUGA_Prim":
|
||||||
case "hwALE-H":
|
case "ELUGA_Ray_X":
|
||||||
case "HWBLN-H":
|
case "EverStar_S":
|
||||||
case "HWCAM-H":
|
case "F3111":
|
||||||
case "HWVNS-H":
|
case "F3113":
|
||||||
case "i9031":
|
case "F3116":
|
||||||
case "iball8735_9806":
|
case "F3211":
|
||||||
case "Infinix-X572":
|
case "F3213":
|
||||||
case "iris60":
|
case "F3215":
|
||||||
case "itel_S41":
|
case "F3311":
|
||||||
case "j2xlteins":
|
case "flo":
|
||||||
case "JGZ":
|
case "fugu":
|
||||||
case "K50a40":
|
case "GiONEE_CBL7513":
|
||||||
case "kate":
|
case "GiONEE_GBL7319":
|
||||||
case "le_x6":
|
case "GIONEE_GBL7360":
|
||||||
case "LS-5017":
|
case "GIONEE_SWW1609":
|
||||||
case "M5c":
|
case "GIONEE_SWW1627":
|
||||||
case "manning":
|
case "GIONEE_SWW1631":
|
||||||
case "marino_f":
|
case "GIONEE_WBL5708":
|
||||||
case "MEIZU_M5":
|
case "GIONEE_WBL7365":
|
||||||
case "mh":
|
case "GIONEE_WBL7519":
|
||||||
case "mido":
|
case "griffin":
|
||||||
case "MX6":
|
case "htc_e56ml_dtul":
|
||||||
case "namath":
|
case "hwALE-H":
|
||||||
case "nicklaus_f":
|
case "HWBLN-H":
|
||||||
case "NX541J":
|
case "HWCAM-H":
|
||||||
case "NX573J":
|
case "HWVNS-H":
|
||||||
case "OnePlus5T":
|
case "i9031":
|
||||||
case "p212":
|
case "iball8735_9806":
|
||||||
case "P681":
|
case "Infinix-X572":
|
||||||
case "P85":
|
case "iris60":
|
||||||
case "panell_d":
|
case "itel_S41":
|
||||||
case "panell_dl":
|
case "j2xlteins":
|
||||||
case "panell_ds":
|
case "JGZ":
|
||||||
case "panell_dt":
|
case "K50a40":
|
||||||
case "PB2-670M":
|
case "kate":
|
||||||
case "PGN528":
|
case "le_x6":
|
||||||
case "PGN610":
|
case "LS-5017":
|
||||||
case "PGN611":
|
case "M5c":
|
||||||
case "Phantom6":
|
case "manning":
|
||||||
case "Pixi4-7_3G":
|
case "marino_f":
|
||||||
case "Pixi5-10_4G":
|
case "MEIZU_M5":
|
||||||
case "PLE":
|
case "mh":
|
||||||
case "PRO7S":
|
case "mido":
|
||||||
case "Q350":
|
case "MX6":
|
||||||
case "Q4260":
|
case "namath":
|
||||||
case "Q427":
|
case "nicklaus_f":
|
||||||
case "Q4310":
|
case "NX541J":
|
||||||
case "Q5":
|
case "NX573J":
|
||||||
case "QM16XE_U":
|
case "OnePlus5T":
|
||||||
case "QX1":
|
case "p212":
|
||||||
case "santoni":
|
case "P681":
|
||||||
case "Slate_Pro":
|
case "P85":
|
||||||
case "SVP-DTV15":
|
case "panell_d":
|
||||||
case "s905x018":
|
case "panell_dl":
|
||||||
case "taido_row":
|
case "panell_ds":
|
||||||
case "TB3-730F":
|
case "panell_dt":
|
||||||
case "TB3-730X":
|
case "PB2-670M":
|
||||||
case "TB3-850F":
|
case "PGN528":
|
||||||
case "TB3-850M":
|
case "PGN610":
|
||||||
case "tcl_eu":
|
case "PGN611":
|
||||||
case "V1":
|
case "Phantom6":
|
||||||
case "V23GB":
|
case "Pixi4-7_3G":
|
||||||
case "V5":
|
case "Pixi5-10_4G":
|
||||||
case "vernee_M5":
|
case "PLE":
|
||||||
case "watson":
|
case "PRO7S":
|
||||||
case "whyred":
|
case "Q350":
|
||||||
case "woods_f":
|
case "Q4260":
|
||||||
case "woods_fn":
|
case "Q427":
|
||||||
case "X3_HK":
|
case "Q4310":
|
||||||
case "XE2X":
|
case "Q5":
|
||||||
case "XT1663":
|
case "QM16XE_U":
|
||||||
case "Z12_PRO":
|
case "QX1":
|
||||||
case "Z80":
|
case "santoni":
|
||||||
deviceNeedsSetOutputSurfaceWorkaround = true;
|
case "Slate_Pro":
|
||||||
break;
|
case "SVP-DTV15":
|
||||||
default:
|
case "s905x018":
|
||||||
// Do nothing.
|
case "taido_row":
|
||||||
break;
|
case "TB3-730F":
|
||||||
}
|
case "TB3-730X":
|
||||||
switch (Util.MODEL) {
|
case "TB3-850F":
|
||||||
case "AFTA":
|
case "TB3-850M":
|
||||||
case "AFTN":
|
case "tcl_eu":
|
||||||
deviceNeedsSetOutputSurfaceWorkaround = true;
|
case "V1":
|
||||||
break;
|
case "V23GB":
|
||||||
default:
|
case "V5":
|
||||||
// Do nothing.
|
case "vernee_M5":
|
||||||
break;
|
case "watson":
|
||||||
|
case "whyred":
|
||||||
|
case "woods_f":
|
||||||
|
case "woods_fn":
|
||||||
|
case "X3_HK":
|
||||||
|
case "XE2X":
|
||||||
|
case "XT1663":
|
||||||
|
case "Z12_PRO":
|
||||||
|
case "Z80":
|
||||||
|
deviceNeedsSetOutputSurfaceWorkaround = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Do nothing.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch (Util.MODEL) {
|
||||||
|
case "AFTA":
|
||||||
|
case "AFTN":
|
||||||
|
deviceNeedsSetOutputSurfaceWorkaround = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Do nothing.
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
evaluatedDeviceNeedsSetOutputSurfaceWorkaround = true;
|
evaluatedDeviceNeedsSetOutputSurfaceWorkaround = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@ import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listener of video {@link Renderer} events.
|
* Listener of video {@link Renderer} events. All methods have no-op default implementations to
|
||||||
|
* allow selective overrides.
|
||||||
*/
|
*/
|
||||||
public interface VideoRendererEventListener {
|
public interface VideoRendererEventListener {
|
||||||
|
|
||||||
|
|
@ -36,7 +37,7 @@ public interface VideoRendererEventListener {
|
||||||
* @param counters {@link DecoderCounters} that will be updated by the renderer for as long as it
|
* @param counters {@link DecoderCounters} that will be updated by the renderer for as long as it
|
||||||
* remains enabled.
|
* remains enabled.
|
||||||
*/
|
*/
|
||||||
void onVideoEnabled(DecoderCounters counters);
|
default void onVideoEnabled(DecoderCounters counters) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a decoder is created.
|
* Called when a decoder is created.
|
||||||
|
|
@ -46,15 +47,15 @@ public interface VideoRendererEventListener {
|
||||||
* finished.
|
* finished.
|
||||||
* @param initializationDurationMs The time taken to initialize the decoder in milliseconds.
|
* @param initializationDurationMs The time taken to initialize the decoder in milliseconds.
|
||||||
*/
|
*/
|
||||||
void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs,
|
default void onVideoDecoderInitialized(
|
||||||
long initializationDurationMs);
|
String decoderName, long initializedTimestampMs, long initializationDurationMs) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the format of the media being consumed by the renderer changes.
|
* Called when the format of the media being consumed by the renderer changes.
|
||||||
*
|
*
|
||||||
* @param format The new format.
|
* @param format The new format.
|
||||||
*/
|
*/
|
||||||
void onVideoInputFormatChanged(Format format);
|
default void onVideoInputFormatChanged(Format format) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to report the number of frames dropped by the renderer. Dropped frames are reported
|
* Called to report the number of frames dropped by the renderer. Dropped frames are reported
|
||||||
|
|
@ -62,12 +63,11 @@ public interface VideoRendererEventListener {
|
||||||
* reaches a specified threshold whilst the renderer is started.
|
* reaches a specified threshold whilst the renderer is started.
|
||||||
*
|
*
|
||||||
* @param count The number of dropped frames.
|
* @param count The number of dropped frames.
|
||||||
* @param elapsedMs The duration in milliseconds over which the frames were dropped. This
|
* @param elapsedMs The duration in milliseconds over which the frames were dropped. This duration
|
||||||
* duration is timed from when the renderer was started or from when dropped frames were
|
* is timed from when the renderer was started or from when dropped frames were last reported
|
||||||
* last reported (whichever was more recent), and not from when the first of the reported
|
* (whichever was more recent), and not from when the first of the reported drops occurred.
|
||||||
* drops occurred.
|
|
||||||
*/
|
*/
|
||||||
void onDroppedFrames(int count, long elapsedMs);
|
default void onDroppedFrames(int count, long elapsedMs) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called before a frame is rendered for the first time since setting the surface, and each time
|
* Called before a frame is rendered for the first time since setting the surface, and each time
|
||||||
|
|
@ -82,12 +82,12 @@ public interface VideoRendererEventListener {
|
||||||
* this is not possible. Applications that use {@link TextureView} can apply the rotation by
|
* this is not possible. Applications that use {@link TextureView} can apply the rotation by
|
||||||
* calling {@link TextureView#setTransform}. Applications that do not expect to encounter
|
* calling {@link TextureView#setTransform}. Applications that do not expect to encounter
|
||||||
* rotated videos can safely ignore this parameter.
|
* rotated videos can safely ignore this parameter.
|
||||||
* @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case
|
* @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case of
|
||||||
* of square pixels this will be equal to 1.0. Different values are indicative of anamorphic
|
* square pixels this will be equal to 1.0. Different values are indicative of anamorphic
|
||||||
* content.
|
* content.
|
||||||
*/
|
*/
|
||||||
void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
|
default void onVideoSizeChanged(
|
||||||
float pixelWidthHeightRatio);
|
int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a frame is rendered for the first time since setting the surface, and when a frame
|
* Called when a frame is rendered for the first time since setting the surface, and when a frame
|
||||||
|
|
@ -96,14 +96,14 @@ public interface VideoRendererEventListener {
|
||||||
* @param surface The {@link Surface} to which a first frame has been rendered, or {@code null} if
|
* @param surface The {@link Surface} to which a first frame has been rendered, or {@code null} if
|
||||||
* the renderer renders to something that isn't a {@link Surface}.
|
* the renderer renders to something that isn't a {@link Surface}.
|
||||||
*/
|
*/
|
||||||
void onRenderedFirstFrame(@Nullable Surface surface);
|
default void onRenderedFirstFrame(@Nullable Surface surface) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the renderer is disabled.
|
* Called when the renderer is disabled.
|
||||||
*
|
*
|
||||||
* @param counters {@link DecoderCounters} that were updated by the renderer.
|
* @param counters {@link DecoderCounters} that were updated by the renderer.
|
||||||
*/
|
*/
|
||||||
void onVideoDisabled(DecoderCounters counters);
|
default void onVideoDisabled(DecoderCounters counters) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatches events to a {@link VideoRendererEventListener}.
|
* Dispatches events to a {@link VideoRendererEventListener}.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
seekMap:
|
seekMap:
|
||||||
isSeekable = true
|
isSeekable = true
|
||||||
duration = 26125
|
duration = 26122
|
||||||
getPosition(0) = [[timeUs=0, position=0]]
|
getPosition(0) = [[timeUs=0, position=0]]
|
||||||
numberOfTracks = 1
|
numberOfTracks = 1
|
||||||
track 0:
|
track 0:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
seekMap:
|
seekMap:
|
||||||
isSeekable = true
|
isSeekable = true
|
||||||
duration = 26125
|
duration = 26122
|
||||||
getPosition(0) = [[timeUs=0, position=0]]
|
getPosition(0) = [[timeUs=0, position=0]]
|
||||||
numberOfTracks = 1
|
numberOfTracks = 1
|
||||||
track 0:
|
track 0:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
seekMap:
|
seekMap:
|
||||||
isSeekable = true
|
isSeekable = true
|
||||||
duration = 26125
|
duration = 26122
|
||||||
getPosition(0) = [[timeUs=0, position=0]]
|
getPosition(0) = [[timeUs=0, position=0]]
|
||||||
numberOfTracks = 1
|
numberOfTracks = 1
|
||||||
track 0:
|
track 0:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
seekMap:
|
seekMap:
|
||||||
isSeekable = true
|
isSeekable = true
|
||||||
duration = 26125
|
duration = 26122
|
||||||
getPosition(0) = [[timeUs=0, position=0]]
|
getPosition(0) = [[timeUs=0, position=0]]
|
||||||
numberOfTracks = 1
|
numberOfTracks = 1
|
||||||
track 0:
|
track 0:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<tt xmlns="http://www.w3.org/ns/ttml" xmlns:ttm="http://www.w3.org/ns/ttml#metadata" xmlns:tts="http://www.w3.org/ns/ttml#styling" xmlns:smpte="http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt" xml:lang="eng">
|
||||||
|
<head>
|
||||||
|
<metadata>
|
||||||
|
<smpte:image imagetype="PNG" encoding="Base64" xml:id="img_0">
|
||||||
|
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
|
||||||
|
</smpte:image>
|
||||||
|
<smpte:image imagetype="PNG" encoding="Base64" xml:id="img_1">
|
||||||
|
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
|
||||||
|
</smpte:image>
|
||||||
|
</metadata>
|
||||||
|
<styling>
|
||||||
|
<style/>
|
||||||
|
</styling>
|
||||||
|
<layout>
|
||||||
|
<region xml:id="region_0" tts:extent="51% 12%" tts:origin="24% 78%"/>
|
||||||
|
<region xml:id="region_1" tts:extent="57% 6%" tts:origin="21% 85%"/>
|
||||||
|
<region xml:id="region_2" tts:extent="51% 12%" tts:origin="24% 28%"/>
|
||||||
|
<region xml:id="region_3" tts:extent="57% 6%" tts:origin="21% 35%"/>
|
||||||
|
</layout>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div begin="00:00:00.200" end="00:00:03.000" region="region_2" smpte:backgroundImage="#img_0"/>
|
||||||
|
<div begin="00:00:03.200" end="00:00:06.937" region="region_3" smpte:backgroundImage="#img_1"/>
|
||||||
|
<div begin="00:00:07.200" end="00:59:03.000" region="region_2" smpte:backgroundImage="#img_0"/>
|
||||||
|
</body>
|
||||||
|
</tt>
|
||||||
23
library/core/src/test/assets/ttml/bitmap_pixel_region.xml
Normal file
23
library/core/src/test/assets/ttml/bitmap_pixel_region.xml
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<tt xmlns="http://www.w3.org/ns/ttml" xmlns:ttm="http://www.w3.org/ns/ttml#metadata" xmlns:tts="http://www.w3.org/ns/ttml#styling" xmlns:smpte="http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt" xml:lang="eng" tts:extent="1280px 720px">
|
||||||
|
<head>
|
||||||
|
<metadata>
|
||||||
|
<smpte:image imagetype="PNG" encoding="Base64" xml:id="img_0">
|
||||||
|
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
|
||||||
|
</smpte:image>
|
||||||
|
<smpte:image imagetype="PNG" encoding="Base64" xml:id="img_1">
|
||||||
|
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
|
||||||
|
</smpte:image>
|
||||||
|
</metadata>
|
||||||
|
<styling>
|
||||||
|
<style/>
|
||||||
|
</styling>
|
||||||
|
<layout>
|
||||||
|
<region xml:id="region_0" tts:extent="653px 86px" tts:origin="307px 562px"/>
|
||||||
|
<region xml:id="region_1" tts:extent="730px 43px" tts:origin="269px 612px"/>
|
||||||
|
</layout>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div begin="00:00:00.200" end="00:00:03.000" region="region_0" smpte:backgroundImage="#img_0"/>
|
||||||
|
<div begin="00:00:03.200" end="00:00:06.937" region="region_1" smpte:backgroundImage="#img_1"/>
|
||||||
|
</body>
|
||||||
|
</tt>
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
<tt xmlns="http://www.w3.org/ns/ttml" xmlns:ttm="http://www.w3.org/ns/ttml#metadata" xmlns:tts="http://www.w3.org/ns/ttml#styling" xmlns:smpte="http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt" xml:lang="eng">
|
||||||
|
<head>
|
||||||
|
<metadata>
|
||||||
|
<smpte:image imagetype="PNG" encoding="Base64" xml:id="img_0">
|
||||||
|
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
|
||||||
|
</smpte:image>
|
||||||
|
<smpte:image imagetype="PNG" encoding="Base64" xml:id="img_1">
|
||||||
|
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
|
||||||
|
</smpte:image>
|
||||||
|
</metadata>
|
||||||
|
<styling>
|
||||||
|
<style/>
|
||||||
|
</styling>
|
||||||
|
<layout>
|
||||||
|
<region xml:id="region_0" tts:extent="653px 86px" tts:origin="307px 562px"/>
|
||||||
|
<region xml:id="region_1" tts:extent="730px 43px" tts:origin="269px 612px"/>
|
||||||
|
</layout>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div begin="00:00:00.200" end="00:00:03.000" region="region_0" smpte:backgroundImage="#img_0"/>
|
||||||
|
<div begin="00:00:03.200" end="00:00:06.937" region="region_1" smpte:backgroundImage="#img_1"/>
|
||||||
|
</body>
|
||||||
|
</tt>
|
||||||
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.extractor.ts;
|
package com.google.android.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.extractor.ts.TsPayloadReader.FLAG_DATA_ALIGNMENT_INDICATOR;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ParserException;
|
import com.google.android.exoplayer2.ParserException;
|
||||||
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
||||||
|
|
@ -198,7 +200,7 @@ public class AdtsReaderTest {
|
||||||
|
|
||||||
private void maybeStartPacket() {
|
private void maybeStartPacket() {
|
||||||
if (firstFeed) {
|
if (firstFeed) {
|
||||||
adtsReader.packetStarted(0, true);
|
adtsReader.packetStarted(0, FLAG_DATA_ALIGNMENT_INDICATOR);
|
||||||
firstFeed = false;
|
firstFeed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.extractor.ts;
|
package com.google.android.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.extractor.ts.TsPayloadReader.FLAG_PAYLOAD_UNIT_START_INDICATOR;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
|
|
@ -55,7 +56,7 @@ public final class SectionReaderTest {
|
||||||
public void testSingleOnePacketSection() {
|
public void testSingleOnePacketSection() {
|
||||||
packetPayload[0] = 3;
|
packetPayload[0] = 3;
|
||||||
insertTableSection(4, (byte) 99, 3);
|
insertTableSection(4, (byte) 99, 3);
|
||||||
reader.consume(new ParsableByteArray(packetPayload), true);
|
reader.consume(new ParsableByteArray(packetPayload), FLAG_PAYLOAD_UNIT_START_INDICATOR);
|
||||||
assertThat(payloadReader.parsedTableIds).isEqualTo(singletonList(99));
|
assertThat(payloadReader.parsedTableIds).isEqualTo(singletonList(99));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,12 +66,12 @@ public final class SectionReaderTest {
|
||||||
insertTableSection(4, (byte) 100, 3); // This section header spreads across both packets.
|
insertTableSection(4, (byte) 100, 3); // This section header spreads across both packets.
|
||||||
|
|
||||||
ParsableByteArray firstPacket = new ParsableByteArray(packetPayload, 5);
|
ParsableByteArray firstPacket = new ParsableByteArray(packetPayload, 5);
|
||||||
reader.consume(firstPacket, true);
|
reader.consume(firstPacket, FLAG_PAYLOAD_UNIT_START_INDICATOR);
|
||||||
assertThat(payloadReader.parsedTableIds).isEmpty();
|
assertThat(payloadReader.parsedTableIds).isEmpty();
|
||||||
|
|
||||||
ParsableByteArray secondPacket = new ParsableByteArray(packetPayload);
|
ParsableByteArray secondPacket = new ParsableByteArray(packetPayload);
|
||||||
secondPacket.setPosition(5);
|
secondPacket.setPosition(5);
|
||||||
reader.consume(secondPacket, false);
|
reader.consume(secondPacket, /* flags= */ 0);
|
||||||
assertThat(payloadReader.parsedTableIds).isEqualTo(singletonList(100));
|
assertThat(payloadReader.parsedTableIds).isEqualTo(singletonList(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,12 +86,12 @@ public final class SectionReaderTest {
|
||||||
insertTableSection(54, (byte) 105, 10);
|
insertTableSection(54, (byte) 105, 10);
|
||||||
|
|
||||||
ParsableByteArray firstPacket = new ParsableByteArray(packetPayload, 40);
|
ParsableByteArray firstPacket = new ParsableByteArray(packetPayload, 40);
|
||||||
reader.consume(firstPacket, true);
|
reader.consume(firstPacket, FLAG_PAYLOAD_UNIT_START_INDICATOR);
|
||||||
assertThat(payloadReader.parsedTableIds).isEqualTo(asList(101, 102, 103));
|
assertThat(payloadReader.parsedTableIds).isEqualTo(asList(101, 102, 103));
|
||||||
|
|
||||||
ParsableByteArray secondPacket = new ParsableByteArray(packetPayload);
|
ParsableByteArray secondPacket = new ParsableByteArray(packetPayload);
|
||||||
secondPacket.setPosition(40);
|
secondPacket.setPosition(40);
|
||||||
reader.consume(secondPacket, true);
|
reader.consume(secondPacket, FLAG_PAYLOAD_UNIT_START_INDICATOR);
|
||||||
assertThat(payloadReader.parsedTableIds).isEqualTo(asList(101, 102, 103, 104, 105));
|
assertThat(payloadReader.parsedTableIds).isEqualTo(asList(101, 102, 103, 104, 105));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,22 +106,22 @@ public final class SectionReaderTest {
|
||||||
insertTableSection(318, (byte) 108, 10);
|
insertTableSection(318, (byte) 108, 10);
|
||||||
|
|
||||||
ParsableByteArray firstPacket = new ParsableByteArray(packetPayload, 100);
|
ParsableByteArray firstPacket = new ParsableByteArray(packetPayload, 100);
|
||||||
reader.consume(firstPacket, true);
|
reader.consume(firstPacket, FLAG_PAYLOAD_UNIT_START_INDICATOR);
|
||||||
assertThat(payloadReader.parsedTableIds).isEmpty();
|
assertThat(payloadReader.parsedTableIds).isEmpty();
|
||||||
|
|
||||||
ParsableByteArray secondPacket = new ParsableByteArray(packetPayload, 200);
|
ParsableByteArray secondPacket = new ParsableByteArray(packetPayload, 200);
|
||||||
secondPacket.setPosition(100);
|
secondPacket.setPosition(100);
|
||||||
reader.consume(secondPacket, false);
|
reader.consume(secondPacket, /* flags= */ 0);
|
||||||
assertThat(payloadReader.parsedTableIds).isEmpty();
|
assertThat(payloadReader.parsedTableIds).isEmpty();
|
||||||
|
|
||||||
ParsableByteArray thirdPacket = new ParsableByteArray(packetPayload, 300);
|
ParsableByteArray thirdPacket = new ParsableByteArray(packetPayload, 300);
|
||||||
thirdPacket.setPosition(200);
|
thirdPacket.setPosition(200);
|
||||||
reader.consume(thirdPacket, false);
|
reader.consume(thirdPacket, /* flags= */ 0);
|
||||||
assertThat(payloadReader.parsedTableIds).isEmpty();
|
assertThat(payloadReader.parsedTableIds).isEmpty();
|
||||||
|
|
||||||
ParsableByteArray fourthPacket = new ParsableByteArray(packetPayload);
|
ParsableByteArray fourthPacket = new ParsableByteArray(packetPayload);
|
||||||
fourthPacket.setPosition(300);
|
fourthPacket.setPosition(300);
|
||||||
reader.consume(fourthPacket, true);
|
reader.consume(fourthPacket, FLAG_PAYLOAD_UNIT_START_INDICATOR);
|
||||||
assertThat(payloadReader.parsedTableIds).isEqualTo(asList(107, 108));
|
assertThat(payloadReader.parsedTableIds).isEqualTo(asList(107, 108));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,24 +136,24 @@ public final class SectionReaderTest {
|
||||||
insertTableSection(318, (byte) 111, 10);
|
insertTableSection(318, (byte) 111, 10);
|
||||||
|
|
||||||
ParsableByteArray firstPacket = new ParsableByteArray(packetPayload, 100);
|
ParsableByteArray firstPacket = new ParsableByteArray(packetPayload, 100);
|
||||||
reader.consume(firstPacket, true);
|
reader.consume(firstPacket, FLAG_PAYLOAD_UNIT_START_INDICATOR);
|
||||||
assertThat(payloadReader.parsedTableIds).isEmpty();
|
assertThat(payloadReader.parsedTableIds).isEmpty();
|
||||||
|
|
||||||
ParsableByteArray secondPacket = new ParsableByteArray(packetPayload, 200);
|
ParsableByteArray secondPacket = new ParsableByteArray(packetPayload, 200);
|
||||||
secondPacket.setPosition(100);
|
secondPacket.setPosition(100);
|
||||||
reader.consume(secondPacket, false);
|
reader.consume(secondPacket, /* flags= */ 0);
|
||||||
assertThat(payloadReader.parsedTableIds).isEmpty();
|
assertThat(payloadReader.parsedTableIds).isEmpty();
|
||||||
|
|
||||||
ParsableByteArray thirdPacket = new ParsableByteArray(packetPayload, 300);
|
ParsableByteArray thirdPacket = new ParsableByteArray(packetPayload, 300);
|
||||||
thirdPacket.setPosition(200);
|
thirdPacket.setPosition(200);
|
||||||
reader.consume(thirdPacket, false);
|
reader.consume(thirdPacket, /* flags= */ 0);
|
||||||
assertThat(payloadReader.parsedTableIds).isEmpty();
|
assertThat(payloadReader.parsedTableIds).isEmpty();
|
||||||
|
|
||||||
reader.seek();
|
reader.seek();
|
||||||
|
|
||||||
ParsableByteArray fourthPacket = new ParsableByteArray(packetPayload);
|
ParsableByteArray fourthPacket = new ParsableByteArray(packetPayload);
|
||||||
fourthPacket.setPosition(300);
|
fourthPacket.setPosition(300);
|
||||||
reader.consume(fourthPacket, true);
|
reader.consume(fourthPacket, FLAG_PAYLOAD_UNIT_START_INDICATOR);
|
||||||
assertThat(payloadReader.parsedTableIds).isEqualTo(singletonList(111));
|
assertThat(payloadReader.parsedTableIds).isEqualTo(singletonList(111));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,9 +166,9 @@ public final class SectionReaderTest {
|
||||||
byte[] incorrectCrcPat = Arrays.copyOf(correctCrcPat, correctCrcPat.length);
|
byte[] incorrectCrcPat = Arrays.copyOf(correctCrcPat, correctCrcPat.length);
|
||||||
// Crc field is incorrect, and should not be passed to the payload reader.
|
// Crc field is incorrect, and should not be passed to the payload reader.
|
||||||
incorrectCrcPat[16]--;
|
incorrectCrcPat[16]--;
|
||||||
reader.consume(new ParsableByteArray(correctCrcPat), true);
|
reader.consume(new ParsableByteArray(correctCrcPat), FLAG_PAYLOAD_UNIT_START_INDICATOR);
|
||||||
assertThat(payloadReader.parsedTableIds).isEqualTo(singletonList(0));
|
assertThat(payloadReader.parsedTableIds).isEqualTo(singletonList(0));
|
||||||
reader.consume(new ParsableByteArray(incorrectCrcPat), true);
|
reader.consume(new ParsableByteArray(incorrectCrcPat), FLAG_PAYLOAD_UNIT_START_INDICATOR);
|
||||||
assertThat(payloadReader.parsedTableIds).isEqualTo(singletonList(0));
|
assertThat(payloadReader.parsedTableIds).isEqualTo(singletonList(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -202,7 +202,7 @@ public final class TsExtractorTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {}
|
public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(ParsableByteArray data) {}
|
public void consume(ParsableByteArray data) {}
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,9 @@ public final class TtmlDecoderTest {
|
||||||
private static final String FONT_SIZE_INVALID_TTML_FILE = "ttml/font_size_invalid.xml";
|
private static final String FONT_SIZE_INVALID_TTML_FILE = "ttml/font_size_invalid.xml";
|
||||||
private static final String FONT_SIZE_EMPTY_TTML_FILE = "ttml/font_size_empty.xml";
|
private static final String FONT_SIZE_EMPTY_TTML_FILE = "ttml/font_size_empty.xml";
|
||||||
private static final String FRAME_RATE_TTML_FILE = "ttml/frame_rate.xml";
|
private static final String FRAME_RATE_TTML_FILE = "ttml/frame_rate.xml";
|
||||||
|
private static final String BITMAP_REGION_FILE = "ttml/bitmap_percentage_region.xml";
|
||||||
|
private static final String BITMAP_PIXEL_REGION_FILE = "ttml/bitmap_pixel_region.xml";
|
||||||
|
private static final String BITMAP_UNSUPPORTED_REGION_FILE = "ttml/bitmap_unsupported_region.xml";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInlineAttributes() throws IOException, SubtitleDecoderException {
|
public void testInlineAttributes() throws IOException, SubtitleDecoderException {
|
||||||
|
|
@ -259,56 +262,56 @@ public final class TtmlDecoderTest {
|
||||||
@Test
|
@Test
|
||||||
public void testMultipleRegions() throws IOException, SubtitleDecoderException {
|
public void testMultipleRegions() throws IOException, SubtitleDecoderException {
|
||||||
TtmlSubtitle subtitle = getSubtitle(MULTIPLE_REGIONS_TTML_FILE);
|
TtmlSubtitle subtitle = getSubtitle(MULTIPLE_REGIONS_TTML_FILE);
|
||||||
List<Cue> output = subtitle.getCues(1000000);
|
List<Cue> cues = subtitle.getCues(1000000);
|
||||||
assertThat(output).hasSize(2);
|
assertThat(cues).hasSize(2);
|
||||||
Cue ttmlCue = output.get(0);
|
Cue cue = cues.get(0);
|
||||||
assertThat(ttmlCue.text.toString()).isEqualTo("lorem");
|
assertThat(cue.text.toString()).isEqualTo("lorem");
|
||||||
assertThat(ttmlCue.position).isEqualTo(10f / 100f);
|
assertThat(cue.position).isEqualTo(10f / 100f);
|
||||||
assertThat(ttmlCue.line).isEqualTo(10f / 100f);
|
assertThat(cue.line).isEqualTo(10f / 100f);
|
||||||
assertThat(ttmlCue.size).isEqualTo(20f / 100f);
|
assertThat(cue.size).isEqualTo(20f / 100f);
|
||||||
|
|
||||||
ttmlCue = output.get(1);
|
cue = cues.get(1);
|
||||||
assertThat(ttmlCue.text.toString()).isEqualTo("amet");
|
assertThat(cue.text.toString()).isEqualTo("amet");
|
||||||
assertThat(ttmlCue.position).isEqualTo(60f / 100f);
|
assertThat(cue.position).isEqualTo(60f / 100f);
|
||||||
assertThat(ttmlCue.line).isEqualTo(10f / 100f);
|
assertThat(cue.line).isEqualTo(10f / 100f);
|
||||||
assertThat(ttmlCue.size).isEqualTo(20f / 100f);
|
assertThat(cue.size).isEqualTo(20f / 100f);
|
||||||
|
|
||||||
output = subtitle.getCues(5000000);
|
cues = subtitle.getCues(5000000);
|
||||||
assertThat(output).hasSize(1);
|
assertThat(cues).hasSize(1);
|
||||||
ttmlCue = output.get(0);
|
cue = cues.get(0);
|
||||||
assertThat(ttmlCue.text.toString()).isEqualTo("ipsum");
|
assertThat(cue.text.toString()).isEqualTo("ipsum");
|
||||||
assertThat(ttmlCue.position).isEqualTo(40f / 100f);
|
assertThat(cue.position).isEqualTo(40f / 100f);
|
||||||
assertThat(ttmlCue.line).isEqualTo(40f / 100f);
|
assertThat(cue.line).isEqualTo(40f / 100f);
|
||||||
assertThat(ttmlCue.size).isEqualTo(20f / 100f);
|
assertThat(cue.size).isEqualTo(20f / 100f);
|
||||||
|
|
||||||
output = subtitle.getCues(9000000);
|
cues = subtitle.getCues(9000000);
|
||||||
assertThat(output).hasSize(1);
|
assertThat(cues).hasSize(1);
|
||||||
ttmlCue = output.get(0);
|
cue = cues.get(0);
|
||||||
assertThat(ttmlCue.text.toString()).isEqualTo("dolor");
|
assertThat(cue.text.toString()).isEqualTo("dolor");
|
||||||
assertThat(ttmlCue.position).isEqualTo(Cue.DIMEN_UNSET);
|
assertThat(cue.position).isEqualTo(Cue.DIMEN_UNSET);
|
||||||
assertThat(ttmlCue.line).isEqualTo(Cue.DIMEN_UNSET);
|
assertThat(cue.line).isEqualTo(Cue.DIMEN_UNSET);
|
||||||
assertThat(ttmlCue.size).isEqualTo(Cue.DIMEN_UNSET);
|
assertThat(cue.size).isEqualTo(Cue.DIMEN_UNSET);
|
||||||
// TODO: Should be as below, once https://github.com/google/ExoPlayer/issues/2953 is fixed.
|
// TODO: Should be as below, once https://github.com/google/ExoPlayer/issues/2953 is fixed.
|
||||||
// assertEquals(10f / 100f, ttmlCue.position);
|
// assertEquals(10f / 100f, cue.position);
|
||||||
// assertEquals(80f / 100f, ttmlCue.line);
|
// assertEquals(80f / 100f, cue.line);
|
||||||
// assertEquals(1f, ttmlCue.size);
|
// assertEquals(1f, cue.size);
|
||||||
|
|
||||||
output = subtitle.getCues(21000000);
|
cues = subtitle.getCues(21000000);
|
||||||
assertThat(output).hasSize(1);
|
assertThat(cues).hasSize(1);
|
||||||
ttmlCue = output.get(0);
|
cue = cues.get(0);
|
||||||
assertThat(ttmlCue.text.toString()).isEqualTo("She first said this");
|
assertThat(cue.text.toString()).isEqualTo("She first said this");
|
||||||
assertThat(ttmlCue.position).isEqualTo(45f / 100f);
|
assertThat(cue.position).isEqualTo(45f / 100f);
|
||||||
assertThat(ttmlCue.line).isEqualTo(45f / 100f);
|
assertThat(cue.line).isEqualTo(45f / 100f);
|
||||||
assertThat(ttmlCue.size).isEqualTo(35f / 100f);
|
assertThat(cue.size).isEqualTo(35f / 100f);
|
||||||
output = subtitle.getCues(25000000);
|
cues = subtitle.getCues(25000000);
|
||||||
ttmlCue = output.get(0);
|
cue = cues.get(0);
|
||||||
assertThat(ttmlCue.text.toString()).isEqualTo("She first said this\nThen this");
|
assertThat(cue.text.toString()).isEqualTo("She first said this\nThen this");
|
||||||
output = subtitle.getCues(29000000);
|
cues = subtitle.getCues(29000000);
|
||||||
assertThat(output).hasSize(1);
|
assertThat(cues).hasSize(1);
|
||||||
ttmlCue = output.get(0);
|
cue = cues.get(0);
|
||||||
assertThat(ttmlCue.text.toString()).isEqualTo("She first said this\nThen this\nFinally this");
|
assertThat(cue.text.toString()).isEqualTo("She first said this\nThen this\nFinally this");
|
||||||
assertThat(ttmlCue.position).isEqualTo(45f / 100f);
|
assertThat(cue.position).isEqualTo(45f / 100f);
|
||||||
assertThat(ttmlCue.line).isEqualTo(45f / 100f);
|
assertThat(cue.line).isEqualTo(45f / 100f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -499,6 +502,91 @@ public final class TtmlDecoderTest {
|
||||||
assertThat((double) subtitle.getEventTime(3)).isWithin(2000).of(2_002_000_000);
|
assertThat((double) subtitle.getEventTime(3)).isWithin(2000).of(2_002_000_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBitmapPercentageRegion() throws IOException, SubtitleDecoderException {
|
||||||
|
TtmlSubtitle subtitle = getSubtitle(BITMAP_REGION_FILE);
|
||||||
|
|
||||||
|
List<Cue> cues = subtitle.getCues(1000000);
|
||||||
|
assertThat(cues).hasSize(1);
|
||||||
|
Cue cue = cues.get(0);
|
||||||
|
assertThat(cue.text).isNull();
|
||||||
|
assertThat(cue.bitmap).isNotNull();
|
||||||
|
assertThat(cue.position).isEqualTo(24f / 100f);
|
||||||
|
assertThat(cue.line).isEqualTo(28f / 100f);
|
||||||
|
assertThat(cue.size).isEqualTo(51f / 100f);
|
||||||
|
assertThat(cue.bitmapHeight).isEqualTo(Cue.DIMEN_UNSET);
|
||||||
|
|
||||||
|
cues = subtitle.getCues(4000000);
|
||||||
|
assertThat(cues).hasSize(1);
|
||||||
|
cue = cues.get(0);
|
||||||
|
assertThat(cue.text).isNull();
|
||||||
|
assertThat(cue.bitmap).isNotNull();
|
||||||
|
assertThat(cue.position).isEqualTo(21f / 100f);
|
||||||
|
assertThat(cue.line).isEqualTo(35f / 100f);
|
||||||
|
assertThat(cue.size).isEqualTo(57f / 100f);
|
||||||
|
assertThat(cue.bitmapHeight).isEqualTo(Cue.DIMEN_UNSET);
|
||||||
|
|
||||||
|
cues = subtitle.getCues(7500000);
|
||||||
|
assertThat(cues).hasSize(1);
|
||||||
|
cue = cues.get(0);
|
||||||
|
assertThat(cue.text).isNull();
|
||||||
|
assertThat(cue.bitmap).isNotNull();
|
||||||
|
assertThat(cue.position).isEqualTo(24f / 100f);
|
||||||
|
assertThat(cue.line).isEqualTo(28f / 100f);
|
||||||
|
assertThat(cue.size).isEqualTo(51f / 100f);
|
||||||
|
assertThat(cue.bitmapHeight).isEqualTo(Cue.DIMEN_UNSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBitmapPixelRegion() throws IOException, SubtitleDecoderException {
|
||||||
|
TtmlSubtitle subtitle = getSubtitle(BITMAP_PIXEL_REGION_FILE);
|
||||||
|
|
||||||
|
List<Cue> cues = subtitle.getCues(1000000);
|
||||||
|
assertThat(cues).hasSize(1);
|
||||||
|
Cue cue = cues.get(0);
|
||||||
|
assertThat(cue.text).isNull();
|
||||||
|
assertThat(cue.bitmap).isNotNull();
|
||||||
|
assertThat(cue.position).isEqualTo(307f / 1280f);
|
||||||
|
assertThat(cue.line).isEqualTo(562f / 720f);
|
||||||
|
assertThat(cue.size).isEqualTo(653f / 1280f);
|
||||||
|
assertThat(cue.bitmapHeight).isEqualTo(Cue.DIMEN_UNSET);
|
||||||
|
|
||||||
|
cues = subtitle.getCues(4000000);
|
||||||
|
assertThat(cues).hasSize(1);
|
||||||
|
cue = cues.get(0);
|
||||||
|
assertThat(cue.text).isNull();
|
||||||
|
assertThat(cue.bitmap).isNotNull();
|
||||||
|
assertThat(cue.position).isEqualTo(269f / 1280f);
|
||||||
|
assertThat(cue.line).isEqualTo(612f / 720f);
|
||||||
|
assertThat(cue.size).isEqualTo(730f / 1280f);
|
||||||
|
assertThat(cue.bitmapHeight).isEqualTo(Cue.DIMEN_UNSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBitmapUnsupportedRegion() throws IOException, SubtitleDecoderException {
|
||||||
|
TtmlSubtitle subtitle = getSubtitle(BITMAP_UNSUPPORTED_REGION_FILE);
|
||||||
|
|
||||||
|
List<Cue> cues = subtitle.getCues(1000000);
|
||||||
|
assertThat(cues).hasSize(1);
|
||||||
|
Cue cue = cues.get(0);
|
||||||
|
assertThat(cue.text).isNull();
|
||||||
|
assertThat(cue.bitmap).isNotNull();
|
||||||
|
assertThat(cue.position).isEqualTo(Cue.DIMEN_UNSET);
|
||||||
|
assertThat(cue.line).isEqualTo(Cue.DIMEN_UNSET);
|
||||||
|
assertThat(cue.size).isEqualTo(Cue.DIMEN_UNSET);
|
||||||
|
assertThat(cue.bitmapHeight).isEqualTo(Cue.DIMEN_UNSET);
|
||||||
|
|
||||||
|
cues = subtitle.getCues(4000000);
|
||||||
|
assertThat(cues).hasSize(1);
|
||||||
|
cue = cues.get(0);
|
||||||
|
assertThat(cue.text).isNull();
|
||||||
|
assertThat(cue.bitmap).isNotNull();
|
||||||
|
assertThat(cue.position).isEqualTo(Cue.DIMEN_UNSET);
|
||||||
|
assertThat(cue.line).isEqualTo(Cue.DIMEN_UNSET);
|
||||||
|
assertThat(cue.size).isEqualTo(Cue.DIMEN_UNSET);
|
||||||
|
assertThat(cue.bitmapHeight).isEqualTo(Cue.DIMEN_UNSET);
|
||||||
|
}
|
||||||
|
|
||||||
private void assertSpans(
|
private void assertSpans(
|
||||||
TtmlSubtitle subtitle,
|
TtmlSubtitle subtitle,
|
||||||
int second,
|
int second,
|
||||||
|
|
|
||||||
|
|
@ -489,11 +489,7 @@ public final class CacheDataSourceTest {
|
||||||
NavigableSet<CacheSpan> cachedSpans = cache.getCachedSpans(expectedCacheKey);
|
NavigableSet<CacheSpan> cachedSpans = cache.getCachedSpans(expectedCacheKey);
|
||||||
for (CacheSpan cachedSpan : cachedSpans) {
|
for (CacheSpan cachedSpan : cachedSpans) {
|
||||||
if (cachedSpan.position >= halfDataLength) {
|
if (cachedSpan.position >= halfDataLength) {
|
||||||
try {
|
cache.removeSpan(cachedSpan);
|
||||||
cache.removeSpan(cachedSpan);
|
|
||||||
} catch (Cache.CacheException e) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ import org.robolectric.RuntimeEnvironment;
|
||||||
public class SimpleCacheTest {
|
public class SimpleCacheTest {
|
||||||
|
|
||||||
private static final String KEY_1 = "key1";
|
private static final String KEY_1 = "key1";
|
||||||
|
private static final String KEY_2 = "key2";
|
||||||
|
|
||||||
private File cacheDir;
|
private File cacheDir;
|
||||||
|
|
||||||
|
|
@ -152,6 +153,40 @@ public class SimpleCacheTest {
|
||||||
assertCachedDataReadCorrect(cacheSpan2);
|
assertCachedDataReadCorrect(cacheSpan2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReloadCacheWithoutRelease() throws Exception {
|
||||||
|
SimpleCache simpleCache = getSimpleCache();
|
||||||
|
|
||||||
|
// Write data for KEY_1.
|
||||||
|
CacheSpan cacheSpan1 = simpleCache.startReadWrite(KEY_1, 0);
|
||||||
|
addCache(simpleCache, KEY_1, 0, 15);
|
||||||
|
simpleCache.releaseHoleSpan(cacheSpan1);
|
||||||
|
// Write and remove data for KEY_2.
|
||||||
|
CacheSpan cacheSpan2 = simpleCache.startReadWrite(KEY_2, 0);
|
||||||
|
addCache(simpleCache, KEY_2, 0, 15);
|
||||||
|
simpleCache.releaseHoleSpan(cacheSpan2);
|
||||||
|
simpleCache.removeSpan(simpleCache.getCachedSpans(KEY_2).first());
|
||||||
|
|
||||||
|
// Don't release the cache. This means the index file wont have been written to disk after the
|
||||||
|
// data for KEY_2 was removed. Move the cache instead, so we can reload it without failing the
|
||||||
|
// folder locking check.
|
||||||
|
File cacheDir2 = Util.createTempFile(RuntimeEnvironment.application, "ExoPlayerTest");
|
||||||
|
cacheDir2.delete();
|
||||||
|
cacheDir.renameTo(cacheDir2);
|
||||||
|
|
||||||
|
// Reload the cache from its new location.
|
||||||
|
simpleCache = new SimpleCache(cacheDir2, new NoOpCacheEvictor());
|
||||||
|
|
||||||
|
// Read data back for KEY_1.
|
||||||
|
CacheSpan cacheSpan3 = simpleCache.startReadWrite(KEY_1, 0);
|
||||||
|
assertCachedDataReadCorrect(cacheSpan3);
|
||||||
|
|
||||||
|
// Check the entry for KEY_2 was removed when the cache was reloaded.
|
||||||
|
assertThat(simpleCache.getCachedSpans(KEY_2)).isEmpty();
|
||||||
|
|
||||||
|
Util.recursiveDelete(cacheDir2);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEncryptedIndex() throws Exception {
|
public void testEncryptedIndex() throws Exception {
|
||||||
byte[] key = "Bar12345Bar12345".getBytes(C.UTF8_NAME); // 128 bit key
|
byte[] key = "Bar12345Bar12345".getBytes(C.UTF8_NAME); // 128 bit key
|
||||||
|
|
|
||||||
|
|
@ -607,6 +607,12 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||||
|
|
||||||
// MediaSource implementation.
|
// MediaSource implementation.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Object getTag() {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSourceInternal(
|
public void prepareSourceInternal(
|
||||||
ExoPlayer player,
|
ExoPlayer player,
|
||||||
|
|
|
||||||
|
|
@ -390,6 +390,12 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||||
this.tag = tag;
|
this.tag = tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Object getTag() {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSourceInternal(
|
public void prepareSourceInternal(
|
||||||
ExoPlayer player,
|
ExoPlayer player,
|
||||||
|
|
|
||||||
|
|
@ -503,6 +503,12 @@ public final class SsMediaSource extends BaseMediaSource
|
||||||
|
|
||||||
// MediaSource implementation.
|
// MediaSource implementation.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Object getTag() {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSourceInternal(
|
public void prepareSourceInternal(
|
||||||
ExoPlayer player,
|
ExoPlayer player,
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import android.graphics.drawable.BitmapDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.support.annotation.IntDef;
|
import android.support.annotation.IntDef;
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|
@ -187,8 +186,9 @@ import java.util.List;
|
||||||
* <li>Type: {@link AspectRatioFrameLayout}
|
* <li>Type: {@link AspectRatioFrameLayout}
|
||||||
* </ul>
|
* </ul>
|
||||||
* <li><b>{@code exo_shutter}</b> - A view that's made visible when video should be hidden. This
|
* <li><b>{@code exo_shutter}</b> - A view that's made visible when video should be hidden. This
|
||||||
* view is typically an opaque view that covers the video surface view, thereby obscuring it
|
* view is typically an opaque view that covers the video surface, thereby obscuring it when
|
||||||
* when visible.
|
* visible. Obscuring the surface in this way also helps to prevent flicker at the start of
|
||||||
|
* playback when {@code surface_type="surface_view"}.
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Type: {@link View}
|
* <li>Type: {@link View}
|
||||||
* </ul>
|
* </ul>
|
||||||
|
|
@ -271,13 +271,13 @@ public class PlayerView extends FrameLayout {
|
||||||
private static final int SURFACE_TYPE_MONO360_VIEW = 3;
|
private static final int SURFACE_TYPE_MONO360_VIEW = 3;
|
||||||
// LINT.ThenChange(../../../../../../res/values/attrs.xml)
|
// LINT.ThenChange(../../../../../../res/values/attrs.xml)
|
||||||
|
|
||||||
private final AspectRatioFrameLayout contentFrame;
|
@Nullable private final AspectRatioFrameLayout contentFrame;
|
||||||
private final View shutterView;
|
private final View shutterView;
|
||||||
private final View surfaceView;
|
@Nullable private final View surfaceView;
|
||||||
private final ImageView artworkView;
|
private final ImageView artworkView;
|
||||||
private final SubtitleView subtitleView;
|
private final SubtitleView subtitleView;
|
||||||
private final @Nullable View bufferingView;
|
@Nullable private final View bufferingView;
|
||||||
private final @Nullable TextView errorMessageView;
|
@Nullable private final TextView errorMessageView;
|
||||||
private final PlayerControlView controller;
|
private final PlayerControlView controller;
|
||||||
private final ComponentListener componentListener;
|
private final ComponentListener componentListener;
|
||||||
private final FrameLayout overlayFrameLayout;
|
private final FrameLayout overlayFrameLayout;
|
||||||
|
|
@ -285,11 +285,11 @@ public class PlayerView extends FrameLayout {
|
||||||
private Player player;
|
private Player player;
|
||||||
private boolean useController;
|
private boolean useController;
|
||||||
private boolean useArtwork;
|
private boolean useArtwork;
|
||||||
private @Nullable Drawable defaultArtwork;
|
@Nullable private Drawable defaultArtwork;
|
||||||
private @ShowBuffering int showBuffering;
|
private @ShowBuffering int showBuffering;
|
||||||
private boolean keepContentOnPlayerReset;
|
private boolean keepContentOnPlayerReset;
|
||||||
private @Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
|
@Nullable private ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
|
||||||
private @Nullable CharSequence customErrorMessage;
|
@Nullable private CharSequence customErrorMessage;
|
||||||
private int controllerShowTimeoutMs;
|
private int controllerShowTimeoutMs;
|
||||||
private boolean controllerAutoShow;
|
private boolean controllerAutoShow;
|
||||||
private boolean controllerHideDuringAds;
|
private boolean controllerHideDuringAds;
|
||||||
|
|
@ -474,9 +474,7 @@ public class PlayerView extends FrameLayout {
|
||||||
* @param newPlayerView The new view to attach to the player.
|
* @param newPlayerView The new view to attach to the player.
|
||||||
*/
|
*/
|
||||||
public static void switchTargetView(
|
public static void switchTargetView(
|
||||||
@NonNull Player player,
|
Player player, @Nullable PlayerView oldPlayerView, @Nullable PlayerView newPlayerView) {
|
||||||
@Nullable PlayerView oldPlayerView,
|
|
||||||
@Nullable PlayerView newPlayerView) {
|
|
||||||
if (oldPlayerView == newPlayerView) {
|
if (oldPlayerView == newPlayerView) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1074,6 +1072,26 @@ public class PlayerView extends FrameLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when there's a change in the aspect ratio of the content being displayed. The default
|
||||||
|
* implementation sets the aspect ratio of the content frame to that of the content, unless the
|
||||||
|
* content view is a {@link SphericalSurfaceView} in which case the frame's aspect ratio is
|
||||||
|
* cleared.
|
||||||
|
*
|
||||||
|
* @param contentAspectRatio The aspect ratio of the content.
|
||||||
|
* @param contentFrame The content frame, or {@code null}.
|
||||||
|
* @param contentView The view that holds the content being displayed, or {@code null}.
|
||||||
|
*/
|
||||||
|
protected void onContentAspectRatioChanged(
|
||||||
|
float contentAspectRatio,
|
||||||
|
@Nullable AspectRatioFrameLayout contentFrame,
|
||||||
|
@Nullable View contentView) {
|
||||||
|
if (contentFrame != null) {
|
||||||
|
contentFrame.setAspectRatio(
|
||||||
|
contentView instanceof SphericalSurfaceView ? 0 : contentAspectRatio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean toggleControllerVisibility() {
|
private boolean toggleControllerVisibility() {
|
||||||
if (!useController || player == null) {
|
if (!useController || player == null) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -1187,9 +1205,8 @@ public class PlayerView extends FrameLayout {
|
||||||
int drawableWidth = drawable.getIntrinsicWidth();
|
int drawableWidth = drawable.getIntrinsicWidth();
|
||||||
int drawableHeight = drawable.getIntrinsicHeight();
|
int drawableHeight = drawable.getIntrinsicHeight();
|
||||||
if (drawableWidth > 0 && drawableHeight > 0) {
|
if (drawableWidth > 0 && drawableHeight > 0) {
|
||||||
if (contentFrame != null) {
|
float artworkAspectRatio = (float) drawableWidth / drawableHeight;
|
||||||
contentFrame.setAspectRatio((float) drawableWidth / drawableHeight);
|
onContentAspectRatioChanged(artworkAspectRatio, contentFrame, artworkView);
|
||||||
}
|
|
||||||
artworkView.setImageDrawable(drawable);
|
artworkView.setImageDrawable(drawable);
|
||||||
artworkView.setVisibility(VISIBLE);
|
artworkView.setVisibility(VISIBLE);
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -1322,9 +1339,6 @@ public class PlayerView extends FrameLayout {
|
||||||
@Override
|
@Override
|
||||||
public void onVideoSizeChanged(
|
public void onVideoSizeChanged(
|
||||||
int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
|
int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
|
||||||
if (contentFrame == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
float videoAspectRatio =
|
float videoAspectRatio =
|
||||||
(height == 0 || width == 0) ? 1 : (width * pixelWidthHeightRatio) / height;
|
(height == 0 || width == 0) ? 1 : (width * pixelWidthHeightRatio) / height;
|
||||||
|
|
||||||
|
|
@ -1345,11 +1359,9 @@ public class PlayerView extends FrameLayout {
|
||||||
surfaceView.addOnLayoutChangeListener(this);
|
surfaceView.addOnLayoutChangeListener(this);
|
||||||
}
|
}
|
||||||
applyTextureViewRotation((TextureView) surfaceView, textureViewRotation);
|
applyTextureViewRotation((TextureView) surfaceView, textureViewRotation);
|
||||||
} else if (surfaceView instanceof SphericalSurfaceView) {
|
|
||||||
videoAspectRatio = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contentFrame.setAspectRatio(videoAspectRatio);
|
onContentAspectRatioChanged(videoAspectRatio, contentFrame, surfaceView);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,13 @@ public class FakeMediaSource extends BaseMediaSource {
|
||||||
this.trackGroupArray = trackGroupArray;
|
this.trackGroupArray = trackGroupArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Object getTag() {
|
||||||
|
boolean hasTimeline = timeline != null && !timeline.isEmpty();
|
||||||
|
return hasTimeline ? timeline.getWindow(0, new Timeline.Window()).tag : null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void prepareSourceInternal(
|
public synchronized void prepareSourceInternal(
|
||||||
ExoPlayer player,
|
ExoPlayer player,
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,11 @@ public abstract class StubExoPlayer extends BasePlayer implements ExoPlayer {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MetadataComponent getMetadataComponent() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Looper getPlaybackLooper() {
|
public Looper getPlaybackLooper() {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue