@memberjunction/ng-conversations 6.2.0-edge.3 → 6.2.0-edge.5
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 +33 -7
- package/dist/lib/components/collection/collection-view.component.d.ts.map +1 -1
- package/dist/lib/components/collection/collection-view.component.js +1 -0
- package/dist/lib/components/collection/collection-view.component.js.map +1 -1
- package/dist/lib/components/composer/ai-composer.component.d.ts +14 -2
- package/dist/lib/components/composer/ai-composer.component.d.ts.map +1 -1
- package/dist/lib/components/composer/ai-composer.component.js +35 -4
- package/dist/lib/components/composer/ai-composer.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-agent-picker.component.d.ts +1 -1
- package/dist/lib/components/conversation/conversation-agent-picker.component.js +3 -3
- package/dist/lib/components/conversation/conversation-agent-picker.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +651 -53
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.js +3002 -661
- package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
- package/dist/lib/components/conversation/forks-popover.component.d.ts +37 -0
- package/dist/lib/components/conversation/forks-popover.component.d.ts.map +1 -0
- package/dist/lib/components/conversation/forks-popover.component.js +164 -0
- package/dist/lib/components/conversation/forks-popover.component.js.map +1 -0
- package/dist/lib/components/export/export-modal.component.d.ts +7 -2
- package/dist/lib/components/export/export-modal.component.d.ts.map +1 -1
- package/dist/lib/components/export/export-modal.component.js +16 -3
- package/dist/lib/components/export/export-modal.component.js.map +1 -1
- package/dist/lib/components/message/conversation-message-rating.component.d.ts.map +1 -1
- package/dist/lib/components/message/conversation-message-rating.component.js +11 -9
- package/dist/lib/components/message/conversation-message-rating.component.js.map +1 -1
- package/dist/lib/components/message/message-input.component.d.ts +96 -11
- package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-input.component.js +253 -112
- package/dist/lib/components/message/message-input.component.js.map +1 -1
- package/dist/lib/components/message/message-item.component.d.ts +304 -16
- package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-item.component.js +2029 -363
- package/dist/lib/components/message/message-item.component.js.map +1 -1
- package/dist/lib/components/message/message-list.component.d.ts +101 -22
- package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-list.component.js +326 -55
- package/dist/lib/components/message/message-list.component.js.map +1 -1
- package/dist/lib/components/realtime/avatar/realtime-avatar-channel.d.ts +34 -0
- package/dist/lib/components/realtime/avatar/realtime-avatar-channel.d.ts.map +1 -0
- package/dist/lib/components/realtime/avatar/realtime-avatar-channel.js +75 -0
- package/dist/lib/components/realtime/avatar/realtime-avatar-channel.js.map +1 -0
- package/dist/lib/components/realtime/avatar/realtime-avatar-surface.component.d.ts +80 -0
- package/dist/lib/components/realtime/avatar/realtime-avatar-surface.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/avatar/realtime-avatar-surface.component.js +202 -0
- package/dist/lib/components/realtime/avatar/realtime-avatar-surface.component.js.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-camera-check-card.component.d.ts +45 -0
- package/dist/lib/components/realtime/capture/realtime-camera-check-card.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-camera-check-card.component.js +123 -0
- package/dist/lib/components/realtime/capture/realtime-camera-check-card.component.js.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-capture-channel.d.ts +90 -0
- package/dist/lib/components/realtime/capture/realtime-capture-channel.d.ts.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-capture-channel.js +223 -0
- package/dist/lib/components/realtime/capture/realtime-capture-channel.js.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-capture-model.d.ts +98 -0
- package/dist/lib/components/realtime/capture/realtime-capture-model.d.ts.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-capture-model.js +161 -0
- package/dist/lib/components/realtime/capture/realtime-capture-model.js.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-capture-surface.component.d.ts +48 -0
- package/dist/lib/components/realtime/capture/realtime-capture-surface.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/capture/realtime-capture-surface.component.js +205 -0
- package/dist/lib/components/realtime/capture/realtime-capture-surface.component.js.map +1 -0
- package/dist/lib/components/realtime/channels/client-context-channel.d.ts +19 -2
- package/dist/lib/components/realtime/channels/client-context-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/channels/client-context-channel.js +83 -9
- package/dist/lib/components/realtime/channels/client-context-channel.js.map +1 -1
- package/dist/lib/components/realtime/channels/realtime-channel-pane.component.d.ts +30 -9
- package/dist/lib/components/realtime/channels/realtime-channel-pane.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/channels/realtime-channel-pane.component.js +78 -14
- package/dist/lib/components/realtime/channels/realtime-channel-pane.component.js.map +1 -1
- package/dist/lib/components/realtime/identity-verification/identity-verification-channel.d.ts +71 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-channel.d.ts.map +1 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-channel.js +259 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-channel.js.map +1 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-model.d.ts +129 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-model.d.ts.map +1 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-model.js +373 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-model.js.map +1 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-surface.component.d.ts +77 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-surface.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-surface.component.js +420 -0
- package/dist/lib/components/realtime/identity-verification/identity-verification-surface.component.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-contract.d.ts +83 -0
- package/dist/lib/components/realtime/interactive-component/component-contract.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-contract.js +292 -0
- package/dist/lib/components/realtime/interactive-component/component-contract.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-data-state.d.ts +39 -0
- package/dist/lib/components/realtime/interactive-component/component-data-state.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-data-state.js +152 -0
- package/dist/lib/components/realtime/interactive-component/component-data-state.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-instance-engine.d.ts +109 -0
- package/dist/lib/components/realtime/interactive-component/component-instance-engine.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-instance-engine.js +146 -0
- package/dist/lib/components/realtime/interactive-component/component-instance-engine.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-spec-content.d.ts +19 -0
- package/dist/lib/components/realtime/interactive-component/component-spec-content.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/component-spec-content.js +39 -0
- package/dist/lib/components/realtime/interactive-component/component-spec-content.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-channel.d.ts +183 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-channel.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-channel.js +761 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-channel.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-config.d.ts +25 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-config.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-config.js +57 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-config.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-host.component.d.ts +40 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-host.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-host.component.js +97 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-host.component.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-types.d.ts +144 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-types.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-types.js +57 -0
- package/dist/lib/components/realtime/interactive-component/interactive-component-types.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/react-component-handle.d.ts +36 -0
- package/dist/lib/components/realtime/interactive-component/react-component-handle.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/react-component-handle.js +55 -0
- package/dist/lib/components/realtime/interactive-component/react-component-handle.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/realtime-interactive-component-surface.component.d.ts +55 -0
- package/dist/lib/components/realtime/interactive-component/realtime-interactive-component-surface.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/realtime-interactive-component-surface.component.js +210 -0
- package/dist/lib/components/realtime/interactive-component/realtime-interactive-component-surface.component.js.map +1 -0
- package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.d.ts +32 -0
- package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.d.ts.map +1 -0
- package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.js +90 -0
- package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.js.map +1 -0
- package/dist/lib/components/realtime/media/media-channel.d.ts +7 -0
- package/dist/lib/components/realtime/media/media-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/media/media-channel.js +28 -1
- package/dist/lib/components/realtime/media/media-channel.js.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts +8 -1
- package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-banner.component.js +89 -71
- package/dist/lib/components/realtime/realtime-agent-banner.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-orb.component.d.ts +21 -0
- package/dist/lib/components/realtime/realtime-agent-orb.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-agent-orb.component.js +49 -0
- package/dist/lib/components/realtime/realtime-agent-orb.component.js.map +1 -0
- package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts +44 -11
- package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-picker.component.js +162 -50
- package/dist/lib/components/realtime/realtime-agent-picker.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-audio-visuals.d.ts +8 -2
- package/dist/lib/components/realtime/realtime-audio-visuals.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-audio-visuals.js +14 -18
- package/dist/lib/components/realtime/realtime-audio-visuals.js.map +1 -1
- package/dist/lib/components/realtime/realtime-composer.component.d.ts +42 -2
- package/dist/lib/components/realtime/realtime-composer.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-composer.component.js +137 -102
- package/dist/lib/components/realtime/realtime-composer.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-perception-chip.component.d.ts +71 -0
- package/dist/lib/components/realtime/realtime-perception-chip.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-perception-chip.component.js +221 -0
- package/dist/lib/components/realtime/realtime-perception-chip.component.js.map +1 -0
- package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +264 -22
- package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-session-overlay.component.js +851 -135
- package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-surface-placement-prefs.d.ts +14 -0
- package/dist/lib/components/realtime/realtime-surface-placement-prefs.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-surface-placement-prefs.js +14 -0
- package/dist/lib/components/realtime/realtime-surface-placement-prefs.js.map +1 -0
- package/dist/lib/components/realtime/realtime-surface-stage.model.d.ts +74 -0
- package/dist/lib/components/realtime/realtime-surface-stage.model.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-surface-stage.model.js +171 -0
- package/dist/lib/components/realtime/realtime-surface-stage.model.js.map +1 -0
- package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts +71 -13
- package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-surface-tabs.component.js +232 -73
- package/dist/lib/components/realtime/realtime-surface-tabs.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts +5 -0
- package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-surface-tabs.model.js +8 -0
- package/dist/lib/components/realtime/realtime-surface-tabs.model.js.map +1 -1
- package/dist/lib/components/realtime/realtime-ui-config.d.ts +12 -2
- package/dist/lib/components/realtime/realtime-ui-config.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-ui-config.js +5 -1
- package/dist/lib/components/realtime/realtime-ui-config.js.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts +10 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js +42 -2
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.d.ts +20 -3
- package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.js +44 -10
- package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.js.map +1 -1
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts +28 -44
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js +76 -162
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js.map +1 -1
- package/dist/lib/components/search/search-panel.component.d.ts +2 -0
- package/dist/lib/components/search/search-panel.component.d.ts.map +1 -1
- package/dist/lib/components/search/search-panel.component.js +25 -12
- package/dist/lib/components/search/search-panel.component.js.map +1 -1
- package/dist/lib/components/sidebar/routines-section.component.js +1 -1
- package/dist/lib/components/sidebar/routines-section.component.js.map +1 -1
- package/dist/lib/components/task/tasks-full-view.component.d.ts +152 -31
- package/dist/lib/components/task/tasks-full-view.component.d.ts.map +1 -1
- package/dist/lib/components/task/tasks-full-view.component.js +2227 -214
- package/dist/lib/components/task/tasks-full-view.component.js.map +1 -1
- package/dist/lib/components/tasks/tasks-dropdown.component.js +4 -4
- package/dist/lib/components/tasks/tasks-dropdown.component.js.map +1 -1
- package/dist/lib/components/workspace/conversation-workspace.component.d.ts +33 -32
- package/dist/lib/components/workspace/conversation-workspace.component.d.ts.map +1 -1
- package/dist/lib/components/workspace/conversation-workspace.component.js +72 -88
- package/dist/lib/components/workspace/conversation-workspace.component.js.map +1 -1
- package/dist/lib/conversations.module.d.ts +30 -29
- package/dist/lib/conversations.module.d.ts.map +1 -1
- package/dist/lib/conversations.module.js +27 -5
- package/dist/lib/conversations.module.js.map +1 -1
- package/dist/lib/events/chat-events.d.ts +25 -0
- package/dist/lib/events/chat-events.d.ts.map +1 -1
- package/dist/lib/events/chat-events.js +36 -0
- package/dist/lib/events/chat-events.js.map +1 -1
- package/dist/lib/models/agent-turn.model.d.ts +5 -0
- package/dist/lib/models/agent-turn.model.d.ts.map +1 -1
- package/dist/lib/models/agent-turn.model.js.map +1 -1
- package/dist/lib/services/agent-state.service.d.ts +34 -8
- package/dist/lib/services/agent-state.service.d.ts.map +1 -1
- package/dist/lib/services/agent-state.service.js +90 -37
- package/dist/lib/services/agent-state.service.js.map +1 -1
- package/dist/lib/services/artifact-state.service.d.ts +0 -9
- package/dist/lib/services/artifact-state.service.d.ts.map +1 -1
- package/dist/lib/services/artifact-state.service.js +1 -32
- package/dist/lib/services/artifact-state.service.js.map +1 -1
- package/dist/lib/services/browser-realtime-media-host.d.ts +19 -7
- package/dist/lib/services/browser-realtime-media-host.d.ts.map +1 -1
- package/dist/lib/services/browser-realtime-media-host.js +21 -72
- package/dist/lib/services/browser-realtime-media-host.js.map +1 -1
- package/dist/lib/services/conversation-agent.service.d.ts +48 -16
- package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
- package/dist/lib/services/conversation-agent.service.js +110 -35
- package/dist/lib/services/conversation-agent.service.js.map +1 -1
- package/dist/lib/services/conversation-detail-window.store.d.ts +22 -4
- package/dist/lib/services/conversation-detail-window.store.d.ts.map +1 -1
- package/dist/lib/services/conversation-detail-window.store.js +50 -9
- package/dist/lib/services/conversation-detail-window.store.js.map +1 -1
- package/dist/lib/services/conversation-naming.d.ts +72 -22
- package/dist/lib/services/conversation-naming.d.ts.map +1 -1
- package/dist/lib/services/conversation-naming.js +93 -12
- package/dist/lib/services/conversation-naming.js.map +1 -1
- package/dist/lib/services/conversation-tasks-filter.d.ts +15 -0
- package/dist/lib/services/conversation-tasks-filter.d.ts.map +1 -0
- package/dist/lib/services/conversation-tasks-filter.js +69 -0
- package/dist/lib/services/conversation-tasks-filter.js.map +1 -0
- package/dist/lib/services/data-cache.service.d.ts +13 -5
- package/dist/lib/services/data-cache.service.d.ts.map +1 -1
- package/dist/lib/services/data-cache.service.js +39 -19
- package/dist/lib/services/data-cache.service.js.map +1 -1
- package/dist/lib/services/export.service.d.ts +26 -9
- package/dist/lib/services/export.service.d.ts.map +1 -1
- package/dist/lib/services/export.service.js +61 -18
- package/dist/lib/services/export.service.js.map +1 -1
- package/dist/lib/services/realtime-audio-recorder.d.ts +115 -79
- package/dist/lib/services/realtime-audio-recorder.d.ts.map +1 -1
- package/dist/lib/services/realtime-audio-recorder.js +273 -186
- package/dist/lib/services/realtime-audio-recorder.js.map +1 -1
- package/dist/lib/services/realtime-pairing.d.ts +10 -5
- package/dist/lib/services/realtime-pairing.d.ts.map +1 -1
- package/dist/lib/services/realtime-pairing.js +15 -6
- package/dist/lib/services/realtime-pairing.js.map +1 -1
- package/dist/lib/services/realtime-session.service.d.ts +31 -2
- package/dist/lib/services/realtime-session.service.d.ts.map +1 -1
- package/dist/lib/services/realtime-session.service.js +34 -2
- package/dist/lib/services/realtime-session.service.js.map +1 -1
- package/dist/lib/services/recording/libopus-wasm-binary.d.ts +3 -0
- package/dist/lib/services/recording/libopus-wasm-binary.d.ts.map +1 -0
- package/dist/lib/services/recording/libopus-wasm-binary.js +489 -0
- package/dist/lib/services/recording/libopus-wasm-binary.js.map +1 -0
- package/dist/lib/services/recording/libopus-worker-source.d.ts +48 -0
- package/dist/lib/services/recording/libopus-worker-source.d.ts.map +1 -0
- package/dist/lib/services/recording/libopus-worker-source.js +170 -0
- package/dist/lib/services/recording/libopus-worker-source.js.map +1 -0
- package/dist/lib/services/recording/linear-resampler.d.ts +45 -0
- package/dist/lib/services/recording/linear-resampler.d.ts.map +1 -0
- package/dist/lib/services/recording/linear-resampler.js +85 -0
- package/dist/lib/services/recording/linear-resampler.js.map +1 -0
- package/dist/lib/services/recording/opus-packet-encoder.d.ts +50 -0
- package/dist/lib/services/recording/opus-packet-encoder.d.ts.map +1 -0
- package/dist/lib/services/recording/opus-packet-encoder.js +41 -0
- package/dist/lib/services/recording/opus-packet-encoder.js.map +1 -0
- package/dist/lib/services/recording/wasm-opus-encoder.d.ts +86 -0
- package/dist/lib/services/recording/wasm-opus-encoder.d.ts.map +1 -0
- package/dist/lib/services/recording/wasm-opus-encoder.js +232 -0
- package/dist/lib/services/recording/wasm-opus-encoder.js.map +1 -0
- package/dist/lib/services/recording/webcodecs-opus-encoder.d.ts +39 -0
- package/dist/lib/services/recording/webcodecs-opus-encoder.d.ts.map +1 -0
- package/dist/lib/services/recording/webcodecs-opus-encoder.js +101 -0
- package/dist/lib/services/recording/webcodecs-opus-encoder.js.map +1 -0
- package/dist/lib/services/search.service.d.ts +7 -2
- package/dist/lib/services/search.service.d.ts.map +1 -1
- package/dist/lib/services/search.service.js +16 -20
- package/dist/lib/services/search.service.js.map +1 -1
- package/dist/lib/utils/conversation-branching.d.ts +4 -0
- package/dist/lib/utils/conversation-branching.d.ts.map +1 -0
- package/dist/lib/utils/conversation-branching.js +5 -0
- package/dist/lib/utils/conversation-branching.js.map +1 -0
- package/dist/lib/utils/conversation-forks.d.ts +315 -0
- package/dist/lib/utils/conversation-forks.d.ts.map +1 -0
- package/dist/lib/utils/conversation-forks.js +456 -0
- package/dist/lib/utils/conversation-forks.js.map +1 -0
- package/dist/lib/utils/distinct-artifacts.d.ts +9 -3
- package/dist/lib/utils/distinct-artifacts.d.ts.map +1 -1
- package/dist/lib/utils/distinct-artifacts.js +24 -5
- package/dist/lib/utils/distinct-artifacts.js.map +1 -1
- package/dist/public-api.d.ts +23 -1
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +30 -1
- package/dist/public-api.js.map +1 -1
- package/package.json +37 -33
- package/dist/lib/components/thread/thread-panel.component.d.ts +0 -126
- package/dist/lib/components/thread/thread-panel.component.d.ts.map +0 -1
- package/dist/lib/components/thread/thread-panel.component.js +0 -440
- package/dist/lib/components/thread/thread-panel.component.js.map +0 -1
- package/dist/lib/services/realtime-pcm-wav.d.ts +0 -79
- package/dist/lib/services/realtime-pcm-wav.d.ts.map +0 -1
- package/dist/lib/services/realtime-pcm-wav.js +0 -213
- package/dist/lib/services/realtime-pcm-wav.js.map +0 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Messages the main thread posts to the libopus Worker. The fields are PascalCase like every other MJ
|
|
3
|
+
* API shape; the worker source below reads them by these exact names.
|
|
4
|
+
*/
|
|
5
|
+
export type OpusWorkerRequest = {
|
|
6
|
+
Type: 'init';
|
|
7
|
+
Wasm: ArrayBuffer;
|
|
8
|
+
} | {
|
|
9
|
+
Type: 'encode';
|
|
10
|
+
Pcm: Float32Array;
|
|
11
|
+
} | {
|
|
12
|
+
Type: 'flush';
|
|
13
|
+
ID: number;
|
|
14
|
+
};
|
|
15
|
+
/** Messages the libopus Worker posts back. */
|
|
16
|
+
export type OpusWorkerReply = {
|
|
17
|
+
Type: 'ready';
|
|
18
|
+
} | {
|
|
19
|
+
Type: 'packets';
|
|
20
|
+
Packets: ArrayBuffer[];
|
|
21
|
+
} | {
|
|
22
|
+
Type: 'flushed';
|
|
23
|
+
ID: number;
|
|
24
|
+
} | {
|
|
25
|
+
Type: 'error';
|
|
26
|
+
Message: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* JavaScript source of the libopus encoder Worker, started from a Blob URL.
|
|
30
|
+
*
|
|
31
|
+
* Why a source string: an Angular library cannot reliably ship a separate worker file that every
|
|
32
|
+
* host app's bundler would pick up, so — like the recorder's AudioWorklet processor — the worker is
|
|
33
|
+
* plain JS with no imports, built into a Blob at runtime. The wasm bytes arrive in the `init`
|
|
34
|
+
* message rather than being fetched, for the same reason.
|
|
35
|
+
*
|
|
36
|
+
* What it does: instantiates libopus (only stub WASI/env imports are needed), creates one encoder
|
|
37
|
+
* (48 kHz mono, OPUS_APPLICATION_VOIP = 2048, OPUS_SET_BITRATE = 4002 → 32 kbps), collects incoming
|
|
38
|
+
* Float32 samples into 960-sample (20 ms) frames, converts each to int16 and encodes it, and posts
|
|
39
|
+
* the packets back (buffers transferred). `flush` pads a pending partial frame with silence, encodes
|
|
40
|
+
* it, and answers `flushed` with the request's id. Messages that arrive while the module is still
|
|
41
|
+
* instantiating are queued and handled in order. After a fault, input is dropped and `flush` still
|
|
42
|
+
* answers, so the main thread never waits on a dead encoder. Typed-array views over wasm memory are
|
|
43
|
+
* re-created for every frame because memory may grow (and detach old views) on any allocation.
|
|
44
|
+
*
|
|
45
|
+
* It references `self` only, so tests can run it in Node with a fake `self`.
|
|
46
|
+
*/
|
|
47
|
+
export declare const LIBOPUS_WORKER_SOURCE = "\n'use strict';\nvar FRAME_SAMPLES = 960;\nvar MAX_PACKET_BYTES = 4000;\nvar SAMPLE_RATE = 48000;\nvar OPUS_APPLICATION_VOIP = 2048;\nvar OPUS_SET_BITRATE = 4002;\nvar BITRATE = 32000;\n\nvar wasm = null;\nvar encoder = 0;\nvar pcmPtr = 0;\nvar outPtr = 0;\nvar frame = new Float32Array(FRAME_SAMPLES);\nvar filled = 0;\nvar queue = [];\nvar failed = false;\n\nfunction describe(error) {\n return error && error.message ? error.message : String(error);\n}\n\nfunction fail(message) {\n failed = true;\n self.postMessage({ Type: 'error', Message: message });\n}\n\nfunction postPackets(packets) {\n if (packets.length > 0) {\n self.postMessage({ Type: 'packets', Packets: packets }, packets);\n }\n}\n\nfunction encodeFrame(packets) {\n var view = new Int16Array(wasm.memory.buffer, pcmPtr, FRAME_SAMPLES);\n for (var i = 0; i < FRAME_SAMPLES; i++) {\n var s = frame[i];\n s = s < -1 ? -1 : s > 1 ? 1 : s;\n view[i] = s < 0 ? s * 32768 : s * 32767;\n }\n var length = wasm.opus_encode(encoder, pcmPtr, FRAME_SAMPLES, outPtr, MAX_PACKET_BYTES);\n if (length < 0) {\n throw new Error('opus_encode returned ' + length);\n }\n packets.push(new Uint8Array(wasm.memory.buffer, outPtr, length).slice().buffer);\n}\n\nfunction encode(pcm) {\n var packets = [];\n var offset = 0;\n while (offset < pcm.length) {\n var take = Math.min(FRAME_SAMPLES - filled, pcm.length - offset);\n frame.set(pcm.subarray(offset, offset + take), filled);\n filled += take;\n offset += take;\n if (filled === FRAME_SAMPLES) {\n encodeFrame(packets);\n filled = 0;\n }\n }\n postPackets(packets);\n}\n\nfunction flush(id) {\n var packets = [];\n if (filled > 0) {\n frame.fill(0, filled);\n encodeFrame(packets);\n filled = 0;\n }\n postPackets(packets);\n self.postMessage({ Type: 'flushed', ID: id });\n}\n\nfunction handle(message) {\n if (failed) {\n if (message.Type === 'flush') {\n self.postMessage({ Type: 'flushed', ID: message.ID });\n }\n return;\n }\n try {\n if (message.Type === 'encode') {\n encode(message.Pcm);\n } else if (message.Type === 'flush') {\n flush(message.ID);\n }\n } catch (error) {\n fail('libopus ' + message.Type + ' failed: ' + describe(error));\n if (message.Type === 'flush') {\n self.postMessage({ Type: 'flushed', ID: message.ID });\n }\n }\n}\n\nfunction drainQueue() {\n var pending = queue;\n queue = [];\n for (var i = 0; i < pending.length; i++) {\n handle(pending[i]);\n }\n}\n\nfunction init(bytes) {\n var imports = {\n wasi_snapshot_preview1: {\n fd_seek: function () { return 0; },\n fd_write: function () { return 0; },\n fd_close: function () { return 0; },\n proc_exit: function () {}\n },\n // Views are re-created per frame, so there is nothing to refresh when memory grows.\n env: { emscripten_notify_memory_growth: function () {} }\n };\n WebAssembly.instantiate(bytes, imports).then(function (result) {\n var exports = result.instance.exports;\n var state = exports.malloc(exports.opus_encoder_get_size(1));\n var rc = exports.opus_encoder_init(state, SAMPLE_RATE, 1, OPUS_APPLICATION_VOIP);\n if (rc !== 0) {\n throw new Error('opus_encoder_init returned ' + rc);\n }\n rc = exports.opus_encoder_ctl_set(state, OPUS_SET_BITRATE, BITRATE);\n if (rc !== 0) {\n throw new Error('opus_encoder_ctl_set(OPUS_SET_BITRATE) returned ' + rc);\n }\n wasm = exports;\n encoder = state;\n pcmPtr = exports.malloc(FRAME_SAMPLES * 2);\n outPtr = exports.malloc(MAX_PACKET_BYTES);\n self.postMessage({ Type: 'ready' });\n drainQueue();\n }).catch(function (error) {\n fail('libopus init failed: ' + describe(error));\n drainQueue();\n });\n}\n\nself.onmessage = function (event) {\n var message = event.data;\n if (message.Type === 'init') {\n init(message.Wasm);\n return;\n }\n if (!wasm && !failed) {\n queue.push(message);\n return;\n }\n handle(message);\n};\n";
|
|
48
|
+
//# sourceMappingURL=libopus-worker-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"libopus-worker-source.d.ts","sourceRoot":"","sources":["../../../../src/lib/services/recording/libopus-worker-source.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,YAAY,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpC,8CAA8C;AAC9C,MAAM,MAAM,eAAe,GACrB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,WAAW,EAAE,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzC;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,qBAAqB,yxIAqJjC,CAAC"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JavaScript source of the libopus encoder Worker, started from a Blob URL.
|
|
3
|
+
*
|
|
4
|
+
* Why a source string: an Angular library cannot reliably ship a separate worker file that every
|
|
5
|
+
* host app's bundler would pick up, so — like the recorder's AudioWorklet processor — the worker is
|
|
6
|
+
* plain JS with no imports, built into a Blob at runtime. The wasm bytes arrive in the `init`
|
|
7
|
+
* message rather than being fetched, for the same reason.
|
|
8
|
+
*
|
|
9
|
+
* What it does: instantiates libopus (only stub WASI/env imports are needed), creates one encoder
|
|
10
|
+
* (48 kHz mono, OPUS_APPLICATION_VOIP = 2048, OPUS_SET_BITRATE = 4002 → 32 kbps), collects incoming
|
|
11
|
+
* Float32 samples into 960-sample (20 ms) frames, converts each to int16 and encodes it, and posts
|
|
12
|
+
* the packets back (buffers transferred). `flush` pads a pending partial frame with silence, encodes
|
|
13
|
+
* it, and answers `flushed` with the request's id. Messages that arrive while the module is still
|
|
14
|
+
* instantiating are queued and handled in order. After a fault, input is dropped and `flush` still
|
|
15
|
+
* answers, so the main thread never waits on a dead encoder. Typed-array views over wasm memory are
|
|
16
|
+
* re-created for every frame because memory may grow (and detach old views) on any allocation.
|
|
17
|
+
*
|
|
18
|
+
* It references `self` only, so tests can run it in Node with a fake `self`.
|
|
19
|
+
*/
|
|
20
|
+
export const LIBOPUS_WORKER_SOURCE = `
|
|
21
|
+
'use strict';
|
|
22
|
+
var FRAME_SAMPLES = 960;
|
|
23
|
+
var MAX_PACKET_BYTES = 4000;
|
|
24
|
+
var SAMPLE_RATE = 48000;
|
|
25
|
+
var OPUS_APPLICATION_VOIP = 2048;
|
|
26
|
+
var OPUS_SET_BITRATE = 4002;
|
|
27
|
+
var BITRATE = 32000;
|
|
28
|
+
|
|
29
|
+
var wasm = null;
|
|
30
|
+
var encoder = 0;
|
|
31
|
+
var pcmPtr = 0;
|
|
32
|
+
var outPtr = 0;
|
|
33
|
+
var frame = new Float32Array(FRAME_SAMPLES);
|
|
34
|
+
var filled = 0;
|
|
35
|
+
var queue = [];
|
|
36
|
+
var failed = false;
|
|
37
|
+
|
|
38
|
+
function describe(error) {
|
|
39
|
+
return error && error.message ? error.message : String(error);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function fail(message) {
|
|
43
|
+
failed = true;
|
|
44
|
+
self.postMessage({ Type: 'error', Message: message });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function postPackets(packets) {
|
|
48
|
+
if (packets.length > 0) {
|
|
49
|
+
self.postMessage({ Type: 'packets', Packets: packets }, packets);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function encodeFrame(packets) {
|
|
54
|
+
var view = new Int16Array(wasm.memory.buffer, pcmPtr, FRAME_SAMPLES);
|
|
55
|
+
for (var i = 0; i < FRAME_SAMPLES; i++) {
|
|
56
|
+
var s = frame[i];
|
|
57
|
+
s = s < -1 ? -1 : s > 1 ? 1 : s;
|
|
58
|
+
view[i] = s < 0 ? s * 32768 : s * 32767;
|
|
59
|
+
}
|
|
60
|
+
var length = wasm.opus_encode(encoder, pcmPtr, FRAME_SAMPLES, outPtr, MAX_PACKET_BYTES);
|
|
61
|
+
if (length < 0) {
|
|
62
|
+
throw new Error('opus_encode returned ' + length);
|
|
63
|
+
}
|
|
64
|
+
packets.push(new Uint8Array(wasm.memory.buffer, outPtr, length).slice().buffer);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function encode(pcm) {
|
|
68
|
+
var packets = [];
|
|
69
|
+
var offset = 0;
|
|
70
|
+
while (offset < pcm.length) {
|
|
71
|
+
var take = Math.min(FRAME_SAMPLES - filled, pcm.length - offset);
|
|
72
|
+
frame.set(pcm.subarray(offset, offset + take), filled);
|
|
73
|
+
filled += take;
|
|
74
|
+
offset += take;
|
|
75
|
+
if (filled === FRAME_SAMPLES) {
|
|
76
|
+
encodeFrame(packets);
|
|
77
|
+
filled = 0;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
postPackets(packets);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function flush(id) {
|
|
84
|
+
var packets = [];
|
|
85
|
+
if (filled > 0) {
|
|
86
|
+
frame.fill(0, filled);
|
|
87
|
+
encodeFrame(packets);
|
|
88
|
+
filled = 0;
|
|
89
|
+
}
|
|
90
|
+
postPackets(packets);
|
|
91
|
+
self.postMessage({ Type: 'flushed', ID: id });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function handle(message) {
|
|
95
|
+
if (failed) {
|
|
96
|
+
if (message.Type === 'flush') {
|
|
97
|
+
self.postMessage({ Type: 'flushed', ID: message.ID });
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
if (message.Type === 'encode') {
|
|
103
|
+
encode(message.Pcm);
|
|
104
|
+
} else if (message.Type === 'flush') {
|
|
105
|
+
flush(message.ID);
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
fail('libopus ' + message.Type + ' failed: ' + describe(error));
|
|
109
|
+
if (message.Type === 'flush') {
|
|
110
|
+
self.postMessage({ Type: 'flushed', ID: message.ID });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function drainQueue() {
|
|
116
|
+
var pending = queue;
|
|
117
|
+
queue = [];
|
|
118
|
+
for (var i = 0; i < pending.length; i++) {
|
|
119
|
+
handle(pending[i]);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function init(bytes) {
|
|
124
|
+
var imports = {
|
|
125
|
+
wasi_snapshot_preview1: {
|
|
126
|
+
fd_seek: function () { return 0; },
|
|
127
|
+
fd_write: function () { return 0; },
|
|
128
|
+
fd_close: function () { return 0; },
|
|
129
|
+
proc_exit: function () {}
|
|
130
|
+
},
|
|
131
|
+
// Views are re-created per frame, so there is nothing to refresh when memory grows.
|
|
132
|
+
env: { emscripten_notify_memory_growth: function () {} }
|
|
133
|
+
};
|
|
134
|
+
WebAssembly.instantiate(bytes, imports).then(function (result) {
|
|
135
|
+
var exports = result.instance.exports;
|
|
136
|
+
var state = exports.malloc(exports.opus_encoder_get_size(1));
|
|
137
|
+
var rc = exports.opus_encoder_init(state, SAMPLE_RATE, 1, OPUS_APPLICATION_VOIP);
|
|
138
|
+
if (rc !== 0) {
|
|
139
|
+
throw new Error('opus_encoder_init returned ' + rc);
|
|
140
|
+
}
|
|
141
|
+
rc = exports.opus_encoder_ctl_set(state, OPUS_SET_BITRATE, BITRATE);
|
|
142
|
+
if (rc !== 0) {
|
|
143
|
+
throw new Error('opus_encoder_ctl_set(OPUS_SET_BITRATE) returned ' + rc);
|
|
144
|
+
}
|
|
145
|
+
wasm = exports;
|
|
146
|
+
encoder = state;
|
|
147
|
+
pcmPtr = exports.malloc(FRAME_SAMPLES * 2);
|
|
148
|
+
outPtr = exports.malloc(MAX_PACKET_BYTES);
|
|
149
|
+
self.postMessage({ Type: 'ready' });
|
|
150
|
+
drainQueue();
|
|
151
|
+
}).catch(function (error) {
|
|
152
|
+
fail('libopus init failed: ' + describe(error));
|
|
153
|
+
drainQueue();
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
self.onmessage = function (event) {
|
|
158
|
+
var message = event.data;
|
|
159
|
+
if (message.Type === 'init') {
|
|
160
|
+
init(message.Wasm);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (!wasm && !failed) {
|
|
164
|
+
queue.push(message);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
handle(message);
|
|
168
|
+
};
|
|
169
|
+
`;
|
|
170
|
+
//# sourceMappingURL=libopus-worker-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"libopus-worker-source.js","sourceRoot":"","sources":["../../../../src/lib/services/recording/libopus-worker-source.ts"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqJpC,CAAC","sourcesContent":["/**\n * Messages the main thread posts to the libopus Worker. The fields are PascalCase like every other MJ\n * API shape; the worker source below reads them by these exact names.\n */\nexport type OpusWorkerRequest =\n | { Type: 'init'; Wasm: ArrayBuffer }\n | { Type: 'encode'; Pcm: Float32Array }\n | { Type: 'flush'; ID: number };\n\n/** Messages the libopus Worker posts back. */\nexport type OpusWorkerReply =\n | { Type: 'ready' }\n | { Type: 'packets'; Packets: ArrayBuffer[] }\n | { Type: 'flushed'; ID: number }\n | { Type: 'error'; Message: string };\n\n/**\n * JavaScript source of the libopus encoder Worker, started from a Blob URL.\n *\n * Why a source string: an Angular library cannot reliably ship a separate worker file that every\n * host app's bundler would pick up, so — like the recorder's AudioWorklet processor — the worker is\n * plain JS with no imports, built into a Blob at runtime. The wasm bytes arrive in the `init`\n * message rather than being fetched, for the same reason.\n *\n * What it does: instantiates libopus (only stub WASI/env imports are needed), creates one encoder\n * (48 kHz mono, OPUS_APPLICATION_VOIP = 2048, OPUS_SET_BITRATE = 4002 → 32 kbps), collects incoming\n * Float32 samples into 960-sample (20 ms) frames, converts each to int16 and encodes it, and posts\n * the packets back (buffers transferred). `flush` pads a pending partial frame with silence, encodes\n * it, and answers `flushed` with the request's id. Messages that arrive while the module is still\n * instantiating are queued and handled in order. After a fault, input is dropped and `flush` still\n * answers, so the main thread never waits on a dead encoder. Typed-array views over wasm memory are\n * re-created for every frame because memory may grow (and detach old views) on any allocation.\n *\n * It references `self` only, so tests can run it in Node with a fake `self`.\n */\nexport const LIBOPUS_WORKER_SOURCE = `\n'use strict';\nvar FRAME_SAMPLES = 960;\nvar MAX_PACKET_BYTES = 4000;\nvar SAMPLE_RATE = 48000;\nvar OPUS_APPLICATION_VOIP = 2048;\nvar OPUS_SET_BITRATE = 4002;\nvar BITRATE = 32000;\n\nvar wasm = null;\nvar encoder = 0;\nvar pcmPtr = 0;\nvar outPtr = 0;\nvar frame = new Float32Array(FRAME_SAMPLES);\nvar filled = 0;\nvar queue = [];\nvar failed = false;\n\nfunction describe(error) {\n return error && error.message ? error.message : String(error);\n}\n\nfunction fail(message) {\n failed = true;\n self.postMessage({ Type: 'error', Message: message });\n}\n\nfunction postPackets(packets) {\n if (packets.length > 0) {\n self.postMessage({ Type: 'packets', Packets: packets }, packets);\n }\n}\n\nfunction encodeFrame(packets) {\n var view = new Int16Array(wasm.memory.buffer, pcmPtr, FRAME_SAMPLES);\n for (var i = 0; i < FRAME_SAMPLES; i++) {\n var s = frame[i];\n s = s < -1 ? -1 : s > 1 ? 1 : s;\n view[i] = s < 0 ? s * 32768 : s * 32767;\n }\n var length = wasm.opus_encode(encoder, pcmPtr, FRAME_SAMPLES, outPtr, MAX_PACKET_BYTES);\n if (length < 0) {\n throw new Error('opus_encode returned ' + length);\n }\n packets.push(new Uint8Array(wasm.memory.buffer, outPtr, length).slice().buffer);\n}\n\nfunction encode(pcm) {\n var packets = [];\n var offset = 0;\n while (offset < pcm.length) {\n var take = Math.min(FRAME_SAMPLES - filled, pcm.length - offset);\n frame.set(pcm.subarray(offset, offset + take), filled);\n filled += take;\n offset += take;\n if (filled === FRAME_SAMPLES) {\n encodeFrame(packets);\n filled = 0;\n }\n }\n postPackets(packets);\n}\n\nfunction flush(id) {\n var packets = [];\n if (filled > 0) {\n frame.fill(0, filled);\n encodeFrame(packets);\n filled = 0;\n }\n postPackets(packets);\n self.postMessage({ Type: 'flushed', ID: id });\n}\n\nfunction handle(message) {\n if (failed) {\n if (message.Type === 'flush') {\n self.postMessage({ Type: 'flushed', ID: message.ID });\n }\n return;\n }\n try {\n if (message.Type === 'encode') {\n encode(message.Pcm);\n } else if (message.Type === 'flush') {\n flush(message.ID);\n }\n } catch (error) {\n fail('libopus ' + message.Type + ' failed: ' + describe(error));\n if (message.Type === 'flush') {\n self.postMessage({ Type: 'flushed', ID: message.ID });\n }\n }\n}\n\nfunction drainQueue() {\n var pending = queue;\n queue = [];\n for (var i = 0; i < pending.length; i++) {\n handle(pending[i]);\n }\n}\n\nfunction init(bytes) {\n var imports = {\n wasi_snapshot_preview1: {\n fd_seek: function () { return 0; },\n fd_write: function () { return 0; },\n fd_close: function () { return 0; },\n proc_exit: function () {}\n },\n // Views are re-created per frame, so there is nothing to refresh when memory grows.\n env: { emscripten_notify_memory_growth: function () {} }\n };\n WebAssembly.instantiate(bytes, imports).then(function (result) {\n var exports = result.instance.exports;\n var state = exports.malloc(exports.opus_encoder_get_size(1));\n var rc = exports.opus_encoder_init(state, SAMPLE_RATE, 1, OPUS_APPLICATION_VOIP);\n if (rc !== 0) {\n throw new Error('opus_encoder_init returned ' + rc);\n }\n rc = exports.opus_encoder_ctl_set(state, OPUS_SET_BITRATE, BITRATE);\n if (rc !== 0) {\n throw new Error('opus_encoder_ctl_set(OPUS_SET_BITRATE) returned ' + rc);\n }\n wasm = exports;\n encoder = state;\n pcmPtr = exports.malloc(FRAME_SAMPLES * 2);\n outPtr = exports.malloc(MAX_PACKET_BYTES);\n self.postMessage({ Type: 'ready' });\n drainQueue();\n }).catch(function (error) {\n fail('libopus init failed: ' + describe(error));\n drainQueue();\n });\n}\n\nself.onmessage = function (event) {\n var message = event.data;\n if (message.Type === 'init') {\n init(message.Wasm);\n return;\n }\n if (!wasm && !failed) {\n queue.push(message);\n return;\n }\n handle(message);\n};\n`;\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming linear-interpolation resampler for mono Float32 PCM.
|
|
3
|
+
*
|
|
4
|
+
* The recorder asks the browser for a 48 kHz `AudioContext`; when a browser refuses that rate the
|
|
5
|
+
* capture runs at the device rate (typically 44.1 kHz) and every captured frame passes through one
|
|
6
|
+
* of these before it reaches the Opus encoder, which only takes 48 kHz.
|
|
7
|
+
*
|
|
8
|
+
* Why the arithmetic is all integers: the recording's timing is the 48 kHz sample count, so the
|
|
9
|
+
* number of output samples must track `inputSamples × toRate / fromRate` exactly over a whole call,
|
|
10
|
+
* not drift by a sample per frame. Output sample `k` sits at input position `k × from / to`
|
|
11
|
+
* (with `from`/`to` reduced by their GCD); the resampler emits every `k` whose position has been
|
|
12
|
+
* captured, so after `N` input samples it has emitted `floor((N − 1) × to / from) + 1`. That never
|
|
13
|
+
* runs ahead of the ideal `N × to / from` and never lags it by more than one input-sample interval
|
|
14
|
+
* (`to / from` output samples, ≈ 1.09 at 44.1 → 48 kHz) — the next output is waiting for the input
|
|
15
|
+
* sample it interpolates towards — and the error never accumulates over a call. Positions stay
|
|
16
|
+
* exact integers for over a month of continuous audio.
|
|
17
|
+
*
|
|
18
|
+
* Linear interpolation is deliberately simple: the signal is speech headed for a 32 kbps voice
|
|
19
|
+
* codec, so a windowed-sinc filter would cost CPU on the audio thread for no audible gain.
|
|
20
|
+
*/
|
|
21
|
+
export declare class LinearResampler {
|
|
22
|
+
/** Reduced input-rate term (`fromRate / gcd`). */
|
|
23
|
+
private readonly from;
|
|
24
|
+
/** Reduced output-rate term (`toRate / gcd`). */
|
|
25
|
+
private readonly to;
|
|
26
|
+
private readonly passthrough;
|
|
27
|
+
/** Input samples consumed so far (absolute index of the next input sample). */
|
|
28
|
+
private consumed;
|
|
29
|
+
/** Output samples emitted so far (index `k` of the next output sample). */
|
|
30
|
+
private produced;
|
|
31
|
+
/** The previous block's last sample; the next output can sit between it and the new block. */
|
|
32
|
+
private lastSample;
|
|
33
|
+
/**
|
|
34
|
+
* @param fromRate capture rate in Hz (a positive integer, e.g. 44100)
|
|
35
|
+
* @param toRate target rate in Hz (a positive integer, e.g. 48000)
|
|
36
|
+
*/
|
|
37
|
+
constructor(fromRate: number, toRate: number);
|
|
38
|
+
/**
|
|
39
|
+
* Resamples the next block of the stream. Blocks may be any length (including 0); state carries
|
|
40
|
+
* across calls, so a stream fed in uneven chunks yields the same samples as one fed whole.
|
|
41
|
+
* On a passthrough resampler (equal rates) the input array itself is returned, not a copy.
|
|
42
|
+
*/
|
|
43
|
+
Process(input: Float32Array): Float32Array;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=linear-resampler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linear-resampler.d.ts","sourceRoot":"","sources":["../../../../src/lib/services/recording/linear-resampler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,eAAe;IACxB,kDAAkD;IAClD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,iDAAiD;IACjD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAU;IACtC,+EAA+E;IAC/E,OAAO,CAAC,QAAQ,CAAK;IACrB,2EAA2E;IAC3E,OAAO,CAAC,QAAQ,CAAK;IACrB,8FAA8F;IAC9F,OAAO,CAAC,UAAU,CAAK;IAEvB;;;OAGG;gBACS,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAU5C;;;;OAIG;IACI,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,YAAY;CAuBpD"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming linear-interpolation resampler for mono Float32 PCM.
|
|
3
|
+
*
|
|
4
|
+
* The recorder asks the browser for a 48 kHz `AudioContext`; when a browser refuses that rate the
|
|
5
|
+
* capture runs at the device rate (typically 44.1 kHz) and every captured frame passes through one
|
|
6
|
+
* of these before it reaches the Opus encoder, which only takes 48 kHz.
|
|
7
|
+
*
|
|
8
|
+
* Why the arithmetic is all integers: the recording's timing is the 48 kHz sample count, so the
|
|
9
|
+
* number of output samples must track `inputSamples × toRate / fromRate` exactly over a whole call,
|
|
10
|
+
* not drift by a sample per frame. Output sample `k` sits at input position `k × from / to`
|
|
11
|
+
* (with `from`/`to` reduced by their GCD); the resampler emits every `k` whose position has been
|
|
12
|
+
* captured, so after `N` input samples it has emitted `floor((N − 1) × to / from) + 1`. That never
|
|
13
|
+
* runs ahead of the ideal `N × to / from` and never lags it by more than one input-sample interval
|
|
14
|
+
* (`to / from` output samples, ≈ 1.09 at 44.1 → 48 kHz) — the next output is waiting for the input
|
|
15
|
+
* sample it interpolates towards — and the error never accumulates over a call. Positions stay
|
|
16
|
+
* exact integers for over a month of continuous audio.
|
|
17
|
+
*
|
|
18
|
+
* Linear interpolation is deliberately simple: the signal is speech headed for a 32 kbps voice
|
|
19
|
+
* codec, so a windowed-sinc filter would cost CPU on the audio thread for no audible gain.
|
|
20
|
+
*/
|
|
21
|
+
export class LinearResampler {
|
|
22
|
+
/** Reduced input-rate term (`fromRate / gcd`). */
|
|
23
|
+
from;
|
|
24
|
+
/** Reduced output-rate term (`toRate / gcd`). */
|
|
25
|
+
to;
|
|
26
|
+
passthrough;
|
|
27
|
+
/** Input samples consumed so far (absolute index of the next input sample). */
|
|
28
|
+
consumed = 0;
|
|
29
|
+
/** Output samples emitted so far (index `k` of the next output sample). */
|
|
30
|
+
produced = 0;
|
|
31
|
+
/** The previous block's last sample; the next output can sit between it and the new block. */
|
|
32
|
+
lastSample = 0;
|
|
33
|
+
/**
|
|
34
|
+
* @param fromRate capture rate in Hz (a positive integer, e.g. 44100)
|
|
35
|
+
* @param toRate target rate in Hz (a positive integer, e.g. 48000)
|
|
36
|
+
*/
|
|
37
|
+
constructor(fromRate, toRate) {
|
|
38
|
+
if (!Number.isInteger(fromRate) || fromRate <= 0 || !Number.isInteger(toRate) || toRate <= 0) {
|
|
39
|
+
throw new Error(`LinearResampler needs positive integer rates, got ${fromRate} → ${toRate}`);
|
|
40
|
+
}
|
|
41
|
+
const divisor = greatestCommonDivisor(fromRate, toRate);
|
|
42
|
+
this.from = fromRate / divisor;
|
|
43
|
+
this.to = toRate / divisor;
|
|
44
|
+
this.passthrough = fromRate === toRate;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resamples the next block of the stream. Blocks may be any length (including 0); state carries
|
|
48
|
+
* across calls, so a stream fed in uneven chunks yields the same samples as one fed whole.
|
|
49
|
+
* On a passthrough resampler (equal rates) the input array itself is returned, not a copy.
|
|
50
|
+
*/
|
|
51
|
+
Process(input) {
|
|
52
|
+
if (this.passthrough || input.length === 0) {
|
|
53
|
+
return this.passthrough ? input : new Float32Array(0);
|
|
54
|
+
}
|
|
55
|
+
const base = this.consumed;
|
|
56
|
+
const total = base + input.length;
|
|
57
|
+
// Last output index whose position k*from/to is <= total-1 (the newest captured sample).
|
|
58
|
+
const lastIndex = Math.floor(((total - 1) * this.to) / this.from);
|
|
59
|
+
const count = Math.max(0, lastIndex - this.produced + 1);
|
|
60
|
+
const output = new Float32Array(count);
|
|
61
|
+
for (let n = 0; n < count; n++) {
|
|
62
|
+
const scaled = (this.produced + n) * this.from;
|
|
63
|
+
const index = Math.floor(scaled / this.to);
|
|
64
|
+
const remainder = scaled - index * this.to;
|
|
65
|
+
const left = index < base ? this.lastSample : input[index - base];
|
|
66
|
+
// remainder > 0 implies index < total-1, so index+1 is always captured.
|
|
67
|
+
output[n] = remainder === 0 ? left : left + ((input[index + 1 - base] - left) * remainder) / this.to;
|
|
68
|
+
}
|
|
69
|
+
this.produced += count;
|
|
70
|
+
this.consumed = total;
|
|
71
|
+
this.lastSample = input[input.length - 1];
|
|
72
|
+
return output;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function greatestCommonDivisor(a, b) {
|
|
76
|
+
let x = a;
|
|
77
|
+
let y = b;
|
|
78
|
+
while (y !== 0) {
|
|
79
|
+
const t = x % y;
|
|
80
|
+
x = y;
|
|
81
|
+
y = t;
|
|
82
|
+
}
|
|
83
|
+
return x;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=linear-resampler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linear-resampler.js","sourceRoot":"","sources":["../../../../src/lib/services/recording/linear-resampler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,OAAO,eAAe;IACxB,kDAAkD;IACjC,IAAI,CAAS;IAC9B,iDAAiD;IAChC,EAAE,CAAS;IACX,WAAW,CAAU;IACtC,+EAA+E;IACvE,QAAQ,GAAG,CAAC,CAAC;IACrB,2EAA2E;IACnE,QAAQ,GAAG,CAAC,CAAC;IACrB,8FAA8F;IACtF,UAAU,GAAG,CAAC,CAAC;IAEvB;;;OAGG;IACH,YAAY,QAAgB,EAAE,MAAc;QACxC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;YAC3F,MAAM,IAAI,KAAK,CAAC,qDAAqD,QAAQ,MAAM,MAAM,EAAE,CAAC,CAAC;QACjG,CAAC;QACD,MAAM,OAAO,GAAG,qBAAqB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACxD,IAAI,CAAC,IAAI,GAAG,QAAQ,GAAG,OAAO,CAAC;QAC/B,IAAI,CAAC,EAAE,GAAG,MAAM,GAAG,OAAO,CAAC;QAC3B,IAAI,CAAC,WAAW,GAAG,QAAQ,KAAK,MAAM,CAAC;IAC3C,CAAC;IAED;;;;OAIG;IACI,OAAO,CAAC,KAAmB;QAC9B,IAAI,IAAI,CAAC,WAAW,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzC,OAAO,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC;QAC1D,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC3B,MAAM,KAAK,GAAG,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC;QAClC,yFAAyF;QACzF,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;QAClE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC;QACzD,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC;QACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7B,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;YAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;YAC3C,MAAM,SAAS,GAAG,MAAM,GAAG,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC;YAC3C,MAAM,IAAI,GAAG,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;YAClE,wEAAwE;YACxE,MAAM,CAAC,CAAC,CAAC,GAAG,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC;QACzG,CAAC;QACD,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC;QACvB,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACtB,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC1C,OAAO,MAAM,CAAC;IAClB,CAAC;CACJ;AAED,SAAS,qBAAqB,CAAC,CAAS,EAAE,CAAS;IAC/C,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACb,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC,GAAG,CAAC,CAAC;QACN,CAAC,GAAG,CAAC,CAAC;IACV,CAAC;IACD,OAAO,CAAC,CAAC;AACb,CAAC","sourcesContent":["/**\n * Streaming linear-interpolation resampler for mono Float32 PCM.\n *\n * The recorder asks the browser for a 48 kHz `AudioContext`; when a browser refuses that rate the\n * capture runs at the device rate (typically 44.1 kHz) and every captured frame passes through one\n * of these before it reaches the Opus encoder, which only takes 48 kHz.\n *\n * Why the arithmetic is all integers: the recording's timing is the 48 kHz sample count, so the\n * number of output samples must track `inputSamples × toRate / fromRate` exactly over a whole call,\n * not drift by a sample per frame. Output sample `k` sits at input position `k × from / to`\n * (with `from`/`to` reduced by their GCD); the resampler emits every `k` whose position has been\n * captured, so after `N` input samples it has emitted `floor((N − 1) × to / from) + 1`. That never\n * runs ahead of the ideal `N × to / from` and never lags it by more than one input-sample interval\n * (`to / from` output samples, ≈ 1.09 at 44.1 → 48 kHz) — the next output is waiting for the input\n * sample it interpolates towards — and the error never accumulates over a call. Positions stay\n * exact integers for over a month of continuous audio.\n *\n * Linear interpolation is deliberately simple: the signal is speech headed for a 32 kbps voice\n * codec, so a windowed-sinc filter would cost CPU on the audio thread for no audible gain.\n */\nexport class LinearResampler {\n /** Reduced input-rate term (`fromRate / gcd`). */\n private readonly from: number;\n /** Reduced output-rate term (`toRate / gcd`). */\n private readonly to: number;\n private readonly passthrough: boolean;\n /** Input samples consumed so far (absolute index of the next input sample). */\n private consumed = 0;\n /** Output samples emitted so far (index `k` of the next output sample). */\n private produced = 0;\n /** The previous block's last sample; the next output can sit between it and the new block. */\n private lastSample = 0;\n\n /**\n * @param fromRate capture rate in Hz (a positive integer, e.g. 44100)\n * @param toRate target rate in Hz (a positive integer, e.g. 48000)\n */\n constructor(fromRate: number, toRate: number) {\n if (!Number.isInteger(fromRate) || fromRate <= 0 || !Number.isInteger(toRate) || toRate <= 0) {\n throw new Error(`LinearResampler needs positive integer rates, got ${fromRate} → ${toRate}`);\n }\n const divisor = greatestCommonDivisor(fromRate, toRate);\n this.from = fromRate / divisor;\n this.to = toRate / divisor;\n this.passthrough = fromRate === toRate;\n }\n\n /**\n * Resamples the next block of the stream. Blocks may be any length (including 0); state carries\n * across calls, so a stream fed in uneven chunks yields the same samples as one fed whole.\n * On a passthrough resampler (equal rates) the input array itself is returned, not a copy.\n */\n public Process(input: Float32Array): Float32Array {\n if (this.passthrough || input.length === 0) {\n return this.passthrough ? input : new Float32Array(0);\n }\n const base = this.consumed;\n const total = base + input.length;\n // Last output index whose position k*from/to is <= total-1 (the newest captured sample).\n const lastIndex = Math.floor(((total - 1) * this.to) / this.from);\n const count = Math.max(0, lastIndex - this.produced + 1);\n const output = new Float32Array(count);\n for (let n = 0; n < count; n++) {\n const scaled = (this.produced + n) * this.from;\n const index = Math.floor(scaled / this.to);\n const remainder = scaled - index * this.to;\n const left = index < base ? this.lastSample : input[index - base];\n // remainder > 0 implies index < total-1, so index+1 is always captured.\n output[n] = remainder === 0 ? left : left + ((input[index + 1 - base] - left) * remainder) / this.to;\n }\n this.produced += count;\n this.consumed = total;\n this.lastSample = input[input.length - 1];\n return output;\n }\n}\n\nfunction greatestCommonDivisor(a: number, b: number): number {\n let x = a;\n let y = b;\n while (y !== 0) {\n const t = x % y;\n x = y;\n y = t;\n }\n return x;\n}\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { AudioDataConstructor, AudioEncoderConstructor } from './webcodecs-opus-encoder';
|
|
2
|
+
/**
|
|
3
|
+
* One continuous Opus encoder for a whole call: mono, 48 kHz, 32 kbps, 20 ms (960-sample) packets.
|
|
4
|
+
*
|
|
5
|
+
* Every packet it emits is one raw Opus packet (no container), in capture order. The runtime cuts
|
|
6
|
+
* that stream into `MJOPUS1` shards and the server decodes all packets with one libopus decoder,
|
|
7
|
+
* which is why the call must keep a single encoder from start to finish: encoder state carries
|
|
8
|
+
* across packets, so switching encoders mid-call would corrupt the join.
|
|
9
|
+
*/
|
|
10
|
+
export interface IOpusPacketEncoder {
|
|
11
|
+
/** Which implementation this is, for logs and tests. */
|
|
12
|
+
readonly Kind: 'webcodecs' | 'wasm';
|
|
13
|
+
/**
|
|
14
|
+
* Queues 48 kHz mono samples for encoding. Any length; the encoder frames them into 960-sample
|
|
15
|
+
* packets itself. The caller keeps ownership of `pcm48k` (the encoder copies what it needs).
|
|
16
|
+
* Never throws: an encoder fault is logged and later input is dropped.
|
|
17
|
+
*/
|
|
18
|
+
Encode(pcm48k: Float32Array): void;
|
|
19
|
+
/**
|
|
20
|
+
* Encodes everything queued, padding a final partial frame with silence, and resolves once those
|
|
21
|
+
* packets are available to {@link TakePackets}. Never rejects; a fault or timeout is logged.
|
|
22
|
+
*/
|
|
23
|
+
Flush(): Promise<void>;
|
|
24
|
+
/** Returns the packets produced since the previous call and forgets them. Works after {@link Close}. */
|
|
25
|
+
TakePackets(): Uint8Array[];
|
|
26
|
+
/** Releases the encoder. Idempotent; packets already produced stay available to {@link TakePackets}. */
|
|
27
|
+
Close(): void;
|
|
28
|
+
}
|
|
29
|
+
/** The platform pieces {@link SelectOpusPacketEncoder} chooses between. Injectable for tests. */
|
|
30
|
+
export interface OpusEncoderEnvironment {
|
|
31
|
+
/** The WebCodecs `AudioEncoder` constructor, or `null` where the platform has none. */
|
|
32
|
+
AudioEncoder: AudioEncoderConstructor | null;
|
|
33
|
+
/** The WebCodecs `AudioData` constructor, or `null` where the platform has none. */
|
|
34
|
+
AudioData: AudioDataConstructor | null;
|
|
35
|
+
/** Starts the libopus WebAssembly encoder (the fallback). */
|
|
36
|
+
CreateWasmEncoder: () => Promise<IOpusPacketEncoder>;
|
|
37
|
+
}
|
|
38
|
+
/** The real browser environment: WebCodecs globals when present, the libopus Worker otherwise. */
|
|
39
|
+
export declare function BrowserOpusEncoderEnvironment(): OpusEncoderEnvironment;
|
|
40
|
+
/**
|
|
41
|
+
* Picks the call's encoder, once, at recording start.
|
|
42
|
+
*
|
|
43
|
+
* WebCodecs is preferred (native, no 440 KB download) but only when the browser says it can encode
|
|
44
|
+
* exactly our configuration: `AudioEncoder` existing is not enough, because Safari shipped
|
|
45
|
+
* `AudioEncoder` long before it supported Opus in it. Anything else — no WebCodecs, an unsupported
|
|
46
|
+
* config, a support probe or configure that throws — falls back to libopus in a Worker. A failure
|
|
47
|
+
* of the fallback rejects, and the recorder treats that as "recording off" for the call.
|
|
48
|
+
*/
|
|
49
|
+
export declare function SelectOpusPacketEncoder(env?: OpusEncoderEnvironment): Promise<IOpusPacketEncoder>;
|
|
50
|
+
//# sourceMappingURL=opus-packet-encoder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opus-packet-encoder.d.ts","sourceRoot":"","sources":["../../../../src/lib/services/recording/opus-packet-encoder.ts"],"names":[],"mappings":"AAEA,OAAO,EACH,oBAAoB,EACpB,uBAAuB,EAG1B,MAAM,0BAA0B,CAAC;AAElC;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB;IAC/B,wDAAwD;IACxD,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAAC;IACpC;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IACnC;;;OAGG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,wGAAwG;IACxG,WAAW,IAAI,UAAU,EAAE,CAAC;IAC5B,wGAAwG;IACxG,KAAK,IAAI,IAAI,CAAC;CACjB;AAED,iGAAiG;AACjG,MAAM,WAAW,sBAAsB;IACnC,uFAAuF;IACvF,YAAY,EAAE,uBAAuB,GAAG,IAAI,CAAC;IAC7C,oFAAoF;IACpF,SAAS,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACvC,6DAA6D;IAC7D,iBAAiB,EAAE,MAAM,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACxD;AAED,kGAAkG;AAClG,wBAAgB,6BAA6B,IAAI,sBAAsB,CAMtE;AAED;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAAC,GAAG,GAAE,sBAAwD,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAGxI"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { RECORDING_SAMPLE_RATE } from '@memberjunction/ai-core-plus';
|
|
2
|
+
import { WasmOpusEncoder } from './wasm-opus-encoder';
|
|
3
|
+
import { WEBCODECS_OPUS_CONFIG, WebCodecsOpusEncoder, } from './webcodecs-opus-encoder';
|
|
4
|
+
/** The real browser environment: WebCodecs globals when present, the libopus Worker otherwise. */
|
|
5
|
+
export function BrowserOpusEncoderEnvironment() {
|
|
6
|
+
return {
|
|
7
|
+
AudioEncoder: typeof AudioEncoder === 'undefined' ? null : AudioEncoder,
|
|
8
|
+
AudioData: typeof AudioData === 'undefined' ? null : AudioData,
|
|
9
|
+
CreateWasmEncoder: () => WasmOpusEncoder.Create(),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Picks the call's encoder, once, at recording start.
|
|
14
|
+
*
|
|
15
|
+
* WebCodecs is preferred (native, no 440 KB download) but only when the browser says it can encode
|
|
16
|
+
* exactly our configuration: `AudioEncoder` existing is not enough, because Safari shipped
|
|
17
|
+
* `AudioEncoder` long before it supported Opus in it. Anything else — no WebCodecs, an unsupported
|
|
18
|
+
* config, a support probe or configure that throws — falls back to libopus in a Worker. A failure
|
|
19
|
+
* of the fallback rejects, and the recorder treats that as "recording off" for the call.
|
|
20
|
+
*/
|
|
21
|
+
export async function SelectOpusPacketEncoder(env = BrowserOpusEncoderEnvironment()) {
|
|
22
|
+
const webCodecs = await tryCreateWebCodecsEncoder(env);
|
|
23
|
+
return webCodecs ?? env.CreateWasmEncoder();
|
|
24
|
+
}
|
|
25
|
+
async function tryCreateWebCodecsEncoder(env) {
|
|
26
|
+
if (!env.AudioEncoder || !env.AudioData) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const support = await env.AudioEncoder.isConfigSupported(WEBCODECS_OPUS_CONFIG);
|
|
31
|
+
if (support.supported !== true) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return new WebCodecsOpusEncoder(env.AudioEncoder, env.AudioData);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
console.warn(`[OpusPacketEncoder] WebCodecs Opus at ${RECORDING_SAMPLE_RATE} Hz could not be set up — using the libopus WebAssembly encoder instead:`, error);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=opus-packet-encoder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opus-packet-encoder.js","sourceRoot":"","sources":["../../../../src/lib/services/recording/opus-packet-encoder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAGH,qBAAqB,EACrB,oBAAoB,GACvB,MAAM,0BAA0B,CAAC;AAwClC,kGAAkG;AAClG,MAAM,UAAU,6BAA6B;IACzC,OAAO;QACH,YAAY,EAAE,OAAO,YAAY,KAAK,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY;QACvE,SAAS,EAAE,OAAO,SAAS,KAAK,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;QAC9D,iBAAiB,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,MAAM,EAAE;KACpD,CAAC;AACN,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,MAA8B,6BAA6B,EAAE;IACvG,MAAM,SAAS,GAAG,MAAM,yBAAyB,CAAC,GAAG,CAAC,CAAC;IACvD,OAAO,SAAS,IAAI,GAAG,CAAC,iBAAiB,EAAE,CAAC;AAChD,CAAC;AAED,KAAK,UAAU,yBAAyB,CAAC,GAA2B;IAChE,IAAI,CAAC,GAAG,CAAC,YAAY,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,IAAI,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,YAAY,CAAC,iBAAiB,CAAC,qBAAqB,CAAC,CAAC;QAChF,IAAI,OAAO,CAAC,SAAS,KAAK,IAAI,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,OAAO,IAAI,oBAAoB,CAAC,GAAG,CAAC,YAAY,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC;IACrE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CACR,yCAAyC,qBAAqB,0EAA0E,EACxI,KAAK,CACR,CAAC;QACF,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC","sourcesContent":["import { RECORDING_SAMPLE_RATE } from '@memberjunction/ai-core-plus';\nimport { WasmOpusEncoder } from './wasm-opus-encoder';\nimport {\n AudioDataConstructor,\n AudioEncoderConstructor,\n WEBCODECS_OPUS_CONFIG,\n WebCodecsOpusEncoder,\n} from './webcodecs-opus-encoder';\n\n/**\n * One continuous Opus encoder for a whole call: mono, 48 kHz, 32 kbps, 20 ms (960-sample) packets.\n *\n * Every packet it emits is one raw Opus packet (no container), in capture order. The runtime cuts\n * that stream into `MJOPUS1` shards and the server decodes all packets with one libopus decoder,\n * which is why the call must keep a single encoder from start to finish: encoder state carries\n * across packets, so switching encoders mid-call would corrupt the join.\n */\nexport interface IOpusPacketEncoder {\n /** Which implementation this is, for logs and tests. */\n readonly Kind: 'webcodecs' | 'wasm';\n /**\n * Queues 48 kHz mono samples for encoding. Any length; the encoder frames them into 960-sample\n * packets itself. The caller keeps ownership of `pcm48k` (the encoder copies what it needs).\n * Never throws: an encoder fault is logged and later input is dropped.\n */\n Encode(pcm48k: Float32Array): void;\n /**\n * Encodes everything queued, padding a final partial frame with silence, and resolves once those\n * packets are available to {@link TakePackets}. Never rejects; a fault or timeout is logged.\n */\n Flush(): Promise<void>;\n /** Returns the packets produced since the previous call and forgets them. Works after {@link Close}. */\n TakePackets(): Uint8Array[];\n /** Releases the encoder. Idempotent; packets already produced stay available to {@link TakePackets}. */\n Close(): void;\n}\n\n/** The platform pieces {@link SelectOpusPacketEncoder} chooses between. Injectable for tests. */\nexport interface OpusEncoderEnvironment {\n /** The WebCodecs `AudioEncoder` constructor, or `null` where the platform has none. */\n AudioEncoder: AudioEncoderConstructor | null;\n /** The WebCodecs `AudioData` constructor, or `null` where the platform has none. */\n AudioData: AudioDataConstructor | null;\n /** Starts the libopus WebAssembly encoder (the fallback). */\n CreateWasmEncoder: () => Promise<IOpusPacketEncoder>;\n}\n\n/** The real browser environment: WebCodecs globals when present, the libopus Worker otherwise. */\nexport function BrowserOpusEncoderEnvironment(): OpusEncoderEnvironment {\n return {\n AudioEncoder: typeof AudioEncoder === 'undefined' ? null : AudioEncoder,\n AudioData: typeof AudioData === 'undefined' ? null : AudioData,\n CreateWasmEncoder: () => WasmOpusEncoder.Create(),\n };\n}\n\n/**\n * Picks the call's encoder, once, at recording start.\n *\n * WebCodecs is preferred (native, no 440 KB download) but only when the browser says it can encode\n * exactly our configuration: `AudioEncoder` existing is not enough, because Safari shipped\n * `AudioEncoder` long before it supported Opus in it. Anything else — no WebCodecs, an unsupported\n * config, a support probe or configure that throws — falls back to libopus in a Worker. A failure\n * of the fallback rejects, and the recorder treats that as \"recording off\" for the call.\n */\nexport async function SelectOpusPacketEncoder(env: OpusEncoderEnvironment = BrowserOpusEncoderEnvironment()): Promise<IOpusPacketEncoder> {\n const webCodecs = await tryCreateWebCodecsEncoder(env);\n return webCodecs ?? env.CreateWasmEncoder();\n}\n\nasync function tryCreateWebCodecsEncoder(env: OpusEncoderEnvironment): Promise<IOpusPacketEncoder | null> {\n if (!env.AudioEncoder || !env.AudioData) {\n return null;\n }\n try {\n const support = await env.AudioEncoder.isConfigSupported(WEBCODECS_OPUS_CONFIG);\n if (support.supported !== true) {\n return null;\n }\n return new WebCodecsOpusEncoder(env.AudioEncoder, env.AudioData);\n } catch (error) {\n console.warn(\n `[OpusPacketEncoder] WebCodecs Opus at ${RECORDING_SAMPLE_RATE} Hz could not be set up — using the libopus WebAssembly encoder instead:`,\n error,\n );\n return null;\n }\n}\n"]}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { OpusWorkerReply, OpusWorkerRequest } from './libopus-worker-source';
|
|
2
|
+
import type { IOpusPacketEncoder } from './opus-packet-encoder';
|
|
3
|
+
/**
|
|
4
|
+
* How long {@link WasmOpusEncoder.Flush} waits for the worker's `flushed` reply. The runtime bounds
|
|
5
|
+
* the whole recorder Stop at 2 s, so this stays below that and leaves room for closing up.
|
|
6
|
+
*/
|
|
7
|
+
export declare const WASM_OPUS_FLUSH_TIMEOUT_MS = 1500;
|
|
8
|
+
/**
|
|
9
|
+
* How long {@link WasmOpusEncoder.Start} waits for the worker to instantiate libopus. Instantiating
|
|
10
|
+
* the ~330 KB module takes milliseconds; a worker that says nothing for this long is not running
|
|
11
|
+
* (a Content-Security-Policy without `worker-src blob:` or `'wasm-unsafe-eval'`, for instance).
|
|
12
|
+
*/
|
|
13
|
+
export declare const WASM_OPUS_READY_TIMEOUT_MS = 5000;
|
|
14
|
+
/** The two-way channel to the encoder worker. A real `Worker` is adapted to it; tests run the worker source in-process. */
|
|
15
|
+
export interface OpusWorkerPort {
|
|
16
|
+
/** Posts a request, transferring the listed buffers. */
|
|
17
|
+
Post(message: OpusWorkerRequest, transfer: Transferable[]): void;
|
|
18
|
+
/** Receives every reply the worker posts. */
|
|
19
|
+
OnReply: ((reply: OpusWorkerReply) => void) | null;
|
|
20
|
+
/** Stops the worker. */
|
|
21
|
+
Terminate(): void;
|
|
22
|
+
}
|
|
23
|
+
/** Decodes base64 into a fresh ArrayBuffer (one the worker can take ownership of). */
|
|
24
|
+
export declare function DecodeBase64ToArrayBuffer(base64: string): ArrayBuffer;
|
|
25
|
+
/**
|
|
26
|
+
* Opus encoding through libopus compiled to WebAssembly, running in a dedicated Worker so encoding
|
|
27
|
+
* never competes with the call's UI or audio on the main thread. The fallback for browsers whose
|
|
28
|
+
* WebCodecs cannot encode Opus.
|
|
29
|
+
*
|
|
30
|
+
* The worker does the 960-sample framing itself (see {@link LIBOPUS_WORKER_SOURCE}); this class only
|
|
31
|
+
* forwards audio, collects the packets the worker posts back, and turns `flush` into a promise.
|
|
32
|
+
*/
|
|
33
|
+
export declare class WasmOpusEncoder implements IOpusPacketEncoder {
|
|
34
|
+
private readonly port;
|
|
35
|
+
private readonly onClose;
|
|
36
|
+
readonly Kind = "wasm";
|
|
37
|
+
private packets;
|
|
38
|
+
private readonly flushWaiters;
|
|
39
|
+
private nextFlushId;
|
|
40
|
+
private closed;
|
|
41
|
+
/** Whether the worker has reported `ready`, or the error it reported instead. */
|
|
42
|
+
private startState;
|
|
43
|
+
/** Notified once when {@link startState} leaves `starting` (null = ready, else the error). */
|
|
44
|
+
private startWaiter;
|
|
45
|
+
/**
|
|
46
|
+
* @param port channel to a worker running {@link LIBOPUS_WORKER_SOURCE}
|
|
47
|
+
* @param wasmBytes the libopus module; ownership moves to the worker
|
|
48
|
+
* @param onClose extra cleanup after the worker stops (the Blob URL revoke)
|
|
49
|
+
*/
|
|
50
|
+
constructor(port: OpusWorkerPort, wasmBytes: ArrayBuffer, onClose?: () => void);
|
|
51
|
+
/**
|
|
52
|
+
* Loads the embedded wasm and starts the worker from a Blob URL. Rejects if either fails, or if the
|
|
53
|
+
* worker never gets libopus running (see {@link Start}).
|
|
54
|
+
*/
|
|
55
|
+
static Create(): Promise<WasmOpusEncoder>;
|
|
56
|
+
/**
|
|
57
|
+
* Creates the encoder on `port` and resolves once the worker reports libopus ready. Rejects — after
|
|
58
|
+
* closing the encoder — when the worker reports an init error or says nothing for
|
|
59
|
+
* {@link WASM_OPUS_READY_TIMEOUT_MS}: an encoder that can never produce a packet must fail selection,
|
|
60
|
+
* so the recorder turns recording off visibly instead of "recording" while producing nothing.
|
|
61
|
+
*/
|
|
62
|
+
static Start(port: OpusWorkerPort, wasmBytes: ArrayBuffer, onClose?: () => void): Promise<WasmOpusEncoder>;
|
|
63
|
+
Encode(pcm48k: Float32Array): void;
|
|
64
|
+
Flush(): Promise<void>;
|
|
65
|
+
TakePackets(): Uint8Array[];
|
|
66
|
+
Close(): void;
|
|
67
|
+
/** Records the worker's start outcome (only the first one counts) and wakes {@link waitUntilStarted}. */
|
|
68
|
+
private settleStart;
|
|
69
|
+
/** Resolves once the worker is ready; rejects with its init error or after the start timeout. */
|
|
70
|
+
private waitUntilStarted;
|
|
71
|
+
private onReply;
|
|
72
|
+
}
|
|
73
|
+
/** The slice of a DOM `Worker` {@link BrowserWorkerPort} drives (the real `Worker` satisfies it). */
|
|
74
|
+
export type WorkerLike = Pick<Worker, 'postMessage' | 'terminate'> & {
|
|
75
|
+
onmessage: ((event: MessageEvent<OpusWorkerReply>) => void) | null;
|
|
76
|
+
onerror: ((event: ErrorEvent) => void) | null;
|
|
77
|
+
};
|
|
78
|
+
/** Adapts a DOM `Worker` to {@link OpusWorkerPort}. */
|
|
79
|
+
export declare class BrowserWorkerPort implements OpusWorkerPort {
|
|
80
|
+
private readonly worker;
|
|
81
|
+
OnReply: ((reply: OpusWorkerReply) => void) | null;
|
|
82
|
+
constructor(worker: WorkerLike);
|
|
83
|
+
Post(message: OpusWorkerRequest, transfer: Transferable[]): void;
|
|
84
|
+
Terminate(): void;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=wasm-opus-encoder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wasm-opus-encoder.d.ts","sourceRoot":"","sources":["../../../../src/lib/services/recording/wasm-opus-encoder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,eAAe,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACpG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE;;;GAGG;AACH,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,2HAA2H;AAC3H,MAAM,WAAW,cAAc;IAC3B,wDAAwD;IACxD,IAAI,CAAC,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IACjE,6CAA6C;IAC7C,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACnD,wBAAwB;IACxB,SAAS,IAAI,IAAI,CAAC;CACrB;AAoBD,sFAAsF;AACtF,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAOrE;AAED;;;;;;;GAOG;AACH,qBAAa,eAAgB,YAAW,kBAAkB;IAgB1C,OAAO,CAAC,QAAQ,CAAC,IAAI;IAA0C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAfnG,SAAgB,IAAI,UAAU;IAC9B,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiC;IAC9D,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,MAAM,CAAS;IACvB,iFAAiF;IACjF,OAAO,CAAC,UAAU,CAAwD;IAC1E,8FAA8F;IAC9F,OAAO,CAAC,WAAW,CAAiD;IAEpE;;;;OAIG;gBAC0B,IAAI,EAAE,cAAc,EAAE,SAAS,EAAE,WAAW,EAAmB,OAAO,GAAE,MAAM,IAAsB;IAKjI;;;OAGG;WACiB,MAAM,IAAI,OAAO,CAAC,eAAe,CAAC;IAatD;;;;;OAKG;WACiB,KAAK,CAAC,IAAI,EAAE,cAAc,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,GAAE,MAAM,IAAsB,GAAG,OAAO,CAAC,eAAe,CAAC;IAWjI,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;IAQlC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBtB,WAAW,IAAI,UAAU,EAAE;IAM3B,KAAK,IAAI,IAAI;IAepB,yGAAyG;IACzG,OAAO,CAAC,WAAW;IAUnB,iGAAiG;IACjG,OAAO,CAAC,gBAAgB;IA0BxB,OAAO,CAAC,OAAO;CAsBlB;AAED,qGAAqG;AACrG,MAAM,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,EAAE,aAAa,GAAG,WAAW,CAAC,GAAG;IAEjE,SAAS,EAAE,CAAC,CAAC,KAAK,EAAE,YAAY,CAAC,eAAe,CAAC,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IAEnE,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;CACjD,CAAC;AAEF,uDAAuD;AACvD,qBAAa,iBAAkB,YAAW,cAAc;IAGxC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAF5B,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC,GAAG,IAAI,CAAQ;gBAEpC,MAAM,EAAE,UAAU;IAYxC,IAAI,CAAC,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,IAAI;IAIhE,SAAS,IAAI,IAAI;CAK3B"}
|