Simplify encryption handling in HLS.

This commit is contained in:
Oliver Woodman 2015-03-12 19:42:00 +00:00
parent fabd470550
commit 12807a7d4b
4 changed files with 37 additions and 33 deletions

View File

@ -306,7 +306,7 @@ public class HlsChunkSource {
Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url); Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);
// Check if encryption is specified. // Check if encryption is specified.
if (HlsMediaPlaylist.ENCRYPTION_METHOD_AES_128.equals(segment.encryptionMethod)) { if (segment.isEncrypted) {
Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.encryptionKeyUri); Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.encryptionKeyUri);
if (!keyUri.equals(encryptionKeyUri)) { if (!keyUri.equals(encryptionKeyUri)) {
// Encryption is specified and the key has changed. // Encryption is specified and the key has changed.

View File

@ -28,24 +28,25 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
* Media segment reference. * Media segment reference.
*/ */
public static final class Segment implements Comparable<Long> { public static final class Segment implements Comparable<Long> {
public final boolean discontinuity; public final boolean discontinuity;
public final double durationSecs; public final double durationSecs;
public final String url; public final String url;
public final long startTimeUs; public final long startTimeUs;
public final String encryptionMethod; public final boolean isEncrypted;
public final String encryptionKeyUri; public final String encryptionKeyUri;
public final String encryptionIV; public final String encryptionIV;
public final int byterangeOffset; public final int byterangeOffset;
public final int byterangeLength; public final int byterangeLength;
public Segment(String uri, double durationSecs, boolean discontinuity, long startTimeUs, public Segment(String uri, double durationSecs, boolean discontinuity, long startTimeUs,
String encryptionMethod, String encryptionKeyUri, String encryptionIV, boolean isEncrypted, String encryptionKeyUri, String encryptionIV, int byterangeOffset,
int byterangeOffset, int byterangeLength) { int byterangeLength) {
this.url = uri; this.url = uri;
this.durationSecs = durationSecs; this.durationSecs = durationSecs;
this.discontinuity = discontinuity; this.discontinuity = discontinuity;
this.startTimeUs = startTimeUs; this.startTimeUs = startTimeUs;
this.encryptionMethod = encryptionMethod; this.isEncrypted = isEncrypted;
this.encryptionKeyUri = encryptionKeyUri; this.encryptionKeyUri = encryptionKeyUri;
this.encryptionIV = encryptionIV; this.encryptionIV = encryptionIV;
this.byterangeOffset = byterangeOffset; this.byterangeOffset = byterangeOffset;

View File

@ -64,6 +64,9 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser<HlsPlayli
private static final String SUBTITLES_TYPE = "SUBTITLES"; private static final String SUBTITLES_TYPE = "SUBTITLES";
private static final String CLOSED_CAPTIONS_TYPE = "CLOSED-CAPTIONS"; private static final String CLOSED_CAPTIONS_TYPE = "CLOSED-CAPTIONS";
private static final String METHOD_NONE = "NONE";
private static final String METHOD_AES128 = "AES-128";
private static final Pattern BANDWIDTH_ATTR_REGEX = private static final Pattern BANDWIDTH_ATTR_REGEX =
Pattern.compile(BANDWIDTH_ATTR + "=(\\d+)\\b"); Pattern.compile(BANDWIDTH_ATTR + "=(\\d+)\\b");
private static final Pattern CODECS_ATTR_REGEX = private static final Pattern CODECS_ATTR_REGEX =
@ -80,8 +83,9 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser<HlsPlayli
Pattern.compile(VERSION_TAG + ":(\\d+)\\b"); Pattern.compile(VERSION_TAG + ":(\\d+)\\b");
private static final Pattern BYTERANGE_REGEX = private static final Pattern BYTERANGE_REGEX =
Pattern.compile(BYTERANGE_TAG + ":(\\d+(?:@\\d+)?)\\b"); Pattern.compile(BYTERANGE_TAG + ":(\\d+(?:@\\d+)?)\\b");
private static final Pattern METHOD_ATTR_REGEX = private static final Pattern METHOD_ATTR_REGEX =
Pattern.compile(METHOD_ATTR + "=([^,.*]+)"); Pattern.compile(METHOD_ATTR + "=(" + METHOD_NONE + "|" + METHOD_AES128 + ")");
private static final Pattern URI_ATTR_REGEX = private static final Pattern URI_ATTR_REGEX =
Pattern.compile(URI_ATTR + "=\"(.+)\""); Pattern.compile(URI_ATTR + "=\"(.+)\"");
private static final Pattern IV_ATTR_REGEX = private static final Pattern IV_ATTR_REGEX =
@ -201,14 +205,14 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser<HlsPlayli
double segmentDurationSecs = 0.0; double segmentDurationSecs = 0.0;
boolean segmentDiscontinuity = false; boolean segmentDiscontinuity = false;
long segmentStartTimeUs = 0; long segmentStartTimeUs = 0;
String segmentEncryptionMethod = null;
String segmentEncryptionKeyUri = null;
String segmentEncryptionIV = null;
int segmentByterangeOffset = 0; int segmentByterangeOffset = 0;
int segmentByterangeLength = C.LENGTH_UNBOUNDED; int segmentByterangeLength = C.LENGTH_UNBOUNDED;
int segmentMediaSequence = 0; int segmentMediaSequence = 0;
boolean isEncrypted = false;
String encryptionKeyUri = null;
String encryptionIV = null;
String line; String line;
while (iterator.hasNext()) { while (iterator.hasNext()) {
line = iterator.next(); line = iterator.next();
@ -224,15 +228,14 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser<HlsPlayli
segmentDurationSecs = HlsParserUtil.parseDoubleAttr(line, MEDIA_DURATION_REGEX, segmentDurationSecs = HlsParserUtil.parseDoubleAttr(line, MEDIA_DURATION_REGEX,
MEDIA_DURATION_TAG); MEDIA_DURATION_TAG);
} else if (line.startsWith(KEY_TAG)) { } else if (line.startsWith(KEY_TAG)) {
segmentEncryptionMethod = HlsParserUtil.parseStringAttr(line, METHOD_ATTR_REGEX, String method = HlsParserUtil.parseStringAttr(line, METHOD_ATTR_REGEX, METHOD_ATTR);
METHOD_ATTR); isEncrypted = METHOD_AES128.equals(method);
if (segmentEncryptionMethod.equals(HlsMediaPlaylist.ENCRYPTION_METHOD_NONE)) { if (isEncrypted) {
segmentEncryptionKeyUri = null; encryptionKeyUri = HlsParserUtil.parseStringAttr(line, URI_ATTR_REGEX, URI_ATTR);
segmentEncryptionIV = null; encryptionIV = HlsParserUtil.parseOptionalStringAttr(line, IV_ATTR_REGEX);
} else { } else {
segmentEncryptionKeyUri = HlsParserUtil.parseStringAttr(line, URI_ATTR_REGEX, encryptionKeyUri = null;
URI_ATTR); encryptionIV = null;
segmentEncryptionIV = HlsParserUtil.parseOptionalStringAttr(line, IV_ATTR_REGEX);
} }
} else if (line.startsWith(BYTERANGE_TAG)) { } else if (line.startsWith(BYTERANGE_TAG)) {
String byteRange = HlsParserUtil.parseStringAttr(line, BYTERANGE_REGEX, BYTERANGE_TAG); String byteRange = HlsParserUtil.parseStringAttr(line, BYTERANGE_REGEX, BYTERANGE_TAG);
@ -244,21 +247,21 @@ public final class HlsPlaylistParser implements NetworkLoadable.Parser<HlsPlayli
} else if (line.equals(DISCONTINUITY_TAG)) { } else if (line.equals(DISCONTINUITY_TAG)) {
segmentDiscontinuity = true; segmentDiscontinuity = true;
} else if (!line.startsWith("#")) { } else if (!line.startsWith("#")) {
String thisSegmentEncryptionIV; String segmentEncryptionIV;
if (segmentEncryptionIV != null) { if (!isEncrypted) {
thisSegmentEncryptionIV = segmentEncryptionIV; segmentEncryptionIV = null;
} else if (HlsMediaPlaylist.ENCRYPTION_METHOD_AES_128.equals(segmentEncryptionMethod)) { } else if (encryptionIV != null) {
thisSegmentEncryptionIV = Integer.toHexString(segmentMediaSequence); segmentEncryptionIV = encryptionIV;
} else { } else {
thisSegmentEncryptionIV = null; segmentEncryptionIV = Integer.toHexString(segmentMediaSequence);
} }
segmentMediaSequence++; segmentMediaSequence++;
if (segmentByterangeLength == C.LENGTH_UNBOUNDED) { if (segmentByterangeLength == C.LENGTH_UNBOUNDED) {
segmentByterangeOffset = 0; segmentByterangeOffset = 0;
} }
segments.add(new Segment(line, segmentDurationSecs, segmentDiscontinuity, segments.add(new Segment(line, segmentDurationSecs, segmentDiscontinuity,
segmentStartTimeUs, segmentEncryptionMethod, segmentEncryptionKeyUri, segmentStartTimeUs, isEncrypted, encryptionKeyUri, segmentEncryptionIV,
thisSegmentEncryptionIV, segmentByterangeOffset, segmentByterangeLength)); segmentByterangeOffset, segmentByterangeLength));
segmentStartTimeUs += (long) (segmentDurationSecs * C.MICROS_PER_SECOND); segmentStartTimeUs += (long) (segmentDurationSecs * C.MICROS_PER_SECOND);
segmentDiscontinuity = false; segmentDiscontinuity = false;
segmentDurationSecs = 0.0; segmentDurationSecs = 0.0;

View File

@ -81,7 +81,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
assertEquals(false, segments.get(0).discontinuity); assertEquals(false, segments.get(0).discontinuity);
assertEquals(7.975, segments.get(0).durationSecs); assertEquals(7.975, segments.get(0).durationSecs);
assertEquals(null, segments.get(0).encryptionMethod); assertEquals(false, segments.get(0).isEncrypted);
assertEquals(null, segments.get(0).encryptionKeyUri); assertEquals(null, segments.get(0).encryptionKeyUri);
assertEquals(null, segments.get(0).encryptionIV); assertEquals(null, segments.get(0).encryptionIV);
assertEquals(51370, segments.get(0).byterangeLength); assertEquals(51370, segments.get(0).byterangeLength);
@ -90,7 +90,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
assertEquals(false, segments.get(1).discontinuity); assertEquals(false, segments.get(1).discontinuity);
assertEquals(7.975, segments.get(1).durationSecs); assertEquals(7.975, segments.get(1).durationSecs);
assertEquals("AES-128", segments.get(1).encryptionMethod); assertEquals(true, segments.get(1).isEncrypted);
assertEquals("https://priv.example.com/key.php?r=2680", segments.get(1).encryptionKeyUri); assertEquals("https://priv.example.com/key.php?r=2680", segments.get(1).encryptionKeyUri);
assertEquals("0x1566B", segments.get(1).encryptionIV); assertEquals("0x1566B", segments.get(1).encryptionIV);
assertEquals(51501, segments.get(1).byterangeLength); assertEquals(51501, segments.get(1).byterangeLength);
@ -99,7 +99,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
assertEquals(false, segments.get(2).discontinuity); assertEquals(false, segments.get(2).discontinuity);
assertEquals(7.941, segments.get(2).durationSecs); assertEquals(7.941, segments.get(2).durationSecs);
assertEquals(HlsMediaPlaylist.ENCRYPTION_METHOD_NONE, segments.get(2).encryptionMethod); assertEquals(false, segments.get(2).isEncrypted);
assertEquals(null, segments.get(2).encryptionKeyUri); assertEquals(null, segments.get(2).encryptionKeyUri);
assertEquals(null, segments.get(2).encryptionIV); assertEquals(null, segments.get(2).encryptionIV);
assertEquals(51501, segments.get(2).byterangeLength); assertEquals(51501, segments.get(2).byterangeLength);
@ -108,7 +108,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
assertEquals(true, segments.get(3).discontinuity); assertEquals(true, segments.get(3).discontinuity);
assertEquals(7.975, segments.get(3).durationSecs); assertEquals(7.975, segments.get(3).durationSecs);
assertEquals("AES-128", segments.get(3).encryptionMethod); assertEquals(true, segments.get(3).isEncrypted);
assertEquals("https://priv.example.com/key.php?r=2682", segments.get(3).encryptionKeyUri); assertEquals("https://priv.example.com/key.php?r=2682", segments.get(3).encryptionKeyUri);
// 0xA7A == 2682. // 0xA7A == 2682.
assertNotNull(segments.get(3).encryptionIV); assertNotNull(segments.get(3).encryptionIV);
@ -119,11 +119,11 @@ public class HlsMediaPlaylistParserTest extends TestCase {
assertEquals(false, segments.get(4).discontinuity); assertEquals(false, segments.get(4).discontinuity);
assertEquals(7.975, segments.get(4).durationSecs); assertEquals(7.975, segments.get(4).durationSecs);
assertEquals("AES-128", segments.get(4).encryptionMethod); assertEquals(true, segments.get(4).isEncrypted);
assertEquals("https://priv.example.com/key.php?r=2682", segments.get(4).encryptionKeyUri); assertEquals("https://priv.example.com/key.php?r=2682", segments.get(4).encryptionKeyUri);
// 0xA7A == 2682. // 0xA7B == 2683.
assertNotNull(segments.get(4).encryptionIV); assertNotNull(segments.get(4).encryptionIV);
assertEquals("A7A", segments.get(4).encryptionIV.toUpperCase(Locale.getDefault())); assertEquals("A7B", segments.get(4).encryptionIV.toUpperCase(Locale.getDefault()));
assertEquals(C.LENGTH_UNBOUNDED, segments.get(4).byterangeLength); assertEquals(C.LENGTH_UNBOUNDED, segments.get(4).byterangeLength);
assertEquals(0, segments.get(4).byterangeOffset); assertEquals(0, segments.get(4).byterangeOffset);
assertEquals("https://priv.example.com/fileSequence2683.ts", segments.get(4).url); assertEquals("https://priv.example.com/fileSequence2683.ts", segments.get(4).url);