restreamer: When generating playlists, include discontinuities, timestamps and endlist

This fills out the incomplete playlist generation functionality to handle holes
and communicate extra information. See comments for details.
pull/6/head
Mike Lang 6 years ago
parent 201959888a
commit b4e627f382

@ -24,6 +24,7 @@ def generate_media(segments, base_url):
"""Generate a media playlist from a list of segments as returned by common.get_best_segments(). """Generate a media playlist from a list of segments as returned by common.get_best_segments().
Segments are specified as hour/name.ts relative to base_url. Segments are specified as hour/name.ts relative to base_url.
""" """
# We have to pick a "target duration". in most circumstances almost all segments # We have to pick a "target duration". in most circumstances almost all segments
# will be of that duration, so we get the most common duration out of all the segments # will be of that duration, so we get the most common duration out of all the segments
# and use that. # and use that.
@ -34,14 +35,45 @@ def generate_media(segments, base_url):
((target_duration, _),) = Counter(segment.duration for segment in non_none_segments).most_common(1) ((target_duration, _),) = Counter(segment.duration for segment in non_none_segments).most_common(1)
else: else:
target_duration = datetime.timedelta(seconds=6) target_duration = datetime.timedelta(seconds=6)
lines = [ lines = [
"#EXTM3U", "#EXTM3U",
"#EXT-X-TARGETDURATION:{:.3f}".format(target_duration.total_seconds()), "#EXT-X-TARGETDURATION:{:.3f}".format(target_duration.total_seconds()),
] ]
# Note and remove any trailing None from the segment list - this indicates there is a hole
# at the end, which means we should mark the stream as incomplete but not include a discontinuity.
if segments and segments[-1] is None:
incomplete = True
segments = segments[:-1]
else:
incomplete = False
# Remove any leading None from the segment list - this indicates there is a hole at the start,
# which isn't actually meaningful in any way to us.
# Note that in the case of segments = [None], we already removed it when we removed the trailing
# None, and segments is now []. This is fine.
if segments and segments[0] is None:
segments = segments[1:]
for segment in segments: for segment in segments:
# TODO handle missing bits, stream endings, other stuff if segment is None:
if segment is not None: # Discontinuity. Adding this tag tells the client that we've missed something
# and it should start decoding fresh on the next segment. This is required when
# someone stops/starts a stream and a good idea if we're missing a segment in a
# continuous stream.
lines.append("#EXT-X-DISCONTINUITY")
else:
# Each segment has two prefixes: timestamp and duration.
# This tells the client exactly what time the segment represents, which is important
# for the editor since it needs to describe cut points in these times.
path = '/'.join(segment.path.split('/')[-2:]) path = '/'.join(segment.path.split('/')[-2:])
lines.append("#EXT-X-PROGRAM-DATE-TIME:{}".format(segment.start.strftime("%Y-%m-%dT%H:%M:%S.%fZ")))
lines.append("#EXTINF:{:.3f},live".format(segment.duration.total_seconds())) lines.append("#EXTINF:{:.3f},live".format(segment.duration.total_seconds()))
lines.append(urllib.quote(os.path.join(base_url, path))) lines.append(urllib.quote(os.path.join(base_url, path)))
# If stream is complete, add an ENDLIST marker to show this.
if not incomplete:
lines.append("#EXT-X-ENDLIST")
return "\n".join(lines) + '\n' return "\n".join(lines) + '\n'

Loading…
Cancel
Save