Package: @networkoptix/webrtc-stream-manager@0.1.29
WebRtcUrlConfigUnknown declares position?: number (dist/types/types.d.ts), but the implementation never reads it. StreamManager.buildSignalingUrl only appends positionMs= from the singleton-global _currentPosition (set via StreamManager.updatePosition()):
// dist/lib/core/stream-manager.js (buildSignalingUrl)
if (this._currentPosition) {
params.push(`positionMs=${this._currentPosition}`);
}
A grep of dist/lib for urlConfig.position / config.position returns zero hits. So passing position on the connect config compiles fine and silently produces a live stream.
Steps to reproduce
const connection = StreamManager.getInstance().connect({
systemId, cameraId, accessToken,
apiVersion: ApiVersions.v2,
targetStream: TargetStream.AUTO,
position: 1712345678000, // epoch ms, in the past, footage exists
}, videoElement);
Expected: archive playback from the given position (v1 behavior — position was baked into the signaling URL).
Actual: live stream; signaling URL contains no positionMs.
Why the global workaround is insufficient
StreamManager.updatePosition() is process-global while the signaling URL is rebuilt lazily per connection attempt, including internal retries. With concurrent players (e.g. a live grid plus an archive dialog), any live connection that retries while the global is set reconnects into archive — and clearing the global after connect makes the archive connection's own retries come back live. Per-connection position is not expressible.
The post-connect path also can't cover the initial position: connect() applies _currentPosition via connection.updatePosition() immediately after construction, but sendSeek silently no-ops until the data channel is open.
Suggested fix
In buildSignalingUrl, honor the per-connection config with the global as fallback:
const positionMs = urlConfig.position ?? this._currentPosition;
if (positionMs) {
params.push(`positionMs=${positionMs}`);
}
The closure in connect() already captures urlConfig per connection, so retries keep the correct per-connection position. We're running exactly this as a local patch and can confirm it restores archive playback.
Environment
@networkoptix/webrtc-stream-manager 0.1.29 (v2 native API)
- Playback via cloud relay (
{systemId}.relay.vmsproxy.com), useRelayPrefix: false
- Chrome / Edge (Chromium)
Package: @networkoptix/webrtc-stream-manager@0.1.29
WebRtcUrlConfigUnknowndeclaresposition?: number(dist/types/types.d.ts), but the implementation never reads it.StreamManager.buildSignalingUrlonly appendspositionMs=from the singleton-global_currentPosition(set viaStreamManager.updatePosition()):A grep of
dist/libforurlConfig.position/config.positionreturns zero hits. So passingpositionon the connect config compiles fine and silently produces a live stream.Steps to reproduce
Expected: archive playback from the given position (v1 behavior — position was baked into the signaling URL).
Actual: live stream; signaling URL contains no
positionMs.Why the global workaround is insufficient
StreamManager.updatePosition()is process-global while the signaling URL is rebuilt lazily per connection attempt, including internal retries. With concurrent players (e.g. a live grid plus an archive dialog), any live connection that retries while the global is set reconnects into archive — and clearing the global after connect makes the archive connection's own retries come back live. Per-connection position is not expressible.The post-connect path also can't cover the initial position:
connect()applies_currentPositionviaconnection.updatePosition()immediately after construction, butsendSeeksilently no-ops until the data channel is open.Suggested fix
In
buildSignalingUrl, honor the per-connection config with the global as fallback:The closure in
connect()already capturesurlConfigper connection, so retries keep the correct per-connection position. We're running exactly this as a local patch and can confirm it restores archive playback.Environment
@networkoptix/webrtc-stream-manager0.1.29 (v2 native API){systemId}.relay.vmsproxy.com),useRelayPrefix: false