-
Notifications
You must be signed in to change notification settings - Fork 94
Support for reading/writing EXT-X-BYTERANGE tag, Enable unknown tags when writing playlists #47
base: master
Are you sure you want to change the base?
Changes from 4 commits
b9e5eb9
acea3b5
03e73b5
b4dd17b
cf33052
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -63,7 +63,7 @@ public void write(TagWriter tagWriter, Playlist playlist) throws IOException { | |
List<String> unknownTags; | ||
if (playlist.hasMasterPlaylist() && playlist.getMasterPlaylist().hasUnknownTags()) { | ||
unknownTags = playlist.getMasterPlaylist().getUnknownTags(); | ||
} else if (playlist.getMediaPlaylist().hasUnknownTags()) { | ||
} else if (playlist.hasMediaPlaylist() && playlist.getMediaPlaylist().hasUnknownTags()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 |
||
unknownTags = playlist.getMediaPlaylist().getUnknownTags(); | ||
} else { | ||
unknownTags = Collections.emptyList(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
import java.util.Map; | ||
import java.util.regex.Matcher; | ||
|
||
import com.iheartradio.m3u8.data.ByteRange; | ||
import com.iheartradio.m3u8.data.EncryptionData; | ||
import com.iheartradio.m3u8.data.EncryptionData.Builder; | ||
import com.iheartradio.m3u8.data.EncryptionMethod; | ||
|
@@ -14,6 +15,7 @@ | |
import com.iheartradio.m3u8.data.TrackInfo; | ||
|
||
class MediaPlaylistLineParser implements LineParser { | ||
|
||
private final IExtTagParser tagParser; | ||
private final LineParser lineParser; | ||
|
||
|
@@ -37,7 +39,6 @@ public void parse(String line, ParseState state) throws ParseException { | |
} | ||
|
||
// media playlist tags | ||
|
||
static final IExtTagParser EXT_X_ENDLIST = new IExtTagParser() { | ||
private final LineParser lineParser = new MediaPlaylistLineParser(this); | ||
|
||
|
@@ -59,7 +60,7 @@ public void parse(String line, ParseState state) throws ParseException { | |
state.getMedia().endOfList = true; | ||
} | ||
}; | ||
|
||
static final IExtTagParser EXT_X_I_FRAMES_ONLY = new IExtTagParser() { | ||
private final LineParser lineParser = new MediaPlaylistLineParser(this); | ||
|
||
|
@@ -76,17 +77,17 @@ public boolean hasData() { | |
@Override | ||
public void parse(String line, ParseState state) throws ParseException { | ||
lineParser.parse(line, state); | ||
|
||
ParseUtil.match(Constants.EXT_X_I_FRAMES_ONLY_PATTERN, line, getTag()); | ||
|
||
if (state.getCompatibilityVersion() < 4) { | ||
throw ParseException.create(ParseExceptionType.REQUIRES_PROTOCOL_VERSION_4_OR_HIGHER, getTag()); | ||
} | ||
|
||
state.setIsIframesOnly(); | ||
} | ||
}; | ||
|
||
static final IExtTagParser EXT_X_PLAYLIST_TYPE = new IExtTagParser() { | ||
private final LineParser lineParser = new MediaPlaylistLineParser(this); | ||
|
||
|
@@ -99,7 +100,7 @@ public String getTag() { | |
public boolean hasData() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public void parse(String line, ParseState state) throws ParseException { | ||
lineParser.parse(line, state); | ||
|
@@ -113,7 +114,6 @@ public void parse(String line, ParseState state) throws ParseException { | |
state.getMedia().playlistType = ParseUtil.parseEnum(matcher.group(1), PlaylistType.class, getTag()); | ||
} | ||
}; | ||
|
||
|
||
static final IExtTagParser EXT_X_PROGRAM_DATE_TIME = new IExtTagParser() { | ||
private final LineParser lineParser = new MediaPlaylistLineParser(this); | ||
|
@@ -127,7 +127,7 @@ public String getTag() { | |
public boolean hasData() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public void parse(String line, ParseState state) throws ParseException { | ||
lineParser.parse(line, state); | ||
|
@@ -138,22 +138,22 @@ public void parse(String line, ParseState state) throws ParseException { | |
throw ParseException.create(ParseExceptionType.MULTIPLE_EXT_TAG_INSTANCES, getTag(), line); | ||
} | ||
|
||
state.getMedia().programDateTime = ParseUtil.parseDateTime(line,getTag()); | ||
state.getMedia().programDateTime = ParseUtil.parseDateTime(line, getTag()); | ||
} | ||
}; | ||
|
||
static final IExtTagParser EXT_X_START = new IExtTagParser() { | ||
private final LineParser lineParser = new MediaPlaylistLineParser(this); | ||
private final Map<String, AttributeParser<StartData.Builder>> HANDLERS = new HashMap<>(); | ||
|
||
{ | ||
HANDLERS.put(Constants.TIME_OFFSET, new AttributeParser<StartData.Builder>() { | ||
@Override | ||
public void parse(Attribute attribute, StartData.Builder builder, ParseState state) throws ParseException { | ||
builder.withTimeOffset(ParseUtil.parseFloat(attribute.value, getTag())); | ||
} | ||
}); | ||
|
||
HANDLERS.put(Constants.PRECISE, new AttributeParser<StartData.Builder>() { | ||
@Override | ||
public void parse(Attribute attribute, StartData.Builder builder, ParseState state) throws ParseException { | ||
|
@@ -171,7 +171,7 @@ public String getTag() { | |
public boolean hasData() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public void parse(String line, ParseState state) throws ParseException { | ||
lineParser.parse(line, state); | ||
|
@@ -183,7 +183,6 @@ public void parse(String line, ParseState state) throws ParseException { | |
state.getMedia().setStartData(startData); | ||
} | ||
}; | ||
|
||
|
||
static final IExtTagParser EXT_X_TARGETDURATION = new IExtTagParser() { | ||
private final LineParser lineParser = new MediaPlaylistLineParser(this); | ||
|
@@ -261,7 +260,6 @@ public void parse(String line, ParseState state) throws ParseException { | |
}; | ||
|
||
// media segment tags | ||
|
||
static final IExtTagParser EXTINF = new IExtTagParser() { | ||
private final LineParser lineParser = new MediaPlaylistLineParser(this); | ||
|
||
|
@@ -285,6 +283,31 @@ public void parse(String line, ParseState state) throws ParseException { | |
} | ||
}; | ||
|
||
static final IExtTagParser EXT_X_BYTE_RANGE = new IExtTagParser() { | ||
private final LineParser lineParser = new MediaPlaylistLineParser(this); | ||
|
||
@Override | ||
public String getTag() { | ||
return Constants.EXT_X_BYTE_RANGE_TAG; | ||
} | ||
|
||
@Override | ||
public boolean hasData() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public void parse(String line, ParseState state) throws ParseException { | ||
lineParser.parse(line, state); | ||
final Matcher matcher = ParseUtil.match(Constants.EXT_X_BYTE_RANGE_PATTERN, line, getTag()); | ||
|
||
if (matcher.groupCount() == 3) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not believe there is a case where the group count can be anything other than the total number of groups. Even if that group is empty, group count should return the same number. I think we can remove the |
||
String offset = matcher.group(3); | ||
state.getMedia().byteRange = new ByteRange(Integer.parseInt(matcher.group(1)), offset == null ? null : Integer.parseInt(offset)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The spec says the following:
This is a bit unfortunate since it means we will need to add a piece of state to the parsing, but it does mean there is no null case. We need to keep track of the last byte range (which may be nothing) to calculate the offset for the current byte range if no offset is specified. So it would look something like this:
We're not quite done. Because of this:
we need to clear the previous byte range when changing media sources. |
||
} | ||
} | ||
}; | ||
|
||
static final IExtTagParser EXT_X_DISCONTINUITY = new IExtTagParser() { | ||
private final LineParser lineParser = new MediaPlaylistLineParser(this); | ||
|
||
|
@@ -336,8 +359,8 @@ public void parse(Attribute attribute, Builder builder, ParseState state) throws | |
public void parse(Attribute attribute, Builder builder, ParseState state) throws ParseException { | ||
final List<Byte> initializationVector = ParseUtil.parseHexadecimal(attribute.value, getTag()); | ||
|
||
if ((initializationVector.size() != Constants.IV_SIZE) && | ||
(initializationVector.size() != Constants.IV_SIZE_ALTERNATIVE)) { | ||
if ((initializationVector.size() != Constants.IV_SIZE) | ||
&& (initializationVector.size() != Constants.IV_SIZE_ALTERNATIVE)) { | ||
throw ParseException.create(ParseExceptionType.INVALID_IV_SIZE, getTag(), attribute.toString()); | ||
} | ||
|
||
|
@@ -365,7 +388,7 @@ public void parse(Attribute attribute, Builder builder, ParseState state) throws | |
throw ParseException.create(ParseExceptionType.INVALID_KEY_FORMAT_VERSIONS, getTag(), attribute.toString()); | ||
} | ||
} | ||
|
||
builder.withKeyFormatVersions(versions); | ||
} | ||
}); | ||
|
@@ -380,7 +403,7 @@ public String getTag() { | |
public boolean hasData() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public void parse(String line, ParseState state) throws ParseException { | ||
lineParser.parse(line, state); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of:
you can do:
to not capture the second group with the '@'. Then the parsing becomes a little more straightforward.