core/homeassistant/components/stream/hls.py

127 lines
3.4 KiB
Python

"""Provide functionality to stream HLS."""
from aiohttp import web
from homeassistant.core import callback
from homeassistant.util.dt import utcnow
from .const import FORMAT_CONTENT_TYPE
from .core import StreamView, StreamOutput, PROVIDERS
@callback
def async_setup_hls(hass):
"""Set up api endpoints."""
hass.http.register_view(HlsPlaylistView())
hass.http.register_view(HlsSegmentView())
return '/api/hls/{}/playlist.m3u8'
class HlsPlaylistView(StreamView):
"""Stream view to serve a M3U8 stream."""
url = r'/api/hls/{token:[a-f0-9]+}/playlist.m3u8'
name = 'api:stream:hls:playlist'
cors_allowed = True
async def handle(self, request, stream, sequence):
"""Return m3u8 playlist."""
renderer = M3U8Renderer(stream)
track = stream.add_provider('hls')
stream.start()
# Wait for a segment to be ready
if not track.segments:
await track.recv()
headers = {
'Content-Type': FORMAT_CONTENT_TYPE['hls']
}
return web.Response(body=renderer.render(
track, utcnow()).encode("utf-8"), headers=headers)
class HlsSegmentView(StreamView):
"""Stream view to serve a MPEG2TS segment."""
url = r'/api/hls/{token:[a-f0-9]+}/segment/{sequence:\d+}.ts'
name = 'api:stream:hls:segment'
cors_allowed = True
async def handle(self, request, stream, sequence):
"""Return mpegts segment."""
track = stream.add_provider('hls')
segment = track.get_segment(int(sequence))
if not segment:
return web.HTTPNotFound()
headers = {
'Content-Type': 'video/mp2t'
}
return web.Response(body=segment.segment.getvalue(), headers=headers)
class M3U8Renderer:
"""M3U8 Render Helper."""
def __init__(self, stream):
"""Initialize renderer."""
self.stream = stream
@staticmethod
def render_preamble(track):
"""Render preamble."""
return [
"#EXT-X-VERSION:3",
"#EXT-X-TARGETDURATION:{}".format(track.target_duration),
]
@staticmethod
def render_playlist(track, start_time):
"""Render playlist."""
segments = track.segments
if not segments:
return []
playlist = ["#EXT-X-MEDIA-SEQUENCE:{}".format(segments[0])]
for sequence in segments:
segment = track.get_segment(sequence)
playlist.extend([
"#EXTINF:{:.04f},".format(float(segment.duration)),
"./segment/{}.ts".format(segment.sequence),
])
return playlist
def render(self, track, start_time):
"""Render M3U8 file."""
lines = (
["#EXTM3U"] +
self.render_preamble(track) +
self.render_playlist(track, start_time)
)
return "\n".join(lines) + "\n"
@PROVIDERS.register('hls')
class HlsStreamOutput(StreamOutput):
"""Represents HLS Output formats."""
@property
def name(self) -> str:
"""Return provider name."""
return 'hls'
@property
def format(self) -> str:
"""Return container format."""
return 'mpegts'
@property
def audio_codec(self) -> str:
"""Return desired audio codec."""
return 'aac'
@property
def video_codec(self) -> str:
"""Return desired video codec."""
return 'h264'