diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java index 7904977dee..c629e365ee 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java @@ -673,7 +673,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { @Override protected boolean isReady() { return format != null && !waitingForKeys - && sourceState != SOURCE_STATE_NOT_READY || outputIndex >= 0 || isWithinHotswapPeriod(); + && (sourceState != SOURCE_STATE_NOT_READY || outputIndex >= 0 || isWithinHotswapPeriod()); } /** diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java index f7d556f3c8..263a47a5a5 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java @@ -140,6 +140,8 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback { private static final int NO_RESET_PENDING = -1; + private static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 1; + private final int eventSourceId; private final LoadControl loadControl; private final ChunkSource chunkSource; @@ -150,6 +152,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback { private final boolean frameAccurateSeeking; private final Handler eventHandler; private final EventListener eventListener; + private final int minLoadableRetryCount; private int state; private long downstreamPositionUs; @@ -175,6 +178,13 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback { public ChunkSampleSource(ChunkSource chunkSource, LoadControl loadControl, int bufferSizeContribution, boolean frameAccurateSeeking, Handler eventHandler, EventListener eventListener, int eventSourceId) { + this(chunkSource, loadControl, bufferSizeContribution, frameAccurateSeeking, eventHandler, + eventListener, eventSourceId, DEFAULT_MIN_LOADABLE_RETRY_COUNT); + } + + public ChunkSampleSource(ChunkSource chunkSource, LoadControl loadControl, + int bufferSizeContribution, boolean frameAccurateSeeking, Handler eventHandler, + EventListener eventListener, int eventSourceId, int minLoadableRetryCount) { this.chunkSource = chunkSource; this.loadControl = loadControl; this.bufferSizeContribution = bufferSizeContribution; @@ -182,6 +192,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback { this.eventHandler = eventHandler; this.eventListener = eventListener; this.eventSourceId = eventSourceId; + this.minLoadableRetryCount = minLoadableRetryCount; currentLoadableHolder = new ChunkOperationHolder(); mediaChunks = new LinkedList(); readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks); @@ -287,9 +298,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback { downstreamPositionUs = positionUs; if (isPendingReset()) { - if (currentLoadableException != null) { - throw currentLoadableException; - } + maybeThrowLoadableException(); IOException chunkSourceException = chunkSource.getError(); if (chunkSourceException != null) { throw chunkSourceException; @@ -342,9 +351,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback { onSampleRead(mediaChunk, sampleHolder); return SAMPLE_READ; } else { - if (currentLoadableException != null) { - throw currentLoadableException; - } + maybeThrowLoadableException(); return NOTHING_READ; } } @@ -369,6 +376,12 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback { } } + private void maybeThrowLoadableException() throws IOException { + if (currentLoadableException != null && currentLoadableExceptionCount > minLoadableRetryCount) { + throw currentLoadableException; + } + } + private MediaChunk getMediaChunk(long positionUs) { Iterator mediaChunkIterator = mediaChunks.iterator(); while (mediaChunkIterator.hasNext()) { diff --git a/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java b/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java index d3a560c2d9..baace215f5 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java +++ b/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java @@ -53,8 +53,13 @@ public class WebvttParser implements SubtitleParser { private static final long SAMPLING_RATE = 90; + private static final String WEBVTT_METADATA_HEADER_STRING = "\\S*[:=]\\S*"; + private static final Pattern WEBVTT_METADATA_HEADER = + Pattern.compile(WEBVTT_METADATA_HEADER_STRING); + private static final String WEBVTT_TIMESTAMP_STRING = "(\\d+:)?[0-5]\\d:[0-5]\\d\\.\\d{3}"; private static final Pattern WEBVTT_TIMESTAMP = Pattern.compile(WEBVTT_TIMESTAMP_STRING); + private static final Pattern MEDIA_TIMESTAMP_OFFSET = Pattern.compile(OFFSET + "\\d+"); private static final Pattern MEDIA_TIMESTAMP = Pattern.compile("MPEGTS:\\d+"); @@ -90,30 +95,33 @@ public class WebvttParser implements SubtitleParser { throw new ParserException("Expected WEBVTT. Got " + line); } - // after "WEBVTT" there should be either an empty line or an "X-TIMESTAMP-MAP" line and then - // and empty line - line = webvttData.readLine(); - if (!line.isEmpty()) { - if (!line.startsWith("X-TIMESTAMP-MAP")) { - throw new ParserException("Expected an empty line or X-TIMESTAMP-MAP. Got " + line); - } - - // parse the media timestamp - Matcher matcher = MEDIA_TIMESTAMP.matcher(line); - if (!matcher.find()) { - throw new ParserException("X-TIMESTAMP-MAP doesn't contain media timestmap: " + line); - } else { - mediaTimestampUs = (Long.parseLong(matcher.group().substring(7)) * 1000) / SAMPLING_RATE - - mediaTimestampOffsetUs; - } - mediaTimestampUs = getAdjustedStartTime(mediaTimestampUs); - - // read in the next line (which should be an empty line) + // parse the remainder of the header + while (true) { line = webvttData.readLine(); - } - if (!line.isEmpty()) { - throw new ParserException("Expected an empty line after WEBVTT or X-TIMESTAMP-MAP. Got " - + line); + if (line == null) { + // we reached EOF before finishing the header + throw new ParserException("Expected an empty line after webvtt header"); + } else if (line.isEmpty()) { + // we've read the newline that separates the header from the body + break; + } + + Matcher matcher = WEBVTT_METADATA_HEADER.matcher(line); + if (!matcher.find()) { + throw new ParserException("Expected webvtt metadata header; got: " + line); + } + + if (line.startsWith("X-TIMESTAMP-MAP")) { + // parse the media timestamp + Matcher timestampMatcher = MEDIA_TIMESTAMP.matcher(line); + if (!timestampMatcher.find()) { + throw new ParserException("X-TIMESTAMP-MAP doesn't contain media timestamp: " + line); + } else { + mediaTimestampUs = (Long.parseLong(timestampMatcher.group().substring(7)) * 1000) + / SAMPLING_RATE - mediaTimestampOffsetUs; + } + mediaTimestampUs = getAdjustedStartTime(mediaTimestampUs); + } } // process the cues and text