Selecting a camera in `live.display.mode: grid` lands in two visible
passes: the cell gets its 2-column width immediately but keeps its 1-row
height for ~400 ms, and the rest of the grid only settles ~700 ms after
the click (measured per-frame on `main`; v7.27.4 has the same two
passes, v8's early position resolution just leaves the late height
correction standing out as a lone vertical jump).
**Cause:** a grid cell's height follows its content in the same style
recalculation as its width (intrinsic media ratio,
`dimensions.aspect_ratio`, or the unsized 16:9 reservation), but the
slotted carousel carries an inline `max-height` from
`MediaHeightController` (debounced 0.3 s + 0.1 s transition; it sizes
the card outside grids). In a grid that cap can only ever delay growth:
it pins the freshly widened cell at its old height until the debounce
fires, and the correction then also rides the 300 ms `_throttledLayout`.
`selectCell()`'s existing `forceReflow()` + `layout()` was already
positioned to do this in one pass; the cap starves it of the final
height.
**Fix:** neutralize the cap on grid cells (`max-height: none
!important`; important is needed to beat the inline style). Covers the
live and viewer grids; non-grid behaviour is untouched. This also stops
the cap clipping cells by their border width (it was measured on the
slide's content box but applied to the cell's border box).
**Trade-off worth flagging:** in the *viewer* grid a cell's carousel
holds all of one camera's media. If those slides do not share one ratio,
the cell now sizes to its tallest slide (letterboxing shorter ones, no
re-layout per swipe) instead of tracking the selected one after a
debounce. If you would rather leave the viewer untouched, the rule can
be scoped to `::slotted(advanced-camera-card-live-carousel)` instead
(live grid cells always hold exactly one slide); say so and I will
rework the PR that way.
**Verification:**
- New browser test `tests/components/live/grid.browser.test.ts`: fails
on any frame where the newly selected cell is selected-wide but still
unselected-high. On `main` it fails with 22 such frames; with this
change there are none: the click settles in a single frame (~28 ms),
every cell at its final size and position.
- `yarn run test`, `yarn run test:browser` (chromium, firefox and webkit
for the new test), `yarn run lint`, `yarn run typecheck` pass.
- No resize oscillation in a cramped viewport with an
appearing/disappearing ancestor scrollbar (the #2306 scenario), on
window resizes, or under a narrow-screen `grid_columns: 2` override;
verified against a live HA 2026.8.3 dashboard (6 go2rtc cameras).
---------
Co-authored-by: dermotduffy <dermot.duffy@gmail.com>
The card claimed a camera's ONVIF audio backchannel in two places that
had
nothing to do with a call: the capability probe at camera init
(`µphone`),
and a pre-armed `sendonly` audio transceiver on every live WebRTC offer.
Merely
looking at a dashboard occupied the camera's speaker line. Outbound
audio now
travels on its own audio-only WebRTC connection, opened when a call is
answered
and closed when it ends.
- The backchannel is claimed only for the duration of a call. Idle
viewing
claims nothing.
- Two-way audio now works in `mse`, `mp4` and `mjpeg` modes (note: the
outbound
audio still traverses WebRTC).
- No renegotiation and no video blink at call start or end.
- A call that cannot carry audio now reports it and ends, instead of
showing a
live microphone that goes nowhere.
- `live.microphone.always_connected` is now purely about the browser
microphone
permission prompt.
- Call setup measured at 66ms (LAN) and ~260ms (cellular) for ICE and
DTLS, plus
~300ms for `go2rtc` to open an RTSP backchannel.
Verified against a live Frigate + `go2rtc` instance, and by unit tests
at 100%
coverage.
- Closes#2691
- Closes#2039
- Closes#2178
Ref #2299 -- the probe no longer opens a backchannel, but it still runs
per
camera on every load and reconnect, and still dials the camera on the
direct-`go2rtc` path. Caching remains to be done.
Ref AlexxIT/go2rtc#1860 -- once a call has opened a backchannel,
`go2rtc` keeps
that media set up on the camera's RTSP session for the life of the
producer.
Diagnoses #2678
This is a high risk change: every byte the card ships is emitted by a
different bundler, minified by a different minifier, and every
stylesheet is compiled by a different sass. The intent is that the
card's behaviour is unchanged.
**Significant development win**: Build time drops from 24s to 1.1s 🎉
Also adds a test suite ('dist') that runs against the built bundle.
- Closes#2556
- Closes#2450
**Key intended features:**
- go2rtc compatible
- 100% test coverage to significantly improve ability to test, maintain
and work around browser weirdnesses (e.g. Safari).
- Written from the ground up in the style of the rest of the project.
**To use:**
- Change `live_provider` from `go2rtc` to `go2rtc-experimental`.
Draws significant inspiration (and direct styling) from
https://github.com/dermotduffy/advanced-camera-card/pull/2447 . Thank
you @Maudfer !
BREAKING CHANGE:
The microphone condition previously bundled two unrelated signals —
whether a two-way-audio session was connected and whether the microphone
was muted. Connection state is now its own dedicated call condition, and
microphone is reserved purely for mute state. Configs are upgraded
automatically (the card rewrites affected conditions under overrides,
elements, and automations). If you maintain config by hand, convert as
follows:
If you only used connected:
# Before
```yaml
condition: microphone
connected: true
```
# After
```yaml
condition: call
call: true
```
If you used both connected and muted — they must be split into two
conditions, since they no longer live together:
# Before
```yaml
condition: microphone
connected: true
muted: false
```
# After
```yaml
condition: and
conditions:
- condition: call
call: true
- condition: microphone
muted: false
```
Refactors media player handling entirely to make the code more
consistent and less boilerplate. Add testing for media player actions.
- Closes#1921
- Closes#1916
- Closes#1889
Whilst a simple change theoretically, the card has such a collection of
surfaces that can overlap other surfaces, it's challenging to get this
to work right! There's a real chance this will have broken something
z-index related (e.g. X overlaps Y when it should not), or (for related
reasons) broken curver corners on the card.
- Much improved API cleanliness and testability to allow further
extensibility in future
- Simplified code in `live` view
This technically contains a small change in how overrides work in the
`live` view. Since that change is _closer_ to the documentation, and
since this is likely to be rarely used, this is not considered a
breaking change. Previously, overrides for a given live camera would
always render _as if_ that camera was selected, vs was actually
selected. Now, overrides will only apply in the live view when the
camera is _actually_ selected. If this is an issue for you in practice,
lets discuss.
Whilst the Frigate support in this card is the best among camera
engines, the name incorrectly suggests that Frigate is a requirement.
Instead, to broaden the appeal, change to more camera agnostic name.
This does not suggest any change in priority, role or support for
Frigate.
This change is likely to be bug prone, due to the size of the rename --
the code contains 1500+ references to "Frigate" most of which make sense
to rename, some which do not, all of which needed human assessment.
- Closes#1298
BREAKING CHANGE: References to `frigate-card` in all kinds of
configuration need to be updated to `advanced-camera-card`. An automated
config upgrade should take care of the majority of usecases (click `Edit
-> Upgrade -> Save`), though may not be perfect.