Show WebRTC cameras that also support HLS in the media browser#108796
Conversation
|
Hey there @home-assistant/core, mind taking a look at this pull request as it has been labeled with an integration ( Code owner commandsCode owners of
|
| media_class=MediaClass.VIDEO, | ||
| media_content_type=content_type, | ||
| title=camera.name, | ||
| title=cast( |
There was a problem hiding this comment.
The previous implementation left an empty title for cameras that took their name from the device. This seems to be a better choice, but if there's a better way to get the name I'll change it.
|
Which cameras are expected to fail? If we know them, could we exclude them? |
|
I don't know of specific cameras that would fail (and if such exist), but the Nest tests seem to suggest there are cameras that support both WebRTC and HLS and cameras that only support WebRTC. Perhaps testing
In the arch discussion I proposed that WebRTC cameras explicitly opt in to an HLS fallback, but it got some push back. It was also decided against when WebRTC cameras were introduced (albeit that was before the introduction of camera media sources). Last but not least, it seems like failures should be rare, and have an appropriate error. |
I agree that testing it against None is more accurate, but yes it can also raise and do I/O. (example) This is kind of why i was waffling a bit on the architecture discussion. The right way to check this is by checking if the stream is none, but that causes I/O that generates a url in the nest case, so, meh. This is what lead me to ask the question about media player and webrtc support. |
|
I agree that real support WebRTC media sources is preferable. In the meantime, I think this PR is a good compromise. |
|
@allenporter are there Nest cameras that only support WebRTC but can't produce a stream for HLS? If so, how common are they? |
There is a new wave of cameras being converted to webrtc and I'm not yet sure if that means they still also support hls. I have not yet converted mine but will at some point in the next few months. The other case is rtsp_to_webrtc component which allows an rtsp camera to support both. |
|
Yeah, rstp_to_webrtc supports both, and so does Frigate after the relevant PR. The only unknown is some Nest cameras. |
|
@allenporter Do you think we can get this merged and continue the discussion on real WebRTC support separately? |
|
I realize i misunderstood your question: Every new camera from the last few years only supports WebRTC. (I misunderstood and thought you were asking if they supports both webrtc and hls, and so my answer was about old cameras moving to google home) As I mentioned above, this is not the correct use of the API and I'm not sure even what checking of stream type is |
|
Do you mean that new Nest cameras do not expose a stream that can be used over HLS? As for the name change - this change kind of exposed it, because some of the newly added cameras weren't named correctly or did not have a name. |
Nest cameras made for the last few years only support webrtc, and do not support rtsp and therefore don't use HLS in the frontend. |
|
OK, that definitely changes the picture. I guess the question is - are we ok with making more cameras more browsable, at the expense of some of them failing (but with a friendly error message)? |
|
I would prefer to only show cameras that will work. I personally think it makes sense to just check the source... |
|
How would you handle cameras that raise in |
|
I would suggest:
FWIW the thumbnail shown in the browser also uses the rtsp stream source so it'll probably be fetched again immediately anyway (and be cached if there is a performance issue) |
9a68e09 to
0febce4
Compare
|
Thanks @allenporter, I updated the code with your suggestions. |
| media_class=MediaClass.VIDEO, | ||
| media_content_type=content_type, | ||
| title=cast( | ||
| State, self.hass.states.get(camera.entity_id) |
There was a problem hiding this comment.
I still want to see this change in a separate PR with its own description, etc. https://developers.home-assistant.io/docs/review-process/#creating-the-perfect-pr as i mentioned before.
| assert len(item.children) == 0 | ||
| assert item.not_shown == 3 | ||
| async def test_browsing_web_rtc(hass: HomeAssistant, mock_camera_web_rtc) -> None: | ||
| """Test browsing camera media source.""" |
There was a problem hiding this comment.
Update test description
| """Test browsing camera media source.""" | ||
| component: EntityComponent[Camera] = hass.data[DOMAIN] | ||
| cameras = list(component.entities) | ||
| with patch.object(cameras[0], "stream_source", return_value="test"), patch.object( |
There was a problem hiding this comment.
Tests do not interact with hass.data and instead should by string name as is done in other parts of the tests in this file..
In this case i think the reason this approach is chosen is because there are different return values for the different cameras?
Instead, patch once and have either the callable or side effect return the different values for each call.
Also a small comment might go a long way here, something like: "One camera supports HLS and WebRTC, one cameras supports only WebRTC, and one camera fails." etc
|
Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍 |
0febce4 to
7b72172
Compare
| children = [] | ||
| not_shown = 0 | ||
| for camera in component.entities: | ||
| async def _media_source_for_camera(camera: Camera) -> BrowseMediaSource | None: |
There was a problem hiding this comment.
I think everything looks good to go here, but now i'm thinking about one more idea to further streamline this, but not positive it would work out: Could the scope of this function be to just filter out the not relevant cameras and leave the browse media source building all together how it was? It could just get the eligible cameras in one pass then build the sources in a simple loop together with building the root.
There was a problem hiding this comment.
We could return the camera instead of BrowseMediaSource, and then iterate over the results to build the sources, but I'm not sure I see why it would be better?
There was a problem hiding this comment.
Its possibly a style preference, but my thinking is: in general, reducing # of verical lines of inline functions or reducing the scope as much as possible seems positive. Having the BrowseMediaSource building closer together together seems logical as well. One exception may be if there is shared logic on both parts but i think the existing stream type filtering looks more complex than it needs to be
There was a problem hiding this comment.
It's a bit tricky because we also have to recalculate the content type (or return a tuple).
Arguably refactoring the creation of the BrowseMediaSource to a separate function would achieve a similar result (see my latest commit)
There was a problem hiding this comment.
Thanks, that looks good. What do you think about gutting all the existing vertical whitespace? It seems like it makes this harder to follow given it has so many branches and is in an inline function. Collapsing vertically will also help make this simpler to grok quickly.
allenporter
left a comment
There was a problem hiding this comment.
Looks great, i think i just have one more quick suggestion to further shorten
| if stream_type != StreamType.HLS: | ||
| source = await camera.stream_source() | ||
| if not source: | ||
| return None |
There was a problem hiding this comment.
Does it work for this to be a single statement? (Given this is 3 levels of nested ifs in an inline function)
| if stream_type != StreamType.HLS: | |
| source = await camera.stream_source() | |
| if not source: | |
| return None | |
| if stream_type != StreamType.HLS and not (source := await camera.stream_source()) | |
| return None |
There was a problem hiding this comment.
I took advantage of the ability to return early in a successful case to refactor a bit differently. Let me know what you think.
| children = [] | ||
| not_shown = 0 | ||
| for camera in component.entities: | ||
| async def _media_source_for_camera(camera: Camera) -> BrowseMediaSource | None: |
There was a problem hiding this comment.
Thanks, that looks good. What do you think about gutting all the existing vertical whitespace? It seems like it makes this harder to follow given it has so many branches and is in an inline function. Collapsing vertically will also help make this simpler to grok quickly.
allenporter
left a comment
There was a problem hiding this comment.
Minor nits otherwise looks good
| if stream_type != StreamType.HLS: | ||
| source = await camera.stream_source() | ||
| if not source: |
There was a problem hiding this comment.
This seems like it can either be 1 or 2 lines rather than 3
|
|
||
| return _media_source_for_camera(camera, content_type) | ||
|
|
||
| # Root. List cameras. |
There was a problem hiding this comment.
Maybe just drop this while we're here
| # Root. List cameras. |
|
Thanks @allenporter. I created a follow up PR for the display name change: #110882 |
Proposed change
As discussed in home-assistant/architecture#1030, support for resolving WebRTC cameras as media sources was added in #80646. Those cameras, however, were still excluded from the media browser. This PR makes them available in the browser instead of being filtered out.
While it's possible some of these cameras will fail to play, most should succeed. Specifically:
Type of change
Additional information
Checklist
ruff format homeassistant tests)If user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
Updated and included derived files by running:
python3 -m script.hassfest.requirements_all.txt.Updated by running
python3 -m script.gen_requirements_all..coveragerc.To help with the load of incoming pull requests: