Fixes issues with EXTINF duration conversion to microseconds

The HLS Parser converts from a string decimal duration in seconds into long
 microseconds.
Because the conversion passes through a java double type it can result in
representation errors.

For example:

`#EXTINF:4.004`  -> `Segment.durationUs` of 4003999

This matters because the first sample (which is the IDR) for a segment will be discarded following a seek because of the logic in the `SampleQueue`:

````java
    buffer.timeUs = timesUs[relativeReadIndex];
    if (buffer.timeUs < startTimeUs) {
      buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
    }
````
This commit is contained in:
Steve Mayhew 2021-10-18 17:04:29 -07:00
parent d601875452
commit 701f343ee5
2 changed files with 15 additions and 3 deletions

View File

@ -48,6 +48,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
@ -759,8 +760,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
parseStringAttr(line, REGEX_VALUE, variableDefinitions));
}
} else if (line.startsWith(TAG_MEDIA_DURATION)) {
segmentDurationUs =
(long) (parseDoubleAttr(line, REGEX_MEDIA_DURATION) * C.MICROS_PER_SECOND);
segmentDurationUs = parseTimeSecondsToUs(line, REGEX_MEDIA_DURATION);
segmentTitle = parseOptionalStringAttr(line, REGEX_MEDIA_TITLE, "", variableDefinitions);
} else if (line.startsWith(TAG_SKIP)) {
int skippedSegmentCount = parseIntAttr(line, REGEX_SKIPPED_SEGMENTS);
@ -1190,6 +1190,12 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
return defaultValue;
}
private static long parseTimeSecondsToUs(String line, Pattern pattern) throws ParserException {
String timeValueSeconds = parseStringAttr(line, pattern, Collections.emptyMap());
BigDecimal timeValue = new BigDecimal(timeValueSeconds);
return timeValue.multiply(new BigDecimal(C.MICROS_PER_SECOND)).longValue();
}
private static double parseDoubleAttr(String line, Pattern pattern) throws ParserException {
return Double.parseDouble(parseStringAttr(line, pattern, Collections.emptyMap()));
}

View File

@ -77,6 +77,9 @@ public class HlsMediaPlaylistParserTest {
+ "\n"
+ "#EXTINF:7.975,\n"
+ "https://priv.example.com/fileSequence2683.ts\n"
+ "\n"
+ "#EXTINF:2.002,\n"
+ "https://priv.example.com/fileSequence2684.ts\n"
+ "#EXT-X-ENDLIST";
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
HlsPlaylist playlist = new HlsPlaylistParser().parse(playlistUri, inputStream);
@ -93,7 +96,7 @@ public class HlsMediaPlaylistParserTest {
assertThat(mediaPlaylist.partTargetDurationUs).isEqualTo(C.TIME_UNSET);
List<Segment> segments = mediaPlaylist.segments;
assertThat(segments).isNotNull();
assertThat(segments).hasSize(5);
assertThat(segments).hasSize(6);
Segment segment = segments.get(0);
assertThat(mediaPlaylist.discontinuitySequence + segment.relativeDiscontinuitySequence)
@ -152,6 +155,9 @@ public class HlsMediaPlaylistParserTest {
assertThat(segment.byteRangeLength).isEqualTo(C.LENGTH_UNSET);
assertThat(segment.byteRangeOffset).isEqualTo(0);
assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2683.ts");
segment = segments.get(5);
assertThat(segment.durationUs).isEqualTo(2002000);
}
@Test