@signalwire/web-components 4.0.0-beta.6 → 4.0.0-beta.8
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/README.md +6 -9
- package/dist/components/call-media.d.ts.map +1 -1
- package/dist/components/call-media.js +13 -9
- package/dist/components/call-media.js.map +1 -1
- package/dist/components/call-status.d.ts +1 -4
- package/dist/components/call-status.d.ts.map +1 -1
- package/dist/components/call-status.js +55 -52
- package/dist/components/call-status.js.map +1 -1
- package/dist/components/device-selector.d.ts +1 -27
- package/dist/components/device-selector.d.ts.map +1 -1
- package/dist/components/device-selector.js.map +1 -1
- package/dist/components/self-media.d.ts.map +1 -1
- package/dist/components/self-media.js +24 -23
- package/dist/components/self-media.js.map +1 -1
- package/dist/constants.d.ts +10 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +5 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -23
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +2 -25
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/video.d.ts.map +1 -1
- package/dist/utils/video.js +15 -15
- package/dist/utils/video.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -214,7 +214,7 @@ Displays call state, status text, and duration timer.
|
|
|
214
214
|
|----------|------|---------|-------------|
|
|
215
215
|
| `call` | `CallStatusCall` | - | Call with status$ observable |
|
|
216
216
|
|
|
217
|
-
**Status Values:** `
|
|
217
|
+
**Status Values:** `new`, `trying`, `ringing`, `connecting`, `connected`, `disconnecting`, `disconnected`, `failed`, `destroyed`
|
|
218
218
|
|
|
219
219
|
**CSS Custom Properties:**
|
|
220
220
|
|
|
@@ -314,7 +314,7 @@ Dropdown for selecting audio/video input and output devices.
|
|
|
314
314
|
| Event | Detail | Description |
|
|
315
315
|
|-------|--------|-------------|
|
|
316
316
|
| `sw-tab-change` | `{ tab: 'microphone' \| 'camera' \| 'speaker' }` | Tab changed |
|
|
317
|
-
| `sw-device-change` | `{ device:
|
|
317
|
+
| `sw-device-change` | `{ device: MediaDeviceInfo }` | Device selected |
|
|
318
318
|
| `sw-test-speaker` | - | Speaker test triggered |
|
|
319
319
|
|
|
320
320
|
**CSS Custom Properties:**
|
|
@@ -668,7 +668,7 @@ sw-call-media::part(video) {
|
|
|
668
668
|
## TypeScript Interfaces
|
|
669
669
|
|
|
670
670
|
```typescript
|
|
671
|
-
import type { Call,
|
|
671
|
+
import type { Call, CallSelf, DeviceController, LayoutLayer, Participant } from '@signalwire/web-components';
|
|
672
672
|
|
|
673
673
|
// Call object interface
|
|
674
674
|
interface Call {
|
|
@@ -694,12 +694,9 @@ interface LayoutLayer {
|
|
|
694
694
|
height: number;
|
|
695
695
|
}
|
|
696
696
|
|
|
697
|
-
//
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
label: string;
|
|
701
|
-
kind: 'audioinput' | 'audiooutput' | 'videoinput';
|
|
702
|
-
}
|
|
697
|
+
// DeviceController is imported from @signalwire/js via @signalwire/web-components
|
|
698
|
+
// It provides observables for device lists and selection methods.
|
|
699
|
+
// See @signalwire/js documentation for the full DeviceController interface.
|
|
703
700
|
|
|
704
701
|
// Directory address
|
|
705
702
|
interface Address {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"call-media.d.ts","sourceRoot":"","sources":["../../src/components/call-media.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAI5C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAK9C,qBACa,SAAU,SAAQ,UAAU;IACvC,MAAM,CAAC,MAAM,0BA4DX;IAEF;;OAEG;IACyB,IAAI,CAAC,EAAE,IAAI,CAAC;IAExC;;;OAGG;IAGH,OAAO,CAAC,aAAa,CAAC,CAAO;IAE7B;;OAEG;IACH,OAAO,CAAC,kBAAkB,CAA4B;IAEtD;;OAEG;IACH,OAAO,CAAC,aAAa,CAAsB;IAE3C;;OAEG;IACH,OAAO,CAAC,cAAc,CAAC,CAAiB;IAExC;;OAEG;IACH,OAAO,CAAC,kBAAkB,CAAC,CAAa;IAExC;;OAEG;IACH,OAAO,CAAC,mBAAmB,CAAC,CAAa;IAEzC;;OAEG;IACH,iBAAiB;IAMjB;;OAEG;IACH,SAAS,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAYhE;;OAEG;IACH,oBAAoB;IAOpB;;OAEG;IACH,SAAS,CAAC,YAAY,IAAI,IAAI;
|
|
1
|
+
{"version":3,"file":"call-media.d.ts","sourceRoot":"","sources":["../../src/components/call-media.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAI5C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAK9C,qBACa,SAAU,SAAQ,UAAU;IACvC,MAAM,CAAC,MAAM,0BA4DX;IAEF;;OAEG;IACyB,IAAI,CAAC,EAAE,IAAI,CAAC;IAExC;;;OAGG;IAGH,OAAO,CAAC,aAAa,CAAC,CAAO;IAE7B;;OAEG;IACH,OAAO,CAAC,kBAAkB,CAA4B;IAEtD;;OAEG;IACH,OAAO,CAAC,aAAa,CAAsB;IAE3C;;OAEG;IACH,OAAO,CAAC,cAAc,CAAC,CAAiB;IAExC;;OAEG;IACH,OAAO,CAAC,kBAAkB,CAAC,CAAa;IAExC;;OAEG;IACH,OAAO,CAAC,mBAAmB,CAAC,CAAa;IAEzC;;OAEG;IACH,iBAAiB;IAMjB;;OAEG;IACH,SAAS,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAYhE;;OAEG;IACH,oBAAoB;IAOpB;;OAEG;IACH,SAAS,CAAC,YAAY,IAAI,IAAI;IAW9B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAkB1B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAK5B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAwC3B;;OAEG;IACH,OAAO,CAAC,aAAa,CAAC,CAAmB;IAEzC;;OAEG;IACH,OAAO,CAAC,eAAe,CAAC,CAAiB;IAEzC;;;;;OAKG;IACH,OAAO,CAAC,qBAAqB;IAyF7B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAkB7B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAO3B;;OAEG;IACH,MAAM;CAqBP;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,eAAe,EAAE,SAAS,CAAC;KAC5B;CACF"}
|
|
@@ -3,11 +3,11 @@ import { property as D, customElement as j } from "lit/decorators.js";
|
|
|
3
3
|
import { provide as L } from "@lit/context";
|
|
4
4
|
import { callContext as V } from "../context/call-context.js";
|
|
5
5
|
import { debounce as $ } from "../utils/debounce.js";
|
|
6
|
-
import { waitForVideoReady as k, attachMediaStream as
|
|
7
|
-
var
|
|
8
|
-
for (var i = o > 1 ? void 0 : o ?
|
|
6
|
+
import { waitForVideoReady as k, attachMediaStream as B, detachMediaStream as P } from "../utils/video.js";
|
|
7
|
+
var X = Object.defineProperty, Y = Object.getOwnPropertyDescriptor, u = (e, t, s, o) => {
|
|
8
|
+
for (var i = o > 1 ? void 0 : o ? Y(t, s) : t, r = e.length - 1, n; r >= 0; r--)
|
|
9
9
|
(n = e[r]) && (i = (o ? n(t, s, i) : n(i)) || i);
|
|
10
|
-
return o && i &&
|
|
10
|
+
return o && i && X(t, s, i), i;
|
|
11
11
|
};
|
|
12
12
|
let a = class extends E {
|
|
13
13
|
constructor() {
|
|
@@ -38,7 +38,7 @@ let a = class extends E {
|
|
|
38
38
|
var t;
|
|
39
39
|
const e = (t = this.shadowRoot) == null ? void 0 : t.querySelector("video.mcu-video");
|
|
40
40
|
e && k(e).then(() => {
|
|
41
|
-
this.setupResizeObserver();
|
|
41
|
+
this.isConnected && this.setupResizeObserver();
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
@@ -50,7 +50,7 @@ let a = class extends E {
|
|
|
50
50
|
var s;
|
|
51
51
|
this._remoteStreamValue = e, this.requestUpdate();
|
|
52
52
|
const t = (s = this.shadowRoot) == null ? void 0 : s.querySelector("video.mcu-video");
|
|
53
|
-
t &&
|
|
53
|
+
t && B(t, e);
|
|
54
54
|
})
|
|
55
55
|
);
|
|
56
56
|
}
|
|
@@ -86,7 +86,11 @@ let a = class extends E {
|
|
|
86
86
|
*/
|
|
87
87
|
recalculateDimensions() {
|
|
88
88
|
const e = this._videoElement, t = this._paddingWrapper;
|
|
89
|
-
if (!e || !t
|
|
89
|
+
if (!e || !t) return;
|
|
90
|
+
if (!e.videoWidth || !e.videoHeight) {
|
|
91
|
+
t.style.width = "100%", t.style.height = "100%", t.style.paddingBottom = "0", t.style.transform = "none";
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
90
94
|
const s = window.innerWidth, o = window.innerHeight, i = this.getBoundingClientRect(), r = i.width, n = i.height;
|
|
91
95
|
if (r <= 0 || n <= 0) return;
|
|
92
96
|
const m = Math.min(1, (s - i.left) / r), b = Math.min(1, (o - i.top) / n), v = Math.min(m, b), l = r * v, h = n * v;
|
|
@@ -94,7 +98,7 @@ let a = class extends E {
|
|
|
94
98
|
const w = e.videoWidth, f = e.videoHeight, p = w / f, g = l / h;
|
|
95
99
|
let d, c;
|
|
96
100
|
p > g ? (d = l, c = d / p) : (c = h, d = c * p);
|
|
97
|
-
const
|
|
101
|
+
const y = Math.max(0, i.left), R = Math.max(0, i.top), z = Math.min(s, i.right), S = Math.min(o, i.bottom), C = (y + z) / 2, H = (R + S) / 2, x = i.left + r / 2, O = i.top + n / 2, _ = C - x, W = H - O;
|
|
98
102
|
t.style.width = `${d}px`, t.style.height = `${c}px`, t.style.paddingBottom = "0", t.style.transform = `translate(${_}px, ${W}px)`;
|
|
99
103
|
}
|
|
100
104
|
/**
|
|
@@ -112,7 +116,7 @@ let a = class extends E {
|
|
|
112
116
|
cleanupVideoElement() {
|
|
113
117
|
var t;
|
|
114
118
|
const e = (t = this.shadowRoot) == null ? void 0 : t.querySelector("video.mcu-video");
|
|
115
|
-
e &&
|
|
119
|
+
e && P(e);
|
|
116
120
|
}
|
|
117
121
|
/**
|
|
118
122
|
* Render the component
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"call-media.js","sources":["../../src/components/call-media.ts"],"sourcesContent":["/**\n * Call Media Component\n *\n * Root component that renders remote video stream and provides call context\n * to child components. Manages aspect ratio with ResizeObserver and handles\n * video stream lifecycle.\n *\n * @example\n * ```html\n * <call-media .call=${call}>\n * <participants>\n * <self-media mirror=${true}></self-media>\n * </participants>\n * </call-media>\n * ```\n */\n\nimport { LitElement, html, css } from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\nimport { provide } from '@lit/context';\nimport { Subscription } from 'rxjs';\nimport type { Call } from '../types/index.js';\nimport { callContext } from '../context/call-context.js';\nimport { debounce } from '../utils/debounce.js';\nimport { waitForVideoReady, attachMediaStream, detachMediaStream } from '../utils/video.js';\n\n@customElement('sw-call-media')\nexport class CallMedia extends LitElement {\n static styles = css`\n :host {\n /* CSS Custom Properties for theming */\n --sw-color-primary: #044cf6;\n --sw-color-background: #000000;\n --sw-border-radius: 0px;\n\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(--sw-color-background);\n border-radius: var(--sw-border-radius);\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 .mcu-wrapper {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n border-radius: var(--sw-border-radius);\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 * Call object with observable streams and properties\n */\n @property({ type: Object }) call?: Call;\n\n /**\n * Provides call context to child components\n * Note: Used by @provide decorator, TypeScript doesn't see the usage\n */\n @provide({ context: callContext })\n // @ts-expect-error - Used by @provide decorator\n private _providedCall?: Call;\n\n /**\n * Current remote stream value from observable\n */\n private _remoteStreamValue: MediaStream | null = null;\n\n /**\n * RxJS subscriptions for cleanup\n */\n private subscriptions: Subscription[] = [];\n\n /**\n * ResizeObserver instance for aspect ratio management\n */\n private resizeObserver?: ResizeObserver;\n\n /**\n * Video resize event handler reference for cleanup\n */\n private videoResizeHandler?: () => void;\n\n /**\n * Window resize event handler reference for cleanup\n */\n private windowResizeHandler?: () => void;\n\n /**\n * Lifecycle: Component connected to DOM\n */\n connectedCallback() {\n super.connectedCallback();\n this._providedCall = this.call;\n this.setupSubscriptions();\n }\n\n /**\n * Lifecycle: React to property changes\n */\n protected updated(changedProperties: Map<string, unknown>): void {\n super.updated(changedProperties);\n if (changedProperties.has('call')) {\n // Clean up old subscriptions first\n this.cleanupSubscriptions();\n // Update context for child components\n this._providedCall = this.call;\n // Set up new subscriptions\n this.setupSubscriptions();\n }\n }\n\n /**\n * Lifecycle: Component disconnected from DOM\n */\n disconnectedCallback() {\n super.disconnectedCallback();\n this.cleanupSubscriptions();\n this.cleanupResizeObserver();\n this.cleanupVideoElement();\n }\n\n /**\n * Lifecycle: First update after render\n */\n protected firstUpdated(): void {\n const video = this.shadowRoot?.querySelector('video.mcu-video') as HTMLVideoElement;\n if (video) {\n waitForVideoReady(video).then(() => {\n this.setupResizeObserver();\n });\n }\n }\n\n /**\n * Subscribe to call observables\n */\n private setupSubscriptions(): void {\n if (!this.call) return;\n\n // Subscribe to remote stream\n this.subscriptions.push(\n this.call.remoteStream$.subscribe((stream: MediaStream | null) => {\n this._remoteStreamValue = stream;\n this.requestUpdate();\n\n // Attach stream to video element\n const video = this.shadowRoot?.querySelector('video.mcu-video') as HTMLVideoElement;\n if (video) {\n attachMediaStream(video, stream);\n }\n })\n );\n }\n\n /**\n * Cleanup all subscriptions\n */\n private cleanupSubscriptions(): void {\n this.subscriptions.forEach((sub) => sub.unsubscribe());\n this.subscriptions = [];\n }\n\n /**\n * Setup ResizeObserver for aspect ratio 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\n if (!video || !paddingWrapper) return;\n\n // Store references for the update function\n this._videoElement = video;\n this._paddingWrapper = paddingWrapper;\n\n // Observe the host element for container size changes\n this.resizeObserver = new ResizeObserver(\n debounce(() => {\n this.recalculateDimensions();\n }, 50)\n );\n\n this.resizeObserver.observe(this);\n\n // Listen for video intrinsic dimension changes (when media orientation flips)\n this.videoResizeHandler = () => {\n this.recalculateDimensions();\n };\n\n video.addEventListener('resize', this.videoResizeHandler);\n\n // Listen for window/viewport resize changes\n // Use requestAnimationFrame to ensure layout has settled before reading dimensions\n this.windowResizeHandler = () => {\n requestAnimationFrame(() => {\n this.recalculateDimensions();\n });\n };\n\n window.addEventListener('resize', this.windowResizeHandler);\n\n // Initial calculation\n this.recalculateDimensions();\n }\n\n /**\n * Video element reference for dimension calculations\n */\n private _videoElement?: HTMLVideoElement;\n\n /**\n * Padding wrapper reference for dimension calculations\n */\n private _paddingWrapper?: HTMLDivElement;\n\n /**\n * Recalculate and apply dimensions based on viewport, container, and video sizes.\n * Video should never grow bigger than the visible area.\n * When container bleeds viewport, scale proportionally to maintain container's aspect ratio.\n * Uses \"contain\" fitting algorithm - compares aspect ratios to determine constraint.\n */\n private recalculateDimensions(): void {\n const video = this._videoElement;\n const paddingWrapper = this._paddingWrapper;\n\n if (!video || !paddingWrapper) return;\n if (!video.videoWidth || !video.videoHeight) return;\n\n // Get viewport dimensions\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n\n // Get container's bounding rect (actual rendered position and size)\n const containerRect = this.getBoundingClientRect();\n const containerWidth = containerRect.width;\n const containerHeight = containerRect.height;\n\n if (containerWidth <= 0 || containerHeight <= 0) return;\n\n // Calculate scale factors for each dimension\n // This tells us how much the container would need to shrink to fit in viewport\n const scaleX = Math.min(1, (viewportWidth - containerRect.left) / containerWidth);\n const scaleY = Math.min(1, (viewportHeight - containerRect.top) / containerHeight);\n\n // Use the minimum scale to maintain proportionality\n // This ensures the container's aspect ratio is preserved when scaling down\n const scale = Math.min(scaleX, scaleY);\n\n // Calculate available space by applying scale to container dimensions\n const maxWidth = containerWidth * scale;\n const maxHeight = containerHeight * scale;\n\n if (maxWidth <= 0 || maxHeight <= 0) return;\n\n const videoWidth = video.videoWidth;\n const videoHeight = video.videoHeight;\n\n // Calculate aspect ratios\n const videoAspectRatio = videoWidth / videoHeight;\n const availableAspectRatio = maxWidth / maxHeight;\n\n let finalWidth: number;\n let finalHeight: number;\n\n // \"Contain\" algorithm: compare aspect ratios to determine which dimension constrains\n if (videoAspectRatio > availableAspectRatio) {\n // Video is wider than available space - constrain by width\n finalWidth = maxWidth;\n finalHeight = finalWidth / videoAspectRatio;\n } else {\n // Video is taller than available space - constrain by height\n finalHeight = maxHeight;\n finalWidth = finalHeight * videoAspectRatio;\n }\n\n // Calculate offset to center video in the visible area of the container\n // When container bleeds viewport, the visible center is different from container center\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 // Center of the visible area (in viewport coordinates)\n const visibleCenterX = (visibleLeft + visibleRight) / 2;\n const visibleCenterY = (visibleTop + visibleBottom) / 2;\n\n // Center of the container (in viewport coordinates)\n const containerCenterX = containerRect.left + containerWidth / 2;\n const containerCenterY = containerRect.top + containerHeight / 2;\n\n // Offset needed to shift from container center to visible center\n const offsetX = visibleCenterX - containerCenterX;\n const offsetY = visibleCenterY - containerCenterY;\n\n // Apply the calculated dimensions and offset\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 /**\n * Cleanup ResizeObserver and event listeners\n */\n private cleanupResizeObserver(): void {\n if (this.resizeObserver) {\n this.resizeObserver.disconnect();\n this.resizeObserver = undefined;\n }\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\n if (this.windowResizeHandler) {\n window.removeEventListener('resize', this.windowResizeHandler);\n this.windowResizeHandler = undefined;\n }\n }\n\n /**\n * Cleanup video element\n */\n private cleanupVideoElement(): void {\n const video = this.shadowRoot?.querySelector('video.mcu-video') as HTMLVideoElement;\n if (video) {\n detachMediaStream(video);\n }\n }\n\n /**\n * Render the component\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\n/**\n * Declare global type for TypeScript\n */\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sw-call-media': CallMedia;\n }\n}\n"],"names":["CallMedia","LitElement","changedProperties","video","_a","waitForVideoReady","stream","attachMediaStream","sub","paddingWrapper","_b","debounce","viewportWidth","viewportHeight","containerRect","containerWidth","containerHeight","scaleX","scaleY","scale","maxWidth","maxHeight","videoWidth","videoHeight","videoAspectRatio","availableAspectRatio","finalWidth","finalHeight","visibleLeft","visibleTop","visibleRight","visibleBottom","visibleCenterX","visibleCenterY","containerCenterX","containerCenterY","offsetX","offsetY","detachMediaStream","html","css","__decorateClass","property","provide","callContext","customElement"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"call-media.js","sources":["../../src/components/call-media.ts"],"sourcesContent":["/**\n * Call Media Component\n *\n * Root component that renders remote video stream and provides call context\n * to child components. Manages aspect ratio with ResizeObserver and handles\n * video stream lifecycle.\n *\n * @example\n * ```html\n * <call-media .call=${call}>\n * <participants>\n * <self-media mirror=${true}></self-media>\n * </participants>\n * </call-media>\n * ```\n */\n\nimport { LitElement, html, css } from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\nimport { provide } from '@lit/context';\nimport { Subscription } from 'rxjs';\nimport type { Call } from '../types/index.js';\nimport { callContext } from '../context/call-context.js';\nimport { debounce } from '../utils/debounce.js';\nimport { waitForVideoReady, attachMediaStream, detachMediaStream } from '../utils/video.js';\n\n@customElement('sw-call-media')\nexport class CallMedia extends LitElement {\n static styles = css`\n :host {\n /* CSS Custom Properties for theming */\n --sw-color-primary: #044cf6;\n --sw-color-background: #000000;\n --sw-border-radius: 0px;\n\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(--sw-color-background);\n border-radius: var(--sw-border-radius);\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 .mcu-wrapper {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n border-radius: var(--sw-border-radius);\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 * Call object with observable streams and properties\n */\n @property({ type: Object }) call?: Call;\n\n /**\n * Provides call context to child components\n * Note: Used by @provide decorator, TypeScript doesn't see the usage\n */\n @provide({ context: callContext })\n // @ts-expect-error - Used by @provide decorator\n private _providedCall?: Call;\n\n /**\n * Current remote stream value from observable\n */\n private _remoteStreamValue: MediaStream | null = null;\n\n /**\n * RxJS subscriptions for cleanup\n */\n private subscriptions: Subscription[] = [];\n\n /**\n * ResizeObserver instance for aspect ratio management\n */\n private resizeObserver?: ResizeObserver;\n\n /**\n * Video resize event handler reference for cleanup\n */\n private videoResizeHandler?: () => void;\n\n /**\n * Window resize event handler reference for cleanup\n */\n private windowResizeHandler?: () => void;\n\n /**\n * Lifecycle: Component connected to DOM\n */\n connectedCallback() {\n super.connectedCallback();\n this._providedCall = this.call;\n this.setupSubscriptions();\n }\n\n /**\n * Lifecycle: React to property changes\n */\n protected updated(changedProperties: Map<string, unknown>): void {\n super.updated(changedProperties);\n if (changedProperties.has('call')) {\n // Clean up old subscriptions first\n this.cleanupSubscriptions();\n // Update context for child components\n this._providedCall = this.call;\n // Set up new subscriptions\n this.setupSubscriptions();\n }\n }\n\n /**\n * Lifecycle: Component disconnected from DOM\n */\n disconnectedCallback() {\n super.disconnectedCallback();\n this.cleanupSubscriptions();\n this.cleanupResizeObserver();\n this.cleanupVideoElement();\n }\n\n /**\n * Lifecycle: First update after render\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) {\n this.setupResizeObserver();\n }\n });\n }\n }\n\n /**\n * Subscribe to call observables\n */\n private setupSubscriptions(): void {\n if (!this.call) return;\n\n // Subscribe to remote stream\n this.subscriptions.push(\n this.call.remoteStream$.subscribe((stream: MediaStream | null) => {\n this._remoteStreamValue = stream;\n this.requestUpdate();\n\n // Attach stream to video element\n const video = this.shadowRoot?.querySelector('video.mcu-video') as HTMLVideoElement;\n if (video) {\n attachMediaStream(video, stream);\n }\n })\n );\n }\n\n /**\n * Cleanup all subscriptions\n */\n private cleanupSubscriptions(): void {\n this.subscriptions.forEach((sub) => sub.unsubscribe());\n this.subscriptions = [];\n }\n\n /**\n * Setup ResizeObserver for aspect ratio 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\n if (!video || !paddingWrapper) return;\n\n // Store references for the update function\n this._videoElement = video;\n this._paddingWrapper = paddingWrapper;\n\n // Observe the host element for container size changes\n this.resizeObserver = new ResizeObserver(\n debounce(() => {\n this.recalculateDimensions();\n }, 50)\n );\n\n this.resizeObserver.observe(this);\n\n // Listen for video intrinsic dimension changes (when media orientation flips)\n this.videoResizeHandler = () => {\n this.recalculateDimensions();\n };\n\n video.addEventListener('resize', this.videoResizeHandler);\n\n // Listen for window/viewport resize changes\n // Use requestAnimationFrame to ensure layout has settled before reading dimensions\n this.windowResizeHandler = () => {\n requestAnimationFrame(() => {\n this.recalculateDimensions();\n });\n };\n\n window.addEventListener('resize', this.windowResizeHandler);\n\n // Initial calculation\n this.recalculateDimensions();\n }\n\n /**\n * Video element reference for dimension calculations\n */\n private _videoElement?: HTMLVideoElement;\n\n /**\n * Padding wrapper reference for dimension calculations\n */\n private _paddingWrapper?: HTMLDivElement;\n\n /**\n * Recalculate and apply dimensions based on viewport, container, and video sizes.\n * Video should never grow bigger than the visible area.\n * When container bleeds viewport, scale proportionally to maintain container's aspect ratio.\n * Uses \"contain\" fitting algorithm - compares aspect ratios to determine constraint.\n */\n private recalculateDimensions(): void {\n const video = this._videoElement;\n const paddingWrapper = this._paddingWrapper;\n\n if (!video || !paddingWrapper) return;\n\n // Audio-only streams have no video dimensions.\n // Fill the container and reset transform so the layout layers remain usable.\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 // Get viewport dimensions\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n\n // Get container's bounding rect (actual rendered position and size)\n const containerRect = this.getBoundingClientRect();\n const containerWidth = containerRect.width;\n const containerHeight = containerRect.height;\n\n if (containerWidth <= 0 || containerHeight <= 0) return;\n\n // Calculate scale factors for each dimension\n // This tells us how much the container would need to shrink to fit in viewport\n const scaleX = Math.min(1, (viewportWidth - containerRect.left) / containerWidth);\n const scaleY = Math.min(1, (viewportHeight - containerRect.top) / containerHeight);\n\n // Use the minimum scale to maintain proportionality\n // This ensures the container's aspect ratio is preserved when scaling down\n const scale = Math.min(scaleX, scaleY);\n\n // Calculate available space by applying scale to container dimensions\n const maxWidth = containerWidth * scale;\n const maxHeight = containerHeight * scale;\n\n if (maxWidth <= 0 || maxHeight <= 0) return;\n\n const videoWidth = video.videoWidth;\n const videoHeight = video.videoHeight;\n\n // Calculate aspect ratios\n const videoAspectRatio = videoWidth / videoHeight;\n const availableAspectRatio = maxWidth / maxHeight;\n\n let finalWidth: number;\n let finalHeight: number;\n\n // \"Contain\" algorithm: compare aspect ratios to determine which dimension constrains\n if (videoAspectRatio > availableAspectRatio) {\n // Video is wider than available space - constrain by width\n finalWidth = maxWidth;\n finalHeight = finalWidth / videoAspectRatio;\n } else {\n // Video is taller than available space - constrain by height\n finalHeight = maxHeight;\n finalWidth = finalHeight * videoAspectRatio;\n }\n\n // Calculate offset to center video in the visible area of the container\n // When container bleeds viewport, the visible center is different from container center\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 // Center of the visible area (in viewport coordinates)\n const visibleCenterX = (visibleLeft + visibleRight) / 2;\n const visibleCenterY = (visibleTop + visibleBottom) / 2;\n\n // Center of the container (in viewport coordinates)\n const containerCenterX = containerRect.left + containerWidth / 2;\n const containerCenterY = containerRect.top + containerHeight / 2;\n\n // Offset needed to shift from container center to visible center\n const offsetX = visibleCenterX - containerCenterX;\n const offsetY = visibleCenterY - containerCenterY;\n\n // Apply the calculated dimensions and offset\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 /**\n * Cleanup ResizeObserver and event listeners\n */\n private cleanupResizeObserver(): void {\n if (this.resizeObserver) {\n this.resizeObserver.disconnect();\n this.resizeObserver = undefined;\n }\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\n if (this.windowResizeHandler) {\n window.removeEventListener('resize', this.windowResizeHandler);\n this.windowResizeHandler = undefined;\n }\n }\n\n /**\n * Cleanup video element\n */\n private cleanupVideoElement(): void {\n const video = this.shadowRoot?.querySelector('video.mcu-video') as HTMLVideoElement;\n if (video) {\n detachMediaStream(video);\n }\n }\n\n /**\n * Render the component\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\n/**\n * Declare global type for TypeScript\n */\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sw-call-media': CallMedia;\n }\n}\n"],"names":["CallMedia","LitElement","changedProperties","video","_a","waitForVideoReady","stream","attachMediaStream","sub","paddingWrapper","_b","debounce","viewportWidth","viewportHeight","containerRect","containerWidth","containerHeight","scaleX","scaleY","scale","maxWidth","maxHeight","videoWidth","videoHeight","videoAspectRatio","availableAspectRatio","finalWidth","finalHeight","visibleLeft","visibleTop","visibleRight","visibleBottom","visibleCenterX","visibleCenterY","containerCenterX","containerCenterY","offsetX","offsetY","detachMediaStream","html","css","__decorateClass","property","provide","callContext","customElement"],"mappings":"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"}
|
|
@@ -11,10 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { LitElement } from 'lit';
|
|
13
13
|
import type { Observable } from 'rxjs';
|
|
14
|
-
|
|
15
|
-
* Call status types
|
|
16
|
-
*/
|
|
17
|
-
export type CallStatus = 'new' | 'trying' | 'idle' | 'connecting' | 'ringing' | 'connected' | 'disconnecting' | 'disconnected' | 'failed' | 'destroyed';
|
|
14
|
+
import type { CallStatus } from '@signalwire/js';
|
|
18
15
|
/**
|
|
19
16
|
* Call interface for status component
|
|
20
17
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"call-status.d.ts","sourceRoot":"","sources":["../../src/components/call-status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAI5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"call-status.d.ts","sourceRoot":"","sources":["../../src/components/call-status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAI5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AACvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGjD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;CACjC;AAED,qBACa,mBAAoB,SAAQ,UAAU;IACjD,MAAM,CAAC,MAAM,0BAsIX;IAEF;;OAEG;IAEH,IAAI,EAAE,cAAc,GAAG,IAAI,CAAQ;IAEnC;;OAEG;IAGH,OAAO,CAAC,WAAW,CAAC,CAAiB;IAErC;;OAEG;IAEH,OAAO,CAAC,MAAM,CAAqB;IAEnC;;OAEG;IAEH,OAAO,CAAC,aAAa,CAAuB;IAE5C;;OAEG;IAEH,OAAO,CAAC,QAAQ,CAAkB;IAElC;;OAEG;IACH,OAAO,CAAC,aAAa,CAAsB;IAE3C;;OAEG;IACH,OAAO,CAAC,gBAAgB,CAAuB;IAE/C,iBAAiB;IAKjB,oBAAoB;IAKpB,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;IAO/C,OAAO,KAAK,UAAU,GAErB;IAED,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,kBAAkB;IAY1B,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,OAAO;IAMf,OAAO,CAAC,aAAa;IA2BrB,MAAM;CAqBP;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,gBAAgB,EAAE,mBAAmB,CAAC;KACvC;CACF"}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { LitElement as d, html as u, css as p } from "lit";
|
|
2
|
-
import { property as
|
|
2
|
+
import { property as h, state as i, customElement as f } from "lit/decorators.js";
|
|
3
3
|
import { consume as m } from "@lit/context";
|
|
4
4
|
import { callContext as w } from "../context/call-context.js";
|
|
5
|
-
var g = Object.defineProperty,
|
|
6
|
-
for (var
|
|
7
|
-
(l = t[c]) && (
|
|
8
|
-
return a &&
|
|
5
|
+
var g = Object.defineProperty, x = Object.getOwnPropertyDescriptor, n = (t, s, e, a) => {
|
|
6
|
+
for (var o = a > 1 ? void 0 : a ? x(s, e) : s, c = t.length - 1, l; c >= 0; c--)
|
|
7
|
+
(l = t[c]) && (o = (a ? l(s, e, o) : l(o)) || o);
|
|
8
|
+
return a && o && g(s, e, o), o;
|
|
9
9
|
};
|
|
10
|
-
let
|
|
10
|
+
let r = class extends d {
|
|
11
11
|
constructor() {
|
|
12
|
-
super(...arguments), this.call = null, this.status = "
|
|
12
|
+
super(...arguments), this.call = null, this.status = "new", this.callStartTime = null, this.duration = "0:00", this.subscriptions = [], this.durationInterval = null;
|
|
13
13
|
}
|
|
14
14
|
connectedCallback() {
|
|
15
15
|
super.connectedCallback(), this.subscribeToCall();
|
|
@@ -52,8 +52,10 @@ let n = class extends d {
|
|
|
52
52
|
}
|
|
53
53
|
getStatusText() {
|
|
54
54
|
switch (this.status) {
|
|
55
|
-
case "
|
|
56
|
-
return "
|
|
55
|
+
case "new":
|
|
56
|
+
return "Ready";
|
|
57
|
+
case "trying":
|
|
58
|
+
return "Trying...";
|
|
57
59
|
case "connecting":
|
|
58
60
|
return "Connecting...";
|
|
59
61
|
case "ringing":
|
|
@@ -64,8 +66,14 @@ let n = class extends d {
|
|
|
64
66
|
return "Disconnecting...";
|
|
65
67
|
case "disconnected":
|
|
66
68
|
return "Disconnected";
|
|
67
|
-
|
|
68
|
-
return "
|
|
69
|
+
case "failed":
|
|
70
|
+
return "Failed";
|
|
71
|
+
case "destroyed":
|
|
72
|
+
return "Ended";
|
|
73
|
+
default: {
|
|
74
|
+
const t = this.status;
|
|
75
|
+
return String(t);
|
|
76
|
+
}
|
|
69
77
|
}
|
|
70
78
|
}
|
|
71
79
|
render() {
|
|
@@ -86,7 +94,7 @@ let n = class extends d {
|
|
|
86
94
|
`;
|
|
87
95
|
}
|
|
88
96
|
};
|
|
89
|
-
|
|
97
|
+
r.styles = p`
|
|
90
98
|
:host {
|
|
91
99
|
/* CSS Custom Properties for theming */
|
|
92
100
|
--sw-color-primary: #044cf6;
|
|
@@ -140,7 +148,7 @@ n.styles = p`
|
|
|
140
148
|
flex-shrink: 0;
|
|
141
149
|
}
|
|
142
150
|
|
|
143
|
-
.status-indicator.
|
|
151
|
+
.status-indicator.new {
|
|
144
152
|
background-color: var(--sw-color-text-muted);
|
|
145
153
|
}
|
|
146
154
|
|
|
@@ -159,10 +167,20 @@ n.styles = p`
|
|
|
159
167
|
animation: pulse 1s ease-in-out infinite;
|
|
160
168
|
}
|
|
161
169
|
|
|
162
|
-
.status-indicator.
|
|
170
|
+
.status-indicator.trying {
|
|
171
|
+
background-color: var(--sw-color-warning);
|
|
172
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.status-indicator.disconnected,
|
|
176
|
+
.status-indicator.failed {
|
|
163
177
|
background-color: var(--sw-color-danger);
|
|
164
178
|
}
|
|
165
179
|
|
|
180
|
+
.status-indicator.destroyed {
|
|
181
|
+
background-color: var(--sw-color-text-muted);
|
|
182
|
+
}
|
|
183
|
+
|
|
166
184
|
@keyframes pulse {
|
|
167
185
|
0%,
|
|
168
186
|
100% {
|
|
@@ -180,6 +198,7 @@ n.styles = p`
|
|
|
180
198
|
white-space: nowrap;
|
|
181
199
|
}
|
|
182
200
|
|
|
201
|
+
.status-text.trying,
|
|
183
202
|
.status-text.connecting,
|
|
184
203
|
.status-text.ringing {
|
|
185
204
|
color: var(--sw-color-warning);
|
|
@@ -193,59 +212,43 @@ n.styles = p`
|
|
|
193
212
|
color: var(--sw-color-success);
|
|
194
213
|
}
|
|
195
214
|
|
|
196
|
-
.status-text.disconnected
|
|
215
|
+
.status-text.disconnected,
|
|
216
|
+
.status-text.failed {
|
|
197
217
|
color: var(--sw-color-danger);
|
|
198
218
|
}
|
|
199
219
|
|
|
220
|
+
.status-text.new,
|
|
221
|
+
.status-text.destroyed {
|
|
222
|
+
color: var(--sw-color-text-muted);
|
|
223
|
+
}
|
|
224
|
+
|
|
200
225
|
.duration {
|
|
201
226
|
font-variant-numeric: tabular-nums;
|
|
202
227
|
color: var(--sw-color-text-muted);
|
|
203
228
|
font-size: var(--sw-font-size-sm);
|
|
204
229
|
}
|
|
205
230
|
|
|
206
|
-
/* Animated dots for transient states */
|
|
207
|
-
.loading-dots::after {
|
|
208
|
-
content: '';
|
|
209
|
-
animation: dots 1.5s steps(4, end) infinite;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
@keyframes dots {
|
|
213
|
-
0%,
|
|
214
|
-
20% {
|
|
215
|
-
content: '';
|
|
216
|
-
}
|
|
217
|
-
40% {
|
|
218
|
-
content: '.';
|
|
219
|
-
}
|
|
220
|
-
60% {
|
|
221
|
-
content: '..';
|
|
222
|
-
}
|
|
223
|
-
80%,
|
|
224
|
-
100% {
|
|
225
|
-
content: '...';
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
231
|
`;
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
],
|
|
232
|
-
|
|
232
|
+
n([
|
|
233
|
+
h({ attribute: !1 })
|
|
234
|
+
], r.prototype, "call", 2);
|
|
235
|
+
n([
|
|
233
236
|
m({ context: w, subscribe: !0 }),
|
|
234
237
|
i()
|
|
235
|
-
],
|
|
236
|
-
|
|
238
|
+
], r.prototype, "contextCall", 2);
|
|
239
|
+
n([
|
|
237
240
|
i()
|
|
238
|
-
],
|
|
239
|
-
|
|
241
|
+
], r.prototype, "status", 2);
|
|
242
|
+
n([
|
|
240
243
|
i()
|
|
241
|
-
],
|
|
242
|
-
|
|
244
|
+
], r.prototype, "callStartTime", 2);
|
|
245
|
+
n([
|
|
243
246
|
i()
|
|
244
|
-
],
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
],
|
|
247
|
+
], r.prototype, "duration", 2);
|
|
248
|
+
r = n([
|
|
249
|
+
f("sw-call-status")
|
|
250
|
+
], r);
|
|
248
251
|
export {
|
|
249
|
-
|
|
252
|
+
r as CallStatusComponent
|
|
250
253
|
};
|
|
251
254
|
//# sourceMappingURL=call-status.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"call-status.js","sources":["../../src/components/call-status.ts"],"sourcesContent":["/**\n * Call Status Component\n *\n * Displays current call state with status text and duration timer.\n * Shows animated indicators for transient states (connecting, ringing, disconnecting).\n *\n * @example\n * ```html\n * <sw-call-status .call=${call}></sw-call-status>\n * ```\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 { Observable } from 'rxjs';\nimport { callContext } from '../context/index.js';\n\n/**\n * Call status types\n */\nexport type CallStatus =\n | 'new'\n | 'trying'\n | 'idle'\n | 'connecting'\n | 'ringing'\n | 'connected'\n | 'disconnecting'\n | 'disconnected'\n | 'failed'\n | 'destroyed';\n\n/**\n * Call interface for status component\n */\nexport interface CallStatusCall {\n status$: Observable<CallStatus>;\n}\n\n@customElement('sw-call-status')\nexport class CallStatusComponent extends LitElement {\n static styles = css`\n :host {\n /* CSS Custom Properties for theming */\n --sw-color-primary: #044cf6;\n --sw-color-success: #10b981;\n --sw-color-warning: #f59e0b;\n --sw-color-danger: #ef4444;\n --sw-color-text: #1f2937;\n --sw-color-text-muted: #6b7280;\n --sw-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n --sw-font-size-sm: 14px;\n --sw-font-size-base: 16px;\n --sw-font-size-lg: 18px;\n --sw-space-1: 4px;\n --sw-space-2: 8px;\n --sw-space-3: 12px;\n --sw-border-radius: 8px;\n\n display: inline-flex;\n align-items: center;\n gap: var(--sw-space-2);\n font-family: var(--sw-font-family);\n font-size: var(--sw-font-size-base);\n }\n\n /* Dark mode support */\n :host([data-theme='dark']) {\n --sw-color-text: #f9fafb;\n --sw-color-text-muted: #9ca3af;\n }\n\n @media (prefers-color-scheme: dark) {\n :host(:not([data-theme='light'])) {\n --sw-color-text: #f9fafb;\n --sw-color-text-muted: #9ca3af;\n }\n }\n\n .container {\n display: inline-flex;\n align-items: center;\n gap: var(--sw-space-2);\n padding: var(--sw-space-2) var(--sw-space-3);\n border-radius: var(--sw-border-radius);\n color: var(--sw-color-text);\n }\n\n .status-indicator {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n flex-shrink: 0;\n }\n\n .status-indicator.idle {\n background-color: var(--sw-color-text-muted);\n }\n\n .status-indicator.connecting,\n .status-indicator.ringing {\n background-color: var(--sw-color-warning);\n animation: pulse 1.5s ease-in-out infinite;\n }\n\n .status-indicator.connected {\n background-color: var(--sw-color-success);\n }\n\n .status-indicator.disconnecting {\n background-color: var(--sw-color-danger);\n animation: pulse 1s ease-in-out infinite;\n }\n\n .status-indicator.disconnected {\n background-color: var(--sw-color-danger);\n }\n\n @keyframes pulse {\n 0%,\n 100% {\n opacity: 1;\n transform: scale(1);\n }\n 50% {\n opacity: 0.5;\n transform: scale(1.1);\n }\n }\n\n .status-text {\n font-weight: 500;\n white-space: nowrap;\n }\n\n .status-text.connecting,\n .status-text.ringing {\n color: var(--sw-color-warning);\n }\n\n .status-text.disconnecting {\n color: var(--sw-color-danger);\n }\n\n .status-text.connected {\n color: var(--sw-color-success);\n }\n\n .status-text.disconnected {\n color: var(--sw-color-danger);\n }\n\n .duration {\n font-variant-numeric: tabular-nums;\n color: var(--sw-color-text-muted);\n font-size: var(--sw-font-size-sm);\n }\n\n /* Animated dots for transient states */\n .loading-dots::after {\n content: '';\n animation: dots 1.5s steps(4, end) infinite;\n }\n\n @keyframes dots {\n 0%,\n 20% {\n content: '';\n }\n 40% {\n content: '.';\n }\n 60% {\n content: '..';\n }\n 80%,\n 100% {\n content: '...';\n }\n }\n `;\n\n /**\n * Call object with status$ observable\n */\n @property({ attribute: false })\n call: CallStatusCall | null = null;\n\n /**\n * Call from context (if nested in sw-call-media)\n */\n @consume({ context: callContext, subscribe: true })\n @state()\n private contextCall?: CallStatusCall;\n\n /**\n * Current call status\n */\n @state()\n private status: CallStatus = 'idle';\n\n /**\n * Call start time for duration calculation\n */\n @state()\n private callStartTime: number | null = null;\n\n /**\n * Formatted duration string\n */\n @state()\n private duration: string = '0:00';\n\n /**\n * RxJS subscriptions for cleanup\n */\n private subscriptions: Subscription[] = [];\n\n /**\n * Duration timer interval\n */\n private durationInterval: number | null = null;\n\n connectedCallback() {\n super.connectedCallback();\n this.subscribeToCall();\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.cleanup();\n }\n\n updated(changedProperties: Map<string, unknown>) {\n if (changedProperties.has('call') || changedProperties.has('contextCall')) {\n this.cleanup();\n this.subscribeToCall();\n }\n }\n\n private get activeCall(): CallStatusCall | null {\n return this.call || this.contextCall || null;\n }\n\n private subscribeToCall() {\n const call = this.activeCall;\n if (!call?.status$) return;\n\n const statusSub = call.status$.subscribe((status) => {\n const prevStatus = this.status;\n this.status = status;\n\n // Start duration timer when connected\n if (status === 'connected' && prevStatus !== 'connected') {\n this.startDurationTimer();\n } else if (status !== 'connected') {\n this.stopDurationTimer();\n }\n });\n\n this.subscriptions.push(statusSub);\n }\n\n private startDurationTimer() {\n this.callStartTime = Date.now();\n this.duration = '0:00';\n\n this.durationInterval = window.setInterval(() => {\n if (this.callStartTime) {\n const elapsed = Math.floor((Date.now() - this.callStartTime) / 1000);\n this.duration = this.formatDuration(elapsed);\n }\n }, 1000);\n }\n\n private stopDurationTimer() {\n if (this.durationInterval) {\n clearInterval(this.durationInterval);\n this.durationInterval = null;\n }\n this.callStartTime = null;\n this.duration = '0:00';\n }\n\n private formatDuration(seconds: number): string {\n const hours = Math.floor(seconds / 3600);\n const minutes = Math.floor((seconds % 3600) / 60);\n const secs = seconds % 60;\n\n if (hours > 0) {\n return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;\n }\n return `${minutes}:${secs.toString().padStart(2, '0')}`;\n }\n\n private cleanup() {\n this.subscriptions.forEach((sub) => sub.unsubscribe());\n this.subscriptions = [];\n this.stopDurationTimer();\n }\n\n private getStatusText(): string {\n switch (this.status) {\n case 'idle':\n return 'Idle';\n case 'connecting':\n return 'Connecting...';\n case 'ringing':\n return 'Ringing...';\n case 'connected':\n return 'Connected';\n case 'disconnecting':\n return 'Disconnecting...';\n case 'disconnected':\n return 'Disconnected';\n default:\n return 'Unknown';\n }\n }\n\n render() {\n const statusText = this.getStatusText();\n const showDuration = this.status === 'connected';\n\n return html`\n <div class=\"container\" part=\"container\">\n <span class=\"status-indicator ${this.status}\" aria-hidden=\"true\"></span>\n <span\n class=\"status-text ${this.status}\"\n part=\"status-text\"\n role=\"status\"\n aria-live=\"polite\"\n >\n ${statusText}\n </span>\n ${showDuration\n ? html`<span class=\"duration\" part=\"duration\">${this.duration}</span>`\n : null}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sw-call-status': CallStatusComponent;\n }\n}\n"],"names":["CallStatusComponent","LitElement","changedProperties","call","statusSub","status","prevStatus","elapsed","seconds","hours","minutes","secs","sub","statusText","showDuration","html","css","__decorateClass","property","consume","callContext","state","customElement"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"call-status.js","sources":["../../src/components/call-status.ts"],"sourcesContent":["/**\n * Call Status Component\n *\n * Displays current call state with status text and duration timer.\n * Shows animated indicators for transient states (connecting, ringing, disconnecting).\n *\n * @example\n * ```html\n * <sw-call-status .call=${call}></sw-call-status>\n * ```\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 { Observable } from 'rxjs';\nimport type { CallStatus } from '@signalwire/js';\nimport { callContext } from '../context/index.js';\n\n/**\n * Call interface for status component\n */\nexport interface CallStatusCall {\n status$: Observable<CallStatus>;\n}\n\n@customElement('sw-call-status')\nexport class CallStatusComponent extends LitElement {\n static styles = css`\n :host {\n /* CSS Custom Properties for theming */\n --sw-color-primary: #044cf6;\n --sw-color-success: #10b981;\n --sw-color-warning: #f59e0b;\n --sw-color-danger: #ef4444;\n --sw-color-text: #1f2937;\n --sw-color-text-muted: #6b7280;\n --sw-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n --sw-font-size-sm: 14px;\n --sw-font-size-base: 16px;\n --sw-font-size-lg: 18px;\n --sw-space-1: 4px;\n --sw-space-2: 8px;\n --sw-space-3: 12px;\n --sw-border-radius: 8px;\n\n display: inline-flex;\n align-items: center;\n gap: var(--sw-space-2);\n font-family: var(--sw-font-family);\n font-size: var(--sw-font-size-base);\n }\n\n /* Dark mode support */\n :host([data-theme='dark']) {\n --sw-color-text: #f9fafb;\n --sw-color-text-muted: #9ca3af;\n }\n\n @media (prefers-color-scheme: dark) {\n :host(:not([data-theme='light'])) {\n --sw-color-text: #f9fafb;\n --sw-color-text-muted: #9ca3af;\n }\n }\n\n .container {\n display: inline-flex;\n align-items: center;\n gap: var(--sw-space-2);\n padding: var(--sw-space-2) var(--sw-space-3);\n border-radius: var(--sw-border-radius);\n color: var(--sw-color-text);\n }\n\n .status-indicator {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n flex-shrink: 0;\n }\n\n .status-indicator.new {\n background-color: var(--sw-color-text-muted);\n }\n\n .status-indicator.connecting,\n .status-indicator.ringing {\n background-color: var(--sw-color-warning);\n animation: pulse 1.5s ease-in-out infinite;\n }\n\n .status-indicator.connected {\n background-color: var(--sw-color-success);\n }\n\n .status-indicator.disconnecting {\n background-color: var(--sw-color-danger);\n animation: pulse 1s ease-in-out infinite;\n }\n\n .status-indicator.trying {\n background-color: var(--sw-color-warning);\n animation: pulse 1.5s ease-in-out infinite;\n }\n\n .status-indicator.disconnected,\n .status-indicator.failed {\n background-color: var(--sw-color-danger);\n }\n\n .status-indicator.destroyed {\n background-color: var(--sw-color-text-muted);\n }\n\n @keyframes pulse {\n 0%,\n 100% {\n opacity: 1;\n transform: scale(1);\n }\n 50% {\n opacity: 0.5;\n transform: scale(1.1);\n }\n }\n\n .status-text {\n font-weight: 500;\n white-space: nowrap;\n }\n\n .status-text.trying,\n .status-text.connecting,\n .status-text.ringing {\n color: var(--sw-color-warning);\n }\n\n .status-text.disconnecting {\n color: var(--sw-color-danger);\n }\n\n .status-text.connected {\n color: var(--sw-color-success);\n }\n\n .status-text.disconnected,\n .status-text.failed {\n color: var(--sw-color-danger);\n }\n\n .status-text.new,\n .status-text.destroyed {\n color: var(--sw-color-text-muted);\n }\n\n .duration {\n font-variant-numeric: tabular-nums;\n color: var(--sw-color-text-muted);\n font-size: var(--sw-font-size-sm);\n }\n\n `;\n\n /**\n * Call object with status$ observable\n */\n @property({ attribute: false })\n call: CallStatusCall | null = null;\n\n /**\n * Call from context (if nested in sw-call-media)\n */\n @consume({ context: callContext, subscribe: true })\n @state()\n private contextCall?: CallStatusCall;\n\n /**\n * Current call status\n */\n @state()\n private status: CallStatus = 'new';\n\n /**\n * Call start time for duration calculation\n */\n @state()\n private callStartTime: number | null = null;\n\n /**\n * Formatted duration string\n */\n @state()\n private duration: string = '0:00';\n\n /**\n * RxJS subscriptions for cleanup\n */\n private subscriptions: Subscription[] = [];\n\n /**\n * Duration timer interval\n */\n private durationInterval: number | null = null;\n\n connectedCallback() {\n super.connectedCallback();\n this.subscribeToCall();\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.cleanup();\n }\n\n updated(changedProperties: Map<string, unknown>) {\n if (changedProperties.has('call') || changedProperties.has('contextCall')) {\n this.cleanup();\n this.subscribeToCall();\n }\n }\n\n private get activeCall(): CallStatusCall | null {\n return this.call || this.contextCall || null;\n }\n\n private subscribeToCall() {\n const call = this.activeCall;\n if (!call?.status$) return;\n\n const statusSub = call.status$.subscribe((status) => {\n const prevStatus = this.status;\n this.status = status;\n\n // Start duration timer when connected\n if (status === 'connected' && prevStatus !== 'connected') {\n this.startDurationTimer();\n } else if (status !== 'connected') {\n this.stopDurationTimer();\n }\n });\n\n this.subscriptions.push(statusSub);\n }\n\n private startDurationTimer() {\n this.callStartTime = Date.now();\n this.duration = '0:00';\n\n this.durationInterval = window.setInterval(() => {\n if (this.callStartTime) {\n const elapsed = Math.floor((Date.now() - this.callStartTime) / 1000);\n this.duration = this.formatDuration(elapsed);\n }\n }, 1000);\n }\n\n private stopDurationTimer() {\n if (this.durationInterval) {\n clearInterval(this.durationInterval);\n this.durationInterval = null;\n }\n this.callStartTime = null;\n this.duration = '0:00';\n }\n\n private formatDuration(seconds: number): string {\n const hours = Math.floor(seconds / 3600);\n const minutes = Math.floor((seconds % 3600) / 60);\n const secs = seconds % 60;\n\n if (hours > 0) {\n return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;\n }\n return `${minutes}:${secs.toString().padStart(2, '0')}`;\n }\n\n private cleanup() {\n this.subscriptions.forEach((sub) => sub.unsubscribe());\n this.subscriptions = [];\n this.stopDurationTimer();\n }\n\n private getStatusText(): string {\n switch (this.status) {\n case 'new':\n return 'Ready';\n case 'trying':\n return 'Trying...';\n case 'connecting':\n return 'Connecting...';\n case 'ringing':\n return 'Ringing...';\n case 'connected':\n return 'Connected';\n case 'disconnecting':\n return 'Disconnecting...';\n case 'disconnected':\n return 'Disconnected';\n case 'failed':\n return 'Failed';\n case 'destroyed':\n return 'Ended';\n default: {\n const _exhaustive: never = this.status;\n return String(_exhaustive);\n }\n }\n }\n\n render() {\n const statusText = this.getStatusText();\n const showDuration = this.status === 'connected';\n\n return html`\n <div class=\"container\" part=\"container\">\n <span class=\"status-indicator ${this.status}\" aria-hidden=\"true\"></span>\n <span\n class=\"status-text ${this.status}\"\n part=\"status-text\"\n role=\"status\"\n aria-live=\"polite\"\n >\n ${statusText}\n </span>\n ${showDuration\n ? html`<span class=\"duration\" part=\"duration\">${this.duration}</span>`\n : null}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sw-call-status': CallStatusComponent;\n }\n}\n"],"names":["CallStatusComponent","LitElement","changedProperties","call","statusSub","status","prevStatus","elapsed","seconds","hours","minutes","secs","sub","_exhaustive","statusText","showDuration","html","css","__decorateClass","property","consume","callContext","state","customElement"],"mappings":"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"}
|
|
@@ -11,34 +11,8 @@
|
|
|
11
11
|
* ```
|
|
12
12
|
*/
|
|
13
13
|
import { LitElement } from 'lit';
|
|
14
|
-
import type {
|
|
14
|
+
import type { DeviceController } from '@signalwire/js';
|
|
15
15
|
import './audio-level.js';
|
|
16
|
-
/**
|
|
17
|
-
* DeviceController interface for observing device lists
|
|
18
|
-
*/
|
|
19
|
-
export interface DeviceController {
|
|
20
|
-
readonly audioInputDevices$: Observable<MediaDeviceInfo[]>;
|
|
21
|
-
readonly audioOutputDevices$: Observable<MediaDeviceInfo[]>;
|
|
22
|
-
readonly videoInputDevices$: Observable<MediaDeviceInfo[]>;
|
|
23
|
-
readonly selectedAudioInputDevice$: Observable<MediaDeviceInfo | null>;
|
|
24
|
-
readonly selectedAudioOutputDevice$: Observable<MediaDeviceInfo | null>;
|
|
25
|
-
readonly selectedVideoInputDevice$: Observable<MediaDeviceInfo | null>;
|
|
26
|
-
readonly selectedAudioInputDevice: MediaDeviceInfo | null;
|
|
27
|
-
readonly selectedAudioOutputDevice: MediaDeviceInfo | null;
|
|
28
|
-
readonly selectedVideoInputDevice: MediaDeviceInfo | null;
|
|
29
|
-
readonly audioInputDevices: MediaDeviceInfo[];
|
|
30
|
-
readonly audioOutputDevices: MediaDeviceInfo[];
|
|
31
|
-
readonly videoInputDevices: MediaDeviceInfo[];
|
|
32
|
-
readonly selectedAudioInputDeviceConstraints: MediaTrackConstraints;
|
|
33
|
-
readonly selectedVideoInputDeviceConstraints: MediaTrackConstraints;
|
|
34
|
-
MediaDeviceInfoToConstraints(MediaDeviceInfo: MediaDeviceInfo | null): MediaTrackConstraints;
|
|
35
|
-
selectAudioInputDevice(device: MediaDeviceInfo | null): void;
|
|
36
|
-
selectVideoInputDevice(device: MediaDeviceInfo | null): void;
|
|
37
|
-
selectAudioOutputDevice(device: MediaDeviceInfo | null): void;
|
|
38
|
-
enableDeviceMonitoring(): void;
|
|
39
|
-
disableDeviceMonitoring(): void;
|
|
40
|
-
getLocalStream?: () => Promise<MediaStream>;
|
|
41
|
-
}
|
|
42
16
|
export declare class DeviceSelector extends LitElement {
|
|
43
17
|
static styles: import("lit").CSSResult;
|
|
44
18
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device-selector.d.ts","sourceRoot":"","sources":["../../src/components/device-selector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAGrD,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"device-selector.d.ts","sourceRoot":"","sources":["../../src/components/device-selector.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device-selector.js","sources":["../../src/components/device-selector.ts"],"sourcesContent":["/**\n * Device Selector Component\n *\n * Dropdown-based selector for choosing audio/video devices. Displays device lists from\n * DeviceController observables with labeled dropdowns for microphone, camera, and speaker.\n * Includes inline preview for video (camera) and audio level (microphone) using browser API.\n *\n * @example\n * ```html\n * <sw-device-selector .deviceController=${deviceController} show-preview></sw-device-selector>\n * ```\n */\n\nimport { LitElement, html, css, nothing } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { Subscription } from 'rxjs';\nimport type { Observable } from 'rxjs';\nimport './audio-level.js';\n\n/**\n * DeviceController interface for observing device lists\n */\nexport interface DeviceController {\n // Observable getters for device lists\n readonly audioInputDevices$: Observable<MediaDeviceInfo[]>;\n readonly audioOutputDevices$: Observable<MediaDeviceInfo[]>;\n readonly videoInputDevices$: Observable<MediaDeviceInfo[]>;\n\n // Observable getters for selected devices\n readonly selectedAudioInputDevice$: Observable<MediaDeviceInfo | null>;\n readonly selectedAudioOutputDevice$: Observable<MediaDeviceInfo | null>;\n readonly selectedVideoInputDevice$: Observable<MediaDeviceInfo | null>;\n\n // Current value getters\n readonly selectedAudioInputDevice: MediaDeviceInfo | null;\n readonly selectedAudioOutputDevice: MediaDeviceInfo | null;\n readonly selectedVideoInputDevice: MediaDeviceInfo | null;\n\n readonly audioInputDevices: MediaDeviceInfo[];\n readonly audioOutputDevices: MediaDeviceInfo[];\n readonly videoInputDevices: MediaDeviceInfo[];\n\n // Device constraints\n readonly selectedAudioInputDeviceConstraints: MediaTrackConstraints;\n readonly selectedVideoInputDeviceConstraints: MediaTrackConstraints;\n\n // Methods\n MediaDeviceInfoToConstraints(MediaDeviceInfo: MediaDeviceInfo | null): MediaTrackConstraints;\n selectAudioInputDevice(device: MediaDeviceInfo | null): void;\n selectVideoInputDevice(device: MediaDeviceInfo | null): void;\n selectAudioOutputDevice(device: MediaDeviceInfo | null): void;\n enableDeviceMonitoring(): void;\n disableDeviceMonitoring(): void;\n\n getLocalStream?: () => Promise<MediaStream>;\n}\n\ntype DeviceType = 'microphone' | 'camera' | 'speaker';\n\n@customElement('sw-device-selector')\nexport class DeviceSelector extends LitElement {\n static styles = css`\n :host {\n /* CSS Custom Properties for theming */\n --sw-color-primary: #044cf6;\n --sw-color-primary-hover: #0339c4;\n --sw-color-background: #1a1a1a;\n --sw-color-surface: #2a2a2a;\n --sw-color-surface-hover: #3a3a3a;\n --sw-color-text: #ffffff;\n --sw-color-text-muted: #a0a0a0;\n --sw-color-border: #404040;\n --sw-color-success: #10b981;\n --sw-border-radius: 8px;\n --sw-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n --sw-space-1: 4px;\n --sw-space-2: 8px;\n --sw-space-3: 12px;\n --sw-space-4: 16px;\n --sw-space-6: 24px;\n\n display: block;\n font-family: var(--sw-font-family);\n color: var(--sw-color-text);\n }\n\n .container {\n background: var(--sw-color-background);\n border-radius: var(--sw-border-radius);\n border: 1px solid var(--sw-color-border);\n overflow: hidden;\n min-width: 300px;\n padding: var(--sw-space-4);\n }\n\n .device-section {\n margin-bottom: var(--sw-space-4);\n }\n\n .device-section:last-of-type {\n margin-bottom: 0;\n }\n\n .device-label {\n display: flex;\n align-items: center;\n gap: var(--sw-space-2);\n font-size: 14px;\n font-weight: 500;\n color: var(--sw-color-text);\n margin-bottom: var(--sw-space-2);\n }\n\n .device-icon {\n width: 18px;\n height: 18px;\n flex-shrink: 0;\n color: var(--sw-color-text-muted);\n }\n\n .device-select-wrapper {\n position: relative;\n }\n\n .device-select {\n width: 100%;\n padding: var(--sw-space-3) var(--sw-space-4);\n padding-right: 36px;\n background: var(--sw-color-surface);\n border: 1px solid var(--sw-color-border);\n border-radius: calc(var(--sw-border-radius) - 4px);\n color: var(--sw-color-text);\n font-size: 14px;\n font-family: var(--sw-font-family);\n cursor: pointer;\n appearance: none;\n -webkit-appearance: none;\n -moz-appearance: none;\n transition:\n border-color 0.2s ease,\n background-color 0.2s ease;\n }\n\n .device-select:hover {\n background: var(--sw-color-surface-hover);\n }\n\n .device-select:focus {\n outline: none;\n border-color: var(--sw-color-primary);\n box-shadow: 0 0 0 2px rgba(4, 76, 246, 0.2);\n }\n\n .device-select:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .device-select option {\n background: var(--sw-color-surface);\n color: var(--sw-color-text);\n padding: var(--sw-space-2);\n }\n\n .select-arrow {\n position: absolute;\n right: var(--sw-space-3);\n top: 50%;\n transform: translateY(-50%);\n width: 16px;\n height: 16px;\n color: var(--sw-color-text-muted);\n pointer-events: none;\n }\n\n .no-devices {\n padding: var(--sw-space-3) var(--sw-space-4);\n background: var(--sw-color-surface);\n border: 1px solid var(--sw-color-border);\n border-radius: calc(var(--sw-border-radius) - 4px);\n color: var(--sw-color-text-muted);\n font-size: 14px;\n }\n\n .device-preview {\n margin-top: var(--sw-space-3);\n }\n\n .video-preview {\n width: 100%;\n aspect-ratio: 16/9;\n background: #000;\n border-radius: calc(var(--sw-border-radius) - 4px);\n overflow: hidden;\n }\n\n .video-preview video {\n width: 100%;\n height: 100%;\n object-fit: cover;\n transform: scaleX(-1);\n }\n\n .video-preview-placeholder {\n width: 100%;\n aspect-ratio: 16/9;\n background: var(--sw-color-surface);\n border-radius: calc(var(--sw-border-radius) - 4px);\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--sw-color-text-muted);\n font-size: 14px;\n }\n\n .audio-preview {\n display: flex;\n align-items: center;\n gap: var(--sw-space-3);\n padding: var(--sw-space-3);\n background: var(--sw-color-surface);\n border-radius: calc(var(--sw-border-radius) - 4px);\n }\n\n .audio-preview-label {\n font-size: 12px;\n color: var(--sw-color-text-muted);\n flex-shrink: 0;\n }\n\n .audio-level-wrapper {\n flex: 1;\n display: flex;\n justify-content: center;\n }\n\n .test-speaker-btn {\n padding: var(--sw-space-2) var(--sw-space-4);\n background: var(--sw-color-primary);\n color: white;\n border: none;\n border-radius: calc(var(--sw-border-radius) - 4px);\n font-size: 14px;\n cursor: pointer;\n transition: background-color 0.2s ease;\n }\n\n .test-speaker-btn:hover {\n background: var(--sw-color-primary-hover);\n }\n\n .test-speaker-btn:focus {\n outline: none;\n box-shadow:\n 0 0 0 2px var(--sw-color-background),\n 0 0 0 4px var(--sw-color-primary);\n }\n\n .test-speaker-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n /* Scrollbar styling for select on some browsers */\n .device-select::-webkit-scrollbar {\n width: 8px;\n }\n\n .device-select::-webkit-scrollbar-track {\n background: var(--sw-color-background);\n }\n\n .device-select::-webkit-scrollbar-thumb {\n background: var(--sw-color-border);\n border-radius: 4px;\n }\n\n .device-select::-webkit-scrollbar-thumb:hover {\n background: var(--sw-color-text-muted);\n }\n `;\n\n /**\n * Device controller with observables for device lists\n */\n @property({ type: Object }) deviceController?: DeviceController;\n\n /**\n * Whether to show the preview panel\n */\n @property({ type: Boolean, attribute: 'show-preview' }) showPreview = false;\n\n /**\n * Audio input devices\n */\n @state() private _audioInputDevices: MediaDeviceInfo[] = [];\n\n /**\n * Video input devices\n */\n @state() private _videoInputDevices: MediaDeviceInfo[] = [];\n\n /**\n * Audio output devices\n */\n @state() private _audioOutputDevices: MediaDeviceInfo[] = [];\n\n /**\n * Selected audio input device\n */\n @state() private _selectedAudioInput: MediaDeviceInfo | null = null;\n\n /**\n * Selected video input device\n */\n @state() private _selectedVideoInput: MediaDeviceInfo | null = null;\n\n /**\n * Selected audio output device\n */\n @state() private _selectedAudioOutput: MediaDeviceInfo | null = null;\n\n /**\n * Video preview stream from camera\n */\n @state() private _videoPreviewStream: MediaStream | null = null;\n\n /**\n * Audio preview stream from microphone\n */\n @state() private _audioPreviewStream: MediaStream | null = null;\n\n /**\n * Is test audio playing\n */\n @state() private _isTestingAudio = false;\n\n /**\n * Whether the component is currently visible (combines all visibility factors)\n */\n private _isComponentVisible = false;\n\n /**\n * Whether the component is in the viewport (IntersectionObserver)\n */\n private _isInViewport = false;\n\n /**\n * Whether the document/tab is visible\n */\n private _isDocumentVisible = true;\n\n /**\n * Whether the component is CSS-visible (display, visibility, opacity)\n */\n private _isCSSVisible = true;\n\n /**\n * Video element reference\n */\n private _videoElement?: HTMLVideoElement;\n\n /**\n * RxJS subscriptions for cleanup\n */\n private subscriptions: Subscription[] = [];\n\n /**\n * Audio element for speaker test\n */\n private _testAudioElement?: HTMLAudioElement;\n\n /**\n * IntersectionObserver for viewport visibility\n */\n private _intersectionObserver?: IntersectionObserver;\n\n /**\n * Bound handler for document visibility change\n */\n private _boundHandleDocumentVisibility = this.handleDocumentVisibilityChange.bind(this);\n\n /**\n * Interval ID for CSS visibility polling\n */\n private _cssVisibilityCheckInterval?: ReturnType<typeof setInterval>;\n\n /**\n * Flag to prevent concurrent preview initialization\n */\n private _isInitializingPreviews = false;\n\n /**\n * Interval in milliseconds for checking CSS visibility changes\n */\n private static readonly CSS_VISIBILITY_CHECK_INTERVAL_MS = 200;\n\n /**\n * Lifecycle: Component connected to DOM\n */\n connectedCallback() {\n super.connectedCallback();\n this.setupSubscriptions();\n this.setupVisibilityObservers();\n }\n\n /**\n * Lifecycle: React to property changes\n */\n protected updated(changedProperties: Map<string, unknown>): void {\n super.updated(changedProperties);\n if (changedProperties.has('deviceController')) {\n this.cleanupSubscriptions();\n this.setupSubscriptions();\n }\n if (changedProperties.has('showPreview')) {\n if (this.showPreview && this._isComponentVisible) {\n // Only initialize if component is visible\n this.initializePreviews();\n } else if (!this.showPreview) {\n // Always cleanup when showPreview is disabled\n this.cleanupVideoPreviewStream();\n this.cleanupAudioPreviewStream();\n }\n }\n }\n\n /**\n * Lifecycle: Component disconnected from DOM\n */\n disconnectedCallback() {\n super.disconnectedCallback();\n this.cleanupSubscriptions();\n this.cleanupVisibilityObservers();\n this.cleanupVideoPreviewStream();\n this.cleanupAudioPreviewStream();\n this.stopTestAudio();\n }\n\n /**\n * Setup all visibility observers (IntersectionObserver, document visibility, CSS polling)\n */\n private setupVisibilityObservers(): void {\n // Defensive cleanup in case of multiple calls without disconnect\n this.cleanupVisibilityObservers();\n\n // Setup IntersectionObserver for viewport visibility\n this._intersectionObserver = new IntersectionObserver(\n (entries) => {\n const entry = entries[0];\n if (entry) {\n this._isInViewport = entry.isIntersecting;\n this.updateVisibilityState();\n }\n },\n { threshold: 0 }\n );\n this._intersectionObserver.observe(this);\n\n // Setup document visibility change listener\n this._isDocumentVisible = document.visibilityState === 'visible';\n document.addEventListener('visibilitychange', this._boundHandleDocumentVisibility);\n\n // Setup CSS visibility polling (checks opacity, display, visibility)\n this._isCSSVisible = this.checkCSSVisibility();\n this._cssVisibilityCheckInterval = setInterval(() => {\n const wasVisible = this._isCSSVisible;\n this._isCSSVisible = this.checkCSSVisibility();\n if (wasVisible !== this._isCSSVisible) {\n this.updateVisibilityState();\n }\n }, DeviceSelector.CSS_VISIBILITY_CHECK_INTERVAL_MS);\n }\n\n /**\n * Cleanup all visibility observers\n */\n private cleanupVisibilityObservers(): void {\n if (this._intersectionObserver) {\n this._intersectionObserver.disconnect();\n this._intersectionObserver = undefined;\n }\n\n document.removeEventListener('visibilitychange', this._boundHandleDocumentVisibility);\n\n if (this._cssVisibilityCheckInterval) {\n clearInterval(this._cssVisibilityCheckInterval);\n this._cssVisibilityCheckInterval = undefined;\n }\n }\n\n /**\n * Handle document visibility change (tab switching)\n */\n private handleDocumentVisibilityChange(): void {\n this._isDocumentVisible = document.visibilityState === 'visible';\n this.updateVisibilityState();\n }\n\n /**\n * Check if the component is visible via CSS (display, visibility, opacity)\n * Traverses the DOM tree including shadow DOM boundaries\n */\n private checkCSSVisibility(): boolean {\n // Guard against being called when disconnected\n if (!this.isConnected) {\n return false;\n }\n\n let element: HTMLElement | null = this;\n while (element) {\n const style = getComputedStyle(element);\n if (\n style.display === 'none' ||\n style.visibility === 'hidden' ||\n parseFloat(style.opacity) === 0\n ) {\n return false;\n }\n\n // Handle shadow DOM boundaries by traversing through shadow hosts\n if (element.parentElement) {\n element = element.parentElement;\n } else {\n const root = element.getRootNode();\n element = root instanceof ShadowRoot ? (root.host as HTMLElement) : null;\n }\n }\n return true;\n }\n\n /**\n * Update the combined visibility state and manage preview streams accordingly\n */\n private updateVisibilityState(): void {\n const wasVisible = this._isComponentVisible;\n this._isComponentVisible = this._isInViewport && this._isDocumentVisible && this._isCSSVisible;\n\n // Only react if visibility actually changed\n if (wasVisible === this._isComponentVisible) {\n return;\n }\n\n if (this._isComponentVisible && this.showPreview) {\n // Component became visible - start previews\n this.initializePreviews();\n } else if (!this._isComponentVisible) {\n // Component became invisible - stop previews\n this.cleanupVideoPreviewStream();\n this.cleanupAudioPreviewStream();\n }\n }\n\n /**\n * Subscribe to device controller observables\n */\n private setupSubscriptions(): void {\n if (!this.deviceController) return;\n\n // Subscribe to audio input devices\n this.subscriptions.push(\n this.deviceController.audioInputDevices$.subscribe((devices) => {\n this._audioInputDevices = devices;\n })\n );\n\n // Subscribe to video input devices\n this.subscriptions.push(\n this.deviceController.videoInputDevices$.subscribe((devices) => {\n this._videoInputDevices = devices;\n })\n );\n\n // Subscribe to audio output devices\n this.subscriptions.push(\n this.deviceController.audioOutputDevices$.subscribe((devices) => {\n this._audioOutputDevices = devices;\n })\n );\n\n // Subscribe to selected devices if available\n if (this.deviceController.selectedAudioInputDevice$) {\n this.subscriptions.push(\n this.deviceController.selectedAudioInputDevice$.subscribe((device) => {\n this._selectedAudioInput = device;\n })\n );\n }\n\n if (this.deviceController.selectedVideoInputDevice$) {\n this.subscriptions.push(\n this.deviceController.selectedVideoInputDevice$.subscribe((device) => {\n this._selectedVideoInput = device;\n })\n );\n }\n\n if (this.deviceController.selectedAudioOutputDevice$) {\n this.subscriptions.push(\n this.deviceController.selectedAudioOutputDevice$.subscribe((device) => {\n this._selectedAudioOutput = device;\n })\n );\n }\n }\n\n /**\n * Cleanup all subscriptions\n */\n private cleanupSubscriptions(): void {\n this.subscriptions.forEach((sub) => sub.unsubscribe());\n this.subscriptions = [];\n }\n\n /**\n * Cleanup video preview stream\n * Best practice from SDK: Pause video, clear srcObject, remove tracks from stream, then stop tracks\n * @see https://webrtchacks.com/srcobject-intervention/\n */\n private cleanupVideoPreviewStream(): void {\n // Step 1: Pause and clear the video element\n if (this._videoElement) {\n this._videoElement.pause();\n this._videoElement.srcObject = null;\n this._videoElement = undefined;\n }\n\n // Also check for any video element in shadow DOM (in case _videoElement wasn't set)\n const videoElement = this.shadowRoot?.querySelector('video');\n if (videoElement) {\n videoElement.pause();\n videoElement.srcObject = null;\n }\n\n // Step 2: Remove tracks from stream before stopping (SDK pattern)\n if (this._videoPreviewStream) {\n const tracks = this._videoPreviewStream.getTracks();\n tracks.forEach((track: MediaStreamTrack) => {\n // Remove track from stream first (important for proper release)\n this._videoPreviewStream?.removeTrack(track);\n track.stop();\n });\n this._videoPreviewStream = null;\n }\n }\n\n /**\n * Cleanup audio preview stream\n * Best practice from SDK: Release audio components, remove tracks from stream, then stop tracks\n * @see https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack/stop\n */\n private cleanupAudioPreviewStream(): void {\n // Step 1: Release resources from all audio-level components synchronously\n // This closes their AudioContext and disconnects MediaStreamAudioSourceNode\n const audioLevelComponents = this.shadowRoot?.querySelectorAll('sw-audio-level');\n audioLevelComponents?.forEach((component) => {\n const audioLevel = component as HTMLElement & {\n releaseResources?: () => void;\n stream?: MediaStream;\n };\n if (audioLevel.releaseResources) {\n audioLevel.releaseResources();\n } else {\n // Fallback for older versions\n audioLevel.stream = undefined;\n }\n });\n\n // Step 2: Remove tracks from stream before stopping (SDK pattern)\n if (this._audioPreviewStream) {\n const tracks = this._audioPreviewStream.getTracks();\n tracks.forEach((track: MediaStreamTrack) => {\n // Remove track from stream first (important for proper release)\n this._audioPreviewStream?.removeTrack(track);\n track.stop();\n });\n this._audioPreviewStream = null;\n }\n }\n\n /**\n * Get video preview stream from browser API\n */\n private async getVideoPreviewStream(deviceId?: string): Promise<void> {\n this.cleanupVideoPreviewStream();\n\n if (!this.showPreview) return;\n\n try {\n const constraints: MediaStreamConstraints = {\n video: deviceId ? { deviceId: { exact: deviceId } } : true,\n audio: false\n };\n this._videoPreviewStream = await navigator.mediaDevices.getUserMedia(constraints);\n this.updateVideoElement();\n } catch (error) {\n console.warn('Failed to get video preview stream:', error);\n this._videoPreviewStream = null;\n }\n }\n\n /**\n * Get audio preview stream from browser API\n */\n private async getAudioPreviewStream(deviceId?: string): Promise<void> {\n this.cleanupAudioPreviewStream();\n\n if (!this.showPreview) return;\n\n try {\n const constraints: MediaStreamConstraints = {\n video: false,\n audio: deviceId ? { deviceId: { exact: deviceId } } : true\n };\n this._audioPreviewStream = await navigator.mediaDevices.getUserMedia(constraints);\n } catch (error) {\n console.warn('Failed to get audio preview stream:', error);\n this._audioPreviewStream = null;\n }\n }\n\n /**\n * Update video element with current stream\n */\n private updateVideoElement(): void {\n if (this._videoElement && this._videoPreviewStream) {\n this._videoElement.srcObject = this._videoPreviewStream;\n }\n }\n\n /**\n * Initialize previews when show-preview is enabled\n */\n private async initializePreviews(): Promise<void> {\n // Prevent concurrent initialization to avoid stream leaks on rapid visibility toggles\n if (!this.showPreview || this._isInitializingPreviews) return;\n\n this._isInitializingPreviews = true;\n try {\n // Get video preview if we have video devices\n if (this._videoInputDevices.length > 0) {\n const deviceId = this._selectedVideoInput?.deviceId;\n await this.getVideoPreviewStream(deviceId);\n }\n\n // Get audio preview if we have audio devices\n if (this._audioInputDevices.length > 0) {\n const deviceId = this._selectedAudioInput?.deviceId;\n await this.getAudioPreviewStream(deviceId);\n }\n } finally {\n this._isInitializingPreviews = false;\n }\n }\n\n /**\n * Handle device selection change from dropdown\n */\n private handleDeviceChange(event: Event, deviceType: DeviceType): void {\n if (!this.deviceController) return;\n\n const select = event.target as HTMLSelectElement;\n const deviceId = select.value;\n\n let device: MediaDeviceInfo | null = null;\n\n switch (deviceType) {\n case 'microphone':\n device = this._audioInputDevices.find((d) => d.deviceId === deviceId) || null;\n this._selectedAudioInput = device;\n this.deviceController.selectAudioInputDevice?.(device);\n // Update audio preview stream with new device\n if (this.showPreview) {\n this.getAudioPreviewStream(deviceId);\n }\n break;\n case 'camera':\n device = this._videoInputDevices.find((d) => d.deviceId === deviceId) || null;\n this._selectedVideoInput = device;\n this.deviceController.selectVideoInputDevice?.(device);\n // Update video preview stream with new device\n if (this.showPreview) {\n this.getVideoPreviewStream(deviceId);\n }\n break;\n case 'speaker':\n device = this._audioOutputDevices.find((d) => d.deviceId === deviceId) || null;\n this._selectedAudioOutput = device;\n this.deviceController.selectAudioOutputDevice?.(device);\n break;\n }\n\n if (device) {\n this.dispatchEvent(\n new CustomEvent('sw-device-change', {\n detail: { device, deviceType },\n bubbles: true,\n composed: true\n })\n );\n }\n }\n\n /**\n * Test speaker by playing a test tone\n */\n private async testSpeaker(): Promise<void> {\n if (this._isTestingAudio) {\n this.stopTestAudio();\n return;\n }\n\n try {\n this._isTestingAudio = true;\n\n // Create oscillator-based test tone\n const audioContext = new AudioContext();\n const oscillator = audioContext.createOscillator();\n const gainNode = audioContext.createGain();\n\n oscillator.type = 'sine';\n oscillator.frequency.value = 440; // A4 note\n gainNode.gain.value = 0.3;\n\n oscillator.connect(gainNode);\n gainNode.connect(audioContext.destination);\n\n oscillator.start();\n\n // Stop after 1 second\n setTimeout(() => {\n oscillator.stop();\n audioContext.close();\n this._isTestingAudio = false;\n }, 1000);\n\n this.dispatchEvent(\n new CustomEvent('sw-test-speaker', {\n bubbles: true,\n composed: true\n })\n );\n } catch (error) {\n console.error('Failed to play test audio:', error);\n this._isTestingAudio = false;\n }\n }\n\n /**\n * Stop test audio\n */\n private stopTestAudio(): void {\n if (this._testAudioElement) {\n this._testAudioElement.pause();\n this._testAudioElement = undefined;\n }\n this._isTestingAudio = false;\n }\n\n /**\n * Render device icon\n */\n private renderDeviceIcon(deviceType: DeviceType) {\n switch (deviceType) {\n case 'microphone':\n return html`<svg class=\"device-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.91-3c-.49 0-.9.36-.98.85C16.52 14.2 14.47 16 12 16s-4.52-1.8-4.93-4.15c-.08-.49-.49-.85-.98-.85-.61 0-1.09.54-1 1.14.49 3 2.89 5.35 5.91 5.78V20c0 .55.45 1 1 1s1-.45 1-1v-2.08c3.02-.43 5.42-2.78 5.91-5.78.1-.6-.39-1.14-1-1.14z\"\n />\n </svg>`;\n case 'camera':\n return html`<svg class=\"device-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z\"\n />\n </svg>`;\n case 'speaker':\n return html`<svg class=\"device-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z\"\n />\n </svg>`;\n }\n }\n\n /**\n * Render dropdown arrow icon\n */\n private renderSelectArrow() {\n return html`<svg class=\"select-arrow\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M7 10l5 5 5-5z\" />\n </svg>`;\n }\n\n /**\n * Render a device selection section\n */\n private renderDeviceSection(\n deviceType: DeviceType,\n label: string,\n devices: MediaDeviceInfo[],\n selectedDevice: MediaDeviceInfo | null\n ) {\n return html`\n <div class=\"device-section\" part=\"device-section\">\n <label class=\"device-label\" for=\"select-${deviceType}\">\n ${this.renderDeviceIcon(deviceType)}\n <span>${label}</span>\n </label>\n ${devices.length === 0\n ? html`<div class=\"no-devices\">No ${label.toLowerCase()} found</div>`\n : html`\n <div class=\"device-select-wrapper\">\n <select\n id=\"select-${deviceType}\"\n class=\"device-select\"\n part=\"device-select\"\n aria-label=\"${label}\"\n .value=${selectedDevice?.deviceId || ''}\n @change=${(e: Event) => this.handleDeviceChange(e, deviceType)}\n >\n ${devices.map(\n (device) => html`\n <option\n value=\"${device.deviceId}\"\n ?selected=${selectedDevice?.deviceId === device.deviceId}\n >\n ${device.label || `Device ${device.deviceId.slice(0, 8)}`}\n </option>\n `\n )}\n </select>\n ${this.renderSelectArrow()}\n </div>\n `}\n </div>\n `;\n }\n\n /**\n * Render video preview for camera section\n */\n private renderVideoPreview() {\n if (!this.showPreview || this._videoInputDevices.length === 0) {\n return nothing;\n }\n\n return html`\n <div class=\"device-preview\">\n ${this._videoPreviewStream\n ? html`\n <div class=\"video-preview\">\n <video\n autoplay\n playsinline\n muted\n .srcObject=${this._videoPreviewStream}\n @loadedmetadata=${(e: Event) => {\n const video = e.target as HTMLVideoElement;\n this._videoElement = video;\n video.play().catch(() => {});\n }}\n ></video>\n </div>\n `\n : html` <div class=\"video-preview-placeholder\">Click to enable camera preview</div> `}\n </div>\n `;\n }\n\n /**\n * Render audio level preview for microphone section\n */\n private renderAudioPreview() {\n if (!this.showPreview || this._audioInputDevices.length === 0) {\n return nothing;\n }\n\n return html`\n <div class=\"device-preview\">\n <div class=\"audio-preview\">\n <span class=\"audio-preview-label\">Level:</span>\n <div class=\"audio-level-wrapper\">\n ${this._audioPreviewStream\n ? html`\n <sw-audio-level\n .stream=${this._audioPreviewStream}\n bars=\"10\"\n orientation=\"horizontal\"\n maxSize=\"20\"\n ></sw-audio-level>\n `\n : html`\n <span style=\"color: var(--sw-color-text-muted); font-size: 12px;\"\n >No audio input</span\n >\n `}\n </div>\n </div>\n </div>\n `;\n }\n\n /**\n * Render the component\n */\n render() {\n return html`\n <div class=\"container\" part=\"container\">\n ${this.renderDeviceSection(\n 'microphone',\n 'Microphone',\n this._audioInputDevices,\n this._selectedAudioInput\n )}\n ${this.renderAudioPreview()}\n ${this.renderDeviceSection(\n 'camera',\n 'Camera',\n this._videoInputDevices,\n this._selectedVideoInput\n )}\n ${this.renderVideoPreview()}\n ${this.renderDeviceSection(\n 'speaker',\n 'Speaker',\n this._audioOutputDevices,\n this._selectedAudioOutput\n )}\n\n <div class=\"device-section\">\n <button\n class=\"test-speaker-btn\"\n @click=${this.testSpeaker}\n ?disabled=${this._isTestingAudio}\n aria-label=\"Test speaker\"\n >\n ${this._isTestingAudio ? 'Playing...' : 'Test Speaker'}\n </button>\n </div>\n </div>\n `;\n }\n}\n\n/**\n * Declare global type for TypeScript\n */\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sw-device-selector': DeviceSelector;\n }\n}\n"],"names":["DeviceSelector","LitElement","changedProperties","entries","entry","wasVisible","element","style","root","devices","device","sub","videoElement","_a","track","audioLevelComponents","component","audioLevel","deviceId","constraints","error","_b","event","deviceType","d","_d","_c","_f","_e","audioContext","oscillator","gainNode","html","label","selectedDevice","e","nothing","video","css","__decorateClass","property","state","customElement"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"device-selector.js","sources":["../../src/components/device-selector.ts"],"sourcesContent":["/**\n * Device Selector Component\n *\n * Dropdown-based selector for choosing audio/video devices. Displays device lists from\n * DeviceController observables with labeled dropdowns for microphone, camera, and speaker.\n * Includes inline preview for video (camera) and audio level (microphone) using browser API.\n *\n * @example\n * ```html\n * <sw-device-selector .deviceController=${deviceController} show-preview></sw-device-selector>\n * ```\n */\n\nimport { LitElement, html, css, nothing } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { Subscription } from 'rxjs';\nimport type { DeviceController } from '@signalwire/js';\nimport './audio-level.js';\n\ntype DeviceType = 'microphone' | 'camera' | 'speaker';\n\n@customElement('sw-device-selector')\nexport class DeviceSelector extends LitElement {\n static styles = css`\n :host {\n /* CSS Custom Properties for theming */\n --sw-color-primary: #044cf6;\n --sw-color-primary-hover: #0339c4;\n --sw-color-background: #1a1a1a;\n --sw-color-surface: #2a2a2a;\n --sw-color-surface-hover: #3a3a3a;\n --sw-color-text: #ffffff;\n --sw-color-text-muted: #a0a0a0;\n --sw-color-border: #404040;\n --sw-color-success: #10b981;\n --sw-border-radius: 8px;\n --sw-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n --sw-space-1: 4px;\n --sw-space-2: 8px;\n --sw-space-3: 12px;\n --sw-space-4: 16px;\n --sw-space-6: 24px;\n\n display: block;\n font-family: var(--sw-font-family);\n color: var(--sw-color-text);\n }\n\n .container {\n background: var(--sw-color-background);\n border-radius: var(--sw-border-radius);\n border: 1px solid var(--sw-color-border);\n overflow: hidden;\n min-width: 300px;\n padding: var(--sw-space-4);\n }\n\n .device-section {\n margin-bottom: var(--sw-space-4);\n }\n\n .device-section:last-of-type {\n margin-bottom: 0;\n }\n\n .device-label {\n display: flex;\n align-items: center;\n gap: var(--sw-space-2);\n font-size: 14px;\n font-weight: 500;\n color: var(--sw-color-text);\n margin-bottom: var(--sw-space-2);\n }\n\n .device-icon {\n width: 18px;\n height: 18px;\n flex-shrink: 0;\n color: var(--sw-color-text-muted);\n }\n\n .device-select-wrapper {\n position: relative;\n }\n\n .device-select {\n width: 100%;\n padding: var(--sw-space-3) var(--sw-space-4);\n padding-right: 36px;\n background: var(--sw-color-surface);\n border: 1px solid var(--sw-color-border);\n border-radius: calc(var(--sw-border-radius) - 4px);\n color: var(--sw-color-text);\n font-size: 14px;\n font-family: var(--sw-font-family);\n cursor: pointer;\n appearance: none;\n -webkit-appearance: none;\n -moz-appearance: none;\n transition:\n border-color 0.2s ease,\n background-color 0.2s ease;\n }\n\n .device-select:hover {\n background: var(--sw-color-surface-hover);\n }\n\n .device-select:focus {\n outline: none;\n border-color: var(--sw-color-primary);\n box-shadow: 0 0 0 2px rgba(4, 76, 246, 0.2);\n }\n\n .device-select:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .device-select option {\n background: var(--sw-color-surface);\n color: var(--sw-color-text);\n padding: var(--sw-space-2);\n }\n\n .select-arrow {\n position: absolute;\n right: var(--sw-space-3);\n top: 50%;\n transform: translateY(-50%);\n width: 16px;\n height: 16px;\n color: var(--sw-color-text-muted);\n pointer-events: none;\n }\n\n .no-devices {\n padding: var(--sw-space-3) var(--sw-space-4);\n background: var(--sw-color-surface);\n border: 1px solid var(--sw-color-border);\n border-radius: calc(var(--sw-border-radius) - 4px);\n color: var(--sw-color-text-muted);\n font-size: 14px;\n }\n\n .device-preview {\n margin-top: var(--sw-space-3);\n }\n\n .video-preview {\n width: 100%;\n aspect-ratio: 16/9;\n background: #000;\n border-radius: calc(var(--sw-border-radius) - 4px);\n overflow: hidden;\n }\n\n .video-preview video {\n width: 100%;\n height: 100%;\n object-fit: cover;\n transform: scaleX(-1);\n }\n\n .video-preview-placeholder {\n width: 100%;\n aspect-ratio: 16/9;\n background: var(--sw-color-surface);\n border-radius: calc(var(--sw-border-radius) - 4px);\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--sw-color-text-muted);\n font-size: 14px;\n }\n\n .audio-preview {\n display: flex;\n align-items: center;\n gap: var(--sw-space-3);\n padding: var(--sw-space-3);\n background: var(--sw-color-surface);\n border-radius: calc(var(--sw-border-radius) - 4px);\n }\n\n .audio-preview-label {\n font-size: 12px;\n color: var(--sw-color-text-muted);\n flex-shrink: 0;\n }\n\n .audio-level-wrapper {\n flex: 1;\n display: flex;\n justify-content: center;\n }\n\n .test-speaker-btn {\n padding: var(--sw-space-2) var(--sw-space-4);\n background: var(--sw-color-primary);\n color: white;\n border: none;\n border-radius: calc(var(--sw-border-radius) - 4px);\n font-size: 14px;\n cursor: pointer;\n transition: background-color 0.2s ease;\n }\n\n .test-speaker-btn:hover {\n background: var(--sw-color-primary-hover);\n }\n\n .test-speaker-btn:focus {\n outline: none;\n box-shadow:\n 0 0 0 2px var(--sw-color-background),\n 0 0 0 4px var(--sw-color-primary);\n }\n\n .test-speaker-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n /* Scrollbar styling for select on some browsers */\n .device-select::-webkit-scrollbar {\n width: 8px;\n }\n\n .device-select::-webkit-scrollbar-track {\n background: var(--sw-color-background);\n }\n\n .device-select::-webkit-scrollbar-thumb {\n background: var(--sw-color-border);\n border-radius: 4px;\n }\n\n .device-select::-webkit-scrollbar-thumb:hover {\n background: var(--sw-color-text-muted);\n }\n `;\n\n /**\n * Device controller with observables for device lists\n */\n @property({ type: Object }) deviceController?: DeviceController;\n\n /**\n * Whether to show the preview panel\n */\n @property({ type: Boolean, attribute: 'show-preview' }) showPreview = false;\n\n /**\n * Audio input devices\n */\n @state() private _audioInputDevices: MediaDeviceInfo[] = [];\n\n /**\n * Video input devices\n */\n @state() private _videoInputDevices: MediaDeviceInfo[] = [];\n\n /**\n * Audio output devices\n */\n @state() private _audioOutputDevices: MediaDeviceInfo[] = [];\n\n /**\n * Selected audio input device\n */\n @state() private _selectedAudioInput: MediaDeviceInfo | null = null;\n\n /**\n * Selected video input device\n */\n @state() private _selectedVideoInput: MediaDeviceInfo | null = null;\n\n /**\n * Selected audio output device\n */\n @state() private _selectedAudioOutput: MediaDeviceInfo | null = null;\n\n /**\n * Video preview stream from camera\n */\n @state() private _videoPreviewStream: MediaStream | null = null;\n\n /**\n * Audio preview stream from microphone\n */\n @state() private _audioPreviewStream: MediaStream | null = null;\n\n /**\n * Is test audio playing\n */\n @state() private _isTestingAudio = false;\n\n /**\n * Whether the component is currently visible (combines all visibility factors)\n */\n private _isComponentVisible = false;\n\n /**\n * Whether the component is in the viewport (IntersectionObserver)\n */\n private _isInViewport = false;\n\n /**\n * Whether the document/tab is visible\n */\n private _isDocumentVisible = true;\n\n /**\n * Whether the component is CSS-visible (display, visibility, opacity)\n */\n private _isCSSVisible = true;\n\n /**\n * Video element reference\n */\n private _videoElement?: HTMLVideoElement;\n\n /**\n * RxJS subscriptions for cleanup\n */\n private subscriptions: Subscription[] = [];\n\n /**\n * Audio element for speaker test\n */\n private _testAudioElement?: HTMLAudioElement;\n\n /**\n * IntersectionObserver for viewport visibility\n */\n private _intersectionObserver?: IntersectionObserver;\n\n /**\n * Bound handler for document visibility change\n */\n private _boundHandleDocumentVisibility = this.handleDocumentVisibilityChange.bind(this);\n\n /**\n * Interval ID for CSS visibility polling\n */\n private _cssVisibilityCheckInterval?: ReturnType<typeof setInterval>;\n\n /**\n * Flag to prevent concurrent preview initialization\n */\n private _isInitializingPreviews = false;\n\n /**\n * Interval in milliseconds for checking CSS visibility changes\n */\n private static readonly CSS_VISIBILITY_CHECK_INTERVAL_MS = 200;\n\n /**\n * Lifecycle: Component connected to DOM\n */\n connectedCallback() {\n super.connectedCallback();\n this.setupSubscriptions();\n this.setupVisibilityObservers();\n }\n\n /**\n * Lifecycle: React to property changes\n */\n protected updated(changedProperties: Map<string, unknown>): void {\n super.updated(changedProperties);\n if (changedProperties.has('deviceController')) {\n this.cleanupSubscriptions();\n this.setupSubscriptions();\n }\n if (changedProperties.has('showPreview')) {\n if (this.showPreview && this._isComponentVisible) {\n // Only initialize if component is visible\n this.initializePreviews();\n } else if (!this.showPreview) {\n // Always cleanup when showPreview is disabled\n this.cleanupVideoPreviewStream();\n this.cleanupAudioPreviewStream();\n }\n }\n }\n\n /**\n * Lifecycle: Component disconnected from DOM\n */\n disconnectedCallback() {\n super.disconnectedCallback();\n this.cleanupSubscriptions();\n this.cleanupVisibilityObservers();\n this.cleanupVideoPreviewStream();\n this.cleanupAudioPreviewStream();\n this.stopTestAudio();\n }\n\n /**\n * Setup all visibility observers (IntersectionObserver, document visibility, CSS polling)\n */\n private setupVisibilityObservers(): void {\n // Defensive cleanup in case of multiple calls without disconnect\n this.cleanupVisibilityObservers();\n\n // Setup IntersectionObserver for viewport visibility\n this._intersectionObserver = new IntersectionObserver(\n (entries) => {\n const entry = entries[0];\n if (entry) {\n this._isInViewport = entry.isIntersecting;\n this.updateVisibilityState();\n }\n },\n { threshold: 0 }\n );\n this._intersectionObserver.observe(this);\n\n // Setup document visibility change listener\n this._isDocumentVisible = document.visibilityState === 'visible';\n document.addEventListener('visibilitychange', this._boundHandleDocumentVisibility);\n\n // Setup CSS visibility polling (checks opacity, display, visibility)\n this._isCSSVisible = this.checkCSSVisibility();\n this._cssVisibilityCheckInterval = setInterval(() => {\n const wasVisible = this._isCSSVisible;\n this._isCSSVisible = this.checkCSSVisibility();\n if (wasVisible !== this._isCSSVisible) {\n this.updateVisibilityState();\n }\n }, DeviceSelector.CSS_VISIBILITY_CHECK_INTERVAL_MS);\n }\n\n /**\n * Cleanup all visibility observers\n */\n private cleanupVisibilityObservers(): void {\n if (this._intersectionObserver) {\n this._intersectionObserver.disconnect();\n this._intersectionObserver = undefined;\n }\n\n document.removeEventListener('visibilitychange', this._boundHandleDocumentVisibility);\n\n if (this._cssVisibilityCheckInterval) {\n clearInterval(this._cssVisibilityCheckInterval);\n this._cssVisibilityCheckInterval = undefined;\n }\n }\n\n /**\n * Handle document visibility change (tab switching)\n */\n private handleDocumentVisibilityChange(): void {\n this._isDocumentVisible = document.visibilityState === 'visible';\n this.updateVisibilityState();\n }\n\n /**\n * Check if the component is visible via CSS (display, visibility, opacity)\n * Traverses the DOM tree including shadow DOM boundaries\n */\n private checkCSSVisibility(): boolean {\n // Guard against being called when disconnected\n if (!this.isConnected) {\n return false;\n }\n\n let element: HTMLElement | null = this;\n while (element) {\n const style = getComputedStyle(element);\n if (\n style.display === 'none' ||\n style.visibility === 'hidden' ||\n parseFloat(style.opacity) === 0\n ) {\n return false;\n }\n\n // Handle shadow DOM boundaries by traversing through shadow hosts\n if (element.parentElement) {\n element = element.parentElement;\n } else {\n const root = element.getRootNode();\n element = root instanceof ShadowRoot ? (root.host as HTMLElement) : null;\n }\n }\n return true;\n }\n\n /**\n * Update the combined visibility state and manage preview streams accordingly\n */\n private updateVisibilityState(): void {\n const wasVisible = this._isComponentVisible;\n this._isComponentVisible = this._isInViewport && this._isDocumentVisible && this._isCSSVisible;\n\n // Only react if visibility actually changed\n if (wasVisible === this._isComponentVisible) {\n return;\n }\n\n if (this._isComponentVisible && this.showPreview) {\n // Component became visible - start previews\n this.initializePreviews();\n } else if (!this._isComponentVisible) {\n // Component became invisible - stop previews\n this.cleanupVideoPreviewStream();\n this.cleanupAudioPreviewStream();\n }\n }\n\n /**\n * Subscribe to device controller observables\n */\n private setupSubscriptions(): void {\n if (!this.deviceController) return;\n\n // Subscribe to audio input devices\n this.subscriptions.push(\n this.deviceController.audioInputDevices$.subscribe((devices) => {\n this._audioInputDevices = devices;\n })\n );\n\n // Subscribe to video input devices\n this.subscriptions.push(\n this.deviceController.videoInputDevices$.subscribe((devices) => {\n this._videoInputDevices = devices;\n })\n );\n\n // Subscribe to audio output devices\n this.subscriptions.push(\n this.deviceController.audioOutputDevices$.subscribe((devices) => {\n this._audioOutputDevices = devices;\n })\n );\n\n // Subscribe to selected devices if available\n if (this.deviceController.selectedAudioInputDevice$) {\n this.subscriptions.push(\n this.deviceController.selectedAudioInputDevice$.subscribe((device) => {\n this._selectedAudioInput = device;\n })\n );\n }\n\n if (this.deviceController.selectedVideoInputDevice$) {\n this.subscriptions.push(\n this.deviceController.selectedVideoInputDevice$.subscribe((device) => {\n this._selectedVideoInput = device;\n })\n );\n }\n\n if (this.deviceController.selectedAudioOutputDevice$) {\n this.subscriptions.push(\n this.deviceController.selectedAudioOutputDevice$.subscribe((device) => {\n this._selectedAudioOutput = device;\n })\n );\n }\n }\n\n /**\n * Cleanup all subscriptions\n */\n private cleanupSubscriptions(): void {\n this.subscriptions.forEach((sub) => sub.unsubscribe());\n this.subscriptions = [];\n }\n\n /**\n * Cleanup video preview stream\n * Best practice from SDK: Pause video, clear srcObject, remove tracks from stream, then stop tracks\n * @see https://webrtchacks.com/srcobject-intervention/\n */\n private cleanupVideoPreviewStream(): void {\n // Step 1: Pause and clear the video element\n if (this._videoElement) {\n this._videoElement.pause();\n this._videoElement.srcObject = null;\n this._videoElement = undefined;\n }\n\n // Also check for any video element in shadow DOM (in case _videoElement wasn't set)\n const videoElement = this.shadowRoot?.querySelector('video');\n if (videoElement) {\n videoElement.pause();\n videoElement.srcObject = null;\n }\n\n // Step 2: Remove tracks from stream before stopping (SDK pattern)\n if (this._videoPreviewStream) {\n const tracks = this._videoPreviewStream.getTracks();\n tracks.forEach((track: MediaStreamTrack) => {\n // Remove track from stream first (important for proper release)\n this._videoPreviewStream?.removeTrack(track);\n track.stop();\n });\n this._videoPreviewStream = null;\n }\n }\n\n /**\n * Cleanup audio preview stream\n * Best practice from SDK: Release audio components, remove tracks from stream, then stop tracks\n * @see https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack/stop\n */\n private cleanupAudioPreviewStream(): void {\n // Step 1: Release resources from all audio-level components synchronously\n // This closes their AudioContext and disconnects MediaStreamAudioSourceNode\n const audioLevelComponents = this.shadowRoot?.querySelectorAll('sw-audio-level');\n audioLevelComponents?.forEach((component) => {\n const audioLevel = component as HTMLElement & {\n releaseResources?: () => void;\n stream?: MediaStream;\n };\n if (audioLevel.releaseResources) {\n audioLevel.releaseResources();\n } else {\n // Fallback for older versions\n audioLevel.stream = undefined;\n }\n });\n\n // Step 2: Remove tracks from stream before stopping (SDK pattern)\n if (this._audioPreviewStream) {\n const tracks = this._audioPreviewStream.getTracks();\n tracks.forEach((track: MediaStreamTrack) => {\n // Remove track from stream first (important for proper release)\n this._audioPreviewStream?.removeTrack(track);\n track.stop();\n });\n this._audioPreviewStream = null;\n }\n }\n\n /**\n * Get video preview stream from browser API\n */\n private async getVideoPreviewStream(deviceId?: string): Promise<void> {\n this.cleanupVideoPreviewStream();\n\n if (!this.showPreview) return;\n\n try {\n const constraints: MediaStreamConstraints = {\n video: deviceId ? { deviceId: { exact: deviceId } } : true,\n audio: false\n };\n this._videoPreviewStream = await navigator.mediaDevices.getUserMedia(constraints);\n this.updateVideoElement();\n } catch (error) {\n console.warn('Failed to get video preview stream:', error);\n this._videoPreviewStream = null;\n }\n }\n\n /**\n * Get audio preview stream from browser API\n */\n private async getAudioPreviewStream(deviceId?: string): Promise<void> {\n this.cleanupAudioPreviewStream();\n\n if (!this.showPreview) return;\n\n try {\n const constraints: MediaStreamConstraints = {\n video: false,\n audio: deviceId ? { deviceId: { exact: deviceId } } : true\n };\n this._audioPreviewStream = await navigator.mediaDevices.getUserMedia(constraints);\n } catch (error) {\n console.warn('Failed to get audio preview stream:', error);\n this._audioPreviewStream = null;\n }\n }\n\n /**\n * Update video element with current stream\n */\n private updateVideoElement(): void {\n if (this._videoElement && this._videoPreviewStream) {\n this._videoElement.srcObject = this._videoPreviewStream;\n }\n }\n\n /**\n * Initialize previews when show-preview is enabled\n */\n private async initializePreviews(): Promise<void> {\n // Prevent concurrent initialization to avoid stream leaks on rapid visibility toggles\n if (!this.showPreview || this._isInitializingPreviews) return;\n\n this._isInitializingPreviews = true;\n try {\n // Get video preview if we have video devices\n if (this._videoInputDevices.length > 0) {\n const deviceId = this._selectedVideoInput?.deviceId;\n await this.getVideoPreviewStream(deviceId);\n }\n\n // Get audio preview if we have audio devices\n if (this._audioInputDevices.length > 0) {\n const deviceId = this._selectedAudioInput?.deviceId;\n await this.getAudioPreviewStream(deviceId);\n }\n } finally {\n this._isInitializingPreviews = false;\n }\n }\n\n /**\n * Handle device selection change from dropdown\n */\n private handleDeviceChange(event: Event, deviceType: DeviceType): void {\n if (!this.deviceController) return;\n\n const select = event.target as HTMLSelectElement;\n const deviceId = select.value;\n\n let device: MediaDeviceInfo | null = null;\n\n switch (deviceType) {\n case 'microphone':\n device = this._audioInputDevices.find((d) => d.deviceId === deviceId) || null;\n this._selectedAudioInput = device;\n this.deviceController.selectAudioInputDevice?.(device);\n // Update audio preview stream with new device\n if (this.showPreview) {\n this.getAudioPreviewStream(deviceId);\n }\n break;\n case 'camera':\n device = this._videoInputDevices.find((d) => d.deviceId === deviceId) || null;\n this._selectedVideoInput = device;\n this.deviceController.selectVideoInputDevice?.(device);\n // Update video preview stream with new device\n if (this.showPreview) {\n this.getVideoPreviewStream(deviceId);\n }\n break;\n case 'speaker':\n device = this._audioOutputDevices.find((d) => d.deviceId === deviceId) || null;\n this._selectedAudioOutput = device;\n this.deviceController.selectAudioOutputDevice?.(device);\n break;\n }\n\n if (device) {\n this.dispatchEvent(\n new CustomEvent('sw-device-change', {\n detail: { device, deviceType },\n bubbles: true,\n composed: true\n })\n );\n }\n }\n\n /**\n * Test speaker by playing a test tone\n */\n private async testSpeaker(): Promise<void> {\n if (this._isTestingAudio) {\n this.stopTestAudio();\n return;\n }\n\n try {\n this._isTestingAudio = true;\n\n // Create oscillator-based test tone\n const audioContext = new AudioContext();\n const oscillator = audioContext.createOscillator();\n const gainNode = audioContext.createGain();\n\n oscillator.type = 'sine';\n oscillator.frequency.value = 440; // A4 note\n gainNode.gain.value = 0.3;\n\n oscillator.connect(gainNode);\n gainNode.connect(audioContext.destination);\n\n oscillator.start();\n\n // Stop after 1 second\n setTimeout(() => {\n oscillator.stop();\n audioContext.close();\n this._isTestingAudio = false;\n }, 1000);\n\n this.dispatchEvent(\n new CustomEvent('sw-test-speaker', {\n bubbles: true,\n composed: true\n })\n );\n } catch (error) {\n console.error('Failed to play test audio:', error);\n this._isTestingAudio = false;\n }\n }\n\n /**\n * Stop test audio\n */\n private stopTestAudio(): void {\n if (this._testAudioElement) {\n this._testAudioElement.pause();\n this._testAudioElement = undefined;\n }\n this._isTestingAudio = false;\n }\n\n /**\n * Render device icon\n */\n private renderDeviceIcon(deviceType: DeviceType) {\n switch (deviceType) {\n case 'microphone':\n return html`<svg class=\"device-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.91-3c-.49 0-.9.36-.98.85C16.52 14.2 14.47 16 12 16s-4.52-1.8-4.93-4.15c-.08-.49-.49-.85-.98-.85-.61 0-1.09.54-1 1.14.49 3 2.89 5.35 5.91 5.78V20c0 .55.45 1 1 1s1-.45 1-1v-2.08c3.02-.43 5.42-2.78 5.91-5.78.1-.6-.39-1.14-1-1.14z\"\n />\n </svg>`;\n case 'camera':\n return html`<svg class=\"device-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z\"\n />\n </svg>`;\n case 'speaker':\n return html`<svg class=\"device-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z\"\n />\n </svg>`;\n }\n }\n\n /**\n * Render dropdown arrow icon\n */\n private renderSelectArrow() {\n return html`<svg class=\"select-arrow\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M7 10l5 5 5-5z\" />\n </svg>`;\n }\n\n /**\n * Render a device selection section\n */\n private renderDeviceSection(\n deviceType: DeviceType,\n label: string,\n devices: MediaDeviceInfo[],\n selectedDevice: MediaDeviceInfo | null\n ) {\n return html`\n <div class=\"device-section\" part=\"device-section\">\n <label class=\"device-label\" for=\"select-${deviceType}\">\n ${this.renderDeviceIcon(deviceType)}\n <span>${label}</span>\n </label>\n ${devices.length === 0\n ? html`<div class=\"no-devices\">No ${label.toLowerCase()} found</div>`\n : html`\n <div class=\"device-select-wrapper\">\n <select\n id=\"select-${deviceType}\"\n class=\"device-select\"\n part=\"device-select\"\n aria-label=\"${label}\"\n .value=${selectedDevice?.deviceId || ''}\n @change=${(e: Event) => this.handleDeviceChange(e, deviceType)}\n >\n ${devices.map(\n (device) => html`\n <option\n value=\"${device.deviceId}\"\n ?selected=${selectedDevice?.deviceId === device.deviceId}\n >\n ${device.label || `Device ${device.deviceId.slice(0, 8)}`}\n </option>\n `\n )}\n </select>\n ${this.renderSelectArrow()}\n </div>\n `}\n </div>\n `;\n }\n\n /**\n * Render video preview for camera section\n */\n private renderVideoPreview() {\n if (!this.showPreview || this._videoInputDevices.length === 0) {\n return nothing;\n }\n\n return html`\n <div class=\"device-preview\">\n ${this._videoPreviewStream\n ? html`\n <div class=\"video-preview\">\n <video\n autoplay\n playsinline\n muted\n .srcObject=${this._videoPreviewStream}\n @loadedmetadata=${(e: Event) => {\n const video = e.target as HTMLVideoElement;\n this._videoElement = video;\n video.play().catch(() => {});\n }}\n ></video>\n </div>\n `\n : html` <div class=\"video-preview-placeholder\">Click to enable camera preview</div> `}\n </div>\n `;\n }\n\n /**\n * Render audio level preview for microphone section\n */\n private renderAudioPreview() {\n if (!this.showPreview || this._audioInputDevices.length === 0) {\n return nothing;\n }\n\n return html`\n <div class=\"device-preview\">\n <div class=\"audio-preview\">\n <span class=\"audio-preview-label\">Level:</span>\n <div class=\"audio-level-wrapper\">\n ${this._audioPreviewStream\n ? html`\n <sw-audio-level\n .stream=${this._audioPreviewStream}\n bars=\"10\"\n orientation=\"horizontal\"\n maxSize=\"20\"\n ></sw-audio-level>\n `\n : html`\n <span style=\"color: var(--sw-color-text-muted); font-size: 12px;\"\n >No audio input</span\n >\n `}\n </div>\n </div>\n </div>\n `;\n }\n\n /**\n * Render the component\n */\n render() {\n return html`\n <div class=\"container\" part=\"container\">\n ${this.renderDeviceSection(\n 'microphone',\n 'Microphone',\n this._audioInputDevices,\n this._selectedAudioInput\n )}\n ${this.renderAudioPreview()}\n ${this.renderDeviceSection(\n 'camera',\n 'Camera',\n this._videoInputDevices,\n this._selectedVideoInput\n )}\n ${this.renderVideoPreview()}\n ${this.renderDeviceSection(\n 'speaker',\n 'Speaker',\n this._audioOutputDevices,\n this._selectedAudioOutput\n )}\n\n <div class=\"device-section\">\n <button\n class=\"test-speaker-btn\"\n @click=${this.testSpeaker}\n ?disabled=${this._isTestingAudio}\n aria-label=\"Test speaker\"\n >\n ${this._isTestingAudio ? 'Playing...' : 'Test Speaker'}\n </button>\n </div>\n </div>\n `;\n }\n}\n\n/**\n * Declare global type for TypeScript\n */\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sw-device-selector': DeviceSelector;\n }\n}\n"],"names":["DeviceSelector","LitElement","changedProperties","entries","entry","wasVisible","element","style","root","devices","device","sub","videoElement","_a","track","audioLevelComponents","component","audioLevel","deviceId","constraints","error","_b","event","deviceType","d","_d","_c","_f","_e","audioContext","oscillator","gainNode","html","label","selectedDevice","e","nothing","video","css","__decorateClass","property","state","customElement"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"self-media.d.ts","sourceRoot":"","sources":["../../src/components/self-media.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAI5C,OAAO,KAAK,EAAE,IAAI,EAAe,MAAM,mBAAmB,CAAC;AAI3D,qBACa,SAAU,SAAQ,UAAU;IACvC,MAAM,CAAC,MAAM,0BAYX;IAEF;;OAEG;IAC0B,MAAM,UAAS;IAE5C;;OAEG;IAGH,OAAO,CAAC,KAAK,CAAC,CAAO;IAErB;;OAEG;IACH,IACI,IAAI,CAAC,KAAK,EAAE,IAAI,GAAG,SAAS,EAI/B;IACD,IAAI,IAAI,IAAI,IAAI,GAAG,SAAS,CAE3B;IAED;;OAEG;IACH,OAAO,CAAC,iBAAiB,CAA4B;IAErD;;OAEG;IACH,OAAO,CAAC,kBAAkB,CAAqB;IAE/C;;OAEG;IACH,OAAO,CAAC,aAAa,CAAsB;IAE3C;;OAEG;IACH,iBAAiB;IAKjB;;OAEG;IACH,SAAS,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAUhE;;OAEG;IACH,oBAAoB;IAKpB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAsB1B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAK5B;;OAEG;IACH,OAAO,CAAC,YAAY;IAMpB;;OAEG;IACH,MAAM;
|
|
1
|
+
{"version":3,"file":"self-media.d.ts","sourceRoot":"","sources":["../../src/components/self-media.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAI5C,OAAO,KAAK,EAAE,IAAI,EAAe,MAAM,mBAAmB,CAAC;AAI3D,qBACa,SAAU,SAAQ,UAAU;IACvC,MAAM,CAAC,MAAM,0BAYX;IAEF;;OAEG;IAC0B,MAAM,UAAS;IAE5C;;OAEG;IAGH,OAAO,CAAC,KAAK,CAAC,CAAO;IAErB;;OAEG;IACH,IACI,IAAI,CAAC,KAAK,EAAE,IAAI,GAAG,SAAS,EAI/B;IACD,IAAI,IAAI,IAAI,IAAI,GAAG,SAAS,CAE3B;IAED;;OAEG;IACH,OAAO,CAAC,iBAAiB,CAA4B;IAErD;;OAEG;IACH,OAAO,CAAC,kBAAkB,CAAqB;IAE/C;;OAEG;IACH,OAAO,CAAC,aAAa,CAAsB;IAE3C;;OAEG;IACH,iBAAiB;IAKjB;;OAEG;IACH,SAAS,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAUhE;;OAEG;IACH,oBAAoB;IAKpB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAsB1B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAK5B;;OAEG;IACH,OAAO,CAAC,YAAY;IAMpB;;OAEG;IACH,MAAM;CAyCP;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,eAAe,EAAE,SAAS,CAAC;KAC5B;CACF"}
|
|
@@ -2,13 +2,13 @@ import { LitElement as u, html as p, css as h } from "lit";
|
|
|
2
2
|
import { property as n, customElement as b } from "lit/decorators.js";
|
|
3
3
|
import { consume as d } from "@lit/context";
|
|
4
4
|
import { getSelfId as f } from "../types/index.js";
|
|
5
|
-
import { callContext as
|
|
6
|
-
var
|
|
7
|
-
for (var
|
|
8
|
-
(c = t[
|
|
9
|
-
return
|
|
5
|
+
import { callContext as m } from "../context/call-context.js";
|
|
6
|
+
var y = Object.defineProperty, _ = Object.getOwnPropertyDescriptor, a = (t, s, r, i) => {
|
|
7
|
+
for (var e = i > 1 ? void 0 : i ? _(s, r) : s, o = t.length - 1, c; o >= 0; o--)
|
|
8
|
+
(c = t[o]) && (e = (i ? c(s, r, e) : c(e)) || e);
|
|
9
|
+
return i && e && y(s, r, e), e;
|
|
10
10
|
};
|
|
11
|
-
let
|
|
11
|
+
let l = class extends u {
|
|
12
12
|
constructor() {
|
|
13
13
|
super(...arguments), this.mirror = !1, this._localStreamValue = null, this._layoutLayersValue = [], this.subscriptions = [];
|
|
14
14
|
}
|
|
@@ -61,15 +61,16 @@ let i = class extends u {
|
|
|
61
61
|
*/
|
|
62
62
|
getSelfLayer() {
|
|
63
63
|
const t = f(this._call);
|
|
64
|
-
return this._layoutLayersValue.find((
|
|
64
|
+
return this._layoutLayersValue.find((s) => s.member_id === t);
|
|
65
65
|
}
|
|
66
66
|
/**
|
|
67
67
|
* Render the component
|
|
68
68
|
*/
|
|
69
69
|
render() {
|
|
70
|
+
var e;
|
|
70
71
|
const t = this.getSelfLayer();
|
|
71
|
-
if (!t) return null;
|
|
72
|
-
const
|
|
72
|
+
if (!t || !((((e = this._localStreamValue) == null ? void 0 : e.getVideoTracks().length) ?? 0) > 0)) return null;
|
|
73
|
+
const r = `
|
|
73
74
|
position: absolute;
|
|
74
75
|
top: ${t.y}%;
|
|
75
76
|
left: ${t.x}%;
|
|
@@ -79,13 +80,13 @@ let i = class extends u {
|
|
|
79
80
|
overflow: hidden;
|
|
80
81
|
transition: top 0.3s ease, left 0.3s ease, width 0.3s ease, height 0.3s ease, opacity 0.3s ease;
|
|
81
82
|
pointer-events: none;
|
|
82
|
-
`,
|
|
83
|
+
`, i = this.mirror ? "transform: scale(-1, 1); -webkit-transform: scale(-1, 1);" : "";
|
|
83
84
|
return p`
|
|
84
|
-
<div class="local-overlay" part="container" style="${
|
|
85
|
+
<div class="local-overlay" part="container" style="${r}">
|
|
85
86
|
<video
|
|
86
87
|
class="local-video"
|
|
87
88
|
part="video"
|
|
88
|
-
style="width: 100%; height: 100%; object-fit: cover; ${
|
|
89
|
+
style="width: 100%; height: 100%; object-fit: cover; ${i}"
|
|
89
90
|
autoplay
|
|
90
91
|
playsinline
|
|
91
92
|
muted
|
|
@@ -96,7 +97,7 @@ let i = class extends u {
|
|
|
96
97
|
`;
|
|
97
98
|
}
|
|
98
99
|
};
|
|
99
|
-
|
|
100
|
+
l.styles = h`
|
|
100
101
|
:host {
|
|
101
102
|
display: contents; /* Doesn't create a box, children inherit positioning */
|
|
102
103
|
}
|
|
@@ -109,20 +110,20 @@ i.styles = h`
|
|
|
109
110
|
display: block;
|
|
110
111
|
}
|
|
111
112
|
`;
|
|
112
|
-
|
|
113
|
+
a([
|
|
113
114
|
n({ type: Boolean })
|
|
114
|
-
],
|
|
115
|
-
|
|
116
|
-
d({ context:
|
|
115
|
+
], l.prototype, "mirror", 2);
|
|
116
|
+
a([
|
|
117
|
+
d({ context: m, subscribe: !0 }),
|
|
117
118
|
n({ attribute: !1 })
|
|
118
|
-
],
|
|
119
|
-
|
|
119
|
+
], l.prototype, "_call", 2);
|
|
120
|
+
a([
|
|
120
121
|
n({ attribute: !1 })
|
|
121
|
-
],
|
|
122
|
-
|
|
122
|
+
], l.prototype, "call", 1);
|
|
123
|
+
l = a([
|
|
123
124
|
b("sw-self-media")
|
|
124
|
-
],
|
|
125
|
+
], l);
|
|
125
126
|
export {
|
|
126
|
-
|
|
127
|
+
l as SelfMedia
|
|
127
128
|
};
|
|
128
129
|
//# sourceMappingURL=self-media.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"self-media.js","sources":["../../src/components/self-media.ts"],"sourcesContent":["/**\n * Self Media Component\n *\n * Renders local video overlay with positioning from layoutLayers.\n * Supports optional mirror transform for the video element.\n *\n * @example\n * ```html\n * <self-media mirror=${true}></self-media>\n * ```\n */\n\nimport { LitElement, html, css } from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\nimport { consume } from '@lit/context';\nimport { Subscription } from 'rxjs';\nimport type { Call, LayoutLayer } from '../types/index.js';\nimport { getSelfId } from '../types/index.js';\nimport { callContext } from '../context/call-context.js';\n\n@customElement('sw-self-media')\nexport class SelfMedia extends LitElement {\n static styles = css`\n :host {\n display: contents; /* Doesn't create a box, children inherit positioning */\n }\n\n .local-overlay {\n box-sizing: border-box;\n }\n\n video {\n display: block;\n }\n `;\n\n /**\n * Mirror the local video horizontally\n */\n @property({ type: Boolean }) mirror = false;\n\n /**\n * Consumes call context from parent call-media component\n */\n @consume({ context: callContext, subscribe: true })\n @property({ attribute: false })\n private _call?: Call;\n\n /**\n * Public call property for direct assignment (when not nested in sw-call-media)\n */\n @property({ attribute: false })\n set call(value: Call | undefined) {\n this._call = value;\n this.cleanupSubscriptions();\n this.setupSubscriptions();\n }\n get call(): Call | undefined {\n return this._call;\n }\n\n /**\n * Current local stream value from observable\n */\n private _localStreamValue: MediaStream | null = null;\n\n /**\n * Current layout layers value from observable\n */\n private _layoutLayersValue: LayoutLayer[] = [];\n\n /**\n * RxJS subscriptions for cleanup\n */\n private subscriptions: Subscription[] = [];\n\n /**\n * Lifecycle: Component connected to DOM\n */\n connectedCallback() {\n super.connectedCallback();\n this.setupSubscriptions();\n }\n\n /**\n * Lifecycle: React to property/context changes\n */\n protected updated(changedProperties: Map<string, unknown>): void {\n super.updated(changedProperties);\n if (changedProperties.has('_call') && this._call) {\n // Clean up old subscriptions first\n this.cleanupSubscriptions();\n // Set up new subscriptions\n this.setupSubscriptions();\n }\n }\n\n /**\n * Lifecycle: Component disconnected from DOM\n */\n disconnectedCallback() {\n super.disconnectedCallback();\n this.cleanupSubscriptions();\n }\n\n /**\n * Subscribe to call observables\n */\n private setupSubscriptions(): void {\n // Continue observing even if _call is undefined initially\n // This allows component to react when call becomes available\n if (!this._call) return;\n\n // Subscribe to local stream\n this.subscriptions.push(\n this._call.localStream$.subscribe((stream: MediaStream | null) => {\n this._localStreamValue = stream;\n this.requestUpdate();\n })\n );\n\n // Subscribe to layout layers\n this.subscriptions.push(\n this._call.layoutLayers$.subscribe((layers: LayoutLayer[]) => {\n this._layoutLayersValue = layers;\n this.requestUpdate();\n })\n );\n }\n\n /**\n * Cleanup all subscriptions\n */\n private cleanupSubscriptions(): void {\n this.subscriptions.forEach((sub) => sub.unsubscribe());\n this.subscriptions = [];\n }\n\n /**\n * Find self layer in layout layers\n */\n private getSelfLayer(): LayoutLayer | undefined {\n // Get selfId dynamically from call.self to handle cases where self is available after subscription setup\n const selfId = getSelfId(this._call);\n return this._layoutLayersValue.find((layer) => layer.member_id === selfId);\n }\n\n /**\n * Render the component\n */\n render() {\n const layer = this.getSelfLayer();\n\n // Skip rendering but continue observing - return null allows reactive updates\n if (!layer) return null;\n\n const style = `\n position: absolute;\n top: ${layer.y}%;\n left: ${layer.x}%;\n width: ${layer.width}%;\n height: ${layer.height}%;\n opacity: ${layer.visible ? 1 : 0};\n overflow: hidden;\n transition: top 0.3s ease, left 0.3s ease, width 0.3s ease, height 0.3s ease, opacity 0.3s ease;\n pointer-events: none;\n `;\n\n const videoStyle = this.mirror\n ? 'transform: scale(-1, 1); -webkit-transform: scale(-1, 1);'\n : '';\n\n return html`\n <div class=\"local-overlay\" part=\"container\" style=\"${style}\">\n <video\n class=\"local-video\"\n part=\"video\"\n style=\"width: 100%; height: 100%; object-fit: cover; ${videoStyle}\"\n autoplay\n playsinline\n muted\n disablePictureInPicture\n .srcObject=${this._localStreamValue}\n ></video>\n </div>\n `;\n }\n}\n\n/**\n * Declare global type for TypeScript\n */\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sw-self-media': SelfMedia;\n }\n}\n"],"names":["SelfMedia","LitElement","value","changedProperties","stream","layers","sub","selfId","getSelfId","layer","style","videoStyle","html","css","__decorateClass","property","consume","callContext","customElement"],"mappings":";;;;;;;;;;AAqBO,IAAMA,IAAN,cAAwBC,EAAW;AAAA,EAAnC,cAAA;AAAA,UAAA,GAAA,SAAA,GAkBwB,KAAA,SAAS,IAyBtC,KAAQ,oBAAwC,MAKhD,KAAQ,qBAAoC,CAAA,GAK5C,KAAQ,gBAAgC,CAAA;AAAA,EAAC;AAAA,EAtBzC,IAAI,KAAKC,GAAyB;AAChC,SAAK,QAAQA,GACb,KAAK,qBAAA,GACL,KAAK,mBAAA;AAAA,EACP;AAAA,EACA,IAAI,OAAyB;AAC3B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAoBA,oBAAoB;AAClB,UAAM,kBAAA,GACN,KAAK,mBAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA,EAKU,QAAQC,GAA+C;AAC/D,UAAM,QAAQA,CAAiB,GAC3BA,EAAkB,IAAI,OAAO,KAAK,KAAK,UAEzC,KAAK,qBAAA,GAEL,KAAK,mBAAA;AAAA,EAET;AAAA;AAAA;AAAA;AAAA,EAKA,uBAAuB;AACrB,UAAM,qBAAA,GACN,KAAK,qBAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA,EAKQ,qBAA2B;AAGjC,IAAK,KAAK,UAGV,KAAK,cAAc;AAAA,MACjB,KAAK,MAAM,aAAa,UAAU,CAACC,MAA+B;AAChE,aAAK,oBAAoBA,GACzB,KAAK,cAAA;AAAA,MACP,CAAC;AAAA,IAAA,GAIH,KAAK,cAAc;AAAA,MACjB,KAAK,MAAM,cAAc,UAAU,CAACC,MAA0B;AAC5D,aAAK,qBAAqBA,GAC1B,KAAK,cAAA;AAAA,MACP,CAAC;AAAA,IAAA;AAAA,EAEL;AAAA;AAAA;AAAA;AAAA,EAKQ,uBAA6B;AACnC,SAAK,cAAc,QAAQ,CAACC,MAAQA,EAAI,aAAa,GACrD,KAAK,gBAAgB,CAAA;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAwC;AAE9C,UAAMC,IAASC,EAAU,KAAK,KAAK;AACnC,WAAO,KAAK,mBAAmB,KAAK,CAACC,MAAUA,EAAM,cAAcF,CAAM;AAAA,EAC3E;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS
|
|
1
|
+
{"version":3,"file":"self-media.js","sources":["../../src/components/self-media.ts"],"sourcesContent":["/**\n * Self Media Component\n *\n * Renders local video overlay with positioning from layoutLayers.\n * Supports optional mirror transform for the video element.\n *\n * @example\n * ```html\n * <self-media mirror=${true}></self-media>\n * ```\n */\n\nimport { LitElement, html, css } from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\nimport { consume } from '@lit/context';\nimport { Subscription } from 'rxjs';\nimport type { Call, LayoutLayer } from '../types/index.js';\nimport { getSelfId } from '../types/index.js';\nimport { callContext } from '../context/call-context.js';\n\n@customElement('sw-self-media')\nexport class SelfMedia extends LitElement {\n static styles = css`\n :host {\n display: contents; /* Doesn't create a box, children inherit positioning */\n }\n\n .local-overlay {\n box-sizing: border-box;\n }\n\n video {\n display: block;\n }\n `;\n\n /**\n * Mirror the local video horizontally\n */\n @property({ type: Boolean }) mirror = false;\n\n /**\n * Consumes call context from parent call-media component\n */\n @consume({ context: callContext, subscribe: true })\n @property({ attribute: false })\n private _call?: Call;\n\n /**\n * Public call property for direct assignment (when not nested in sw-call-media)\n */\n @property({ attribute: false })\n set call(value: Call | undefined) {\n this._call = value;\n this.cleanupSubscriptions();\n this.setupSubscriptions();\n }\n get call(): Call | undefined {\n return this._call;\n }\n\n /**\n * Current local stream value from observable\n */\n private _localStreamValue: MediaStream | null = null;\n\n /**\n * Current layout layers value from observable\n */\n private _layoutLayersValue: LayoutLayer[] = [];\n\n /**\n * RxJS subscriptions for cleanup\n */\n private subscriptions: Subscription[] = [];\n\n /**\n * Lifecycle: Component connected to DOM\n */\n connectedCallback() {\n super.connectedCallback();\n this.setupSubscriptions();\n }\n\n /**\n * Lifecycle: React to property/context changes\n */\n protected updated(changedProperties: Map<string, unknown>): void {\n super.updated(changedProperties);\n if (changedProperties.has('_call') && this._call) {\n // Clean up old subscriptions first\n this.cleanupSubscriptions();\n // Set up new subscriptions\n this.setupSubscriptions();\n }\n }\n\n /**\n * Lifecycle: Component disconnected from DOM\n */\n disconnectedCallback() {\n super.disconnectedCallback();\n this.cleanupSubscriptions();\n }\n\n /**\n * Subscribe to call observables\n */\n private setupSubscriptions(): void {\n // Continue observing even if _call is undefined initially\n // This allows component to react when call becomes available\n if (!this._call) return;\n\n // Subscribe to local stream\n this.subscriptions.push(\n this._call.localStream$.subscribe((stream: MediaStream | null) => {\n this._localStreamValue = stream;\n this.requestUpdate();\n })\n );\n\n // Subscribe to layout layers\n this.subscriptions.push(\n this._call.layoutLayers$.subscribe((layers: LayoutLayer[]) => {\n this._layoutLayersValue = layers;\n this.requestUpdate();\n })\n );\n }\n\n /**\n * Cleanup all subscriptions\n */\n private cleanupSubscriptions(): void {\n this.subscriptions.forEach((sub) => sub.unsubscribe());\n this.subscriptions = [];\n }\n\n /**\n * Find self layer in layout layers\n */\n private getSelfLayer(): LayoutLayer | undefined {\n // Get selfId dynamically from call.self to handle cases where self is available after subscription setup\n const selfId = getSelfId(this._call);\n return this._layoutLayersValue.find((layer) => layer.member_id === selfId);\n }\n\n /**\n * Render the component\n */\n render() {\n const layer = this.getSelfLayer();\n\n // Skip rendering but continue observing - return null allows reactive updates\n if (!layer) return null;\n\n // Skip video overlay for audio-only streams (no video tracks)\n const hasVideoTracks = (this._localStreamValue?.getVideoTracks().length ?? 0) > 0;\n if (!hasVideoTracks) return null;\n\n const style = `\n position: absolute;\n top: ${layer.y}%;\n left: ${layer.x}%;\n width: ${layer.width}%;\n height: ${layer.height}%;\n opacity: ${layer.visible ? 1 : 0};\n overflow: hidden;\n transition: top 0.3s ease, left 0.3s ease, width 0.3s ease, height 0.3s ease, opacity 0.3s ease;\n pointer-events: none;\n `;\n\n const videoStyle = this.mirror\n ? 'transform: scale(-1, 1); -webkit-transform: scale(-1, 1);'\n : '';\n\n return html`\n <div class=\"local-overlay\" part=\"container\" style=\"${style}\">\n <video\n class=\"local-video\"\n part=\"video\"\n style=\"width: 100%; height: 100%; object-fit: cover; ${videoStyle}\"\n autoplay\n playsinline\n muted\n disablePictureInPicture\n .srcObject=${this._localStreamValue}\n ></video>\n </div>\n `;\n }\n}\n\n/**\n * Declare global type for TypeScript\n */\ndeclare global {\n interface HTMLElementTagNameMap {\n 'sw-self-media': SelfMedia;\n }\n}\n"],"names":["SelfMedia","LitElement","value","changedProperties","stream","layers","sub","selfId","getSelfId","layer","_a","style","videoStyle","html","css","__decorateClass","property","consume","callContext","customElement"],"mappings":";;;;;;;;;;AAqBO,IAAMA,IAAN,cAAwBC,EAAW;AAAA,EAAnC,cAAA;AAAA,UAAA,GAAA,SAAA,GAkBwB,KAAA,SAAS,IAyBtC,KAAQ,oBAAwC,MAKhD,KAAQ,qBAAoC,CAAA,GAK5C,KAAQ,gBAAgC,CAAA;AAAA,EAAC;AAAA,EAtBzC,IAAI,KAAKC,GAAyB;AAChC,SAAK,QAAQA,GACb,KAAK,qBAAA,GACL,KAAK,mBAAA;AAAA,EACP;AAAA,EACA,IAAI,OAAyB;AAC3B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAoBA,oBAAoB;AAClB,UAAM,kBAAA,GACN,KAAK,mBAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA,EAKU,QAAQC,GAA+C;AAC/D,UAAM,QAAQA,CAAiB,GAC3BA,EAAkB,IAAI,OAAO,KAAK,KAAK,UAEzC,KAAK,qBAAA,GAEL,KAAK,mBAAA;AAAA,EAET;AAAA;AAAA;AAAA;AAAA,EAKA,uBAAuB;AACrB,UAAM,qBAAA,GACN,KAAK,qBAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA,EAKQ,qBAA2B;AAGjC,IAAK,KAAK,UAGV,KAAK,cAAc;AAAA,MACjB,KAAK,MAAM,aAAa,UAAU,CAACC,MAA+B;AAChE,aAAK,oBAAoBA,GACzB,KAAK,cAAA;AAAA,MACP,CAAC;AAAA,IAAA,GAIH,KAAK,cAAc;AAAA,MACjB,KAAK,MAAM,cAAc,UAAU,CAACC,MAA0B;AAC5D,aAAK,qBAAqBA,GAC1B,KAAK,cAAA;AAAA,MACP,CAAC;AAAA,IAAA;AAAA,EAEL;AAAA;AAAA;AAAA;AAAA,EAKQ,uBAA6B;AACnC,SAAK,cAAc,QAAQ,CAACC,MAAQA,EAAI,aAAa,GACrD,KAAK,gBAAgB,CAAA;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAwC;AAE9C,UAAMC,IAASC,EAAU,KAAK,KAAK;AACnC,WAAO,KAAK,mBAAmB,KAAK,CAACC,MAAUA,EAAM,cAAcF,CAAM;AAAA,EAC3E;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS;;AACP,UAAME,IAAQ,KAAK,aAAA;AAOnB,QAJI,CAACA,KAID,KADoBC,IAAA,KAAK,sBAAL,gBAAAA,EAAwB,iBAAiB,WAAU,KAAK,GAC3D,QAAO;AAE5B,UAAMC,IAAQ;AAAA;AAAA,aAELF,EAAM,CAAC;AAAA,cACNA,EAAM,CAAC;AAAA,eACNA,EAAM,KAAK;AAAA,gBACVA,EAAM,MAAM;AAAA,iBACXA,EAAM,UAAU,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA,OAM5BG,IAAa,KAAK,SACpB,8DACA;AAEJ,WAAOC;AAAA,2DACgDF,CAAK;AAAA;AAAA;AAAA;AAAA,iEAICC,CAAU;AAAA;AAAA;AAAA;AAAA;AAAA,uBAKpD,KAAK,iBAAiB;AAAA;AAAA;AAAA;AAAA,EAI3C;AACF;AA1KaZ,EACJ,SAASc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBaC,EAAA;AAAA,EAA5BC,EAAS,EAAE,MAAM,QAAA,CAAS;AAAA,GAlBhBhB,EAkBkB,WAAA,UAAA,CAAA;AAOrBe,EAAA;AAAA,EAFPE,EAAQ,EAAE,SAASC,GAAa,WAAW,IAAM;AAAA,EACjDF,EAAS,EAAE,WAAW,GAAA,CAAO;AAAA,GAxBnBhB,EAyBH,WAAA,SAAA,CAAA;AAMJe,EAAA;AAAA,EADHC,EAAS,EAAE,WAAW,GAAA,CAAO;AAAA,GA9BnBhB,EA+BP,WAAA,QAAA,CAAA;AA/BOA,IAANe,EAAA;AAAA,EADNI,EAAc,eAAe;AAAA,GACjBnB,CAAA;"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constants for the web-components package
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Timeout in milliseconds for waiting for a video element to become ready.
|
|
6
|
+
* Audio-only streams attached to video elements may never fire `resize` or `canplay`,
|
|
7
|
+
* so this timeout ensures the component doesn't block indefinitely.
|
|
8
|
+
*/
|
|
9
|
+
export declare const VIDEO_READY_TIMEOUT_MS = 2000;
|
|
10
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,OAAO,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sources":["../src/constants.ts"],"sourcesContent":["/**\n * Constants for the web-components package\n */\n\n/**\n * Timeout in milliseconds for waiting for a video element to become ready.\n * Audio-only streams attached to video elements may never fire `resize` or `canplay`,\n * so this timeout ensures the component doesn't block indefinitely.\n */\nexport const VIDEO_READY_TIMEOUT_MS = 2000;\n"],"names":["VIDEO_READY_TIMEOUT_MS"],"mappings":"AASO,MAAMA,IAAyB;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export { DialpadComponent } from './components/dialpad.js';
|
|
|
14
14
|
export { ClickToCallComponent } from './components/click-to-call.js';
|
|
15
15
|
export { DirectoryComponent } from './components/directory.js';
|
|
16
16
|
export { ParticipantControlsComponent } from './components/participant-controls.js';
|
|
17
|
-
export
|
|
17
|
+
export type { Call, CallSelf, DeviceController, LayoutLayer, Participant } from './types/index.js';
|
|
18
18
|
export * from './context/index.js';
|
|
19
19
|
export { html, css, LitElement } from 'lit';
|
|
20
20
|
export type { TemplateResult, CSSResult } from 'lit';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,4BAA4B,EAAE,MAAM,sCAAsC,CAAC;AAGpF,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,4BAA4B,EAAE,MAAM,sCAAsC,CAAC;AAGpF,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGnG,cAAc,oBAAoB,CAAC;AAGnC,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAC5C,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,KAAK,CAAC;AAQrD;;GAEG;AACH,eAAO,MAAM,OAAO,EAAE,MAAoB,CAAC;AAE3C;;GAEG;AACH,eAAO,MAAM,KAAK,EAAE,OAAc,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { ExampleButton as m } from "./components/example-button.js";
|
|
2
|
-
import { CallMedia as
|
|
2
|
+
import { CallMedia as l } from "./components/call-media.js";
|
|
3
3
|
import { Participants as f } from "./components/participants.js";
|
|
4
|
-
import { SelfMedia as
|
|
5
|
-
import { AudioLevel as
|
|
4
|
+
import { SelfMedia as c } from "./components/self-media.js";
|
|
5
|
+
import { AudioLevel as C } from "./components/audio-level.js";
|
|
6
6
|
import { DeviceSelector as v } from "./components/device-selector.js";
|
|
7
7
|
import { CallControls as u } from "./components/call-controls.js";
|
|
8
8
|
import { CallStatusComponent as E } from "./components/call-status.js";
|
|
9
|
-
import { DialpadComponent as
|
|
10
|
-
import { ClickToCallComponent as
|
|
11
|
-
import { DirectoryComponent as
|
|
12
|
-
import { ParticipantControlsComponent as
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { callContext as z } from "./context/call-context.js";
|
|
9
|
+
import { DialpadComponent as S } from "./components/dialpad.js";
|
|
10
|
+
import { ClickToCallComponent as L } from "./components/click-to-call.js";
|
|
11
|
+
import { DirectoryComponent as P } from "./components/directory.js";
|
|
12
|
+
import { ParticipantControlsComponent as g } from "./components/participant-controls.js";
|
|
13
|
+
import { LitElement as A, css as B, html as R } from "lit";
|
|
14
|
+
import { callContext as j } from "./context/call-context.js";
|
|
16
15
|
const t = "1.0.0-rc.0", r = !0, e = () => {
|
|
17
16
|
if (typeof window < "u") {
|
|
18
17
|
const o = new CustomEvent("signalwire:web-components:ready", {
|
|
@@ -23,24 +22,22 @@ const t = "1.0.0-rc.0", r = !0, e = () => {
|
|
|
23
22
|
};
|
|
24
23
|
e();
|
|
25
24
|
export {
|
|
26
|
-
|
|
25
|
+
C as AudioLevel,
|
|
27
26
|
u as CallControls,
|
|
28
|
-
|
|
27
|
+
l as CallMedia,
|
|
29
28
|
E as CallStatusComponent,
|
|
30
|
-
|
|
29
|
+
L as ClickToCallComponent,
|
|
31
30
|
v as DeviceSelector,
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
S as DialpadComponent,
|
|
32
|
+
P as DirectoryComponent,
|
|
34
33
|
m as ExampleButton,
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
A as LitElement,
|
|
35
|
+
g as ParticipantControlsComponent,
|
|
37
36
|
f as Participants,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
B as getSelfId,
|
|
43
|
-
j as html,
|
|
37
|
+
c as SelfMedia,
|
|
38
|
+
j as callContext,
|
|
39
|
+
B as css,
|
|
40
|
+
R as html,
|
|
44
41
|
r as ready,
|
|
45
42
|
t as version
|
|
46
43
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * @signalwire/web-components\n * UI Components Library built with Lit\n */\n\n// Export components\nexport { ExampleButton } from './components/example-button.js';\nexport { CallMedia } from './components/call-media.js';\nexport { Participants } from './components/participants.js';\nexport { SelfMedia } from './components/self-media.js';\nexport { AudioLevel } from './components/audio-level.js';\nexport { DeviceSelector } from './components/device-selector.js';\nexport { CallControls } from './components/call-controls.js';\nexport { CallStatusComponent } from './components/call-status.js';\nexport { DialpadComponent } from './components/dialpad.js';\nexport { ClickToCallComponent } from './components/click-to-call.js';\nexport { DirectoryComponent } from './components/directory.js';\nexport { ParticipantControlsComponent } from './components/participant-controls.js';\n\n// Export types (re-exported from core)\nexport
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * @signalwire/web-components\n * UI Components Library built with Lit\n */\n\n// Export components\nexport { ExampleButton } from './components/example-button.js';\nexport { CallMedia } from './components/call-media.js';\nexport { Participants } from './components/participants.js';\nexport { SelfMedia } from './components/self-media.js';\nexport { AudioLevel } from './components/audio-level.js';\nexport { DeviceSelector } from './components/device-selector.js';\nexport { CallControls } from './components/call-controls.js';\nexport { CallStatusComponent } from './components/call-status.js';\nexport { DialpadComponent } from './components/dialpad.js';\nexport { ClickToCallComponent } from './components/click-to-call.js';\nexport { DirectoryComponent } from './components/directory.js';\nexport { ParticipantControlsComponent } from './components/participant-controls.js';\n\n// Export types (re-exported from core)\nexport type { Call, CallSelf, DeviceController, LayoutLayer, Participant } from './types/index.js';\n\n// Export context\nexport * from './context/index.js';\n\n// Re-export Lit utilities for convenience\nexport { html, css, LitElement } from 'lit';\nexport type { TemplateResult, CSSResult } from 'lit';\n\n// ============================================================================\n// Library Ready Event (for async/dynamic script loading)\n// ============================================================================\n\ndeclare const __VERSION__: string;\n\n/**\n * Library version from package.json, injected at build time.\n */\nexport const version: string = __VERSION__;\n\n/**\n * Flag indicating the library has been loaded and is ready to use.\n */\nexport const ready: boolean = true;\n\n/**\n * Emits 'signalwire:web-components:ready' event when the library is loaded.\n *\n * Scripts that might load BEFORE the library (check flag first):\n * ```js\n * if (window.SignalWireUI?.ready) {\n * // Library already loaded, use it directly\n * initApp();\n * } else {\n * window.addEventListener('signalwire:web-components:ready', () => initApp());\n * }\n * ```\n */\nconst emitReadyEvent = (): void => {\n if (typeof window !== 'undefined') {\n const event = new CustomEvent('signalwire:web-components:ready', {\n detail: { version: __VERSION__ }\n });\n window.dispatchEvent(event);\n }\n};\n\nemitReadyEvent();\n"],"names":["version","ready","emitReadyEvent","event"],"mappings":";;;;;;;;;;;;;;AAsCO,MAAMA,IAAkB,cAKlBC,IAAiB,IAexBC,IAAiB,MAAY;AACjC,MAAI,OAAO,SAAW,KAAa;AACjC,UAAMC,IAAQ,IAAI,YAAY,mCAAmC;AAAA,MAC/D,QAAQ,EAAE,SAAS,aAAA;AAAA,IAAY,CAChC;AACD,WAAO,cAAcA,CAAK;AAAA,EAC5B;AACF;AAEAD,EAAA;"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* Other types are defined locally for web-component specific needs.
|
|
6
6
|
*/
|
|
7
7
|
import type { Observable } from 'rxjs';
|
|
8
|
-
import type { Call, LayoutLayer } from '@signalwire/js';
|
|
9
|
-
export type { Call, LayoutLayer };
|
|
8
|
+
import type { Call, DeviceController, LayoutLayer } from '@signalwire/js';
|
|
9
|
+
export type { Call, DeviceController, LayoutLayer };
|
|
10
10
|
/**
|
|
11
11
|
* Type helper for accessing call.self with proper typing
|
|
12
12
|
* The SDK's Call.self is typed as unknown for flexibility
|
|
@@ -41,27 +41,4 @@ export interface Participant {
|
|
|
41
41
|
unmuteVideo?(): Promise<void>;
|
|
42
42
|
remove?(): Promise<void>;
|
|
43
43
|
}
|
|
44
|
-
/**
|
|
45
|
-
* Device info interface matching MediaDeviceInfo structure
|
|
46
|
-
*/
|
|
47
|
-
export interface DeviceInfo {
|
|
48
|
-
deviceId: string;
|
|
49
|
-
label: string;
|
|
50
|
-
kind: 'audioinput' | 'audiooutput' | 'videoinput';
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* DeviceController interface for observing device lists
|
|
54
|
-
*/
|
|
55
|
-
export interface DeviceController {
|
|
56
|
-
audioInputDevices$: Observable<DeviceInfo[]>;
|
|
57
|
-
videoInputDevices$: Observable<DeviceInfo[]>;
|
|
58
|
-
audioOutputDevices$: Observable<DeviceInfo[]>;
|
|
59
|
-
selectedAudioInputDevice$?: Observable<DeviceInfo | null>;
|
|
60
|
-
selectedVideoInputDevice$?: Observable<DeviceInfo | null>;
|
|
61
|
-
selectedAudioOutputDevice$?: Observable<DeviceInfo | null>;
|
|
62
|
-
selectAudioInputDevice?: (device: DeviceInfo) => void;
|
|
63
|
-
selectVideoInputDevice?: (device: DeviceInfo) => void;
|
|
64
|
-
selectAudioOutputDevice?: (device: DeviceInfo) => void;
|
|
65
|
-
getLocalStream?: () => Promise<MediaStream>;
|
|
66
|
-
}
|
|
67
44
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AACvC,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AACvC,OAAO,KAAK,EAAE,IAAI,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAG1E,YAAY,EAAE,IAAI,EAAE,gBAAgB,EAAE,WAAW,EAAE,CAAC;AAEpD;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGpE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,OAAO,EAAE,GAAG,WAAW,EAAE,CAEvE;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;IAClC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;IAClC,IAAI,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,SAAS,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,WAAW,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B"}
|
package/dist/types/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../src/types/index.ts"],"sourcesContent":["/**\n * Type definitions for call-media components.\n *\n * Call and LayoutLayer types are imported from the SDK.\n * Other types are defined locally for web-component specific needs.\n */\n\nimport type { Observable } from 'rxjs';\nimport type { Call, LayoutLayer } from '@signalwire/js';\n\n// Re-export SDK types for internal use\nexport type { Call, LayoutLayer };\n\n/**\n * Type helper for accessing call.self with proper typing\n * The SDK's Call.self is typed as unknown for flexibility\n */\nexport interface CallSelf {\n id: string;\n}\n\n/**\n * Helper to safely get self ID from a Call\n */\nexport function getSelfId(call: Call | undefined): string | undefined {\n const self = call?.self as CallSelf | undefined;\n return self?.id;\n}\n\n/**\n * Helper to cast participants from unknown[] to Participant[]\n * The SDK uses unknown[] for flexibility, but web-components knows the shape\n */\nexport function castParticipants(participants: unknown[]): Participant[] {\n return participants as Participant[];\n}\n\n/**\n * Participant interface for call participants\n * Web-component specific with optional methods\n */\nexport interface Participant {\n id: string;\n name?: string;\n name$?: Observable<string>;\n audioMuted?: boolean;\n audioMuted$?: Observable<boolean>;\n videoMuted?: boolean;\n videoMuted$?: Observable<boolean>;\n mute?(): Promise<void>;\n unmute?(): Promise<void>;\n muteVideo?(): Promise<void>;\n unmuteVideo?(): Promise<void>;\n remove?(): Promise<void>;\n}\n\n
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../src/types/index.ts"],"sourcesContent":["/**\n * Type definitions for call-media components.\n *\n * Call and LayoutLayer types are imported from the SDK.\n * Other types are defined locally for web-component specific needs.\n */\n\nimport type { Observable } from 'rxjs';\nimport type { Call, DeviceController, LayoutLayer } from '@signalwire/js';\n\n// Re-export SDK types for internal use\nexport type { Call, DeviceController, LayoutLayer };\n\n/**\n * Type helper for accessing call.self with proper typing\n * The SDK's Call.self is typed as unknown for flexibility\n */\nexport interface CallSelf {\n id: string;\n}\n\n/**\n * Helper to safely get self ID from a Call\n */\nexport function getSelfId(call: Call | undefined): string | undefined {\n const self = call?.self as CallSelf | undefined;\n return self?.id;\n}\n\n/**\n * Helper to cast participants from unknown[] to Participant[]\n * The SDK uses unknown[] for flexibility, but web-components knows the shape\n */\nexport function castParticipants(participants: unknown[]): Participant[] {\n return participants as Participant[];\n}\n\n/**\n * Participant interface for call participants\n * Web-component specific with optional methods\n */\nexport interface Participant {\n id: string;\n name?: string;\n name$?: Observable<string>;\n audioMuted?: boolean;\n audioMuted$?: Observable<boolean>;\n videoMuted?: boolean;\n videoMuted$?: Observable<boolean>;\n mute?(): Promise<void>;\n unmute?(): Promise<void>;\n muteVideo?(): Promise<void>;\n unmuteVideo?(): Promise<void>;\n remove?(): Promise<void>;\n}\n\n"],"names":["getSelfId","call","self","castParticipants","participants"],"mappings":"AAwBO,SAASA,EAAUC,GAA4C;AACpE,QAAMC,IAAOD,KAAA,gBAAAA,EAAM;AACnB,SAAOC,KAAA,gBAAAA,EAAM;AACf;AAMO,SAASC,EAAiBC,GAAwC;AACvE,SAAOA;AACT;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video.d.ts","sourceRoot":"","sources":["../../src/utils/video.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"video.d.ts","sourceRoot":"","sources":["../../src/utils/video.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,gBAAgB,CAcrD;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuB1E;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAE7F;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAIjE"}
|
package/dist/utils/video.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { VIDEO_READY_TIMEOUT_MS as n } from "../constants.js";
|
|
2
|
+
function c(e) {
|
|
3
|
+
return new Promise((r) => {
|
|
3
4
|
if (e.readyState >= HTMLMediaElement.HAVE_METADATA) {
|
|
4
|
-
|
|
5
|
+
r();
|
|
5
6
|
return;
|
|
6
7
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
e.addEventListener("canplay", n), e.addEventListener("resize", r);
|
|
8
|
+
let i = !1;
|
|
9
|
+
const t = () => {
|
|
10
|
+
i || (i = !0, clearTimeout(a), e.removeEventListener("canplay", t), e.removeEventListener("resize", t), r());
|
|
11
|
+
}, a = setTimeout(t, n);
|
|
12
|
+
e.addEventListener("canplay", t), e.addEventListener("resize", t);
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
|
-
function
|
|
16
|
-
e.srcObject =
|
|
15
|
+
function d(e, r) {
|
|
16
|
+
e.srcObject = r;
|
|
17
17
|
}
|
|
18
|
-
function
|
|
18
|
+
function s(e) {
|
|
19
19
|
e.srcObject && (e.srcObject = null);
|
|
20
20
|
}
|
|
21
21
|
export {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
d as attachMediaStream,
|
|
23
|
+
s as detachMediaStream,
|
|
24
|
+
c as waitForVideoReady
|
|
25
25
|
};
|
|
26
26
|
//# sourceMappingURL=video.js.map
|
package/dist/utils/video.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video.js","sources":["../../src/utils/video.ts"],"sourcesContent":["/**\n * Video element utilities for creating and managing video elements\n * with proper configuration for WebRTC streams.\n */\n\n/**\n * Creates a video element configured for WebRTC streams.\n * The video is muted, autoplays, and plays inline (mobile Safari support).\n * Prevents pause events which can occur on camera switches or PiP mode.\n *\n * @returns A configured HTMLVideoElement\n */\nexport function createVideoElement(): HTMLVideoElement {\n const video = document.createElement('video');\n video.muted = true;\n video.autoplay = true;\n video.playsInline = true;\n\n // Prevent pause (Safari/Firefox pause on PiP, camera switch)\n video.addEventListener('pause', () => {\n video.play().catch((error) => {\n console.error('Video Element Paused', error);\n });\n });\n\n return video;\n}\n\n/**\n * Waits for a video element to be ready for playback.\n * Resolves when the video has metadata or when it can play.\n *\n * @param element - The video element to wait for\n * @returns A promise that resolves when the video is ready\n */\nexport function waitForVideoReady(element: HTMLVideoElement): Promise<void> {\n return new Promise<void>((resolve) => {\n if (element.readyState >= HTMLMediaElement.HAVE_METADATA) {\n resolve();\n return;\n }\n\n const
|
|
1
|
+
{"version":3,"file":"video.js","sources":["../../src/utils/video.ts"],"sourcesContent":["/**\n * Video element utilities for creating and managing video elements\n * with proper configuration for WebRTC streams.\n */\n\nimport { VIDEO_READY_TIMEOUT_MS } from '../constants.js';\n\n/**\n * Creates a video element configured for WebRTC streams.\n * The video is muted, autoplays, and plays inline (mobile Safari support).\n * Prevents pause events which can occur on camera switches or PiP mode.\n *\n * @returns A configured HTMLVideoElement\n */\nexport function createVideoElement(): HTMLVideoElement {\n const video = document.createElement('video');\n video.muted = true;\n video.autoplay = true;\n video.playsInline = true;\n\n // Prevent pause (Safari/Firefox pause on PiP, camera switch)\n video.addEventListener('pause', () => {\n video.play().catch((error) => {\n console.error('Video Element Paused', error);\n });\n });\n\n return video;\n}\n\n/**\n * Waits for a video element to be ready for playback.\n * Resolves when the video has metadata or when it can play.\n *\n * @param element - The video element to wait for\n * @returns A promise that resolves when the video is ready\n */\nexport function waitForVideoReady(element: HTMLVideoElement): Promise<void> {\n return new Promise<void>((resolve) => {\n if (element.readyState >= HTMLMediaElement.HAVE_METADATA) {\n resolve();\n return;\n }\n\n let resolved = false;\n\n const done = () => {\n if (resolved) return;\n resolved = true;\n clearTimeout(timeoutId);\n element.removeEventListener('canplay', done);\n element.removeEventListener('resize', done);\n resolve();\n };\n\n // Timeout fallback for audio-only streams where resize/canplay may never fire\n const timeoutId = setTimeout(done, VIDEO_READY_TIMEOUT_MS);\n element.addEventListener('canplay', done);\n element.addEventListener('resize', done);\n });\n}\n\n/**\n * Attaches a MediaStream to a video element.\n *\n * @param element - The video element to attach the stream to\n * @param stream - The MediaStream to attach, or null to detach\n */\nexport function attachMediaStream(element: HTMLVideoElement, stream: MediaStream | null): void {\n element.srcObject = stream;\n}\n\n/**\n * Detaches any MediaStream from a video element.\n *\n * @param element - The video element to detach the stream from\n */\nexport function detachMediaStream(element: HTMLVideoElement): void {\n if (element.srcObject) {\n element.srcObject = null;\n }\n}\n"],"names":["waitForVideoReady","element","resolve","resolved","done","timeoutId","VIDEO_READY_TIMEOUT_MS","attachMediaStream","stream","detachMediaStream"],"mappings":";AAqCO,SAASA,EAAkBC,GAA0C;AAC1E,SAAO,IAAI,QAAc,CAACC,MAAY;AACpC,QAAID,EAAQ,cAAc,iBAAiB,eAAe;AACxD,MAAAC,EAAA;AACA;AAAA,IACF;AAEA,QAAIC,IAAW;AAEf,UAAMC,IAAO,MAAM;AACjB,MAAID,MACJA,IAAW,IACX,aAAaE,CAAS,GACtBJ,EAAQ,oBAAoB,WAAWG,CAAI,GAC3CH,EAAQ,oBAAoB,UAAUG,CAAI,GAC1CF,EAAA;AAAA,IACF,GAGMG,IAAY,WAAWD,GAAME,CAAsB;AACzD,IAAAL,EAAQ,iBAAiB,WAAWG,CAAI,GACxCH,EAAQ,iBAAiB,UAAUG,CAAI;AAAA,EACzC,CAAC;AACH;AAQO,SAASG,EAAkBN,GAA2BO,GAAkC;AAC7F,EAAAP,EAAQ,YAAYO;AACtB;AAOO,SAASC,EAAkBR,GAAiC;AACjE,EAAIA,EAAQ,cACVA,EAAQ,YAAY;AAExB;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signalwire/web-components",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.8",
|
|
4
4
|
"description": "UI components library built with Lit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"rxjs": "^7.8.2"
|
|
116
116
|
},
|
|
117
117
|
"peerDependencies": {
|
|
118
|
-
"@signalwire/js": "4.0.0-beta.
|
|
118
|
+
"@signalwire/js": "4.0.0-beta.8"
|
|
119
119
|
},
|
|
120
120
|
"devDependencies": {
|
|
121
121
|
"@playwright/test": "^1.56.1",
|