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 @@ ...@@ -70,6 +70,8 @@
* Allow configuration of the Loader retry delay * Allow configuration of the Loader retry delay
([#3370](https://github.com/google/ExoPlayer/issues/3370)). ([#3370](https://github.com/google/ExoPlayer/issues/3370)).
* HLS: * HLS:
* Add support for variable substitution
([#4422](https://github.com/google/ExoPlayer/issues/4422)).
* Add support for PlayReady. * Add support for PlayReady.
* Add support for alternative EXT-X-KEY tags. * Add support for alternative EXT-X-KEY tags.
* Set the bitrate on primary track sample formats * Set the bitrate on primary track sample formats
......
...@@ -21,6 +21,7 @@ import com.google.android.exoplayer2.util.MimeTypes; ...@@ -21,6 +21,7 @@ import com.google.android.exoplayer2.util.MimeTypes;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
/** Represents an HLS master playlist. */ /** Represents an HLS master playlist. */
public final class HlsMasterPlaylist extends HlsPlaylist { public final class HlsMasterPlaylist extends HlsPlaylist {
...@@ -35,7 +36,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist { ...@@ -35,7 +36,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
/* subtitles= */ Collections.emptyList(), /* subtitles= */ Collections.emptyList(),
/* muxedAudioFormat= */ null, /* muxedAudioFormat= */ null,
/* muxedCaptionFormats= */ Collections.emptyList(), /* muxedCaptionFormats= */ Collections.emptyList(),
/* hasIndependentSegments= */ false); /* hasIndependentSegments= */ false,
/* variableDefinitions= */ Collections.emptyMap());
public static final int GROUP_INDEX_VARIANT = 0; public static final int GROUP_INDEX_VARIANT = 0;
public static final int GROUP_INDEX_AUDIO = 1; public static final int GROUP_INDEX_AUDIO = 1;
...@@ -110,6 +112,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist { ...@@ -110,6 +112,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
* captions information. * captions information.
*/ */
public final List<Format> muxedCaptionFormats; 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}. * @param baseUri See {@link #baseUri}.
...@@ -120,6 +124,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist { ...@@ -120,6 +124,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
* @param muxedAudioFormat See {@link #muxedAudioFormat}. * @param muxedAudioFormat See {@link #muxedAudioFormat}.
* @param muxedCaptionFormats See {@link #muxedCaptionFormats}. * @param muxedCaptionFormats See {@link #muxedCaptionFormats}.
* @param hasIndependentSegments See {@link #hasIndependentSegments}. * @param hasIndependentSegments See {@link #hasIndependentSegments}.
* @param variableDefinitions See {@link #variableDefinitions}.
*/ */
public HlsMasterPlaylist( public HlsMasterPlaylist(
String baseUri, String baseUri,
...@@ -129,7 +134,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist { ...@@ -129,7 +134,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
List<HlsUrl> subtitles, List<HlsUrl> subtitles,
Format muxedAudioFormat, Format muxedAudioFormat,
List<Format> muxedCaptionFormats, List<Format> muxedCaptionFormats,
boolean hasIndependentSegments) { boolean hasIndependentSegments,
Map<String, String> variableDefinitions) {
super(baseUri, tags, hasIndependentSegments); super(baseUri, tags, hasIndependentSegments);
this.variants = Collections.unmodifiableList(variants); this.variants = Collections.unmodifiableList(variants);
this.audios = Collections.unmodifiableList(audios); this.audios = Collections.unmodifiableList(audios);
...@@ -137,6 +143,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist { ...@@ -137,6 +143,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
this.muxedAudioFormat = muxedAudioFormat; this.muxedAudioFormat = muxedAudioFormat;
this.muxedCaptionFormats = muxedCaptionFormats != null this.muxedCaptionFormats = muxedCaptionFormats != null
? Collections.unmodifiableList(muxedCaptionFormats) : null; ? Collections.unmodifiableList(muxedCaptionFormats) : null;
this.variableDefinitions = Collections.unmodifiableMap(variableDefinitions);
} }
@Override @Override
...@@ -149,7 +156,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist { ...@@ -149,7 +156,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
copyRenditionsList(subtitles, GROUP_INDEX_SUBTITLE, streamKeys), copyRenditionsList(subtitles, GROUP_INDEX_SUBTITLE, streamKeys),
muxedAudioFormat, muxedAudioFormat,
muxedCaptionFormats, muxedCaptionFormats,
hasIndependentSegments); hasIndependentSegments,
variableDefinitions);
} }
/** /**
...@@ -169,7 +177,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist { ...@@ -169,7 +177,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
emptyList, emptyList,
/* muxedAudioFormat= */ null, /* muxedAudioFormat= */ null,
/* muxedCaptionFormats= */ null, /* muxedCaptionFormats= */ null,
/* hasIndependentSegments= */ false); /* hasIndependentSegments= */ false,
/* variableDefinitions= */ Collections.emptyMap());
} }
private static List<HlsUrl> copyRenditionsList( private static List<HlsUrl> copyRenditionsList(
......
...@@ -117,6 +117,15 @@ public class HlsMasterPlaylistParserTest { ...@@ -117,6 +117,15 @@ public class HlsMasterPlaylistParserTest {
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2 , avc1.66.30 \"\n" + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2 , avc1.66.30 \"\n"
+ "http://example.com/spaces_in_codecs.m3u8\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 @Test
public void testParseMasterPlaylist() throws IOException { public void testParseMasterPlaylist() throws IOException {
HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_SIMPLE); HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_SIMPLE);
...@@ -218,6 +227,15 @@ public class HlsMasterPlaylistParserTest { ...@@ -218,6 +227,15 @@ public class HlsMasterPlaylistParserTest {
assertThat(playlistWithoutIndependentSegments.hasIndependentSegments).isFalse(); 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) private static HlsMasterPlaylist parseMasterPlaylist(String uri, String playlistString)
throws IOException { throws IOException {
Uri playlistUri = Uri.parse(uri); Uri playlistUri = Uri.parse(uri);
......
...@@ -25,6 +25,7 @@ import java.io.ByteArrayInputStream; ...@@ -25,6 +25,7 @@ import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
...@@ -397,9 +398,71 @@ public class HlsMediaPlaylistParserTest { ...@@ -397,9 +398,71 @@ public class HlsMediaPlaylistParserTest {
/* subtitles= */ Collections.emptyList(), /* subtitles= */ Collections.emptyList(),
/* muxedAudioFormat= */ null, /* muxedAudioFormat= */ null,
/* muxedCaptionFormats= */ null, /* muxedCaptionFormats= */ null,
/* hasIndependentSegments= */ true); /* hasIndependentSegments= */ true,
/* variableDefinitions */ Collections.emptyMap());
HlsMediaPlaylist playlistWithInheritance = HlsMediaPlaylist playlistWithInheritance =
(HlsMediaPlaylist) new HlsPlaylistParser(masterPlaylist).parse(playlistUri, inputStream); (HlsMediaPlaylist) new HlsPlaylistParser(masterPlaylist).parse(playlistUri, inputStream);
assertThat(playlistWithInheritance.hasIndependentSegments).isTrue(); 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