Commit 24d04a26 by aquilescanta Committed by Oliver Woodman

Add support for variable substition in HLS

Issue:#4422

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=209958623
parent 4d8a5c44
......@@ -70,6 +70,8 @@
* Allow configuration of the Loader retry delay
([#3370](https://github.com/google/ExoPlayer/issues/3370)).
* HLS:
* Add support for variable substitution
([#4422](https://github.com/google/ExoPlayer/issues/4422)).
* Add support for PlayReady.
* Add support for alternative EXT-X-KEY tags.
* Set the bitrate on primary track sample formats
......
......@@ -21,6 +21,7 @@ import com.google.android.exoplayer2.util.MimeTypes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/** Represents an HLS master playlist. */
public final class HlsMasterPlaylist extends HlsPlaylist {
......@@ -35,7 +36,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
/* subtitles= */ Collections.emptyList(),
/* muxedAudioFormat= */ null,
/* muxedCaptionFormats= */ Collections.emptyList(),
/* hasIndependentSegments= */ false);
/* hasIndependentSegments= */ false,
/* variableDefinitions= */ Collections.emptyMap());
public static final int GROUP_INDEX_VARIANT = 0;
public static final int GROUP_INDEX_AUDIO = 1;
......@@ -110,6 +112,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
* captions information.
*/
public final List<Format> muxedCaptionFormats;
/** Contains variable definitions, as defined by the #EXT-X-DEFINE tag. */
public final Map<String, String> variableDefinitions;
/**
* @param baseUri See {@link #baseUri}.
......@@ -120,6 +124,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
* @param muxedAudioFormat See {@link #muxedAudioFormat}.
* @param muxedCaptionFormats See {@link #muxedCaptionFormats}.
* @param hasIndependentSegments See {@link #hasIndependentSegments}.
* @param variableDefinitions See {@link #variableDefinitions}.
*/
public HlsMasterPlaylist(
String baseUri,
......@@ -129,7 +134,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
List<HlsUrl> subtitles,
Format muxedAudioFormat,
List<Format> muxedCaptionFormats,
boolean hasIndependentSegments) {
boolean hasIndependentSegments,
Map<String, String> variableDefinitions) {
super(baseUri, tags, hasIndependentSegments);
this.variants = Collections.unmodifiableList(variants);
this.audios = Collections.unmodifiableList(audios);
......@@ -137,6 +143,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
this.muxedAudioFormat = muxedAudioFormat;
this.muxedCaptionFormats = muxedCaptionFormats != null
? Collections.unmodifiableList(muxedCaptionFormats) : null;
this.variableDefinitions = Collections.unmodifiableMap(variableDefinitions);
}
@Override
......@@ -149,7 +156,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
copyRenditionsList(subtitles, GROUP_INDEX_SUBTITLE, streamKeys),
muxedAudioFormat,
muxedCaptionFormats,
hasIndependentSegments);
hasIndependentSegments,
variableDefinitions);
}
/**
......@@ -169,7 +177,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
emptyList,
/* muxedAudioFormat= */ null,
/* muxedCaptionFormats= */ null,
/* hasIndependentSegments= */ false);
/* hasIndependentSegments= */ false,
/* variableDefinitions= */ Collections.emptyMap());
}
private static List<HlsUrl> copyRenditionsList(
......
......@@ -117,6 +117,15 @@ public class HlsMasterPlaylistParserTest {
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2 , avc1.66.30 \"\n"
+ "http://example.com/spaces_in_codecs.m3u8\n";
private static final String PLAYLIST_WITH_VARIABLE_SUBSTITUTION =
" #EXTM3U \n"
+ "\n"
+ "#EXT-X-DEFINE:NAME=\"codecs\",VALUE=\"mp4a.40.5\"\n"
+ "#EXT-X-DEFINE:NAME=\"tricky\",VALUE=\"This/{$nested}/reference/shouldnt/work\"\n"
+ "#EXT-X-DEFINE:NAME=\"nested\",VALUE=\"This should not be inserted\"\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"{$codecs}\"\n"
+ "http://example.com/{$tricky}\n";
@Test
public void testParseMasterPlaylist() throws IOException {
HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_SIMPLE);
......@@ -218,6 +227,15 @@ public class HlsMasterPlaylistParserTest {
assertThat(playlistWithoutIndependentSegments.hasIndependentSegments).isFalse();
}
@Test
public void testVariableSubstitution() throws IOException {
HlsMasterPlaylist playlistWithSubstitutions =
parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_VARIABLE_SUBSTITUTION);
HlsMasterPlaylist.HlsUrl variant = playlistWithSubstitutions.variants.get(0);
assertThat(variant.format.codecs).isEqualTo("mp4a.40.5");
assertThat(variant.url).isEqualTo("http://example.com/This/{$nested}/reference/shouldnt/work");
}
private static HlsMasterPlaylist parseMasterPlaylist(String uri, String playlistString)
throws IOException {
Uri playlistUri = Uri.parse(uri);
......
......@@ -25,6 +25,7 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
......@@ -397,9 +398,71 @@ public class HlsMediaPlaylistParserTest {
/* subtitles= */ Collections.emptyList(),
/* muxedAudioFormat= */ null,
/* muxedCaptionFormats= */ null,
/* hasIndependentSegments= */ true);
/* hasIndependentSegments= */ true,
/* variableDefinitions */ Collections.emptyMap());
HlsMediaPlaylist playlistWithInheritance =
(HlsMediaPlaylist) new HlsPlaylistParser(masterPlaylist).parse(playlistUri, inputStream);
assertThat(playlistWithInheritance.hasIndependentSegments).isTrue();
}
@Test
public void testVariableSubstitution() throws IOException {
Uri playlistUri = Uri.parse("https://example.com/substitution.m3u8");
String playlistString =
"#EXTM3U\n"
+ "#EXT-X-VERSION:8\n"
+ "#EXT-X-DEFINE:NAME=\"underscore_1\",VALUE=\"{\"\n"
+ "#EXT-X-DEFINE:NAME=\"dash-1\",VALUE=\"replaced_value.ts\"\n"
+ "#EXT-X-TARGETDURATION:5\n"
+ "#EXT-X-MEDIA-SEQUENCE:10\n"
+ "#EXTINF:5.005,\n"
+ "segment1.ts\n"
+ "#EXT-X-MAP:URI=\"{$dash-1}\""
+ "#EXTINF:5.005,\n"
+ "segment{$underscore_1}$name_1}\n";
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
HlsMediaPlaylist playlist =
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
Segment segment = playlist.segments.get(1);
assertThat(segment.initializationSegment.url).isEqualTo("replaced_value.ts");
assertThat(segment.url).isEqualTo("segment{$name_1}");
}
@Test
public void testInheritedVariableSubstitution() throws IOException {
Uri playlistUri = Uri.parse("https://example.com/test3.m3u8");
String playlistString =
"#EXTM3U\n"
+ "#EXT-X-VERSION:8\n"
+ "#EXT-X-TARGETDURATION:5\n"
+ "#EXT-X-MEDIA-SEQUENCE:10\n"
+ "#EXT-X-DEFINE:IMPORT=\"imported_base\"\n"
+ "#EXTINF:5.005,\n"
+ "{$imported_base}1.ts\n"
+ "#EXTINF:5.005,\n"
+ "{$imported_base}2.ts\n"
+ "#EXTINF:5.005,\n"
+ "{$imported_base}3.ts\n"
+ "#EXTINF:5.005,\n"
+ "{$imported_base}4.ts\n";
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
HashMap<String, String> variableDefinitions = new HashMap<>();
variableDefinitions.put("imported_base", "long_path");
HlsMasterPlaylist masterPlaylist =
new HlsMasterPlaylist(
/* baseUri= */ "",
/* tags= */ Collections.emptyList(),
/* variants= */ Collections.emptyList(),
/* audios= */ Collections.emptyList(),
/* subtitles= */ Collections.emptyList(),
/* muxedAudioFormat= */ null,
/* muxedCaptionFormats= */ Collections.emptyList(),
/* hasIndependentSegments= */ false,
variableDefinitions);
HlsMediaPlaylist playlist =
(HlsMediaPlaylist) new HlsPlaylistParser(masterPlaylist).parse(playlistUri, inputStream);
for (int i = 1; i <= 4; i++) {
assertThat(playlist.segments.get(i - 1).url).isEqualTo("long_path" + i + ".ts");
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment