mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Block HLS playlist requests at part level
Issue: #5011 PiperOrigin-RevId: 340625816
This commit is contained in:
parent
4332dc2304
commit
7f49b33fea
@ -30,6 +30,7 @@ import com.google.android.exoplayer2.source.MediaLoadData;
|
||||
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
|
||||
import com.google.android.exoplayer2.source.hls.HlsDataSourceFactory;
|
||||
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Variant;
|
||||
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Part;
|
||||
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||
@ -40,6 +41,7 @@ import com.google.android.exoplayer2.upstream.Loader.LoadErrorAction;
|
||||
import com.google.android.exoplayer2.upstream.ParsingLoadable;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.common.collect.Iterables;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@ -465,6 +467,7 @@ public final class DefaultHlsPlaylistTracker
|
||||
private final class MediaPlaylistBundle implements Loader.Callback<ParsingLoadable<HlsPlaylist>> {
|
||||
|
||||
private static final String BLOCK_MSN_PARAM = "_HLS_msn";
|
||||
private static final String BLOCK_PART_PARAM = "_HLS_part";
|
||||
private static final String SKIP_PARAM = "_HLS_skip";
|
||||
|
||||
private final Uri playlistUrl;
|
||||
@ -729,16 +732,25 @@ public final class DefaultHlsPlaylistTracker
|
||||
return playlistUrl;
|
||||
}
|
||||
Uri.Builder uriBuilder = playlistUrl.buildUpon();
|
||||
if (playlistSnapshot.serverControl.skipUntilUs != C.TIME_UNSET) {
|
||||
uriBuilder.appendQueryParameter(
|
||||
SKIP_PARAM, playlistSnapshot.serverControl.canSkipDateRanges ? "v2" : "YES");
|
||||
}
|
||||
if (playlistSnapshot.serverControl.canBlockReload) {
|
||||
long reloadMediaSequence =
|
||||
long targetMediaSequence =
|
||||
playlistSnapshot.mediaSequence
|
||||
+ playlistSnapshot.segments.size()
|
||||
+ playlistSnapshot.skippedSegmentCount;
|
||||
uriBuilder.appendQueryParameter(BLOCK_MSN_PARAM, String.valueOf(reloadMediaSequence));
|
||||
uriBuilder.appendQueryParameter(BLOCK_MSN_PARAM, String.valueOf(targetMediaSequence));
|
||||
if (playlistSnapshot.partTargetDurationUs != C.TIME_UNSET) {
|
||||
List<Part> trailingParts = playlistSnapshot.trailingParts;
|
||||
int targetPartIndex = trailingParts.size();
|
||||
if (!trailingParts.isEmpty() && Iterables.getLast(trailingParts).isPreload) {
|
||||
// Ignore the preload part.
|
||||
targetPartIndex--;
|
||||
}
|
||||
uriBuilder.appendQueryParameter(BLOCK_PART_PARAM, String.valueOf(targetPartIndex));
|
||||
}
|
||||
}
|
||||
if (playlistSnapshot.serverControl.skipUntilUs != C.TIME_UNSET) {
|
||||
uriBuilder.appendQueryParameter(
|
||||
SKIP_PARAM, playlistSnapshot.serverControl.canSkipDateRanges ? "v2" : "YES");
|
||||
}
|
||||
return uriBuilder.build();
|
||||
}
|
||||
|
@ -176,6 +176,8 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||
|
||||
/** Whether the part is independent. */
|
||||
public final boolean isIndependent;
|
||||
/** Whether the part is a preloading part. */
|
||||
public final boolean isPreload;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
@ -192,6 +194,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||
* @param byteRangeLength See {@link #byteRangeLength}.
|
||||
* @param hasGapTag See {@link #hasGapTag}.
|
||||
* @param isIndependent See {@link #isIndependent}.
|
||||
* @param isPreload See {@link #isPreload}.
|
||||
*/
|
||||
public Part(
|
||||
String url,
|
||||
@ -205,7 +208,8 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||
long byteRangeOffset,
|
||||
long byteRangeLength,
|
||||
boolean hasGapTag,
|
||||
boolean isIndependent) {
|
||||
boolean isIndependent,
|
||||
boolean isPreload) {
|
||||
super(
|
||||
url,
|
||||
initializationSegment,
|
||||
@ -219,6 +223,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||
byteRangeLength,
|
||||
hasGapTag);
|
||||
this.isIndependent = isIndependent;
|
||||
this.isPreload = isPreload;
|
||||
}
|
||||
}
|
||||
|
||||
@ -502,8 +507,13 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||
// The media sequences are equal.
|
||||
int segmentCount = segments.size() + skippedSegmentCount;
|
||||
int otherSegmentCount = other.segments.size() + other.skippedSegmentCount;
|
||||
return segmentCount > otherSegmentCount
|
||||
|| (segmentCount == otherSegmentCount && hasEndTag && !other.hasEndTag);
|
||||
if (segmentCount != otherSegmentCount) {
|
||||
return segmentCount > otherSegmentCount;
|
||||
}
|
||||
int partCount = trailingParts.size();
|
||||
int otherPartCount = other.trailingParts.size();
|
||||
return partCount > otherPartCount
|
||||
|| (partCount == otherPartCount && hasEndTag && !other.hasEndTag);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -824,7 +824,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
byteRangeStart,
|
||||
byteRangeLength,
|
||||
/* hasGapTag= */ false,
|
||||
/* isIndependent= */ false);
|
||||
/* isIndependent= */ false,
|
||||
/* isPreload= */ true);
|
||||
} else if (line.startsWith(TAG_PART)) {
|
||||
@Nullable
|
||||
String segmentEncryptionIV =
|
||||
@ -869,7 +870,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
partByteRangeOffset,
|
||||
partByteRangeLength,
|
||||
isGap,
|
||||
isIndependent));
|
||||
isIndependent,
|
||||
/* isPreload= */ false));
|
||||
partStartTimeUs += partDurationUs;
|
||||
if (partByteRangeLength != C.LENGTH_UNSET) {
|
||||
partByteRangeOffset += partByteRangeLength;
|
||||
|
@ -64,6 +64,21 @@ public class DefaultHlsPlaylistTrackerTest {
|
||||
"media/m3u8/live_low_latency_media_can_block_reload";
|
||||
private static final String SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_NEXT =
|
||||
"media/m3u8/live_low_latency_media_can_block_reload_next";
|
||||
private static final String SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY =
|
||||
"media/m3u8/live_low_latency_media_can_block_reload_low_latency";
|
||||
private static final String SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_NEXT =
|
||||
"media/m3u8/live_low_latency_media_can_block_reload_low_latency_next";
|
||||
private static final String SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT =
|
||||
"media/m3u8/live_low_latency_media_can_block_reload_low_latency_full_segment";
|
||||
private static final String
|
||||
SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT_NEXT =
|
||||
"media/m3u8/live_low_latency_media_can_block_reload_low_latency_full_segment_next";
|
||||
private static final String
|
||||
SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT_PRELOAD =
|
||||
"media/m3u8/live_low_latency_media_can_block_reload_low_latency_full_segment_preload";
|
||||
private static final String
|
||||
SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT_PRELOAD_NEXT =
|
||||
"media/m3u8/live_low_latency_media_can_block_reload_low_latency_full_segment_preload_next";
|
||||
private static final String SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL_AND_BLOCK_RELOAD =
|
||||
"media/m3u8/live_low_latency_media_can_skip_until_and_block_reload";
|
||||
private static final String SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL_AND_BLOCK_RELOAD_NEXT =
|
||||
@ -255,6 +270,96 @@ public class DefaultHlsPlaylistTrackerTest {
|
||||
assertThat(mediaPlaylists.get(1).mediaSequence).isEqualTo(11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
start_playlistCanBlockReloadLowLatency_requestBlockingReloadWithCorrectMediaSequenceAndPart()
|
||||
throws IOException, TimeoutException, InterruptedException {
|
||||
List<HttpUrl> httpUrls =
|
||||
enqueueWebServerResponses(
|
||||
new String[] {
|
||||
"/master.m3u8",
|
||||
"/media0/playlist.m3u8",
|
||||
"/media0/playlist.m3u8?_HLS_msn=14&_HLS_part=1"
|
||||
},
|
||||
getMockResponse(SAMPLE_M3U8_LIVE_MASTER),
|
||||
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY),
|
||||
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_NEXT));
|
||||
|
||||
List<HlsMediaPlaylist> mediaPlaylists =
|
||||
runPlaylistTrackerAndCollectMediaPlaylists(
|
||||
new DefaultHttpDataSourceFactory(),
|
||||
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
|
||||
/* awaitedMediaPlaylistCount= */ 2);
|
||||
|
||||
assertRequestUrlsCalled(httpUrls);
|
||||
assertThat(mediaPlaylists.get(0).mediaSequence).isEqualTo(10);
|
||||
assertThat(mediaPlaylists.get(0).segments).hasSize(4);
|
||||
assertThat(mediaPlaylists.get(0).trailingParts).hasSize(2);
|
||||
assertThat(mediaPlaylists.get(1).mediaSequence).isEqualTo(10);
|
||||
assertThat(mediaPlaylists.get(1).segments).hasSize(4);
|
||||
assertThat(mediaPlaylists.get(1).trailingParts).hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void start_playlistCanBlockReloadLowLatencyFullSegment_correctMsnAndPartParams()
|
||||
throws IOException, TimeoutException, InterruptedException {
|
||||
List<HttpUrl> httpUrls =
|
||||
enqueueWebServerResponses(
|
||||
new String[] {
|
||||
"/master.m3u8",
|
||||
"/media0/playlist.m3u8",
|
||||
"/media0/playlist.m3u8?_HLS_msn=14&_HLS_part=0"
|
||||
},
|
||||
getMockResponse(SAMPLE_M3U8_LIVE_MASTER),
|
||||
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT),
|
||||
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT_NEXT));
|
||||
|
||||
List<HlsMediaPlaylist> mediaPlaylists =
|
||||
runPlaylistTrackerAndCollectMediaPlaylists(
|
||||
new DefaultHttpDataSourceFactory(),
|
||||
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
|
||||
/* awaitedMediaPlaylistCount= */ 2);
|
||||
|
||||
assertRequestUrlsCalled(httpUrls);
|
||||
assertThat(mediaPlaylists.get(0).mediaSequence).isEqualTo(10);
|
||||
assertThat(mediaPlaylists.get(0).segments).hasSize(4);
|
||||
assertThat(mediaPlaylists.get(0).trailingParts).isEmpty();
|
||||
assertThat(mediaPlaylists.get(1).mediaSequence).isEqualTo(10);
|
||||
assertThat(mediaPlaylists.get(1).segments).hasSize(4);
|
||||
assertThat(mediaPlaylists.get(1).trailingParts).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void start_playlistCanBlockReloadLowLatencyFullSegmentWithPreloadPart_ignoresPreloadPart()
|
||||
throws IOException, TimeoutException, InterruptedException {
|
||||
List<HttpUrl> httpUrls =
|
||||
enqueueWebServerResponses(
|
||||
new String[] {
|
||||
"/master.m3u8",
|
||||
"/media0/playlist.m3u8",
|
||||
"/media0/playlist.m3u8?_HLS_msn=14&_HLS_part=0"
|
||||
},
|
||||
getMockResponse(SAMPLE_M3U8_LIVE_MASTER),
|
||||
getMockResponse(
|
||||
SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT_PRELOAD),
|
||||
getMockResponse(
|
||||
SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_LOW_LATENCY_FULL_SEGMENT_PRELOAD_NEXT));
|
||||
|
||||
List<HlsMediaPlaylist> mediaPlaylists =
|
||||
runPlaylistTrackerAndCollectMediaPlaylists(
|
||||
new DefaultHttpDataSourceFactory(),
|
||||
Uri.parse(mockWebServer.url("/master.m3u8").toString()),
|
||||
/* awaitedMediaPlaylistCount= */ 2);
|
||||
|
||||
assertRequestUrlsCalled(httpUrls);
|
||||
assertThat(mediaPlaylists.get(0).mediaSequence).isEqualTo(10);
|
||||
assertThat(mediaPlaylists.get(0).segments).hasSize(4);
|
||||
assertThat(mediaPlaylists.get(0).trailingParts).hasSize(1);
|
||||
assertThat(mediaPlaylists.get(1).mediaSequence).isEqualTo(10);
|
||||
assertThat(mediaPlaylists.get(1).segments).hasSize(4);
|
||||
assertThat(mediaPlaylists.get(1).trailingParts).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void start_httpBadRequest_forcesFullNonBlockingPlaylistRequest()
|
||||
throws IOException, TimeoutException, InterruptedException {
|
||||
@ -263,9 +368,9 @@ public class DefaultHlsPlaylistTrackerTest {
|
||||
new String[] {
|
||||
"/master.m3u8",
|
||||
"/media0/playlist.m3u8",
|
||||
"/media0/playlist.m3u8?_HLS_skip=YES&_HLS_msn=16",
|
||||
"/media0/playlist.m3u8?_HLS_msn=16&_HLS_skip=YES",
|
||||
"/media0/playlist.m3u8",
|
||||
"/media0/playlist.m3u8?_HLS_skip=YES&_HLS_msn=17"
|
||||
"/media0/playlist.m3u8?_HLS_msn=17&_HLS_skip=YES"
|
||||
},
|
||||
getMockResponse(SAMPLE_M3U8_LIVE_MASTER),
|
||||
getMockResponse(SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL_AND_BLOCK_RELOAD),
|
||||
|
@ -354,6 +354,7 @@ public class HlsMediaPlaylistParserTest {
|
||||
assertThat(firstPart.durationUs).isEqualTo(2_000_000);
|
||||
assertThat(firstPart.relativeStartTimeUs).isEqualTo(playlist.segments.get(0).durationUs);
|
||||
assertThat(firstPart.isIndependent).isTrue();
|
||||
assertThat(firstPart.isPreload).isFalse();
|
||||
assertThat(firstPart.hasGapTag).isTrue();
|
||||
assertThat(firstPart.url).isEqualTo("part267.1.ts");
|
||||
HlsMediaPlaylist.Part secondPart = playlist.segments.get(1).parts.get(1);
|
||||
@ -518,6 +519,7 @@ public class HlsMediaPlaylistParserTest {
|
||||
assertThat(preloadPart.byteRangeOffset).isEqualTo(1234);
|
||||
assertThat(preloadPart.initializationSegment.url).isEqualTo("map.mp4");
|
||||
assertThat(preloadPart.encryptionIV).isEqualTo("0x410C8AC18AA42EFA18B5155484F5FC34");
|
||||
assertThat(preloadPart.isPreload).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -539,6 +541,7 @@ public class HlsMediaPlaylistParserTest {
|
||||
|
||||
assertThat(playlist.trailingParts).hasSize(2);
|
||||
assertThat(playlist.trailingParts.get(1).url).isEqualTo("filePart267.2.ts");
|
||||
assertThat(playlist.trailingParts.get(1).isPreload).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
16
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency
vendored
Normal file
16
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
#EXTM3U
|
||||
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES
|
||||
#EXT-X-TARGETDURATION:4
|
||||
#EXT-X-PART-INF:PART-TARGET=1.000000
|
||||
#EXT-X-VERSION:3
|
||||
#EXT-X-MEDIA-SEQUENCE:10
|
||||
#EXTINF:4.00000,
|
||||
fileSequence10.ts
|
||||
#EXTINF:4.00000,
|
||||
fileSequence11.ts
|
||||
#EXTINF:4.00000,
|
||||
fileSequence12.ts
|
||||
#EXTINF:4.00000,
|
||||
fileSequence13.ts
|
||||
#EXT-X-PART:DURATION=1.00000,URI="fileSequence14.0.ts"
|
||||
#EXT-X-PRELOAD-HINT:TYPE=PART,URI="fileSequence14.1.ts"
|
@ -0,0 +1,18 @@
|
||||
#EXTM3U
|
||||
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES
|
||||
#EXT-X-TARGETDURATION:4
|
||||
#EXT-X-PART-INF:PART-TARGET=1.000000
|
||||
#EXT-X-VERSION:3
|
||||
#EXT-X-MEDIA-SEQUENCE:10
|
||||
#EXTINF:4.00000,
|
||||
fileSequence10.ts
|
||||
#EXTINF:4.00000,
|
||||
fileSequence11.ts
|
||||
#EXTINF:4.00000,
|
||||
fileSequence12.ts
|
||||
#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.0.ts"
|
||||
#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.1.ts"
|
||||
#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.2.ts"
|
||||
#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.3.ts"
|
||||
#EXTINF:4.00000,
|
||||
fileSequence13.ts
|
@ -0,0 +1,19 @@
|
||||
#EXTM3U
|
||||
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES
|
||||
#EXT-X-TARGETDURATION:4
|
||||
#EXT-X-PART-INF:PART-TARGET=1.000000
|
||||
#EXT-X-VERSION:3
|
||||
#EXT-X-MEDIA-SEQUENCE:10
|
||||
#EXTINF:4.00000,
|
||||
fileSequence10.ts
|
||||
#EXTINF:4.00000,
|
||||
fileSequence11.ts
|
||||
#EXTINF:4.00000,
|
||||
fileSequence12.ts
|
||||
#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.0.ts"
|
||||
#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.1.ts"
|
||||
#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.2.ts"
|
||||
#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.3.ts"
|
||||
#EXTINF:4.00000,
|
||||
fileSequence13.ts
|
||||
#EXT-X-PART:DURATION=1.00000,URI="fileSequence14.0.ts"
|
@ -0,0 +1,19 @@
|
||||
#EXTM3U
|
||||
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES
|
||||
#EXT-X-TARGETDURATION:4
|
||||
#EXT-X-PART-INF:PART-TARGET=1.000000
|
||||
#EXT-X-VERSION:3
|
||||
#EXT-X-MEDIA-SEQUENCE:10
|
||||
#EXTINF:4.00000,
|
||||
fileSequence10.ts
|
||||
#EXTINF:4.00000,
|
||||
fileSequence11.ts
|
||||
#EXTINF:4.00000,
|
||||
fileSequence12.ts
|
||||
#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.0.ts"
|
||||
#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.1.ts"
|
||||
#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.2.ts"
|
||||
#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.3.ts"
|
||||
#EXTINF:4.00000,
|
||||
fileSequence13.ts
|
||||
#EXT-X-PRELOAD-HINT:TYPE=PART,URI="fileSequence14.0.ts"
|
@ -0,0 +1,20 @@
|
||||
#EXTM3U
|
||||
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES
|
||||
#EXT-X-TARGETDURATION:4
|
||||
#EXT-X-PART-INF:PART-TARGET=1.000000
|
||||
#EXT-X-VERSION:3
|
||||
#EXT-X-MEDIA-SEQUENCE:10
|
||||
#EXTINF:4.00000,
|
||||
fileSequence10.ts
|
||||
#EXTINF:4.00000,
|
||||
fileSequence11.ts
|
||||
#EXTINF:4.00000,
|
||||
fileSequence12.ts
|
||||
#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.0.ts"
|
||||
#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.1.ts"
|
||||
#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.2.ts"
|
||||
#EXT-X-PART:DURATION=1.00000,URI="fileSequence13.3.ts"
|
||||
#EXTINF:4.00000,
|
||||
fileSequence13.ts
|
||||
#EXT-X-PART:DURATION=1.00000,URI="fileSequence14.0.ts"
|
||||
#EXT-X-PRELOAD-HINT:TYPE=PART,URI="fileSequence14.1.ts"
|
17
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency_next
vendored
Normal file
17
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_low_latency_next
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
#EXTM3U
|
||||
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES
|
||||
#EXT-X-TARGETDURATION:4
|
||||
#EXT-X-PART-INF:PART-TARGET=1.000000
|
||||
#EXT-X-VERSION:3
|
||||
#EXT-X-MEDIA-SEQUENCE:10
|
||||
#EXTINF:4.00000,
|
||||
fileSequence10.ts
|
||||
#EXTINF:4.00000,
|
||||
fileSequence11.ts
|
||||
#EXTINF:4.00000,
|
||||
fileSequence12.ts
|
||||
#EXTINF:4.00000,
|
||||
fileSequence13.ts
|
||||
#EXT-X-PART:DURATION=1.00000,URI="fileSequence14.0.ts"
|
||||
#EXT-X-PART:DURATION=1.00000,URI="fileSequence14.1.ts"
|
||||
#EXT-X-PRELOAD-HINT:TYPE=PART,URI="fileSequence14.2.ts"
|
Loading…
x
Reference in New Issue
Block a user