Refactor stream to create partial segments (#51282)
This commit is contained in:
parent
1adeb82930
commit
123e8f01a1
10 changed files with 499 additions and 305 deletions
|
@ -37,9 +37,12 @@ class HlsMasterPlaylistView(StreamView):
|
|||
# Need to calculate max bandwidth as input_container.bit_rate doesn't seem to work
|
||||
# Calculate file size / duration and use a small multiplier to account for variation
|
||||
# hls spec already allows for 25% variation
|
||||
segment = track.get_segment(track.sequences[-1])
|
||||
segment = track.get_segment(track.sequences[-2])
|
||||
bandwidth = round(
|
||||
(len(segment.init) + len(segment.moof_data)) * 8 / segment.duration * 1.2
|
||||
(len(segment.init) + sum(len(part.data) for part in segment.parts))
|
||||
* 8
|
||||
/ segment.duration
|
||||
* 1.2
|
||||
)
|
||||
codecs = get_codec_string(segment.init)
|
||||
lines = [
|
||||
|
@ -53,9 +56,11 @@ class HlsMasterPlaylistView(StreamView):
|
|||
"""Return m3u8 playlist."""
|
||||
track = stream.add_provider(HLS_PROVIDER)
|
||||
stream.start()
|
||||
# Wait for a segment to be ready
|
||||
# Make sure at least two segments are ready (last one may not be complete)
|
||||
if not track.sequences and not await track.recv():
|
||||
return web.HTTPNotFound()
|
||||
if len(track.sequences) == 1 and not await track.recv():
|
||||
return web.HTTPNotFound()
|
||||
headers = {"Content-Type": FORMAT_CONTENT_TYPE[HLS_PROVIDER]}
|
||||
return web.Response(body=self.render(track).encode("utf-8"), headers=headers)
|
||||
|
||||
|
@ -68,69 +73,72 @@ class HlsPlaylistView(StreamView):
|
|||
cors_allowed = True
|
||||
|
||||
@staticmethod
|
||||
def render_preamble(track):
|
||||
"""Render preamble."""
|
||||
return [
|
||||
"#EXT-X-VERSION:6",
|
||||
f"#EXT-X-TARGETDURATION:{track.target_duration}",
|
||||
'#EXT-X-MAP:URI="init.mp4"',
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def render_playlist(track):
|
||||
def render(track):
|
||||
"""Render playlist."""
|
||||
segments = list(track.get_segments())[-NUM_PLAYLIST_SEGMENTS:]
|
||||
# NUM_PLAYLIST_SEGMENTS+1 because most recent is probably not yet complete
|
||||
segments = list(track.get_segments())[-(NUM_PLAYLIST_SEGMENTS + 1) :]
|
||||
|
||||
if not segments:
|
||||
return []
|
||||
# To cap the number of complete segments at NUM_PLAYLIST_SEGMENTS,
|
||||
# remove the first segment if the last segment is actually complete
|
||||
if segments[-1].complete:
|
||||
segments = segments[-NUM_PLAYLIST_SEGMENTS:]
|
||||
|
||||
first_segment = segments[0]
|
||||
playlist = [
|
||||
"#EXTM3U",
|
||||
"#EXT-X-VERSION:6",
|
||||
"#EXT-X-INDEPENDENT-SEGMENTS",
|
||||
'#EXT-X-MAP:URI="init.mp4"',
|
||||
f"#EXT-X-TARGETDURATION:{track.target_duration:.0f}",
|
||||
f"#EXT-X-MEDIA-SEQUENCE:{first_segment.sequence}",
|
||||
f"#EXT-X-DISCONTINUITY-SEQUENCE:{first_segment.stream_id}",
|
||||
"#EXT-X-PROGRAM-DATE-TIME:"
|
||||
+ first_segment.start_time.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]
|
||||
+ "Z",
|
||||
# Since our window doesn't have many segments, we don't want to start
|
||||
# at the beginning or we risk a behind live window exception in exoplayer.
|
||||
# at the beginning or we risk a behind live window exception in Exoplayer.
|
||||
# EXT-X-START is not supposed to be within 3 target durations of the end,
|
||||
# but this seems ok
|
||||
f"#EXT-X-START:TIME-OFFSET=-{EXT_X_START * track.target_duration:.3f},PRECISE=YES",
|
||||
# but a value as low as 1.5 doesn't seem to hurt.
|
||||
# A value below 3 may not be as useful for hls.js as many hls.js clients
|
||||
# don't autoplay. Also, hls.js uses the player parameter liveSyncDuration
|
||||
# which seems to take precedence for setting target delay. Yet it also
|
||||
# doesn't seem to hurt, so we can stick with it for now.
|
||||
f"#EXT-X-START:TIME-OFFSET=-{EXT_X_START * track.target_duration:.3f}",
|
||||
]
|
||||
|
||||
last_stream_id = first_segment.stream_id
|
||||
# Add playlist sections
|
||||
for segment in segments:
|
||||
if last_stream_id != segment.stream_id:
|
||||
# Skip last segment if it is not complete
|
||||
if segment.complete:
|
||||
if last_stream_id != segment.stream_id:
|
||||
playlist.extend(
|
||||
[
|
||||
"#EXT-X-DISCONTINUITY",
|
||||
"#EXT-X-PROGRAM-DATE-TIME:"
|
||||
+ segment.start_time.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]
|
||||
+ "Z",
|
||||
]
|
||||
)
|
||||
playlist.extend(
|
||||
[
|
||||
"#EXT-X-DISCONTINUITY",
|
||||
"#EXT-X-PROGRAM-DATE-TIME:"
|
||||
+ segment.start_time.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]
|
||||
+ "Z",
|
||||
f"#EXTINF:{segment.duration:.3f},",
|
||||
f"./segment/{segment.sequence}.m4s",
|
||||
]
|
||||
)
|
||||
playlist.extend(
|
||||
[
|
||||
f"#EXTINF:{float(segment.duration):.04f},",
|
||||
f"./segment/{segment.sequence}.m4s",
|
||||
]
|
||||
)
|
||||
last_stream_id = segment.stream_id
|
||||
last_stream_id = segment.stream_id
|
||||
|
||||
return playlist
|
||||
|
||||
def render(self, track):
|
||||
"""Render M3U8 file."""
|
||||
lines = ["#EXTM3U"] + self.render_preamble(track) + self.render_playlist(track)
|
||||
return "\n".join(lines) + "\n"
|
||||
return "\n".join(playlist) + "\n"
|
||||
|
||||
async def handle(self, request, stream, sequence):
|
||||
"""Return m3u8 playlist."""
|
||||
track = stream.add_provider(HLS_PROVIDER)
|
||||
stream.start()
|
||||
# Wait for a segment to be ready
|
||||
# Make sure at least two segments are ready (last one may not be complete)
|
||||
if not track.sequences and not await track.recv():
|
||||
return web.HTTPNotFound()
|
||||
if len(track.sequences) == 1 and not await track.recv():
|
||||
return web.HTTPNotFound()
|
||||
headers = {"Content-Type": FORMAT_CONTENT_TYPE[HLS_PROVIDER]}
|
||||
response = web.Response(
|
||||
body=self.render(track).encode("utf-8"), headers=headers
|
||||
|
@ -170,7 +178,7 @@ class HlsSegmentView(StreamView):
|
|||
return web.HTTPNotFound()
|
||||
headers = {"Content-Type": "video/iso.segment"}
|
||||
return web.Response(
|
||||
body=segment.moof_data,
|
||||
body=segment.get_bytes_without_init(),
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue