-
Notifications
You must be signed in to change notification settings - Fork 192
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 436da5d
Showing
9 changed files
with
353 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
|
||
.homeassistant/ | ||
|
||
.idea/ | ||
|
||
custom_components/webrtc/__pycache__/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
# WebRTC Camera | ||
|
||
[![hacs_badge](https://img.shields.io/badge/HACS-Custom-orange.svg)](https://github.com/custom-components/hacs) | ||
[![Donate](https://img.shields.io/badge/donate-Coffee-yellow.svg)](https://www.buymeacoffee.com/AlexxIT) | ||
[![Donate](https://img.shields.io/badge/donate-Yandex-red.svg)](https://money.yandex.ru/to/41001428278477) | ||
|
||
Home Assistant custom component for viewing IP cameras [RTSP](https://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol) stream in real time using [WebRTC](https://en.wikipedia.org/wiki/WebRTC) technology. | ||
|
||
Based on: | ||
- [Pion](https://github.com/pion/webrtc) - pure Go implementation of WebRTC | ||
- [RTSPtoWebRTC](https://github.com/deepch/RTSPtoWebRTC) - Go app by [@deepch](https://github.com/deepch) and [@vdalex25](https://github.com/vdalex25) | ||
|
||
Why WebRTC: | ||
- works in any modern browser, even on mobiles | ||
- the only browser technology with minimal camera stream delays (0.5 seconds and below) | ||
- works well with unstable channel | ||
- does not use transcoding and does not load the CPU | ||
- support camera stream with sound | ||
|
||
## Install | ||
|
||
You can install component with [HACS](https://hacs.xyz/) custom repo: . HACS > Integrations > 3 dots (upper top corner) > Custom repositories > URL: `AlexxIT/WebRTC` > Category: Integration | ||
|
||
Or manually copy `webrtc` folder from [latest release](https://github.com/AlexxIT/WebRTC/releases/latest) to `custom_components` folder in your config folder. | ||
|
||
## Config | ||
|
||
With GUI. Configuration > Integration > Add Integration > WebRTC Camera. | ||
|
||
If the integration is not in the list, you need to clear the browser cache. | ||
|
||
Component **doesn't create devices/entities/services**. It creates only lovelace custom card: | ||
|
||
```yaml | ||
type: 'custom:webrtc-camera' | ||
url: 'rtsp://rtsp:[email protected]:554/av_stream/ch0' | ||
``` | ||
# About | ||
Supported clients: | ||
- macOS: Google Chrome, Safari | ||
- Windows: Google Chrome | ||
- Android: Google Chrome, Home Assistant Mobile App | ||
- iOS: Home Assistant Mobile App | ||
Limitations: | ||
- works only with H.264 camaras | ||
- for external access you need a white IP address (without provider NAT), dynamic IP is also supported | ||
Known work cameras: | ||
- Sonoff GK-200MP2-B (support sound) | ||
- EZVIZ C3S | ||
- Hikvision: DS-2CD2T47G1-L, DS-2CD1321-I, DS-2CD2143G0-IS | ||
- Reolink: RLC-410, RLC-410W, E1 Pro, 4505MP | ||
- TP-Link: Tapo C200 | ||
Support external camera access. You need to forward UDP ports 50000-50009 to Hass.io server on your router. | ||
50000-50009 ports are used only during video streaming. At each start of the streaming, a random port is occupied. The port is released when the streaming ends. The data should theoretically be encrypted, but I haven't tested :) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import logging | ||
import os | ||
import pathlib | ||
import subprocess | ||
from threading import Thread | ||
|
||
import voluptuous as vol | ||
from homeassistant.components import websocket_api | ||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP | ||
from homeassistant.helpers.aiohttp_client import async_get_clientsession | ||
from homeassistant.helpers.typing import HomeAssistantType, ConfigType | ||
|
||
from . import utils | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
DOMAIN = 'webrtc' | ||
|
||
BINARY_VERSION = 'v1' | ||
|
||
|
||
async def async_setup(hass: HomeAssistantType, config: ConfigType): | ||
curdir = pathlib.Path(__file__).parent.absolute() | ||
|
||
# check and download file if needed | ||
filepath = hass.config.path(utils.get_binary_name(BINARY_VERSION)) | ||
if not os.path.isfile(filepath): | ||
for file in os.listdir(hass.config.config_dir): | ||
if file.startswith('rtsp2webrtc_'): | ||
_LOGGER.debug(f"Remove old binary: {file}") | ||
os.remove(file) | ||
|
||
url = utils.get_binary_url(BINARY_VERSION) | ||
_LOGGER.debug(f"Donwload new binary: {url}") | ||
|
||
session = async_get_clientsession(hass) | ||
r = await session.get(url) | ||
raw = await r.read() | ||
open(filepath, 'wb').write(raw) | ||
os.chmod(filepath, 744) | ||
|
||
# serve lovelace card | ||
path = curdir / 'www/webrtc-camera.js' | ||
url_path = '/webrtc/webrtc-camera.js' | ||
hass.http.register_static_path(url_path, path, cache_headers=False) | ||
|
||
# register lovelace card | ||
if await utils.init_resource(hass, url_path): | ||
_LOGGER.debug(f"Init new lovelace custom card: {url_path}") | ||
|
||
websocket_api.async_register_command(hass, websocket_webrtc_stream) | ||
|
||
hass.data[DOMAIN] = { | ||
'filepath': filepath | ||
} | ||
|
||
return True | ||
|
||
|
||
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): | ||
filepath = hass.data[DOMAIN]['filepath'] | ||
|
||
# run communication webserver on localhost:8083 | ||
process = subprocess.Popen([filepath], stdout=subprocess.PIPE, | ||
stderr=subprocess.STDOUT) | ||
|
||
hass.data[DOMAIN][entry.entry_id] = process | ||
|
||
def run(): | ||
# check alive | ||
while process.poll() is None: | ||
line = process.stdout.readline() | ||
if line == b'': | ||
break | ||
_LOGGER.debug(line[:-1].decode()) | ||
|
||
def stop(*args): | ||
process.terminate() | ||
|
||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop) | ||
|
||
Thread(name=DOMAIN, target=run).start() | ||
|
||
return True | ||
|
||
|
||
async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): | ||
process = hass.data[DOMAIN][entry.entry_id] | ||
process.terminate() | ||
return True | ||
|
||
|
||
@websocket_api.websocket_command({ | ||
vol.Required('type'): 'webrtc/stream', | ||
vol.Required('url'): str, | ||
vol.Required('sdp64'): str | ||
}) | ||
@websocket_api.async_response | ||
async def websocket_webrtc_stream(hass: HomeAssistantType, connection, msg): | ||
try: | ||
session = async_get_clientsession(hass) | ||
r = await session.post('http://localhost:8083/stream', data={ | ||
'url': msg['url'], 'sdp64': msg['sdp64'] | ||
}) | ||
raw = await r.json() | ||
|
||
_LOGGER.debug(f"New stream to url: {msg['url']}") | ||
connection.send_result(msg['id'], raw) | ||
|
||
except Exception as e: | ||
_LOGGER.error(f"Can't start stream: {msg['url']}, because: {e}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import os | ||
|
||
from homeassistant.config_entries import ConfigFlow | ||
|
||
from . import DOMAIN, utils | ||
|
||
|
||
class FlowHandler(ConfigFlow, domain=DOMAIN): | ||
|
||
async def async_step_user(self, user_input=None): | ||
if utils.get_arch(): | ||
return self.async_create_entry(title="WebRTC Camera", data={}) | ||
|
||
return self.async_abort(reason='arch', description_placeholders={ | ||
'uname': os.uname() if os.name != 'nt' else os.name | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"domain": "webrtc", | ||
"name": "WebRTC Camera", | ||
"config_flow": true, | ||
"documentation": "https://github.com/AlexxIT/WebRTC", | ||
"issue_tracker": "https://github.com/AlexxIT/WebRTC/issues", | ||
"requirements": [], | ||
"dependencies": ["lovelace"], | ||
"codeowners": ["@AlexxIT"], | ||
"version": "v1.0.0" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"config": { | ||
"abort": { | ||
"arch": "Unsupported OS architecture: {uname}" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import os | ||
from typing import Optional | ||
|
||
from homeassistant.components.lovelace.resources import \ | ||
ResourceStorageCollection | ||
from homeassistant.helpers.typing import HomeAssistantType | ||
|
||
ARCH = { | ||
'armv7l': 'armv7', | ||
'aarch64': 'aarch64', | ||
'x86_64': 'amd64', | ||
} | ||
|
||
|
||
def get_arch() -> Optional[str]: | ||
uname = ('Windows',) if os.name == 'nt' else os.uname() | ||
if uname[0] == 'Windows': | ||
return 'amd64.exe' | ||
elif uname[0] == 'Linux' and uname[4] in ARCH: | ||
return ARCH[uname[4]] | ||
return None | ||
|
||
|
||
def get_binary_name(version: str) -> str: | ||
return f"rtsp2webrtc_{version}_{get_arch()}" | ||
|
||
|
||
def get_binary_url(version: str) -> str: | ||
return "https://github.com/AlexxIT/RTSPtoWebRTC/releases/download/" \ | ||
f"{version}/rtsp2webrtc_{get_arch()}" | ||
|
||
|
||
async def init_resource(hass: HomeAssistantType, url: str) -> bool: | ||
resources: ResourceStorageCollection = hass.data['lovelace']['resources'] | ||
# force load storage | ||
await resources.async_get_info() | ||
|
||
for item in resources.async_items(): | ||
if item['url'] == url: | ||
return False | ||
|
||
await resources.async_create_item({'res_type': 'module', 'url': url}) | ||
return True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
class WebRTCCamera extends HTMLElement { | ||
async _init(hass) { | ||
const pc = new RTCPeerConnection({ | ||
iceServers: [{ | ||
urls: ['stun:stun.l.google.com:19302'] | ||
}] | ||
}); | ||
|
||
pc.onnegotiationneeded = async () => { | ||
// console.log('onnegotiationneeded'); | ||
const offer = await pc.createOffer(); | ||
await pc.setLocalDescription(offer); | ||
|
||
const data = await hass.callWS({ | ||
type: 'webrtc/stream', | ||
url: this.config.url, | ||
sdp64: btoa(pc.localDescription.sdp) | ||
}); | ||
// console.log(data); | ||
|
||
try { | ||
const remoteDesc = new RTCSessionDescription({ | ||
type: 'answer', | ||
sdp: atob(data.sdp64) | ||
}); | ||
await pc.setRemoteDescription(remoteDesc); | ||
} catch (e) { | ||
console.warn(e); | ||
} | ||
} | ||
|
||
pc.ontrack = (event) => { | ||
// console.log('ontrack', event); | ||
const el = document.createElement(event.track.kind); | ||
el.srcObject = event.streams[0]; | ||
el.muted = true; | ||
el.autoplay = true; | ||
el.controls = true; | ||
el.style.width = '100%'; | ||
this.content.appendChild(el); | ||
} | ||
|
||
pc.addTransceiver('video', {'direction': 'recvonly'}) | ||
pc.addTransceiver('audio', {'direction': 'recvonly'}) | ||
|
||
const pingChannel = pc.createDataChannel('foo'); | ||
pingChannel.onopen = () => { | ||
setInterval(() => { | ||
try { | ||
pingChannel.send('ping'); | ||
} catch (e) { | ||
console.warn(e); | ||
} | ||
}, 1000); | ||
} | ||
} | ||
|
||
set hass(hass) { | ||
if (!this.content) { | ||
this.content = document.createElement('div'); | ||
|
||
const card = document.createElement('ha-card'); | ||
// card.header = 'WebRTC Card'; | ||
card.appendChild(this.content); | ||
|
||
this.appendChild(card); | ||
|
||
this._init(hass); | ||
} | ||
} | ||
|
||
setConfig(config) { | ||
if (!config.url) { | ||
throw new Error('Missing `url: "..."`'); | ||
} | ||
this.config = config; | ||
} | ||
|
||
static getStubConfig() { | ||
return { | ||
url: 'rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov' | ||
} | ||
} | ||
} | ||
|
||
customElements.define('webrtc-camera', WebRTCCamera); | ||
|
||
|
||
window.customCards = window.customCards || []; | ||
window.customCards.push({ | ||
type: 'webrtc-camera', | ||
name: 'WebRTC Camera', | ||
preview: false, | ||
description: 'WebRTC Camera allows you to watch RTSP-camera stream without any delay', | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"name": "WebRTC Camera", | ||
"render_readme": true | ||
} |