Live Streams Component (#21473)
* initial commit of streams * refactor stream component * refactor so stream formats are not considered a platform * initial test and minor refactor * fix linting * update requirements * need av in tests as well * fix import in class def vs method * fix travis and docker builds * address code review comments * fix logger, add stream start/stop logs, listen to HASS stop * address additional code review comments * beef up tests * fix tests * fix lint * add stream_source to onvif camera * address pr comments * add keepalive to camera play_stream service * remove keepalive and move import * implement registry and have output provider remove itself from stream after idle, set libav log level to error
This commit is contained in:
parent
0a6ba14444
commit
7ccd0bba9a
18 changed files with 993 additions and 5 deletions
126
homeassistant/components/stream/hls.py
Normal file
126
homeassistant/components/stream/hls.py
Normal file
|
@ -0,0 +1,126 @@
|
|||
"""
|
||||
Provide functionality to stream HLS.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/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:{:.04},".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 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'
|
Loading…
Add table
Add a link
Reference in a new issue