* fix(alexa): v3 API compliance fixes, fan speed, and security panel control
Bring the Alexa Smart Home integration in line with the current v3 API.
Tier 1 (malformed responses / missing handlers):
- BrightnessController: report brightness under the Alexa.BrightnessController
namespace instead of Alexa.PowerController so Set/AdjustBrightness responses
match the advertised capability.
- SmartVision ObjectDetection: flatten the doubly-nested payload.events array.
- SecurityPanelController: add Arm/Disarm directive handlers (previously
advertised but unhandled, silently falling through to a fake success), wired
to armSecuritySystem/disarmSecuritySystem. Enforce the spec rule rejecting a
direct ARMED_AWAY -> other armed-state transition with AUTHORIZATION_REQUIRED.
Drop the advertised FOUR_DIGIT_PIN authorization, which Scrypted cannot honor.
- Auth failures now return an Alexa INVALID_AUTHORIZATION_CREDENTIAL
ErrorResponse instead of a bare HTTP 500 that Alexa retries and surfaces as a
generic skill error.
API modernization:
- EndpointHealth: downgrade to the documented v3.1 / connectivity-only shape and
stop emitting the undocumented battery property (and 3.2 version), which risks
the whole capability being rejected.
- Fan: add Alexa.RangeController (Fan.Speed, 0-100%) for devices implementing the
Fan interface, alongside the existing PowerController on/off.
Cleanups:
- addAccessToken: initialize event.endpoint as an object, not an array.
- Hoist getArmState to a shared export instead of duplicating it.
Bump version to 0.4.0.
* fix(alexa): improve camera stream quality and connectivity
Tune the WebRTC negotiation for Alexa camera sessions.
- Resolution: raise the proxied stream cap from 720p to 1080p. The previous
1280x720 screen hint forced the transcoder into medium-resolution mode for
every endpoint, so even an Echo Show 15 or Fire TV only received 720p. 1080p
lets larger displays render sharply while the transcoder still clamps width
and falls back to 720p when H.264 High can't be negotiated. We cap rather
than send the uncapped source, since Alexa's directive carries no display
hint and a 4K stream would waste bandwidth on smaller Echo devices.
- Two-way audio: only advertise isFullDuplexAudioSupported when the camera
implements the Intercom interface. Advertising full duplex on a one-way
camera makes Alexa set up a return mic path that goes nowhere.
- TURN: stop unconditionally disabling TURN, and expose a "Use TURN Servers"
plugin setting (on by default). Alexa sessions are proxied and often cross
NATs where a TURN relay is the only path that connects; combined with
disabled trickle ICE (all candidates in the initial SDP), omitting the relay
candidate left NAT-blocked sessions with no fallback. When enabled, TURN
usage defers to the WebRTC plugin's own setting, matching the Google Home
integration; disabling it force-disables TURN for Alexa sessions only.
Bump version to 0.5.0.
The two-way audio talkback loopback (RtspServer over listenZeroSingleClient)
only supports TCP interleaved transport. The talkback ffmpeg input was built
without -rtsp_transport, so ffmpeg attempts a UDP SETUP first and the loopback
responds 461 Unsupported Transport.
The equivalent webrtc talkback path (plugins/webrtc/src/session-control.ts)
has always specified -rtsp_transport tcp; the homekit path did not. Align it.
Before: talkback was silent and the log showed "method SETUP failed: 461
Unsupported Transport". After: the 461 is gone and talkback audio works.
ffmpeg's libavformat RTMP client (gen_get_stream_length in rtmpproto.c) only sends getStreamLength for seekable/VOD streams, never as an unconditional preamble to play. Some live RTMP servers - notably Reolink cameras - respond to an unsolicited getStreamLength on a live stream by sending TCP FIN ~100-150ms later, killing the connection before the play command can take effect. The rebroadcast prebuffer then enters a tight reconnect loop that never produces a usable stream.
Removing the unconditional getStreamLength send lets Reolink RTMP rebroadcast work indefinitely without disconnects, and should not regress other servers since ffmpeg already runs without it.
Fixes#2055
Co-authored-by: thllxb <223556219+Copilot@users.noreply.github.com>
The Reolink RTMP server does not percent-decode query parameter values - it compares the raw bytes from the URL against the stored password. URLSearchParams.set() percent-encodes values when the URL is serialised (WHATWG application/x-www-form-urlencoded), which corrupts passwords containing characters such as '!' (-> '%21'), '#' (-> '%23'), '+' (-> '%2B'), space, etc.
Affected: users whose firmware causes getLoginParameters to return {user, password} (instead of a Login-API token) and whose password contains any of those characters. Symptom: rebroadcast prebuffer fails with 'Socket received FIN' immediately after 'Sending play command'.
Append the RTMP credential pairs as raw bytes instead of going through URLSearchParams. Token-only path is unaffected (hex tokens contain no characters that require encoding). Mirrors the rationale of #1509 for the HTTP API path.
Fixes#2057
Co-authored-by: thllxb <223556219+Copilot@users.noreply.github.com>
* fix: show stream selection options when synthetic streams are configured
* fix: show stream selection options and enforce FFmpeg parser for synthetic streams
The ReolinkCameraClient never called the Logout API before requesting a
new token, causing stale sessions to accumulate on the camera. When the
camera's session limit was reached (~68 min cycle), it would close the
RTMP/RTSP connection, dropping the stream.
Add a logout() method that releases the old token session before login()
requests a new one, matching the pattern already used by ReolinkNvrClient.
This prevents session buildup and eliminates periodic stream drops.
Fixes#1873
Co-authored-by: Josh Casada <joshcasada@Joshs-Mac-mini.ts.net lan>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The RTMP client's totalBytesReceived counter grows unbounded as a
JavaScript number. When it exceeds 2^31 (~2.15 GB received), the
writeUInt32BE call in sendAcknowledgementIfNeeded throws a RangeError
because JavaScript bitwise operations produce signed 32-bit integers.
This crashes the RTMP session and drops the video stream.
For a high-bitrate camera like the Reolink D340P (~4 Mbps main stream),
this overflow occurs after approximately 90 minutes of continuous
streaming, causing periodic stream drops at a consistent interval.
Fix by using the unsigned right shift operator (>>> 0) to keep
totalBytesReceived and bytesToAck in the unsigned uint32 range [0,
4294967295], matching the RTMP spec's sequence number wrapping behavior.
Co-authored-by: Josh Casada <joshcasada@Joshs-Mac-mini.ts.net lan>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>