Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
60a64c8
Support for MPEG-TS live streaming over local TCP socket
mback2k Apr 10, 2025
ab25f68
Improve livestream connection handling and logging
mback2k Apr 11, 2025
9028a99
Restore legacy get_liveview and add init_livestream method
mback2k Apr 11, 2025
c257b02
Do not wait on client connection to be closed
mback2k Apr 11, 2025
f6bff88
Add punctuation to make ruff happy
mback2k Apr 11, 2025
f439919
Do not treat ConnectionResetError as error
mback2k Apr 11, 2025
2262164
Initialize internal object properties
mback2k Apr 11, 2025
61ca105
Rename is_streaming to is_serving
mback2k Apr 11, 2025
fd0e047
Rename stream method to feed
mback2k Apr 11, 2025
100f0e1
Make sure coroutines are properly aborted
mback2k Apr 11, 2025
5dbd4a0
Skip formatting of byte frames to avoid just one byte per line
mback2k Apr 18, 2025
4b1caa1
Fix formatting of livestream.py
mback2k Apr 18, 2025
db30292
Log incomplete details for command API
mback2k Jul 6, 2025
51f080d
Add method for command done API endpoint
mback2k Jul 6, 2025
ab52dec
Fix closing of network connections
mback2k Jul 6, 2025
3181e0c
Fix stream authentication header
mback2k Jul 6, 2025
9a203cf
Wait with connection until device is ready
mback2k Jul 6, 2025
33f46bd
Fix receiving of stream and sending of ping and stat packets
mback2k Jul 6, 2025
7ce0354
Filter received stream data for packets with MPEG-TS data
mback2k Jul 6, 2025
566fa2f
For now assume the command is ready, skip the polling
mback2k Jul 6, 2025
576ca5e
Ignore SSL error APPLICATION_DATA_AFTER_CLOSE_NOTIFY
mback2k Jul 6, 2025
832ef6b
Shorten lines and fix formatting
mback2k Jul 6, 2025
0c5b9e1
Add test_request_command_done for request_command_done
mback2k Jul 6, 2025
220338f
Rename class BlinkStream to BlinkLiveStream
mback2k Jul 6, 2025
e6a52b0
Add test_camera_livestream for init_livestream
mback2k Jul 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion blinkpy/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,18 @@ async def request_command_status(blink, network, command_id):
return await http_get(blink, url)


async def request_command_done(blink, network, command_id):
"""
Request command to be done.

:param blink: Blink instance.
:param network: Sync module network id.
:param command_id: Command id to mark as done.
"""
url = f"{blink.urls.base_url}/network/{network}/command/{command_id}/done/"
return await http_post(blink, url)


@Throttle(seconds=MIN_THROTTLE_TIME)
async def request_homescreen(blink, **kwargs):
"""Request homescreen info."""
Expand Down Expand Up @@ -327,7 +339,8 @@ async def request_camera_liveview(blink, network, camera_id):
f"{blink.urls.base_url}/api/v5/accounts/{blink.account_id}"
f"/networks/{network}/cameras/{camera_id}/liveview"
)
response = await http_post(blink, url)
data = dumps({"intent": "liveview"})
response = await http_post(blink, url, data=data)
await wait_for_command(blink, response)
return response

Expand Down Expand Up @@ -541,6 +554,7 @@ async def wait_for_command(blink, json_data: dict) -> bool:
network_id = json_data.get("network_id")
command_id = json_data.get("id")
except AttributeError:
_LOGGER.exception("No network_id or id in response")
return False
if command_id and network_id:
for _ in range(0, MAX_RETRY):
Expand All @@ -553,3 +567,5 @@ async def wait_for_command(blink, json_data: dict) -> bool:
if status.get("complete"):
return True
await sleep(COMMAND_POLL_TIME)
else:
_LOGGER.debug("No network_id or id in response")
54 changes: 44 additions & 10 deletions blinkpy/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from blinkpy import api
from blinkpy.helpers.constants import TIMEOUT_MEDIA
from blinkpy.helpers.util import to_alphanumeric
from blinkpy.livestream import BlinkLiveStream

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -413,6 +414,15 @@ async def get_liveview(self):
)
return response["server"]

async def init_livestream(self):
"""Initialize livestream."""
response = await api.request_camera_liveview(
self.sync.blink, self.sync.network_id, self.camera_id
)
if not response["server"].startswith("immis://"):
raise NotImplementedError("Unsupported: {}".format(response["server"]))
return BlinkLiveStream(self, response)

async def image_to_file(self, path):
"""
Write image to file.
Expand Down Expand Up @@ -548,13 +558,24 @@ async def get_liveview(self):
f"{self.sync.blink.account_id}/networks/"
f"{self.network_id}/owls/{self.camera_id}/liveview"
)
response = await api.http_post(self.sync.blink, url)
data = dumps({"intent": "liveview"})
response = await api.http_post(self.sync.blink, url, data=data)
await api.wait_for_command(self.sync.blink, response)
return response["server"]

async def init_livestream(self):
"""Initialize livestream."""
url = (
f"{self.sync.urls.base_url}/api/v1/accounts/"
f"{self.sync.blink.account_id}/networks/"
f"{self.network_id}/owls/{self.camera_id}/liveview"
)
data = dumps({"intent": "liveview"})
response = await api.http_post(self.sync.blink, url, data=data)
await api.wait_for_command(self.sync.blink, response)
server = response["server"]
server_split = server.split(":")
server_split[0] = "rtsps"
link = ":".join(server_split)
return link
if not response["server"].startswith("immis://"):
raise NotImplementedError("Unsupported: {}".format(response["server"]))
return BlinkLiveStream(self, response)


class BlinkDoorbell(BlinkCamera):
Expand Down Expand Up @@ -620,8 +641,21 @@ async def get_liveview(self):
f"{self.sync.blink.account_id}/networks/"
f"{self.sync.network_id}/doorbells/{self.camera_id}/liveview"
)
response = await api.http_post(self.sync.blink, url)
data = dumps({"intent": "liveview"})
response = await api.http_post(self.sync.blink, url, data=data)
await api.wait_for_command(self.sync.blink, response)
return response["server"]

async def init_livestream(self):
"""Initialize livestream."""
url = (
f"{self.sync.urls.base_url}/api/v1/accounts/"
f"{self.sync.blink.account_id}/networks/"
f"{self.sync.network_id}/doorbells/{self.camera_id}/liveview"
)
data = dumps({"intent": "liveview"})
response = await api.http_post(self.sync.blink, url, data=data)
await api.wait_for_command(self.sync.blink, response)
server = response["server"]
link = server.replace("immis://", "rtsps://")
return link
if not response["server"].startswith("immis://"):
raise NotImplementedError("Unsupported: {}".format(response["server"]))
return BlinkLiveStream(self, response)
Loading
Loading