Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
bd29e08
Support for MPEG-TS live streaming over local TCP socket
mback2k Apr 10, 2025
8c9486e
Improve livestream connection handling and logging
mback2k Apr 11, 2025
357df9f
Restore legacy get_liveview and add init_livestream method
mback2k Apr 11, 2025
6444f73
Do not wait on client connection to be closed
mback2k Apr 11, 2025
6279e24
Add punctuation to make ruff happy
mback2k Apr 11, 2025
36b02d9
Do not treat ConnectionResetError as error
mback2k Apr 11, 2025
bcdf1b9
Initialize internal object properties
mback2k Apr 11, 2025
1a97662
Rename is_streaming to is_serving
mback2k Apr 11, 2025
4c0d51b
Rename stream method to feed
mback2k Apr 11, 2025
df9697b
Make sure coroutines are properly aborted
mback2k Apr 11, 2025
ead0254
Skip formatting of byte frames to avoid just one byte per line
mback2k Apr 18, 2025
77b9504
Fix formatting of livestream.py
mback2k Apr 18, 2025
a94e694
Log incomplete details for command API
mback2k Jul 6, 2025
91df639
Add method for command done API endpoint
mback2k Jul 6, 2025
25b2ab7
Fix closing of network connections
mback2k Jul 6, 2025
146defa
Fix stream authentication header
mback2k Jul 6, 2025
da0177a
Wait with connection until device is ready
mback2k Jul 6, 2025
4879886
Fix receiving of stream and sending of ping and stat packets
mback2k Jul 6, 2025
a4e1fe7
Filter received stream data for packets with MPEG-TS data
mback2k Jul 6, 2025
cbba7d6
For now assume the command is ready, skip the polling
mback2k Jul 6, 2025
dca48a4
Ignore SSL error APPLICATION_DATA_AFTER_CLOSE_NOTIFY
mback2k Jul 6, 2025
431b61f
Shorten lines and fix formatting
mback2k Jul 6, 2025
a104573
Add test_request_command_done for request_command_done
mback2k Jul 6, 2025
ef16116
Rename class BlinkStream to BlinkLiveStream
mback2k Jul 6, 2025
c8e64ff
Add test_camera_livestream for init_livestream
mback2k Jul 6, 2025
ff7659f
Replace readyness check with status check during polling
mback2k Jul 7, 2025
f1d3461
Initial set of tests for BlinkLiveStream
mback2k Jul 7, 2025
882869b
Add more exception handling tests
mback2k Jul 7, 2025
8535e89
Add missing tests to reach full coverage for BlinkLiveStream
mback2k Jul 7, 2025
7c054ca
Replace `asyncio.TimeoutError` with builtin `TimeoutError`
mback2k Jul 7, 2025
4ba2e79
Rewrite as bytes literal
mback2k Jul 7, 2025
6235962
Improve client connection logging
mback2k Jul 8, 2025
43e8488
Fix server URL parsing and improve tests
mback2k Jul 8, 2025
09a5e79
Remove use of modern asyncio.timeout
mback2k Jul 31, 2025
bca565c
Make it possible specify stream host and port
mback2k Jul 31, 2025
4a1ef43
Merge upstream branch dev into livestreaming
mback2k Oct 27, 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 @@ -230,6 +230,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 @@ -347,7 +359,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 @@ -561,6 +574,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 @@ -573,3 +587,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