@signalwire/web-components 1.0.0-dev-20260508182243 → 1.0.0-dev-20260511184148
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/sw-call-media.d.ts +0 -9
- package/dist/components/sw-call-media.d.ts.map +1 -1
- package/dist/components/sw-call-media.js +60 -102
- package/dist/components/sw-call-media.js.map +1 -1
- package/dist/embed/signalwire-web-components-embed.iife.js +92 -94
- package/dist/embed/signalwire-web-components-embed.iife.js.map +1 -1
- package/dist/embed/signalwire-web-components-embed.umd.cjs +92 -94
- package/dist/embed/signalwire-web-components-embed.umd.cjs.map +1 -1
- package/dist/utils/video.js +6 -21
- package/dist/utils/video.js.map +1 -1
- package/package.json +2 -2
- package/dist/constants.js +0 -5
- package/dist/constants.js.map +0 -1
- package/dist/utils/debounce.js +0 -13
- package/dist/utils/debounce.js.map +0 -1
|
@@ -41,22 +41,13 @@ export declare class SwCallMedia extends LitElement {
|
|
|
41
41
|
private _remoteStreamValue;
|
|
42
42
|
private _lastTrackSignature;
|
|
43
43
|
private _subscriptions;
|
|
44
|
-
private _resizeObserver?;
|
|
45
|
-
private _videoResizeHandler?;
|
|
46
|
-
private _windowResizeHandler?;
|
|
47
|
-
private _videoElement?;
|
|
48
|
-
private _paddingWrapper?;
|
|
49
44
|
connectedCallback(): void;
|
|
50
45
|
protected updated(changedProperties: Map<string, unknown>): void;
|
|
51
46
|
disconnectedCallback(): void;
|
|
52
|
-
protected firstUpdated(): void;
|
|
53
47
|
private _setupDirectSubscriptions;
|
|
54
48
|
private _computeTrackSignature;
|
|
55
49
|
private _cleanupDirectSubscriptions;
|
|
56
50
|
private _applySinkId;
|
|
57
|
-
private _setupResizeObserver;
|
|
58
|
-
private _recalculateDimensions;
|
|
59
|
-
private _cleanupResizeObserver;
|
|
60
51
|
private _cleanupVideoElement;
|
|
61
52
|
render(): import("lit-html").TemplateResult<1>;
|
|
62
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sw-call-media.d.ts","sourceRoot":"","sources":["../../src/components/sw-call-media.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAI5C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"sw-call-media.d.ts","sourceRoot":"","sources":["../../src/components/sw-call-media.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAI5C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAQ9C,qBACa,WAAY,SAAQ,UAAU;IACzC,MAAM,CAAC,MAAM,0BAsDX;IAEF;;;OAGG;IACyB,IAAI,CAAC,EAAE,IAAI,CAAC;IAExC;;;OAGG;IAC6B,MAAM,EAAE,WAAW,GAAG,IAAI,CAAQ;IAIlE,OAAO,CAAC,UAAU,CAAC,CAAY;IAI/B,OAAO,CAAC,aAAa,CAAC,CAAe;IAErC,OAAO,CAAC,kBAAkB,CAA4B;IACtD,OAAO,CAAC,mBAAmB,CAAM;IACjC,OAAO,CAAC,cAAc,CAAsB;IAI5C,iBAAiB;IASjB,SAAS,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IA4ChE,oBAAoB;IAQpB,OAAO,CAAC,yBAAyB;IAiBjC,OAAO,CAAC,sBAAsB;IAK9B,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,oBAAoB;IAO5B,MAAM;CAqBP;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,eAAe,EAAE,WAAW,CAAC;KAC9B;CACF"}
|
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
import { LitElement as
|
|
2
|
-
import { property as
|
|
3
|
-
import { consume as
|
|
4
|
-
import { callStateContext as
|
|
5
|
-
import { devicesContext as
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
var
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return o && i && B(e, s, i), i;
|
|
1
|
+
import { LitElement as v, html as S, css as _ } from "lit";
|
|
2
|
+
import { property as d, state as p, customElement as b } from "lit/decorators.js";
|
|
3
|
+
import { consume as m } from "@lit/context";
|
|
4
|
+
import { callStateContext as f } from "../context/call-state-context.js";
|
|
5
|
+
import { devicesContext as g } from "../context/devices-context.js";
|
|
6
|
+
import { attachMediaStream as h, detachMediaStream as w } from "../utils/video.js";
|
|
7
|
+
import { getLogger as y } from "@signalwire/js";
|
|
8
|
+
var k = Object.defineProperty, T = Object.getOwnPropertyDescriptor, u = (t, e, s, r) => {
|
|
9
|
+
for (var a = r > 1 ? void 0 : r ? T(e, s) : e, c = t.length - 1, i; c >= 0; c--)
|
|
10
|
+
(i = t[c]) && (a = (r ? i(e, s, a) : i(a)) || a);
|
|
11
|
+
return r && a && k(e, s, a), a;
|
|
13
12
|
};
|
|
14
|
-
const
|
|
15
|
-
let
|
|
13
|
+
const V = y();
|
|
14
|
+
let o = class extends v {
|
|
16
15
|
constructor() {
|
|
17
16
|
super(...arguments), this.stream = null, this._remoteStreamValue = null, this._lastTrackSignature = "", this._subscriptions = [];
|
|
18
17
|
}
|
|
@@ -21,47 +20,40 @@ let l = class extends V {
|
|
|
21
20
|
super.connectedCallback(), !this.stream && this.call && this._setupDirectSubscriptions(), this.stream && (this._remoteStreamValue = this.stream, this._lastTrackSignature = this._computeTrackSignature(this.stream));
|
|
22
21
|
}
|
|
23
22
|
updated(t) {
|
|
24
|
-
var e, s,
|
|
23
|
+
var e, s, r, a, c;
|
|
25
24
|
if (super.updated(t), t.has("call") && (this._cleanupDirectSubscriptions(), !this.stream && this.call && this._setupDirectSubscriptions()), t.has("stream")) {
|
|
26
25
|
this._cleanupDirectSubscriptions();
|
|
27
|
-
const
|
|
28
|
-
if (
|
|
29
|
-
this._remoteStreamValue =
|
|
30
|
-
const
|
|
31
|
-
|
|
26
|
+
const i = this.stream, l = this._computeTrackSignature(i);
|
|
27
|
+
if (i !== this._remoteStreamValue || l !== this._lastTrackSignature) {
|
|
28
|
+
this._remoteStreamValue = i, this._lastTrackSignature = l;
|
|
29
|
+
const n = (e = this.shadowRoot) == null ? void 0 : e.querySelector("video.mcu-video");
|
|
30
|
+
n && h(n, i);
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
33
|
if (!this.stream && !this.call && t.has("_callState")) {
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
37
|
-
this._remoteStreamValue =
|
|
38
|
-
const
|
|
39
|
-
|
|
34
|
+
const i = ((s = this._callState) == null ? void 0 : s.remoteStream) ?? null, l = this._computeTrackSignature(i);
|
|
35
|
+
if (i !== this._remoteStreamValue || l !== this._lastTrackSignature) {
|
|
36
|
+
this._remoteStreamValue = i, this._lastTrackSignature = l;
|
|
37
|
+
const n = (r = this.shadowRoot) == null ? void 0 : r.querySelector("video.mcu-video");
|
|
38
|
+
n && h(n, i);
|
|
40
39
|
}
|
|
41
40
|
}
|
|
42
|
-
t.has("_devicesState") && this._applySinkId(((
|
|
41
|
+
t.has("_devicesState") && this._applySinkId(((c = (a = this._devicesState) == null ? void 0 : a.selectedAudioOutput) == null ? void 0 : c.deviceId) ?? "");
|
|
43
42
|
}
|
|
44
43
|
disconnectedCallback() {
|
|
45
|
-
super.disconnectedCallback(), this._cleanupDirectSubscriptions(), this.
|
|
46
|
-
}
|
|
47
|
-
firstUpdated() {
|
|
48
|
-
var e;
|
|
49
|
-
const t = (e = this.shadowRoot) == null ? void 0 : e.querySelector("video.mcu-video");
|
|
50
|
-
t && L(t).then(() => {
|
|
51
|
-
this.isConnected && this._setupResizeObserver();
|
|
52
|
-
});
|
|
44
|
+
super.disconnectedCallback(), this._cleanupDirectSubscriptions(), this._cleanupVideoElement();
|
|
53
45
|
}
|
|
54
46
|
// ── Direct call subscriptions (legacy / standalone) ────────────────
|
|
55
47
|
_setupDirectSubscriptions() {
|
|
56
48
|
this.call && this._subscriptions.push(
|
|
57
49
|
this.call.remoteStream$.subscribe((t) => {
|
|
58
|
-
var
|
|
50
|
+
var r;
|
|
59
51
|
const e = this._computeTrackSignature(t);
|
|
60
52
|
if (t === this._remoteStreamValue && e === this._lastTrackSignature)
|
|
61
53
|
return;
|
|
62
54
|
this._remoteStreamValue = t, this._lastTrackSignature = e, this.requestUpdate();
|
|
63
|
-
const s = (
|
|
64
|
-
s &&
|
|
55
|
+
const s = (r = this.shadowRoot) == null ? void 0 : r.querySelector("video.mcu-video");
|
|
56
|
+
s && h(s, t);
|
|
65
57
|
})
|
|
66
58
|
);
|
|
67
59
|
}
|
|
@@ -75,51 +67,19 @@ let l = class extends V {
|
|
|
75
67
|
_applySinkId(t) {
|
|
76
68
|
var s;
|
|
77
69
|
const e = (s = this.shadowRoot) == null ? void 0 : s.querySelector("video.mcu-video");
|
|
78
|
-
e != null && e.setSinkId && e.setSinkId(t).catch((
|
|
79
|
-
|
|
70
|
+
e != null && e.setSinkId && e.setSinkId(t).catch((r) => {
|
|
71
|
+
V.error("[SwCallMedia] Failed to set audio output device:", r);
|
|
80
72
|
});
|
|
81
73
|
}
|
|
82
|
-
// ──
|
|
83
|
-
_setupResizeObserver() {
|
|
84
|
-
var s, o;
|
|
85
|
-
const t = (s = this.shadowRoot) == null ? void 0 : s.querySelector("video.mcu-video"), e = (o = this.shadowRoot) == null ? void 0 : o.querySelector(".padding-wrapper");
|
|
86
|
-
!t || !e || (this._videoElement = t, this._paddingWrapper = e, this._resizeObserver = new ResizeObserver(
|
|
87
|
-
j(() => this._recalculateDimensions(), 50)
|
|
88
|
-
), this._resizeObserver.observe(this), this._videoResizeHandler = () => this._recalculateDimensions(), t.addEventListener("resize", this._videoResizeHandler), this._windowResizeHandler = () => {
|
|
89
|
-
requestAnimationFrame(() => this._recalculateDimensions());
|
|
90
|
-
}, window.addEventListener("resize", this._windowResizeHandler), this._recalculateDimensions());
|
|
91
|
-
}
|
|
92
|
-
_recalculateDimensions() {
|
|
93
|
-
const t = this._videoElement, e = this._paddingWrapper;
|
|
94
|
-
if (!t || !e) return;
|
|
95
|
-
if (!t.videoWidth || !t.videoHeight) {
|
|
96
|
-
e.style.width = "100%", e.style.height = "100%", e.style.paddingBottom = "0", e.style.transform = "none";
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
const s = window.innerWidth, o = window.innerHeight, i = this.getBoundingClientRect(), a = i.width, r = i.height;
|
|
100
|
-
if (a <= 0 || r <= 0) return;
|
|
101
|
-
const n = Math.min(1, (s - i.left) / a), c = Math.min(1, (o - i.top) / r), f = Math.min(n, c), p = a * f, m = r * f;
|
|
102
|
-
if (p <= 0 || m <= 0) return;
|
|
103
|
-
const v = t.videoWidth / t.videoHeight, S = p / m;
|
|
104
|
-
let h, u;
|
|
105
|
-
v > S ? (h = p, u = h / v) : (u = m, h = u * v);
|
|
106
|
-
const y = Math.max(0, i.left), R = Math.max(0, i.top), z = Math.min(s, i.right), k = Math.min(o, i.bottom), x = (y + z) / 2, O = (R + k) / 2, H = i.left + a / 2, C = i.top + r / 2, T = x - H, D = O - C;
|
|
107
|
-
e.style.width = `${h}px`, e.style.height = `${u}px`, e.style.paddingBottom = "0", e.style.transform = `translate(${T}px, ${D}px)`;
|
|
108
|
-
}
|
|
109
|
-
_cleanupResizeObserver() {
|
|
110
|
-
var e;
|
|
111
|
-
this._resizeObserver && (this._resizeObserver.disconnect(), this._resizeObserver = void 0);
|
|
112
|
-
const t = (e = this.shadowRoot) == null ? void 0 : e.querySelector("video.mcu-video");
|
|
113
|
-
t && this._videoResizeHandler && (t.removeEventListener("resize", this._videoResizeHandler), this._videoResizeHandler = void 0), this._windowResizeHandler && (window.removeEventListener("resize", this._windowResizeHandler), this._windowResizeHandler = void 0);
|
|
114
|
-
}
|
|
74
|
+
// ── Cleanup ─────────────────────────────────────────────────────────
|
|
115
75
|
_cleanupVideoElement() {
|
|
116
76
|
var e;
|
|
117
77
|
const t = (e = this.shadowRoot) == null ? void 0 : e.querySelector("video.mcu-video");
|
|
118
|
-
t &&
|
|
78
|
+
t && w(t);
|
|
119
79
|
}
|
|
120
80
|
// ── Render ─────────────────────────────────────────────────────────
|
|
121
81
|
render() {
|
|
122
|
-
return
|
|
82
|
+
return S`
|
|
123
83
|
<div class="mcu-content" part="container">
|
|
124
84
|
<div class="padding-wrapper">
|
|
125
85
|
<div class="mcu-wrapper">
|
|
@@ -128,6 +88,7 @@ let l = class extends V {
|
|
|
128
88
|
part="video"
|
|
129
89
|
autoplay
|
|
130
90
|
playsinline
|
|
91
|
+
muted
|
|
131
92
|
.srcObject=${this._remoteStreamValue}
|
|
132
93
|
></video>
|
|
133
94
|
</div>
|
|
@@ -139,7 +100,7 @@ let l = class extends V {
|
|
|
139
100
|
`;
|
|
140
101
|
}
|
|
141
102
|
};
|
|
142
|
-
|
|
103
|
+
o.styles = _`
|
|
143
104
|
:host {
|
|
144
105
|
display: block;
|
|
145
106
|
width: 100%;
|
|
@@ -159,13 +120,13 @@ l.styles = E`
|
|
|
159
120
|
overflow: hidden;
|
|
160
121
|
}
|
|
161
122
|
|
|
123
|
+
/* Fill the parent box. The video element below uses object-fit: contain
|
|
124
|
+
so the stream letterboxes into whatever rectangle the parent gives us
|
|
125
|
+
— no JS sizing pass required. */
|
|
162
126
|
.padding-wrapper {
|
|
163
127
|
position: relative;
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
transition:
|
|
167
|
-
width 0.3s ease,
|
|
168
|
-
height 0.3s ease;
|
|
128
|
+
width: 100%;
|
|
129
|
+
height: 100%;
|
|
169
130
|
}
|
|
170
131
|
|
|
171
132
|
/* Outer rounding is owned by sw-ui-call-layout's :host border-radius +
|
|
@@ -174,10 +135,7 @@ l.styles = E`
|
|
|
174
135
|
(page / modal backdrop) as a black sliver in light mode. */
|
|
175
136
|
.mcu-wrapper {
|
|
176
137
|
position: absolute;
|
|
177
|
-
|
|
178
|
-
left: 0;
|
|
179
|
-
right: 0;
|
|
180
|
-
bottom: 0;
|
|
138
|
+
inset: 0;
|
|
181
139
|
overflow: hidden;
|
|
182
140
|
}
|
|
183
141
|
|
|
@@ -197,24 +155,24 @@ l.styles = E`
|
|
|
197
155
|
pointer-events: none;
|
|
198
156
|
}
|
|
199
157
|
`;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
],
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
],
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
],
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
],
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
],
|
|
158
|
+
u([
|
|
159
|
+
d({ type: Object })
|
|
160
|
+
], o.prototype, "call", 2);
|
|
161
|
+
u([
|
|
162
|
+
d({ attribute: !1 })
|
|
163
|
+
], o.prototype, "stream", 2);
|
|
164
|
+
u([
|
|
165
|
+
m({ context: f, subscribe: !0 }),
|
|
166
|
+
p()
|
|
167
|
+
], o.prototype, "_callState", 2);
|
|
168
|
+
u([
|
|
169
|
+
m({ context: g, subscribe: !0 }),
|
|
170
|
+
p()
|
|
171
|
+
], o.prototype, "_devicesState", 2);
|
|
172
|
+
o = u([
|
|
173
|
+
b("sw-call-media")
|
|
174
|
+
], o);
|
|
217
175
|
export {
|
|
218
|
-
|
|
176
|
+
o as SwCallMedia
|
|
219
177
|
};
|
|
220
178
|
//# sourceMappingURL=sw-call-media.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sw-call-media.js","sources":["../../src/components/sw-call-media.ts"],"sourcesContent":["/**\n * Call Media Component\n *\n * Renders the remote video stream with aspect-ratio-aware sizing.\n *\n * Input precedence (most specific wins): `.stream` > `.call` > context.\n *\n * @example\n * ```html\n * <!-- Inside a context provider (sw-call-widget): -->\n * <sw-call-media></sw-call-media>\n *\n * <!-- With an explicit Call: -->\n * <sw-call-media .call=${call}></sw-call-media>\n *\n * <!-- With a raw remote stream: -->\n * <sw-call-media .stream=${remoteStream}></sw-call-media>\n * ```\n *\n * @csspart container - Outer container that holds the video and overlay layers.\n * @csspart video - The `<video>` element rendering the remote stream.\n *\n * @slot - Default slot for overlay layers (e.g. `<sw-self-media>`, `<sw-participants>`).\n */\n\nimport { LitElement, html, css } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { consume } from '@lit/context';\nimport { Subscription } from 'rxjs';\nimport type { Call } from '../types/index.js';\nimport { callStateContext, type CallState } from '../context/call-state-context.js';\nimport { devicesContext, type DevicesState } from '../context/devices-context.js';\nimport { debounce } from '../utils/debounce.js';\nimport { waitForVideoReady, attachMediaStream, detachMediaStream } from '../utils/video.js';\nimport { getLogger } from '@signalwire/js';\n\nconst logger = getLogger();\n\n@customElement('sw-call-media')\nexport class SwCallMedia extends LitElement {\n static styles = css`\n :host {\n display: block;\n width: 100%;\n height: 100%;\n overflow: hidden;\n }\n\n .mcu-content {\n position: relative;\n width: 100%;\n height: 100%;\n margin: 0 auto;\n display: flex;\n align-items: center;\n justify-content: center;\n background-color: var(--bg-page, #0e0e18);\n overflow: hidden;\n }\n\n .padding-wrapper {\n position: relative;\n max-width: 100%;\n max-height: 100%;\n transition:\n width 0.3s ease,\n height 0.3s ease;\n }\n\n /* Outer rounding is owned by sw-ui-call-layout's :host border-radius +\n overflow:hidden. Rounding here too carves a notch out of the right\n edge of the video cell that exposes whatever sits behind the widget\n (page / modal backdrop) as a black sliver in light mode. */\n .mcu-wrapper {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n overflow: hidden;\n }\n\n .mcu-video {\n width: 100%;\n height: 100%;\n display: block;\n object-fit: contain;\n }\n\n .mcu-layers {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n }\n `;\n\n /**\n * Explicit Call — when set, subscribes directly to its observables instead\n * of relying on context. Overridden by `.stream` if both are set.\n */\n @property({ type: Object }) call?: Call;\n\n /**\n * Explicit remote MediaStream — highest precedence. Bypasses both `.call`\n * and context. Useful for raw rendering with no SDK at all.\n */\n @property({ attribute: false }) stream: MediaStream | null = null;\n\n @consume({ context: callStateContext, subscribe: true })\n @state()\n private _callState?: CallState;\n\n @consume({ context: devicesContext, subscribe: true })\n @state()\n private _devicesState?: DevicesState;\n\n private _remoteStreamValue: MediaStream | null = null;\n private _lastTrackSignature = '';\n private _subscriptions: Subscription[] = [];\n private _resizeObserver?: ResizeObserver;\n private _videoResizeHandler?: () => void;\n private _windowResizeHandler?: () => void;\n private _videoElement?: HTMLVideoElement;\n private _paddingWrapper?: HTMLDivElement;\n\n // ── Lifecycle ──────────────────────────────────────────────────────\n\n connectedCallback() {\n super.connectedCallback();\n if (!this.stream && this.call) this._setupDirectSubscriptions();\n if (this.stream) {\n this._remoteStreamValue = this.stream;\n this._lastTrackSignature = this._computeTrackSignature(this.stream);\n }\n }\n\n protected updated(changedProperties: Map<string, unknown>): void {\n super.updated(changedProperties);\n\n // Direct call prop changed — re-subscribe (only meaningful when `.stream` isn't set)\n if (changedProperties.has('call')) {\n this._cleanupDirectSubscriptions();\n if (!this.stream && this.call) this._setupDirectSubscriptions();\n }\n\n // Explicit `.stream` prop wins — attach it and skip everything else.\n if (changedProperties.has('stream')) {\n this._cleanupDirectSubscriptions();\n const stream = this.stream;\n const signature = this._computeTrackSignature(stream);\n if (stream !== this._remoteStreamValue || signature !== this._lastTrackSignature) {\n this._remoteStreamValue = stream;\n this._lastTrackSignature = signature;\n const video = this.shadowRoot?.querySelector('video.mcu-video') as HTMLVideoElement;\n if (video) attachMediaStream(video, stream);\n }\n }\n\n // Context-driven: stream or its track set changed.\n // WebRTC delivers tracks one at a time via ontrack, and the SDK re-emits\n // the same MediaStream reference each time — so we must also re-attach when\n // the track set changes, otherwise Chromium may never render a video track\n // added after the initial srcObject assignment.\n if (!this.stream && !this.call && changedProperties.has('_callState')) {\n const stream = this._callState?.remoteStream ?? null;\n const signature = this._computeTrackSignature(stream);\n if (stream !== this._remoteStreamValue || signature !== this._lastTrackSignature) {\n this._remoteStreamValue = stream;\n this._lastTrackSignature = signature;\n const video = this.shadowRoot?.querySelector('video.mcu-video') as HTMLVideoElement;\n if (video) attachMediaStream(video, stream);\n }\n }\n\n // Audio output device changed\n if (changedProperties.has('_devicesState')) {\n this._applySinkId(this._devicesState?.selectedAudioOutput?.deviceId ?? '');\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this._cleanupDirectSubscriptions();\n this._cleanupResizeObserver();\n this._cleanupVideoElement();\n }\n\n protected firstUpdated(): void {\n const video = this.shadowRoot?.querySelector('video.mcu-video') as HTMLVideoElement;\n if (video) {\n waitForVideoReady(video).then(() => {\n if (this.isConnected) this._setupResizeObserver();\n });\n }\n }\n\n // ── Direct call subscriptions (legacy / standalone) ────────────────\n\n private _setupDirectSubscriptions(): void {\n if (!this.call) return;\n this._subscriptions.push(\n this.call.remoteStream$.subscribe((stream: MediaStream | null) => {\n const signature = this._computeTrackSignature(stream);\n if (stream === this._remoteStreamValue && signature === this._lastTrackSignature) {\n return;\n }\n this._remoteStreamValue = stream;\n this._lastTrackSignature = signature;\n this.requestUpdate();\n const video = this.shadowRoot?.querySelector('video.mcu-video') as HTMLVideoElement;\n if (video) attachMediaStream(video, stream);\n })\n );\n }\n\n private _computeTrackSignature(stream: MediaStream | null): string {\n if (!stream) return '';\n return stream.getTracks().map((t) => `${t.kind}:${t.id}`).sort().join('|');\n }\n\n private _cleanupDirectSubscriptions(): void {\n this._subscriptions.forEach((sub) => sub.unsubscribe());\n this._subscriptions = [];\n }\n\n // ── Audio output ───────────────────────────────────────────────────\n\n private _applySinkId(deviceId: string): void {\n const video = this.shadowRoot?.querySelector('video.mcu-video') as HTMLVideoElement & {\n setSinkId?: (sinkId: string) => Promise<void>;\n };\n if (!video?.setSinkId) return;\n video.setSinkId(deviceId).catch((err) => {\n logger.error('[SwCallMedia] Failed to set audio output device:', err);\n });\n }\n\n // ── Resize / dimension management ──────────────────────────────────\n\n private _setupResizeObserver(): void {\n const video = this.shadowRoot?.querySelector('video.mcu-video') as HTMLVideoElement;\n const paddingWrapper = this.shadowRoot?.querySelector('.padding-wrapper') as HTMLDivElement;\n if (!video || !paddingWrapper) return;\n\n this._videoElement = video;\n this._paddingWrapper = paddingWrapper;\n\n this._resizeObserver = new ResizeObserver(\n debounce(() => this._recalculateDimensions(), 50)\n );\n this._resizeObserver.observe(this);\n\n this._videoResizeHandler = () => this._recalculateDimensions();\n video.addEventListener('resize', this._videoResizeHandler);\n\n this._windowResizeHandler = () => {\n requestAnimationFrame(() => this._recalculateDimensions());\n };\n window.addEventListener('resize', this._windowResizeHandler);\n\n this._recalculateDimensions();\n }\n\n private _recalculateDimensions(): void {\n const video = this._videoElement;\n const paddingWrapper = this._paddingWrapper;\n if (!video || !paddingWrapper) return;\n\n if (!video.videoWidth || !video.videoHeight) {\n paddingWrapper.style.width = '100%';\n paddingWrapper.style.height = '100%';\n paddingWrapper.style.paddingBottom = '0';\n paddingWrapper.style.transform = 'none';\n return;\n }\n\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n const containerRect = this.getBoundingClientRect();\n const containerWidth = containerRect.width;\n const containerHeight = containerRect.height;\n if (containerWidth <= 0 || containerHeight <= 0) return;\n\n const scaleX = Math.min(1, (viewportWidth - containerRect.left) / containerWidth);\n const scaleY = Math.min(1, (viewportHeight - containerRect.top) / containerHeight);\n const scale = Math.min(scaleX, scaleY);\n\n const maxWidth = containerWidth * scale;\n const maxHeight = containerHeight * scale;\n if (maxWidth <= 0 || maxHeight <= 0) return;\n\n const videoAspectRatio = video.videoWidth / video.videoHeight;\n const availableAspectRatio = maxWidth / maxHeight;\n\n let finalWidth: number;\n let finalHeight: number;\n\n if (videoAspectRatio > availableAspectRatio) {\n finalWidth = maxWidth;\n finalHeight = finalWidth / videoAspectRatio;\n } else {\n finalHeight = maxHeight;\n finalWidth = finalHeight * videoAspectRatio;\n }\n\n const visibleLeft = Math.max(0, containerRect.left);\n const visibleTop = Math.max(0, containerRect.top);\n const visibleRight = Math.min(viewportWidth, containerRect.right);\n const visibleBottom = Math.min(viewportHeight, containerRect.bottom);\n\n const visibleCenterX = (visibleLeft + visibleRight) / 2;\n const visibleCenterY = (visibleTop + visibleBottom) / 2;\n const containerCenterX = containerRect.left + containerWidth / 2;\n const containerCenterY = containerRect.top + containerHeight / 2;\n\n const offsetX = visibleCenterX - containerCenterX;\n const offsetY = visibleCenterY - containerCenterY;\n\n paddingWrapper.style.width = `${finalWidth}px`;\n paddingWrapper.style.height = `${finalHeight}px`;\n paddingWrapper.style.paddingBottom = '0';\n paddingWrapper.style.transform = `translate(${offsetX}px, ${offsetY}px)`;\n }\n\n private _cleanupResizeObserver(): void {\n if (this._resizeObserver) {\n this._resizeObserver.disconnect();\n this._resizeObserver = undefined;\n }\n const video = this.shadowRoot?.querySelector('video.mcu-video') as HTMLVideoElement;\n if (video && this._videoResizeHandler) {\n video.removeEventListener('resize', this._videoResizeHandler);\n this._videoResizeHandler = undefined;\n }\n if (this._windowResizeHandler) {\n window.removeEventListener('resize', this._windowResizeHandler);\n this._windowResizeHandler = undefined;\n }\n }\n\n private _cleanupVideoElement(): void {\n const video = this.shadowRoot?.querySelector('video.mcu-video') as HTMLVideoElement;\n if (video) detachMediaStream(video);\n }\n\n // ── Render ─────────────────────────────────────────────────────────\n\n render() {\n return html`\n <div class=\"mcu-content\" part=\"container\">\n <div class=\"padding-wrapper\">\n <div class=\"mcu-wrapper\">\n <video\n class=\"mcu-video\"\n part=\"video\"\n autoplay\n playsinline\n .srcObject=${this._remoteStreamValue}\n ></video>\n </div>\n <div class=\"mcu-layers\">\n <slot></slot>\n </div>\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sw-call-media': SwCallMedia;\n }\n}\n"],"names":["logger","getLogger","SwCallMedia","LitElement","changedProperties","stream","signature","video","_a","attachMediaStream","_b","_c","_e","_d","waitForVideoReady","t","sub","deviceId","err","paddingWrapper","debounce","viewportWidth","viewportHeight","containerRect","containerWidth","containerHeight","scaleX","scaleY","scale","maxWidth","maxHeight","videoAspectRatio","availableAspectRatio","finalWidth","finalHeight","visibleLeft","visibleTop","visibleRight","visibleBottom","visibleCenterX","visibleCenterY","containerCenterX","containerCenterY","offsetX","offsetY","html","css","__decorateClass","property","consume","callStateContext","state","devicesContext","customElement"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sw-call-media.js","sources":["../../src/components/sw-call-media.ts"],"sourcesContent":["/**\n * Call Media Component\n *\n * Renders the remote video stream with aspect-ratio-aware sizing.\n *\n * Input precedence (most specific wins): `.stream` > `.call` > context.\n *\n * @example\n * ```html\n * <!-- Inside a context provider (sw-call-widget): -->\n * <sw-call-media></sw-call-media>\n *\n * <!-- With an explicit Call: -->\n * <sw-call-media .call=${call}></sw-call-media>\n *\n * <!-- With a raw remote stream: -->\n * <sw-call-media .stream=${remoteStream}></sw-call-media>\n * ```\n *\n * @csspart container - Outer container that holds the video and overlay layers.\n * @csspart video - The `<video>` element rendering the remote stream.\n *\n * @slot - Default slot for overlay layers (e.g. `<sw-self-media>`, `<sw-participants>`).\n */\n\nimport { LitElement, html, css } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { consume } from '@lit/context';\nimport { Subscription } from 'rxjs';\nimport type { Call } from '../types/index.js';\nimport { callStateContext, type CallState } from '../context/call-state-context.js';\nimport { devicesContext, type DevicesState } from '../context/devices-context.js';\nimport { attachMediaStream, detachMediaStream } from '../utils/video.js';\nimport { getLogger } from '@signalwire/js';\n\nconst logger = getLogger();\n\n@customElement('sw-call-media')\nexport class SwCallMedia extends LitElement {\n static styles = css`\n :host {\n display: block;\n width: 100%;\n height: 100%;\n overflow: hidden;\n }\n\n .mcu-content {\n position: relative;\n width: 100%;\n height: 100%;\n margin: 0 auto;\n display: flex;\n align-items: center;\n justify-content: center;\n background-color: var(--bg-page, #0e0e18);\n overflow: hidden;\n }\n\n /* Fill the parent box. The video element below uses object-fit: contain\n so the stream letterboxes into whatever rectangle the parent gives us\n — no JS sizing pass required. */\n .padding-wrapper {\n position: relative;\n width: 100%;\n height: 100%;\n }\n\n /* Outer rounding is owned by sw-ui-call-layout's :host border-radius +\n overflow:hidden. Rounding here too carves a notch out of the right\n edge of the video cell that exposes whatever sits behind the widget\n (page / modal backdrop) as a black sliver in light mode. */\n .mcu-wrapper {\n position: absolute;\n inset: 0;\n overflow: hidden;\n }\n\n .mcu-video {\n width: 100%;\n height: 100%;\n display: block;\n object-fit: contain;\n }\n\n .mcu-layers {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n }\n `;\n\n /**\n * Explicit Call — when set, subscribes directly to its observables instead\n * of relying on context. Overridden by `.stream` if both are set.\n */\n @property({ type: Object }) call?: Call;\n\n /**\n * Explicit remote MediaStream — highest precedence. Bypasses both `.call`\n * and context. Useful for raw rendering with no SDK at all.\n */\n @property({ attribute: false }) stream: MediaStream | null = null;\n\n @consume({ context: callStateContext, subscribe: true })\n @state()\n private _callState?: CallState;\n\n @consume({ context: devicesContext, subscribe: true })\n @state()\n private _devicesState?: DevicesState;\n\n private _remoteStreamValue: MediaStream | null = null;\n private _lastTrackSignature = '';\n private _subscriptions: Subscription[] = [];\n\n // ── Lifecycle ──────────────────────────────────────────────────────\n\n connectedCallback() {\n super.connectedCallback();\n if (!this.stream && this.call) this._setupDirectSubscriptions();\n if (this.stream) {\n this._remoteStreamValue = this.stream;\n this._lastTrackSignature = this._computeTrackSignature(this.stream);\n }\n }\n\n protected updated(changedProperties: Map<string, unknown>): void {\n super.updated(changedProperties);\n\n // Direct call prop changed — re-subscribe (only meaningful when `.stream` isn't set)\n if (changedProperties.has('call')) {\n this._cleanupDirectSubscriptions();\n if (!this.stream && this.call) this._setupDirectSubscriptions();\n }\n\n // Explicit `.stream` prop wins — attach it and skip everything else.\n if (changedProperties.has('stream')) {\n this._cleanupDirectSubscriptions();\n const stream = this.stream;\n const signature = this._computeTrackSignature(stream);\n if (stream !== this._remoteStreamValue || signature !== this._lastTrackSignature) {\n this._remoteStreamValue = stream;\n this._lastTrackSignature = signature;\n const video = this.shadowRoot?.querySelector('video.mcu-video') as HTMLVideoElement;\n if (video) attachMediaStream(video, stream);\n }\n }\n\n // Context-driven: stream or its track set changed.\n // WebRTC delivers tracks one at a time via ontrack, and the SDK re-emits\n // the same MediaStream reference each time — so we must also re-attach when\n // the track set changes, otherwise Chromium may never render a video track\n // added after the initial srcObject assignment.\n if (!this.stream && !this.call && changedProperties.has('_callState')) {\n const stream = this._callState?.remoteStream ?? null;\n const signature = this._computeTrackSignature(stream);\n if (stream !== this._remoteStreamValue || signature !== this._lastTrackSignature) {\n this._remoteStreamValue = stream;\n this._lastTrackSignature = signature;\n const video = this.shadowRoot?.querySelector('video.mcu-video') as HTMLVideoElement;\n if (video) attachMediaStream(video, stream);\n }\n }\n\n // Audio output device changed\n if (changedProperties.has('_devicesState')) {\n this._applySinkId(this._devicesState?.selectedAudioOutput?.deviceId ?? '');\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this._cleanupDirectSubscriptions();\n this._cleanupVideoElement();\n }\n\n // ── Direct call subscriptions (legacy / standalone) ────────────────\n\n private _setupDirectSubscriptions(): void {\n if (!this.call) return;\n this._subscriptions.push(\n this.call.remoteStream$.subscribe((stream: MediaStream | null) => {\n const signature = this._computeTrackSignature(stream);\n if (stream === this._remoteStreamValue && signature === this._lastTrackSignature) {\n return;\n }\n this._remoteStreamValue = stream;\n this._lastTrackSignature = signature;\n this.requestUpdate();\n const video = this.shadowRoot?.querySelector('video.mcu-video') as HTMLVideoElement;\n if (video) attachMediaStream(video, stream);\n })\n );\n }\n\n private _computeTrackSignature(stream: MediaStream | null): string {\n if (!stream) return '';\n return stream.getTracks().map((t) => `${t.kind}:${t.id}`).sort().join('|');\n }\n\n private _cleanupDirectSubscriptions(): void {\n this._subscriptions.forEach((sub) => sub.unsubscribe());\n this._subscriptions = [];\n }\n\n // ── Audio output ───────────────────────────────────────────────────\n\n private _applySinkId(deviceId: string): void {\n const video = this.shadowRoot?.querySelector('video.mcu-video') as HTMLVideoElement & {\n setSinkId?: (sinkId: string) => Promise<void>;\n };\n if (!video?.setSinkId) return;\n video.setSinkId(deviceId).catch((err) => {\n logger.error('[SwCallMedia] Failed to set audio output device:', err);\n });\n }\n\n // ── Cleanup ─────────────────────────────────────────────────────────\n\n private _cleanupVideoElement(): void {\n const video = this.shadowRoot?.querySelector('video.mcu-video') as HTMLVideoElement;\n if (video) detachMediaStream(video);\n }\n\n // ── Render ─────────────────────────────────────────────────────────\n\n render() {\n return html`\n <div class=\"mcu-content\" part=\"container\">\n <div class=\"padding-wrapper\">\n <div class=\"mcu-wrapper\">\n <video\n class=\"mcu-video\"\n part=\"video\"\n autoplay\n playsinline\n muted\n .srcObject=${this._remoteStreamValue}\n ></video>\n </div>\n <div class=\"mcu-layers\">\n <slot></slot>\n </div>\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sw-call-media': SwCallMedia;\n }\n}\n"],"names":["logger","getLogger","SwCallMedia","LitElement","changedProperties","stream","signature","video","_a","attachMediaStream","_b","_c","_e","_d","t","sub","deviceId","err","html","css","__decorateClass","property","consume","callStateContext","state","devicesContext","customElement"],"mappings":"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"}
|