@memberjunction/ng-conversations 5.43.0 → 5.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/components/active-tasks/active-tasks-panel.component.d.ts +0 -1
- package/dist/lib/components/active-tasks/active-tasks-panel.component.d.ts.map +1 -1
- package/dist/lib/components/active-tasks/active-tasks-panel.component.js +56 -52
- package/dist/lib/components/active-tasks/active-tasks-panel.component.js.map +1 -1
- package/dist/lib/components/artifact/artifact-share-modal.component.js +37 -41
- package/dist/lib/components/artifact/artifact-share-modal.component.js.map +1 -1
- package/dist/lib/components/collection/collection-form-modal.component.d.ts.map +1 -1
- package/dist/lib/components/collection/collection-form-modal.component.js +10 -14
- package/dist/lib/components/collection/collection-form-modal.component.js.map +1 -1
- package/dist/lib/components/collection/collection-share-modal.component.js +41 -45
- package/dist/lib/components/collection/collection-share-modal.component.js.map +1 -1
- package/dist/lib/components/collection/collection-view.component.d.ts.map +1 -1
- package/dist/lib/components/collection/collection-view.component.js +22 -36
- package/dist/lib/components/collection/collection-view.component.js.map +1 -1
- package/dist/lib/components/collection/collections-full-view.component.d.ts +4 -0
- package/dist/lib/components/collection/collections-full-view.component.d.ts.map +1 -1
- package/dist/lib/components/collection/collections-full-view.component.js +134 -166
- package/dist/lib/components/collection/collections-full-view.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +73 -8
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.js +487 -123
- package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
- package/dist/lib/components/conversation/pinned-messages-panel.component.js +6 -9
- package/dist/lib/components/conversation/pinned-messages-panel.component.js.map +1 -1
- package/dist/lib/components/library/library-full-view.component.d.ts.map +1 -1
- package/dist/lib/components/library/library-full-view.component.js +20 -24
- package/dist/lib/components/library/library-full-view.component.js.map +1 -1
- package/dist/lib/components/members/members-modal.component.d.ts.map +1 -1
- package/dist/lib/components/members/members-modal.component.js +10 -11
- package/dist/lib/components/members/members-modal.component.js.map +1 -1
- package/dist/lib/components/mention/mention-dropdown.component.js +49 -28
- package/dist/lib/components/mention/mention-dropdown.component.js.map +1 -1
- package/dist/lib/components/mention/mention-editor.component.d.ts.map +1 -1
- package/dist/lib/components/mention/mention-editor.component.js +24 -10
- package/dist/lib/components/mention/mention-editor.component.js.map +1 -1
- package/dist/lib/components/message/message-input-box.component.d.ts +11 -16
- package/dist/lib/components/message/message-input-box.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-input-box.component.js +52 -46
- package/dist/lib/components/message/message-input-box.component.js.map +1 -1
- package/dist/lib/components/message/message-input.component.d.ts +83 -18
- package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-input.component.js +238 -151
- package/dist/lib/components/message/message-input.component.js.map +1 -1
- package/dist/lib/components/message/message-item.component.d.ts +10 -1
- package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-item.component.js +67 -15
- package/dist/lib/components/message/message-item.component.js.map +1 -1
- package/dist/lib/components/message/message-list.component.js +6 -9
- package/dist/lib/components/message/message-list.component.js.map +1 -1
- package/dist/lib/components/overlay/chat-overlay.component.d.ts +3 -0
- package/dist/lib/components/overlay/chat-overlay.component.d.ts.map +1 -1
- package/dist/lib/components/overlay/chat-overlay.component.js +9 -4
- package/dist/lib/components/overlay/chat-overlay.component.js.map +1 -1
- package/dist/lib/components/realtime/channels/base-realtime-channel-client.d.ts +45 -0
- package/dist/lib/components/realtime/channels/base-realtime-channel-client.d.ts.map +1 -1
- package/dist/lib/components/realtime/channels/base-realtime-channel-client.js +10 -0
- package/dist/lib/components/realtime/channels/base-realtime-channel-client.js.map +1 -1
- package/dist/lib/components/realtime/channels/client-context-channel.d.ts +46 -0
- package/dist/lib/components/realtime/channels/client-context-channel.d.ts.map +1 -0
- package/dist/lib/components/realtime/channels/client-context-channel.js +174 -0
- package/dist/lib/components/realtime/channels/client-context-channel.js.map +1 -0
- package/dist/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.d.ts +123 -0
- package/dist/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.js +397 -0
- package/dist/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.js.map +1 -0
- package/dist/lib/components/realtime/media/media-channel-state.d.ts +103 -0
- package/dist/lib/components/realtime/media/media-channel-state.d.ts.map +1 -0
- package/dist/lib/components/realtime/media/media-channel-state.js +143 -0
- package/dist/lib/components/realtime/media/media-channel-state.js.map +1 -0
- package/dist/lib/components/realtime/media/media-channel-tools.d.ts +24 -0
- package/dist/lib/components/realtime/media/media-channel-tools.d.ts.map +1 -0
- package/dist/lib/components/realtime/media/media-channel-tools.js +107 -0
- package/dist/lib/components/realtime/media/media-channel-tools.js.map +1 -0
- package/dist/lib/components/realtime/media/media-channel.d.ts +89 -0
- package/dist/lib/components/realtime/media/media-channel.d.ts.map +1 -0
- package/dist/lib/components/realtime/media/media-channel.js +253 -0
- package/dist/lib/components/realtime/media/media-channel.js.map +1 -0
- package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts +141 -0
- package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/media/realtime-media-surface.component.js +574 -0
- package/dist/lib/components/realtime/media/realtime-media-surface.component.js.map +1 -0
- package/dist/lib/components/realtime/realtime-activity-rail.component.d.ts +59 -14
- package/dist/lib/components/realtime/realtime-activity-rail.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-activity-rail.component.js +277 -74
- package/dist/lib/components/realtime/realtime-activity-rail.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts +28 -27
- package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-banner.component.js +144 -143
- package/dist/lib/components/realtime/realtime-agent-banner.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts +12 -0
- package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-picker.component.js +105 -43
- package/dist/lib/components/realtime/realtime-agent-picker.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-audio-visuals.d.ts +2 -2
- package/dist/lib/components/realtime/realtime-audio-visuals.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-audio-visuals.js.map +1 -1
- package/dist/lib/components/realtime/realtime-composer.component.d.ts +41 -7
- package/dist/lib/components/realtime/realtime-composer.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-composer.component.js +216 -102
- package/dist/lib/components/realtime/realtime-composer.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-delegation-card.component.d.ts +0 -2
- package/dist/lib/components/realtime/realtime-delegation-card.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-delegation-card.component.js +67 -71
- package/dist/lib/components/realtime/realtime-delegation-card.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +364 -44
- package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-session-overlay.component.js +839 -153
- package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-session-state.d.ts +5 -5
- package/dist/lib/components/realtime/realtime-session-state.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-session-state.js.map +1 -1
- package/dist/lib/components/realtime/realtime-surface-tab-style.d.ts +70 -0
- package/dist/lib/components/realtime/realtime-surface-tab-style.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-surface-tab-style.js +115 -0
- package/dist/lib/components/realtime/realtime-surface-tab-style.js.map +1 -0
- package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts +55 -57
- package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-surface-tabs.component.js +165 -143
- package/dist/lib/components/realtime/realtime-surface-tabs.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts +80 -71
- package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-surface-tabs.model.js +83 -113
- package/dist/lib/components/realtime/realtime-surface-tabs.model.js.map +1 -1
- package/dist/lib/components/realtime/realtime-ui-config.d.ts +270 -0
- package/dist/lib/components/realtime/realtime-ui-config.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-ui-config.js +212 -0
- package/dist/lib/components/realtime/realtime-ui-config.js.map +1 -0
- 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 +92 -101
- package/dist/lib/components/search/search-panel.component.js.map +1 -1
- package/dist/lib/components/share/share-modal.component.d.ts.map +1 -1
- package/dist/lib/components/share/share-modal.component.js +10 -11
- package/dist/lib/components/share/share-modal.component.js.map +1 -1
- package/dist/lib/components/shared/user-picker.component.d.ts.map +1 -1
- package/dist/lib/components/shared/user-picker.component.js +12 -15
- package/dist/lib/components/shared/user-picker.component.js.map +1 -1
- package/dist/lib/components/workspace/conversation-workspace.component.d.ts +5 -0
- package/dist/lib/components/workspace/conversation-workspace.component.d.ts.map +1 -1
- package/dist/lib/components/workspace/conversation-workspace.component.js +18 -7
- package/dist/lib/components/workspace/conversation-workspace.component.js.map +1 -1
- package/dist/lib/conversations.module.d.ts +29 -26
- package/dist/lib/conversations.module.d.ts.map +1 -1
- package/dist/lib/conversations.module.js +37 -6
- package/dist/lib/conversations.module.js.map +1 -1
- package/dist/lib/models/conversation-state.model.d.ts +6 -3
- package/dist/lib/models/conversation-state.model.d.ts.map +1 -1
- package/dist/lib/models/conversation-state.model.js.map +1 -1
- package/dist/lib/services/conversation-agent.service.d.ts +2 -2
- package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
- package/dist/lib/services/conversation-agent.service.js +6 -2
- package/dist/lib/services/conversation-agent.service.js.map +1 -1
- package/dist/lib/services/mention-autocomplete.service.d.ts +17 -1
- package/dist/lib/services/mention-autocomplete.service.d.ts.map +1 -1
- package/dist/lib/services/mention-autocomplete.service.js +58 -2
- package/dist/lib/services/mention-autocomplete.service.js.map +1 -1
- package/dist/lib/services/realtime-audio-recorder.d.ts +143 -0
- package/dist/lib/services/realtime-audio-recorder.d.ts.map +1 -0
- package/dist/lib/services/realtime-audio-recorder.js +379 -0
- package/dist/lib/services/realtime-audio-recorder.js.map +1 -0
- package/dist/lib/services/realtime-pcm-wav.d.ts +75 -0
- package/dist/lib/services/realtime-pcm-wav.d.ts.map +1 -0
- package/dist/lib/services/realtime-pcm-wav.js +205 -0
- package/dist/lib/services/realtime-pcm-wav.js.map +1 -0
- package/dist/lib/services/realtime-session-review.service.d.ts +28 -1
- package/dist/lib/services/realtime-session-review.service.d.ts.map +1 -1
- package/dist/lib/services/realtime-session-review.service.js +12 -3
- package/dist/lib/services/realtime-session-review.service.js.map +1 -1
- package/dist/lib/services/realtime-session.service.d.ts +210 -20
- package/dist/lib/services/realtime-session.service.d.ts.map +1 -1
- package/dist/lib/services/realtime-session.service.js +452 -21
- package/dist/lib/services/realtime-session.service.js.map +1 -1
- package/dist/lib/services/realtime-sessions-adapter.js +1 -1
- package/dist/lib/services/realtime-sessions-adapter.js.map +1 -1
- package/dist/lib/services/ui-command-handler.service.d.ts +7 -2
- package/dist/lib/services/ui-command-handler.service.d.ts.map +1 -1
- package/dist/lib/services/ui-command-handler.service.js +6 -2
- package/dist/lib/services/ui-command-handler.service.js.map +1 -1
- package/dist/lib/utils/plan-mode-preference.d.ts +62 -0
- package/dist/lib/utils/plan-mode-preference.d.ts.map +1 -0
- package/dist/lib/utils/plan-mode-preference.js +130 -0
- package/dist/lib/utils/plan-mode-preference.js.map +1 -0
- package/dist/public-api.d.ts +6 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +8 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +28 -26
- package/dist/__tests__/attachment-badge.test.d.ts +0 -2
- package/dist/__tests__/attachment-badge.test.d.ts.map +0 -1
- package/dist/__tests__/attachment-badge.test.js +0 -66
- package/dist/__tests__/attachment-badge.test.js.map +0 -1
- package/dist/__tests__/channel-optional-surface.test.d.ts +0 -2
- package/dist/__tests__/channel-optional-surface.test.d.ts.map +0 -1
- package/dist/__tests__/channel-optional-surface.test.js +0 -53
- package/dist/__tests__/channel-optional-surface.test.js.map +0 -1
- package/dist/__tests__/chat-events.test.d.ts +0 -14
- package/dist/__tests__/chat-events.test.d.ts.map +0 -1
- package/dist/__tests__/chat-events.test.js +0 -109
- package/dist/__tests__/chat-events.test.js.map +0 -1
- package/dist/__tests__/collections-full-view.test.d.ts +0 -2
- package/dist/__tests__/collections-full-view.test.d.ts.map +0 -1
- package/dist/__tests__/collections-full-view.test.js +0 -225
- package/dist/__tests__/collections-full-view.test.js.map +0 -1
- package/dist/__tests__/conversation-cache.test.d.ts +0 -2
- package/dist/__tests__/conversation-cache.test.d.ts.map +0 -1
- package/dist/__tests__/conversation-cache.test.js +0 -233
- package/dist/__tests__/conversation-cache.test.js.map +0 -1
- package/dist/__tests__/conversation-naming.test.d.ts +0 -2
- package/dist/__tests__/conversation-naming.test.d.ts.map +0 -1
- package/dist/__tests__/conversation-naming.test.js +0 -110
- package/dist/__tests__/conversation-naming.test.js.map +0 -1
- package/dist/__tests__/delegation-result-parser.test.d.ts +0 -2
- package/dist/__tests__/delegation-result-parser.test.d.ts.map +0 -1
- package/dist/__tests__/delegation-result-parser.test.js +0 -107
- package/dist/__tests__/delegation-result-parser.test.js.map +0 -1
- package/dist/__tests__/distinct-artifacts.test.d.ts +0 -2
- package/dist/__tests__/distinct-artifacts.test.d.ts.map +0 -1
- package/dist/__tests__/distinct-artifacts.test.js +0 -56
- package/dist/__tests__/distinct-artifacts.test.js.map +0 -1
- package/dist/__tests__/event-wiring.test.d.ts +0 -15
- package/dist/__tests__/event-wiring.test.d.ts.map +0 -1
- package/dist/__tests__/event-wiring.test.js +0 -100
- package/dist/__tests__/event-wiring.test.js.map +0 -1
- package/dist/__tests__/index.test.d.ts +0 -2
- package/dist/__tests__/index.test.d.ts.map +0 -1
- package/dist/__tests__/index.test.js +0 -7
- package/dist/__tests__/index.test.js.map +0 -1
- package/dist/__tests__/narration-template.test.d.ts +0 -2
- package/dist/__tests__/narration-template.test.d.ts.map +0 -1
- package/dist/__tests__/narration-template.test.js +0 -76
- package/dist/__tests__/narration-template.test.js.map +0 -1
- package/dist/__tests__/notification-model.test.d.ts +0 -2
- package/dist/__tests__/notification-model.test.d.ts.map +0 -1
- package/dist/__tests__/notification-model.test.js +0 -68
- package/dist/__tests__/notification-model.test.js.map +0 -1
- package/dist/__tests__/realtime-agent-picker-models.test.d.ts +0 -2
- package/dist/__tests__/realtime-agent-picker-models.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-agent-picker-models.test.js +0 -49
- package/dist/__tests__/realtime-agent-picker-models.test.js.map +0 -1
- package/dist/__tests__/realtime-audio-visuals.test.d.ts +0 -2
- package/dist/__tests__/realtime-audio-visuals.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-audio-visuals.test.js +0 -123
- package/dist/__tests__/realtime-audio-visuals.test.js.map +0 -1
- package/dist/__tests__/realtime-delegation-card-cancel.test.d.ts +0 -2
- package/dist/__tests__/realtime-delegation-card-cancel.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-delegation-card-cancel.test.js +0 -48
- package/dist/__tests__/realtime-delegation-card-cancel.test.js.map +0 -1
- package/dist/__tests__/realtime-disclosure.test.d.ts +0 -2
- package/dist/__tests__/realtime-disclosure.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-disclosure.test.js +0 -164
- package/dist/__tests__/realtime-disclosure.test.js.map +0 -1
- package/dist/__tests__/realtime-pairing.test.d.ts +0 -2
- package/dist/__tests__/realtime-pairing.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-pairing.test.js +0 -207
- package/dist/__tests__/realtime-pairing.test.js.map +0 -1
- package/dist/__tests__/realtime-review-lifecycle.test.d.ts +0 -2
- package/dist/__tests__/realtime-review-lifecycle.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-review-lifecycle.test.js +0 -154
- package/dist/__tests__/realtime-review-lifecycle.test.js.map +0 -1
- package/dist/__tests__/realtime-session-cancel-usage.test.d.ts +0 -2
- package/dist/__tests__/realtime-session-cancel-usage.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-session-cancel-usage.test.js +0 -230
- package/dist/__tests__/realtime-session-cancel-usage.test.js.map +0 -1
- package/dist/__tests__/realtime-session-channels.test.d.ts +0 -2
- package/dist/__tests__/realtime-session-channels.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-session-channels.test.js +0 -252
- package/dist/__tests__/realtime-session-channels.test.js.map +0 -1
- package/dist/__tests__/realtime-session-client-tools.test.d.ts +0 -2
- package/dist/__tests__/realtime-session-client-tools.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-session-client-tools.test.js +0 -103
- package/dist/__tests__/realtime-session-client-tools.test.js.map +0 -1
- package/dist/__tests__/realtime-session-minimized.test.d.ts +0 -2
- package/dist/__tests__/realtime-session-minimized.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-session-minimized.test.js +0 -32
- package/dist/__tests__/realtime-session-minimized.test.js.map +0 -1
- package/dist/__tests__/realtime-session-mint.test.d.ts +0 -2
- package/dist/__tests__/realtime-session-mint.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-session-mint.test.js +0 -69
- package/dist/__tests__/realtime-session-mint.test.js.map +0 -1
- package/dist/__tests__/realtime-session-policy.test.d.ts +0 -2
- package/dist/__tests__/realtime-session-policy.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-session-policy.test.js +0 -303
- package/dist/__tests__/realtime-session-policy.test.js.map +0 -1
- package/dist/__tests__/realtime-session-review.service.test.d.ts +0 -2
- package/dist/__tests__/realtime-session-review.service.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-session-review.service.test.js +0 -743
- package/dist/__tests__/realtime-session-review.service.test.js.map +0 -1
- package/dist/__tests__/realtime-session-state.test.d.ts +0 -2
- package/dist/__tests__/realtime-session-state.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-session-state.test.js +0 -83
- package/dist/__tests__/realtime-session-state.test.js.map +0 -1
- package/dist/__tests__/realtime-session-timeline-card.test.d.ts +0 -2
- package/dist/__tests__/realtime-session-timeline-card.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-session-timeline-card.test.js +0 -106
- package/dist/__tests__/realtime-session-timeline-card.test.js.map +0 -1
- package/dist/__tests__/realtime-session-timeline.test.d.ts +0 -2
- package/dist/__tests__/realtime-session-timeline.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-session-timeline.test.js +0 -142
- package/dist/__tests__/realtime-session-timeline.test.js.map +0 -1
- package/dist/__tests__/realtime-sessions-adapter.test.d.ts +0 -19
- package/dist/__tests__/realtime-sessions-adapter.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-sessions-adapter.test.js +0 -188
- package/dist/__tests__/realtime-sessions-adapter.test.js.map +0 -1
- package/dist/__tests__/realtime-surface-panel-prefs.test.d.ts +0 -2
- package/dist/__tests__/realtime-surface-panel-prefs.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-surface-panel-prefs.test.js +0 -100
- package/dist/__tests__/realtime-surface-panel-prefs.test.js.map +0 -1
- package/dist/__tests__/realtime-surface-tabs-model.test.d.ts +0 -2
- package/dist/__tests__/realtime-surface-tabs-model.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-surface-tabs-model.test.js +0 -193
- package/dist/__tests__/realtime-surface-tabs-model.test.js.map +0 -1
- package/dist/__tests__/remote-browser-audio-player.test.d.ts +0 -2
- package/dist/__tests__/remote-browser-audio-player.test.d.ts.map +0 -1
- package/dist/__tests__/remote-browser-audio-player.test.js +0 -137
- package/dist/__tests__/remote-browser-audio-player.test.js.map +0 -1
- package/dist/__tests__/remote-browser-channel.test.d.ts +0 -2
- package/dist/__tests__/remote-browser-channel.test.d.ts.map +0 -1
- package/dist/__tests__/remote-browser-channel.test.js +0 -585
- package/dist/__tests__/remote-browser-channel.test.js.map +0 -1
- package/dist/__tests__/search-service.test.d.ts +0 -2
- package/dist/__tests__/search-service.test.d.ts.map +0 -1
- package/dist/__tests__/search-service.test.js +0 -114
- package/dist/__tests__/search-service.test.js.map +0 -1
- package/dist/__tests__/slot-defaults.test.d.ts +0 -24
- package/dist/__tests__/slot-defaults.test.d.ts.map +0 -1
- package/dist/__tests__/slot-defaults.test.js +0 -63
- package/dist/__tests__/slot-defaults.test.js.map +0 -1
- package/dist/__tests__/toast-service.test.d.ts +0 -2
- package/dist/__tests__/toast-service.test.d.ts.map +0 -1
- package/dist/__tests__/toast-service.test.js +0 -143
- package/dist/__tests__/toast-service.test.js.map +0 -1
- package/dist/__tests__/user-authorization.test.d.ts +0 -2
- package/dist/__tests__/user-authorization.test.d.ts.map +0 -1
- package/dist/__tests__/user-authorization.test.js +0 -97
- package/dist/__tests__/user-authorization.test.js.map +0 -1
- package/dist/__tests__/voice-session-narration.test.d.ts +0 -2
- package/dist/__tests__/voice-session-narration.test.d.ts.map +0 -1
- package/dist/__tests__/voice-session-narration.test.js +0 -609
- package/dist/__tests__/voice-session-narration.test.js.map +0 -1
- package/dist/__tests__/whiteboard-artifact-viewer.test.d.ts +0 -2
- package/dist/__tests__/whiteboard-artifact-viewer.test.d.ts.map +0 -1
- package/dist/__tests__/whiteboard-artifact-viewer.test.js +0 -101
- package/dist/__tests__/whiteboard-artifact-viewer.test.js.map +0 -1
- package/dist/__tests__/whiteboard-channel.test.d.ts +0 -2
- package/dist/__tests__/whiteboard-channel.test.d.ts.map +0 -1
- package/dist/__tests__/whiteboard-channel.test.js +0 -260
- package/dist/__tests__/whiteboard-channel.test.js.map +0 -1
- package/dist/__tests__/whiteboard-restore-state.test.d.ts +0 -2
- package/dist/__tests__/whiteboard-restore-state.test.d.ts.map +0 -1
- package/dist/__tests__/whiteboard-restore-state.test.js +0 -108
- package/dist/__tests__/whiteboard-restore-state.test.js.map +0 -1
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import { encodePcm16Wav, PeakAccumulator } from './realtime-pcm-wav';
|
|
2
|
+
/**
|
|
3
|
+
* Browser-side audio recorder for a CLIENT-DIRECT realtime voice session.
|
|
4
|
+
*
|
|
5
|
+
* Mixes the user's microphone stream with the agent's remote-audio stream (when the driver
|
|
6
|
+
* exposes one — `OpenAIRealtimeClient.GetRemoteMediaStream()`) into a single mono **WAV** (16-bit
|
|
7
|
+
* PCM) blob via the Web Audio API. Rather than a `MediaRecorder` (which only emits webm/opus —
|
|
8
|
+
* header-less, no duration/cues, so HTTP-range seeking is unreliable), the mix is routed into an
|
|
9
|
+
* {@link AudioWorkletNode} (preferred) or a {@link ScriptProcessorNode} (fallback) that captures
|
|
10
|
+
* Float32 PCM frames. PCM is accumulated in memory and encoded as a seekable RIFF/WAVE file at
|
|
11
|
+
* {@link Stop}. WAV's header carries the exact duration and maps byte-offset → time linearly, so
|
|
12
|
+
* range seeking is exact and waveform peaks are computed cheaply during capture. The tradeoff is
|
|
13
|
+
* size (PCM WAV is larger than opus) — acceptable for these short session recordings.
|
|
14
|
+
*
|
|
15
|
+
* Robust + MVP by design — every step degrades gracefully:
|
|
16
|
+
* - No `AudioContext` (server-side render / unsupported env) → the recorder disables itself
|
|
17
|
+
* ({@link IsRecording} stays `false`, {@link Stop} resolves `null`); the voice call proceeds.
|
|
18
|
+
* - No `AudioWorkletNode` AND no `ScriptProcessorNode` → disables itself rather than throwing.
|
|
19
|
+
* - No remote stream (non-WebRTC drivers, or the track hasn't landed yet) → records mic only.
|
|
20
|
+
* - Any construction/start error is logged and disables the recorder rather than throwing into
|
|
21
|
+
* the session-start path.
|
|
22
|
+
*
|
|
23
|
+
* Not an Angular service: it owns transient per-session media plumbing the
|
|
24
|
+
* {@link RealtimeSessionService} creates and discards per call, so a plain class is the right
|
|
25
|
+
* shape (no DI, no singleton).
|
|
26
|
+
*/
|
|
27
|
+
export class RealtimeAudioRecorder {
|
|
28
|
+
/** The container/codec we now produce — a seekable mono 16-bit PCM WAV. */
|
|
29
|
+
static WavMimeType = 'audio/wav';
|
|
30
|
+
/** Target waveform-peak resolution (buckets) across the whole recording. */
|
|
31
|
+
static PeakBuckets = 600;
|
|
32
|
+
/** PCM frame size for the ScriptProcessor fallback (worklet uses its native 128-frame quantum). */
|
|
33
|
+
static ScriptProcessorBufferSize = 4096;
|
|
34
|
+
/** Inline AudioWorklet processor: forwards each 128-frame mono input block to the main thread. */
|
|
35
|
+
static WorkletProcessorSource = `
|
|
36
|
+
class MJPcmCaptureProcessor extends AudioWorkletProcessor {
|
|
37
|
+
process(inputs) {
|
|
38
|
+
const input = inputs[0];
|
|
39
|
+
if (input && input[0] && input[0].length > 0) {
|
|
40
|
+
// Copy out of the reused worklet buffer before posting.
|
|
41
|
+
this.port.postMessage(input[0].slice(0));
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
registerProcessor('mj-pcm-capture', MJPcmCaptureProcessor);
|
|
47
|
+
`;
|
|
48
|
+
audioContext = null;
|
|
49
|
+
destination = null;
|
|
50
|
+
workletNode = null;
|
|
51
|
+
scriptNode = null;
|
|
52
|
+
/** Captured mono Float32 PCM frames (in capture order); concatenated + encoded to WAV at Stop. */
|
|
53
|
+
pcmFrames = [];
|
|
54
|
+
/** Total captured sample count across {@link pcmFrames} (kept in step to avoid re-summing). */
|
|
55
|
+
totalSamples = 0;
|
|
56
|
+
/** Streaming waveform-peak accumulator — bounded regardless of recording length. */
|
|
57
|
+
peaks = new PeakAccumulator(RealtimeAudioRecorder.PeakBuckets);
|
|
58
|
+
/** Final normalized peaks, snapshotted at {@link Stop} so {@link GetPeaks} survives cleanup. */
|
|
59
|
+
finalPeaks = [];
|
|
60
|
+
recording = false;
|
|
61
|
+
/** True once the agent's remote stream has been wired into the mix (prevents double-attach). */
|
|
62
|
+
remoteAttached = false;
|
|
63
|
+
/** A remote stream handed to AttachRemoteStream before the audio graph was ready; wired at setup. */
|
|
64
|
+
pendingRemoteStream = null;
|
|
65
|
+
/** Sample index up to which crash-recovery shards have already been emitted (the segment cursor). */
|
|
66
|
+
flushedSampleCount = 0;
|
|
67
|
+
/** Wall-clock ms at recording start; anchors {@link NowOffsetMs}. 0 before {@link Start}. */
|
|
68
|
+
startedAtMs = 0;
|
|
69
|
+
/** Sample rate of the capturing {@link AudioContext}; 0 before {@link Start}. Needed to wrap shards. */
|
|
70
|
+
sampleRate = 0;
|
|
71
|
+
/** True while a recorder is actively capturing. */
|
|
72
|
+
get IsRecording() {
|
|
73
|
+
return this.recording;
|
|
74
|
+
}
|
|
75
|
+
/** Wall-clock ms when recording started (0 before {@link Start}). */
|
|
76
|
+
get StartedAtMs() {
|
|
77
|
+
return this.startedAtMs;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The container/codec the active recording uses. Always `'audio/wav'` once recording (empty
|
|
81
|
+
* string before {@link Start} / when disabled) — the consolidated upload and the WAV-wrapped
|
|
82
|
+
* crash-recovery shards are all 16-bit PCM WAV.
|
|
83
|
+
*/
|
|
84
|
+
get MimeType() {
|
|
85
|
+
return this.recording ? RealtimeAudioRecorder.WavMimeType : '';
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The captured AudioContext sample rate (0 before {@link Start} / when disabled). Exposed so a
|
|
89
|
+
* recovery path can WAV-wrap the raw-PCM shards with the correct rate.
|
|
90
|
+
*/
|
|
91
|
+
get SampleRate() {
|
|
92
|
+
return this.sampleRate;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Begins recording a mix of `micStream` and (when present) `remoteStream`. Best-effort:
|
|
96
|
+
* a missing `AudioContext` / no worklet+scriptprocessor support / any setup failure disables
|
|
97
|
+
* the recorder silently (logged) so the live call is never blocked.
|
|
98
|
+
*
|
|
99
|
+
* @param micStream The user's microphone capture stream (required).
|
|
100
|
+
* @param remoteStream The agent's remote-audio stream, or `null` for mic-only capture.
|
|
101
|
+
*/
|
|
102
|
+
Start(micStream, remoteStream) {
|
|
103
|
+
if (this.recording) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (typeof AudioContext === 'undefined') {
|
|
107
|
+
console.warn('[RealtimeAudioRecorder] AudioContext unavailable — recording disabled for this session.');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// Mark recording + stamp t0 SYNCHRONOUSLY so a caller that checks IsRecording immediately after
|
|
111
|
+
// Start() (the session service does) sees `true`, even though the audio-graph setup
|
|
112
|
+
// (AudioContext.resume + worklet module load) completes asynchronously below. Without this the
|
|
113
|
+
// caller discards the recorder before it finishes starting and nothing is ever uploaded.
|
|
114
|
+
this.startedAtMs = Date.now();
|
|
115
|
+
this.finalPeaks = [];
|
|
116
|
+
this.recording = true;
|
|
117
|
+
// Fire-and-forget the async setup (it must `await audioContext.resume()` + worklet load); any
|
|
118
|
+
// failure disables the recorder rather than throwing into the session-start path.
|
|
119
|
+
void this.startMixedRecording(micStream, remoteStream).catch((error) => {
|
|
120
|
+
console.warn('[RealtimeAudioRecorder] Failed to start recording — disabling for this session:', error);
|
|
121
|
+
this.cleanup();
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Wires the agent's remote-audio stream into the live mix once it arrives. The WebRTC
|
|
126
|
+
* `ontrack` that delivers the agent voice usually lands AFTER recording has already begun
|
|
127
|
+
* (mic-only), so the session attaches it here when it lands. Idempotent, and safe to call
|
|
128
|
+
* before the audio graph finishes its async setup (the stream is connected once ready).
|
|
129
|
+
*/
|
|
130
|
+
AttachRemoteStream(stream) {
|
|
131
|
+
if (this.remoteAttached || stream.getAudioTracks().length === 0) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (this.audioContext && this.captureNode()) {
|
|
135
|
+
this.connectStream(this.audioContext, stream);
|
|
136
|
+
this.remoteAttached = true;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
// Audio-graph setup still in flight (awaiting resume / worklet load) — connect at setup.
|
|
140
|
+
this.pendingRemoteStream = stream;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Returns a Blob of the raw PCM captured since the last call (a window of header-less,
|
|
145
|
+
* little-endian PCM16 bytes), advancing the segment cursor. `null` when nothing new. Used to
|
|
146
|
+
* upload crash-recovery shards mid-call. NOTE: shards are header-less raw PCM (NOT individually
|
|
147
|
+
* playable WAV) — recovery is concatenation in order, then WAV-wrapping with {@link SampleRate}.
|
|
148
|
+
* The canonical consolidated file is still authored as a single WAV from full memory at {@link Stop}.
|
|
149
|
+
*/
|
|
150
|
+
SnapshotNewSegment() {
|
|
151
|
+
if (!this.recording || this.flushedSampleCount >= this.totalSamples) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
const fresh = this.collectSamples(this.flushedSampleCount, this.totalSamples);
|
|
155
|
+
this.flushedSampleCount = this.totalSamples;
|
|
156
|
+
if (fresh.length === 0) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const pcm16 = this.floatToPcm16Bytes(fresh);
|
|
160
|
+
return new Blob([pcm16], { type: 'application/octet-stream' });
|
|
161
|
+
}
|
|
162
|
+
/** Ms elapsed since recording started (>= 0); 0 when not recording. */
|
|
163
|
+
NowOffsetMs() {
|
|
164
|
+
if (this.startedAtMs === 0) {
|
|
165
|
+
return 0;
|
|
166
|
+
}
|
|
167
|
+
return Math.max(0, Date.now() - this.startedAtMs);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The downsampled waveform peaks (each 0..1, max-abs per bucket, ~{@link RealtimeAudioRecorder.PeakBuckets}
|
|
171
|
+
* buckets across the whole recording). Computed incrementally during capture so it's cheap and
|
|
172
|
+
* bounded regardless of length. Returns `[]` before any audio / for a silent recording. Safe to
|
|
173
|
+
* call after {@link Stop} — the peaks are snapshotted there before {@link cleanup} clears state.
|
|
174
|
+
*/
|
|
175
|
+
GetPeaks() {
|
|
176
|
+
return this.recording ? this.peaks.Normalize() : this.finalPeaks;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Stops recording and resolves the captured audio as a single mono 16-bit PCM **WAV**
|
|
180
|
+
* {@link Blob}, then releases the audio graph. Resolves `null` when nothing was recorded
|
|
181
|
+
* (recorder disabled, or no samples captured). Safe to call when not recording.
|
|
182
|
+
*/
|
|
183
|
+
async Stop() {
|
|
184
|
+
if (!this.recording) {
|
|
185
|
+
this.cleanup();
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
// Detach the capture node so no further frames arrive while we assemble the file. Snapshot the
|
|
189
|
+
// peaks BEFORE cleanup so GetPeaks() still returns them for the post-Stop upload.
|
|
190
|
+
this.recording = false;
|
|
191
|
+
this.finalPeaks = this.peaks.Normalize();
|
|
192
|
+
const blob = this.encodeWavBlob();
|
|
193
|
+
this.cleanup();
|
|
194
|
+
return blob;
|
|
195
|
+
}
|
|
196
|
+
/** Wires up the Web Audio mix graph + PCM capture node (worklet preferred) and starts capture. */
|
|
197
|
+
async startMixedRecording(micStream, remoteStream) {
|
|
198
|
+
const audioContext = new AudioContext();
|
|
199
|
+
// CRITICAL: browsers create an AudioContext in the `suspended` state under the autoplay policy.
|
|
200
|
+
// Without resuming it, the mix graph never processes audio and we capture only silence. Starting
|
|
201
|
+
// a voice call is a user gesture, so resume() succeeds here.
|
|
202
|
+
if (audioContext.state === 'suspended') {
|
|
203
|
+
await audioContext.resume();
|
|
204
|
+
}
|
|
205
|
+
const destination = audioContext.createMediaStreamDestination();
|
|
206
|
+
// Publish the context + sample rate BEFORE wiring the remote so a concurrent AttachRemoteStream()
|
|
207
|
+
// (the agent track can land during the awaits above) sees a ready graph to connect to.
|
|
208
|
+
this.audioContext = audioContext;
|
|
209
|
+
this.destination = destination;
|
|
210
|
+
this.sampleRate = audioContext.sampleRate;
|
|
211
|
+
// Build the PCM capture node: AudioWorklet preferred, ScriptProcessor fallback. Either way the
|
|
212
|
+
// node both pulls audio from the mix AND drives the destination so the graph keeps processing.
|
|
213
|
+
await this.createCaptureNode(audioContext, destination);
|
|
214
|
+
this.connectStream(audioContext, micStream);
|
|
215
|
+
// Connect the agent's stream if it was passed now, or if AttachRemoteStream() stashed one while
|
|
216
|
+
// we were setting up. Either way, capture the agent voice — not just the mic.
|
|
217
|
+
const remote = remoteStream ?? this.pendingRemoteStream;
|
|
218
|
+
this.pendingRemoteStream = null;
|
|
219
|
+
if (remote && remote.getAudioTracks().length > 0) {
|
|
220
|
+
this.connectStream(audioContext, remote);
|
|
221
|
+
this.remoteAttached = true;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Creates the PCM capture node and inserts it into the graph: sources → captureNode → destination.
|
|
226
|
+
* Prefers an {@link AudioWorkletNode} (off-main-thread, no deprecation); falls back to a
|
|
227
|
+
* {@link ScriptProcessorNode} where worklets are unavailable. Throws when neither is supported
|
|
228
|
+
* (caught by {@link Start}, which then disables the recorder).
|
|
229
|
+
*/
|
|
230
|
+
async createCaptureNode(audioContext, destination) {
|
|
231
|
+
if (await this.tryCreateWorkletNode(audioContext, destination)) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
this.createScriptProcessorNode(audioContext, destination);
|
|
235
|
+
}
|
|
236
|
+
/** Attempts to build the AudioWorklet capture node; returns `false` (no throw) when unsupported. */
|
|
237
|
+
async tryCreateWorkletNode(audioContext, destination) {
|
|
238
|
+
if (!audioContext.audioWorklet || typeof AudioWorkletNode === 'undefined' || typeof Blob === 'undefined') {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
const moduleUrl = URL.createObjectURL(new Blob([RealtimeAudioRecorder.WorkletProcessorSource], { type: 'application/javascript' }));
|
|
243
|
+
try {
|
|
244
|
+
await audioContext.audioWorklet.addModule(moduleUrl);
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
URL.revokeObjectURL(moduleUrl);
|
|
248
|
+
}
|
|
249
|
+
const node = new AudioWorkletNode(audioContext, 'mj-pcm-capture', { numberOfInputs: 1, numberOfOutputs: 1 });
|
|
250
|
+
node.port.onmessage = (event) => this.captureFrame(event.data);
|
|
251
|
+
node.connect(destination);
|
|
252
|
+
this.workletNode = node;
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
console.warn('[RealtimeAudioRecorder] AudioWorklet unavailable — falling back to ScriptProcessor:', error);
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/** Builds the ScriptProcessor fallback capture node and inserts it into the graph. */
|
|
261
|
+
createScriptProcessorNode(audioContext, destination) {
|
|
262
|
+
if (typeof audioContext.createScriptProcessor !== 'function') {
|
|
263
|
+
throw new Error('Neither AudioWorklet nor ScriptProcessor is supported');
|
|
264
|
+
}
|
|
265
|
+
const node = audioContext.createScriptProcessor(RealtimeAudioRecorder.ScriptProcessorBufferSize, 1, 1);
|
|
266
|
+
node.onaudioprocess = (event) => {
|
|
267
|
+
// Copy out of the reused input buffer before retaining.
|
|
268
|
+
this.captureFrame(event.inputBuffer.getChannelData(0).slice(0));
|
|
269
|
+
};
|
|
270
|
+
// ScriptProcessor only fires onaudioprocess while connected to the destination.
|
|
271
|
+
node.connect(destination);
|
|
272
|
+
this.scriptNode = node;
|
|
273
|
+
}
|
|
274
|
+
/** The active capture node (worklet or script processor), or `null` before setup. */
|
|
275
|
+
captureNode() {
|
|
276
|
+
return this.workletNode ?? this.scriptNode;
|
|
277
|
+
}
|
|
278
|
+
/** Absorbs one captured PCM frame: accumulate samples + update the bounded peak array. */
|
|
279
|
+
captureFrame(frame) {
|
|
280
|
+
if (!this.recording || frame.length === 0) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
// Defensive copy — worklet/script buffers are reused by the engine.
|
|
284
|
+
const copy = frame.slice(0);
|
|
285
|
+
this.pcmFrames.push(copy);
|
|
286
|
+
this.totalSamples += copy.length;
|
|
287
|
+
this.peaks.Push(copy);
|
|
288
|
+
}
|
|
289
|
+
/** Routes one stream's audio tracks into the capture node (no-op when track-less / no node). */
|
|
290
|
+
connectStream(audioContext, stream) {
|
|
291
|
+
const node = this.captureNode();
|
|
292
|
+
if (!node || stream.getAudioTracks().length === 0) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const source = audioContext.createMediaStreamSource(stream);
|
|
296
|
+
source.connect(node);
|
|
297
|
+
}
|
|
298
|
+
/** Concatenates a half-open sample range [from, to) across the captured frames into one Float32Array. */
|
|
299
|
+
collectSamples(from, to) {
|
|
300
|
+
const out = new Float32Array(Math.max(0, to - from));
|
|
301
|
+
if (out.length === 0) {
|
|
302
|
+
return out;
|
|
303
|
+
}
|
|
304
|
+
let cursor = 0; // absolute sample index at the start of the current frame
|
|
305
|
+
let written = 0;
|
|
306
|
+
for (const frame of this.pcmFrames) {
|
|
307
|
+
const frameStart = cursor;
|
|
308
|
+
const frameEnd = cursor + frame.length;
|
|
309
|
+
cursor = frameEnd;
|
|
310
|
+
if (frameEnd <= from) {
|
|
311
|
+
continue; // entirely before the window
|
|
312
|
+
}
|
|
313
|
+
if (frameStart >= to) {
|
|
314
|
+
break; // entirely after the window
|
|
315
|
+
}
|
|
316
|
+
const sliceStart = Math.max(0, from - frameStart);
|
|
317
|
+
const sliceEnd = Math.min(frame.length, to - frameStart);
|
|
318
|
+
for (let i = sliceStart; i < sliceEnd; i++) {
|
|
319
|
+
out[written++] = frame[i];
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return written === out.length ? out : out.subarray(0, written);
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Converts a mono Float32 buffer to a little-endian PCM16 `ArrayBuffer` (for raw crash-recovery
|
|
326
|
+
* shards). Returns the backing `ArrayBuffer` directly so it's a valid `BlobPart`.
|
|
327
|
+
*/
|
|
328
|
+
floatToPcm16Bytes(samples) {
|
|
329
|
+
const buffer = new ArrayBuffer(samples.length * 2);
|
|
330
|
+
const view = new DataView(buffer);
|
|
331
|
+
for (let i = 0; i < samples.length; i++) {
|
|
332
|
+
const s = samples[i] < -1 ? -1 : samples[i] > 1 ? 1 : samples[i];
|
|
333
|
+
view.setInt16(i * 2, s < 0 ? s * 0x8000 : s * 0x7fff, true);
|
|
334
|
+
}
|
|
335
|
+
return buffer;
|
|
336
|
+
}
|
|
337
|
+
/** Encodes all accumulated PCM into a single seekable WAV blob, or `null` when nothing captured. */
|
|
338
|
+
encodeWavBlob() {
|
|
339
|
+
if (this.totalSamples === 0 || this.sampleRate === 0) {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
const all = this.collectSamples(0, this.totalSamples);
|
|
343
|
+
const wav = encodePcm16Wav(all, this.sampleRate);
|
|
344
|
+
return new Blob([wav], { type: RealtimeAudioRecorder.WavMimeType });
|
|
345
|
+
}
|
|
346
|
+
/** Releases the audio graph + capture node and resets state. Idempotent. */
|
|
347
|
+
cleanup() {
|
|
348
|
+
this.recording = false;
|
|
349
|
+
this.pcmFrames = [];
|
|
350
|
+
this.totalSamples = 0;
|
|
351
|
+
this.flushedSampleCount = 0;
|
|
352
|
+
this.sampleRate = 0;
|
|
353
|
+
this.peaks = new PeakAccumulator(RealtimeAudioRecorder.PeakBuckets);
|
|
354
|
+
this.remoteAttached = false;
|
|
355
|
+
this.pendingRemoteStream = null;
|
|
356
|
+
if (this.workletNode) {
|
|
357
|
+
this.workletNode.port.onmessage = null;
|
|
358
|
+
try {
|
|
359
|
+
this.workletNode.disconnect();
|
|
360
|
+
}
|
|
361
|
+
catch { /* already disconnected */ }
|
|
362
|
+
this.workletNode = null;
|
|
363
|
+
}
|
|
364
|
+
if (this.scriptNode) {
|
|
365
|
+
this.scriptNode.onaudioprocess = null;
|
|
366
|
+
try {
|
|
367
|
+
this.scriptNode.disconnect();
|
|
368
|
+
}
|
|
369
|
+
catch { /* already disconnected */ }
|
|
370
|
+
this.scriptNode = null;
|
|
371
|
+
}
|
|
372
|
+
this.destination = null;
|
|
373
|
+
if (this.audioContext) {
|
|
374
|
+
void this.audioContext.close().catch(() => { });
|
|
375
|
+
this.audioContext = null;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
//# sourceMappingURL=realtime-audio-recorder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime-audio-recorder.js","sourceRoot":"","sources":["../../../src/lib/services/realtime-audio-recorder.ts"],"names":[],"mappings":"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","sourcesContent":["import { encodePcm16Wav, PeakAccumulator } from './realtime-pcm-wav';\n\n/**\n * Browser-side audio recorder for a CLIENT-DIRECT realtime voice session.\n *\n * Mixes the user's microphone stream with the agent's remote-audio stream (when the driver\n * exposes one — `OpenAIRealtimeClient.GetRemoteMediaStream()`) into a single mono **WAV** (16-bit\n * PCM) blob via the Web Audio API. Rather than a `MediaRecorder` (which only emits webm/opus —\n * header-less, no duration/cues, so HTTP-range seeking is unreliable), the mix is routed into an\n * {@link AudioWorkletNode} (preferred) or a {@link ScriptProcessorNode} (fallback) that captures\n * Float32 PCM frames. PCM is accumulated in memory and encoded as a seekable RIFF/WAVE file at\n * {@link Stop}. WAV's header carries the exact duration and maps byte-offset → time linearly, so\n * range seeking is exact and waveform peaks are computed cheaply during capture. The tradeoff is\n * size (PCM WAV is larger than opus) — acceptable for these short session recordings.\n *\n * Robust + MVP by design — every step degrades gracefully:\n * - No `AudioContext` (server-side render / unsupported env) → the recorder disables itself\n * ({@link IsRecording} stays `false`, {@link Stop} resolves `null`); the voice call proceeds.\n * - No `AudioWorkletNode` AND no `ScriptProcessorNode` → disables itself rather than throwing.\n * - No remote stream (non-WebRTC drivers, or the track hasn't landed yet) → records mic only.\n * - Any construction/start error is logged and disables the recorder rather than throwing into\n * the session-start path.\n *\n * Not an Angular service: it owns transient per-session media plumbing the\n * {@link RealtimeSessionService} creates and discards per call, so a plain class is the right\n * shape (no DI, no singleton).\n */\nexport class RealtimeAudioRecorder {\n /** The container/codec we now produce — a seekable mono 16-bit PCM WAV. */\n private static readonly WavMimeType = 'audio/wav';\n /** Target waveform-peak resolution (buckets) across the whole recording. */\n private static readonly PeakBuckets = 600;\n /** PCM frame size for the ScriptProcessor fallback (worklet uses its native 128-frame quantum). */\n private static readonly ScriptProcessorBufferSize = 4096;\n /** Inline AudioWorklet processor: forwards each 128-frame mono input block to the main thread. */\n private static readonly WorkletProcessorSource = `\n class MJPcmCaptureProcessor extends AudioWorkletProcessor {\n process(inputs) {\n const input = inputs[0];\n if (input && input[0] && input[0].length > 0) {\n // Copy out of the reused worklet buffer before posting.\n this.port.postMessage(input[0].slice(0));\n }\n return true;\n }\n }\n registerProcessor('mj-pcm-capture', MJPcmCaptureProcessor);\n `;\n\n private audioContext: AudioContext | null = null;\n private destination: MediaStreamAudioDestinationNode | null = null;\n private workletNode: AudioWorkletNode | null = null;\n private scriptNode: ScriptProcessorNode | null = null;\n /** Captured mono Float32 PCM frames (in capture order); concatenated + encoded to WAV at Stop. */\n private pcmFrames: Float32Array[] = [];\n /** Total captured sample count across {@link pcmFrames} (kept in step to avoid re-summing). */\n private totalSamples = 0;\n /** Streaming waveform-peak accumulator — bounded regardless of recording length. */\n private peaks = new PeakAccumulator(RealtimeAudioRecorder.PeakBuckets);\n /** Final normalized peaks, snapshotted at {@link Stop} so {@link GetPeaks} survives cleanup. */\n private finalPeaks: number[] = [];\n private recording = false;\n\n /** True once the agent's remote stream has been wired into the mix (prevents double-attach). */\n private remoteAttached = false;\n /** A remote stream handed to AttachRemoteStream before the audio graph was ready; wired at setup. */\n private pendingRemoteStream: MediaStream | null = null;\n\n /** Sample index up to which crash-recovery shards have already been emitted (the segment cursor). */\n private flushedSampleCount = 0;\n\n /** Wall-clock ms at recording start; anchors {@link NowOffsetMs}. 0 before {@link Start}. */\n private startedAtMs = 0;\n\n /** Sample rate of the capturing {@link AudioContext}; 0 before {@link Start}. Needed to wrap shards. */\n private sampleRate = 0;\n\n /** True while a recorder is actively capturing. */\n public get IsRecording(): boolean {\n return this.recording;\n }\n\n /** Wall-clock ms when recording started (0 before {@link Start}). */\n public get StartedAtMs(): number {\n return this.startedAtMs;\n }\n\n /**\n * The container/codec the active recording uses. Always `'audio/wav'` once recording (empty\n * string before {@link Start} / when disabled) — the consolidated upload and the WAV-wrapped\n * crash-recovery shards are all 16-bit PCM WAV.\n */\n public get MimeType(): string {\n return this.recording ? RealtimeAudioRecorder.WavMimeType : '';\n }\n\n /**\n * The captured AudioContext sample rate (0 before {@link Start} / when disabled). Exposed so a\n * recovery path can WAV-wrap the raw-PCM shards with the correct rate.\n */\n public get SampleRate(): number {\n return this.sampleRate;\n }\n\n /**\n * Begins recording a mix of `micStream` and (when present) `remoteStream`. Best-effort:\n * a missing `AudioContext` / no worklet+scriptprocessor support / any setup failure disables\n * the recorder silently (logged) so the live call is never blocked.\n *\n * @param micStream The user's microphone capture stream (required).\n * @param remoteStream The agent's remote-audio stream, or `null` for mic-only capture.\n */\n public Start(micStream: MediaStream, remoteStream: MediaStream | null): void {\n if (this.recording) {\n return;\n }\n if (typeof AudioContext === 'undefined') {\n console.warn('[RealtimeAudioRecorder] AudioContext unavailable — recording disabled for this session.');\n return;\n }\n // Mark recording + stamp t0 SYNCHRONOUSLY so a caller that checks IsRecording immediately after\n // Start() (the session service does) sees `true`, even though the audio-graph setup\n // (AudioContext.resume + worklet module load) completes asynchronously below. Without this the\n // caller discards the recorder before it finishes starting and nothing is ever uploaded.\n this.startedAtMs = Date.now();\n this.finalPeaks = [];\n this.recording = true;\n // Fire-and-forget the async setup (it must `await audioContext.resume()` + worklet load); any\n // failure disables the recorder rather than throwing into the session-start path.\n void this.startMixedRecording(micStream, remoteStream).catch((error) => {\n console.warn('[RealtimeAudioRecorder] Failed to start recording — disabling for this session:', error);\n this.cleanup();\n });\n }\n\n /**\n * Wires the agent's remote-audio stream into the live mix once it arrives. The WebRTC\n * `ontrack` that delivers the agent voice usually lands AFTER recording has already begun\n * (mic-only), so the session attaches it here when it lands. Idempotent, and safe to call\n * before the audio graph finishes its async setup (the stream is connected once ready).\n */\n public AttachRemoteStream(stream: MediaStream): void {\n if (this.remoteAttached || stream.getAudioTracks().length === 0) {\n return;\n }\n if (this.audioContext && this.captureNode()) {\n this.connectStream(this.audioContext, stream);\n this.remoteAttached = true;\n } else {\n // Audio-graph setup still in flight (awaiting resume / worklet load) — connect at setup.\n this.pendingRemoteStream = stream;\n }\n }\n\n /**\n * Returns a Blob of the raw PCM captured since the last call (a window of header-less,\n * little-endian PCM16 bytes), advancing the segment cursor. `null` when nothing new. Used to\n * upload crash-recovery shards mid-call. NOTE: shards are header-less raw PCM (NOT individually\n * playable WAV) — recovery is concatenation in order, then WAV-wrapping with {@link SampleRate}.\n * The canonical consolidated file is still authored as a single WAV from full memory at {@link Stop}.\n */\n public SnapshotNewSegment(): Blob | null {\n if (!this.recording || this.flushedSampleCount >= this.totalSamples) {\n return null;\n }\n const fresh = this.collectSamples(this.flushedSampleCount, this.totalSamples);\n this.flushedSampleCount = this.totalSamples;\n if (fresh.length === 0) {\n return null;\n }\n const pcm16 = this.floatToPcm16Bytes(fresh);\n return new Blob([pcm16], { type: 'application/octet-stream' });\n }\n\n /** Ms elapsed since recording started (>= 0); 0 when not recording. */\n public NowOffsetMs(): number {\n if (this.startedAtMs === 0) {\n return 0;\n }\n return Math.max(0, Date.now() - this.startedAtMs);\n }\n\n /**\n * The downsampled waveform peaks (each 0..1, max-abs per bucket, ~{@link RealtimeAudioRecorder.PeakBuckets}\n * buckets across the whole recording). Computed incrementally during capture so it's cheap and\n * bounded regardless of length. Returns `[]` before any audio / for a silent recording. Safe to\n * call after {@link Stop} — the peaks are snapshotted there before {@link cleanup} clears state.\n */\n public GetPeaks(): number[] {\n return this.recording ? this.peaks.Normalize() : this.finalPeaks;\n }\n\n /**\n * Stops recording and resolves the captured audio as a single mono 16-bit PCM **WAV**\n * {@link Blob}, then releases the audio graph. Resolves `null` when nothing was recorded\n * (recorder disabled, or no samples captured). Safe to call when not recording.\n */\n public async Stop(): Promise<Blob | null> {\n if (!this.recording) {\n this.cleanup();\n return null;\n }\n // Detach the capture node so no further frames arrive while we assemble the file. Snapshot the\n // peaks BEFORE cleanup so GetPeaks() still returns them for the post-Stop upload.\n this.recording = false;\n this.finalPeaks = this.peaks.Normalize();\n const blob = this.encodeWavBlob();\n this.cleanup();\n return blob;\n }\n\n /** Wires up the Web Audio mix graph + PCM capture node (worklet preferred) and starts capture. */\n private async startMixedRecording(micStream: MediaStream, remoteStream: MediaStream | null): Promise<void> {\n const audioContext = new AudioContext();\n // CRITICAL: browsers create an AudioContext in the `suspended` state under the autoplay policy.\n // Without resuming it, the mix graph never processes audio and we capture only silence. Starting\n // a voice call is a user gesture, so resume() succeeds here.\n if (audioContext.state === 'suspended') {\n await audioContext.resume();\n }\n const destination = audioContext.createMediaStreamDestination();\n // Publish the context + sample rate BEFORE wiring the remote so a concurrent AttachRemoteStream()\n // (the agent track can land during the awaits above) sees a ready graph to connect to.\n this.audioContext = audioContext;\n this.destination = destination;\n this.sampleRate = audioContext.sampleRate;\n\n // Build the PCM capture node: AudioWorklet preferred, ScriptProcessor fallback. Either way the\n // node both pulls audio from the mix AND drives the destination so the graph keeps processing.\n await this.createCaptureNode(audioContext, destination);\n\n this.connectStream(audioContext, micStream);\n // Connect the agent's stream if it was passed now, or if AttachRemoteStream() stashed one while\n // we were setting up. Either way, capture the agent voice — not just the mic.\n const remote = remoteStream ?? this.pendingRemoteStream;\n this.pendingRemoteStream = null;\n if (remote && remote.getAudioTracks().length > 0) {\n this.connectStream(audioContext, remote);\n this.remoteAttached = true;\n }\n }\n\n /**\n * Creates the PCM capture node and inserts it into the graph: sources → captureNode → destination.\n * Prefers an {@link AudioWorkletNode} (off-main-thread, no deprecation); falls back to a\n * {@link ScriptProcessorNode} where worklets are unavailable. Throws when neither is supported\n * (caught by {@link Start}, which then disables the recorder).\n */\n private async createCaptureNode(audioContext: AudioContext, destination: MediaStreamAudioDestinationNode): Promise<void> {\n if (await this.tryCreateWorkletNode(audioContext, destination)) {\n return;\n }\n this.createScriptProcessorNode(audioContext, destination);\n }\n\n /** Attempts to build the AudioWorklet capture node; returns `false` (no throw) when unsupported. */\n private async tryCreateWorkletNode(audioContext: AudioContext, destination: MediaStreamAudioDestinationNode): Promise<boolean> {\n if (!audioContext.audioWorklet || typeof AudioWorkletNode === 'undefined' || typeof Blob === 'undefined') {\n return false;\n }\n try {\n const moduleUrl = URL.createObjectURL(\n new Blob([RealtimeAudioRecorder.WorkletProcessorSource], { type: 'application/javascript' }),\n );\n try {\n await audioContext.audioWorklet.addModule(moduleUrl);\n } finally {\n URL.revokeObjectURL(moduleUrl);\n }\n const node = new AudioWorkletNode(audioContext, 'mj-pcm-capture', { numberOfInputs: 1, numberOfOutputs: 1 });\n node.port.onmessage = (event: MessageEvent<Float32Array>) => this.captureFrame(event.data);\n node.connect(destination);\n this.workletNode = node;\n return true;\n } catch (error) {\n console.warn('[RealtimeAudioRecorder] AudioWorklet unavailable — falling back to ScriptProcessor:', error);\n return false;\n }\n }\n\n /** Builds the ScriptProcessor fallback capture node and inserts it into the graph. */\n private createScriptProcessorNode(audioContext: AudioContext, destination: MediaStreamAudioDestinationNode): void {\n if (typeof audioContext.createScriptProcessor !== 'function') {\n throw new Error('Neither AudioWorklet nor ScriptProcessor is supported');\n }\n const node = audioContext.createScriptProcessor(RealtimeAudioRecorder.ScriptProcessorBufferSize, 1, 1);\n node.onaudioprocess = (event: AudioProcessingEvent) => {\n // Copy out of the reused input buffer before retaining.\n this.captureFrame(event.inputBuffer.getChannelData(0).slice(0));\n };\n // ScriptProcessor only fires onaudioprocess while connected to the destination.\n node.connect(destination);\n this.scriptNode = node;\n }\n\n /** The active capture node (worklet or script processor), or `null` before setup. */\n private captureNode(): AudioNode | null {\n return this.workletNode ?? this.scriptNode;\n }\n\n /** Absorbs one captured PCM frame: accumulate samples + update the bounded peak array. */\n private captureFrame(frame: Float32Array): void {\n if (!this.recording || frame.length === 0) {\n return;\n }\n // Defensive copy — worklet/script buffers are reused by the engine.\n const copy = frame.slice(0);\n this.pcmFrames.push(copy);\n this.totalSamples += copy.length;\n this.peaks.Push(copy);\n }\n\n /** Routes one stream's audio tracks into the capture node (no-op when track-less / no node). */\n private connectStream(audioContext: AudioContext, stream: MediaStream): void {\n const node = this.captureNode();\n if (!node || stream.getAudioTracks().length === 0) {\n return;\n }\n const source = audioContext.createMediaStreamSource(stream);\n source.connect(node);\n }\n\n /** Concatenates a half-open sample range [from, to) across the captured frames into one Float32Array. */\n private collectSamples(from: number, to: number): Float32Array {\n const out = new Float32Array(Math.max(0, to - from));\n if (out.length === 0) {\n return out;\n }\n let cursor = 0; // absolute sample index at the start of the current frame\n let written = 0;\n for (const frame of this.pcmFrames) {\n const frameStart = cursor;\n const frameEnd = cursor + frame.length;\n cursor = frameEnd;\n if (frameEnd <= from) {\n continue; // entirely before the window\n }\n if (frameStart >= to) {\n break; // entirely after the window\n }\n const sliceStart = Math.max(0, from - frameStart);\n const sliceEnd = Math.min(frame.length, to - frameStart);\n for (let i = sliceStart; i < sliceEnd; i++) {\n out[written++] = frame[i];\n }\n }\n return written === out.length ? out : out.subarray(0, written);\n }\n\n /**\n * Converts a mono Float32 buffer to a little-endian PCM16 `ArrayBuffer` (for raw crash-recovery\n * shards). Returns the backing `ArrayBuffer` directly so it's a valid `BlobPart`.\n */\n private floatToPcm16Bytes(samples: Float32Array): ArrayBuffer {\n const buffer = new ArrayBuffer(samples.length * 2);\n const view = new DataView(buffer);\n for (let i = 0; i < samples.length; i++) {\n const s = samples[i] < -1 ? -1 : samples[i] > 1 ? 1 : samples[i];\n view.setInt16(i * 2, s < 0 ? s * 0x8000 : s * 0x7fff, true);\n }\n return buffer;\n }\n\n /** Encodes all accumulated PCM into a single seekable WAV blob, or `null` when nothing captured. */\n private encodeWavBlob(): Blob | null {\n if (this.totalSamples === 0 || this.sampleRate === 0) {\n return null;\n }\n const all = this.collectSamples(0, this.totalSamples);\n const wav = encodePcm16Wav(all, this.sampleRate);\n return new Blob([wav], { type: RealtimeAudioRecorder.WavMimeType });\n }\n\n /** Releases the audio graph + capture node and resets state. Idempotent. */\n private cleanup(): void {\n this.recording = false;\n this.pcmFrames = [];\n this.totalSamples = 0;\n this.flushedSampleCount = 0;\n this.sampleRate = 0;\n this.peaks = new PeakAccumulator(RealtimeAudioRecorder.PeakBuckets);\n this.remoteAttached = false;\n this.pendingRemoteStream = null;\n if (this.workletNode) {\n this.workletNode.port.onmessage = null;\n try { this.workletNode.disconnect(); } catch { /* already disconnected */ }\n this.workletNode = null;\n }\n if (this.scriptNode) {\n this.scriptNode.onaudioprocess = null;\n try { this.scriptNode.disconnect(); } catch { /* already disconnected */ }\n this.scriptNode = null;\n }\n this.destination = null;\n if (this.audioContext) {\n void this.audioContext.close().catch(() => { /* already closed */ });\n this.audioContext = null;\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Pure, browser-and-node-safe helpers for the CLIENT-DIRECT realtime recorder:
|
|
3
|
+
* encoding accumulated Float32 PCM into a seekable 16-bit PCM **WAV** container and computing a
|
|
4
|
+
* bounded, downsampled waveform **peak** array at capture time.
|
|
5
|
+
*
|
|
6
|
+
* These functions are deliberately free of any browser API (`AudioContext`, `Blob`, …) so they can
|
|
7
|
+
* be unit-tested in a plain Node environment. {@link RealtimeAudioRecorder} owns the live audio
|
|
8
|
+
* graph and feeds the captured samples here.
|
|
9
|
+
*
|
|
10
|
+
* Why WAV (PCM) over webm/opus: a RIFF/WAVE header carries the exact duration and maps a byte
|
|
11
|
+
* offset linearly to a time offset, so HTTP-range seeking is exact (no client-side duration nudge),
|
|
12
|
+
* and peak extraction is trivial. The tradeoff is size — PCM WAV is larger than opus — which is
|
|
13
|
+
* acceptable for these short session recordings.
|
|
14
|
+
*
|
|
15
|
+
* @module @memberjunction/ng-conversations/realtime-pcm-wav
|
|
16
|
+
*/
|
|
17
|
+
/** Canonical WAV/PCM header size in bytes (RIFF + fmt + data chunk headers). */
|
|
18
|
+
export declare const WAV_HEADER_BYTES = 44;
|
|
19
|
+
/**
|
|
20
|
+
* Encodes mono Float32 PCM samples as a canonical single-chunk 16-bit PCM WAV (RIFF/WAVE).
|
|
21
|
+
* The returned `ArrayBuffer` is `WAV_HEADER_BYTES + samples.length * 2` bytes — a proper
|
|
22
|
+
* 44-byte header followed by little-endian PCM16 data, so it is seekable and self-describing.
|
|
23
|
+
*
|
|
24
|
+
* @param samples Mono Float32 PCM in `[-1, 1]` (downmixed by the caller).
|
|
25
|
+
* @param sampleRate Sample rate in Hz (from the capturing `AudioContext`).
|
|
26
|
+
* @returns A standalone `ArrayBuffer` containing the full WAV file.
|
|
27
|
+
*/
|
|
28
|
+
export declare function encodePcm16Wav(samples: Float32Array, sampleRate: number): ArrayBuffer;
|
|
29
|
+
/**
|
|
30
|
+
* A bounded, streaming waveform-peak accumulator. It maintains a fixed number of buckets
|
|
31
|
+
* (`targetBuckets`) of max-absolute amplitude across the WHOLE recording regardless of length:
|
|
32
|
+
* once every bucket has absorbed `samplesPerBucket` samples, the buckets are halved (pairwise
|
|
33
|
+
* max-merged) and the per-bucket capacity doubles — so memory + cost stay O(targetBuckets) for an
|
|
34
|
+
* arbitrarily long call. {@link Normalize} returns the final 0..1 peaks.
|
|
35
|
+
*/
|
|
36
|
+
export declare class PeakAccumulator {
|
|
37
|
+
/** Target (max) number of buckets in the produced peak array. */
|
|
38
|
+
private readonly targetBuckets;
|
|
39
|
+
/** Current max-abs amplitude per bucket (length grows up to `2 * targetBuckets` before folding). */
|
|
40
|
+
private buckets;
|
|
41
|
+
/** How many samples each bucket currently absorbs before a new bucket opens. */
|
|
42
|
+
private samplesPerBucket;
|
|
43
|
+
/** Samples absorbed into the current (last) bucket so far. */
|
|
44
|
+
private samplesInCurrent;
|
|
45
|
+
/**
|
|
46
|
+
* @param targetBuckets Desired bucket count (waveform resolution). Clamped to >= 1.
|
|
47
|
+
* ~600 gives a smooth overview waveform at trivial cost.
|
|
48
|
+
*/
|
|
49
|
+
constructor(targetBuckets?: number);
|
|
50
|
+
/** Number of buckets currently held (for tests/inspection). */
|
|
51
|
+
get BucketCount(): number;
|
|
52
|
+
/** Absorbs a block of Float32 PCM samples, updating the running per-bucket max-abs. */
|
|
53
|
+
Push(samples: Float32Array): void;
|
|
54
|
+
/** Absorbs one sample into the current bucket, opening/folding buckets as capacity is reached. */
|
|
55
|
+
private pushSample;
|
|
56
|
+
/** Pairwise max-merges adjacent buckets, halving the count and doubling per-bucket capacity. */
|
|
57
|
+
private fold;
|
|
58
|
+
/**
|
|
59
|
+
* Produces the final normalized peak array (each value 0..1, divided by the global max).
|
|
60
|
+
* Trailing empty buckets are trimmed; an all-silent / empty recording yields `[]`.
|
|
61
|
+
*/
|
|
62
|
+
Normalize(): number[];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* One-shot peak downsampling of a complete Float32 PCM buffer into `targetBuckets` normalized
|
|
66
|
+
* max-abs peaks (0..1). The streaming {@link PeakAccumulator} is the production path; this is the
|
|
67
|
+
* direct, allocation-light equivalent used where the whole buffer is already in hand (and in tests).
|
|
68
|
+
*
|
|
69
|
+
* @param samples The full mono Float32 PCM buffer.
|
|
70
|
+
* @param targetBuckets Desired bucket count (clamped to >= 1). Buckets beyond the sample count are
|
|
71
|
+
* not produced — a short buffer yields at most `samples.length` peaks.
|
|
72
|
+
* @returns Normalized peaks; `[]` for an empty or all-silent buffer.
|
|
73
|
+
*/
|
|
74
|
+
export declare function downsamplePeaks(samples: Float32Array, targetBuckets?: number): number[];
|
|
75
|
+
//# sourceMappingURL=realtime-pcm-wav.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime-pcm-wav.d.ts","sourceRoot":"","sources":["../../../src/lib/services/realtime-pcm-wav.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH,gFAAgF;AAChF,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAcnC;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,CAkCrF;AASD;;;;;;GAMG;AACH,qBAAa,eAAe;IACxB,iEAAiE;IACjE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,oGAAoG;IACpG,OAAO,CAAC,OAAO,CAAgB;IAC/B,gFAAgF;IAChF,OAAO,CAAC,gBAAgB,CAAK;IAC7B,8DAA8D;IAC9D,OAAO,CAAC,gBAAgB,CAAK;IAE7B;;;OAGG;gBACS,aAAa,SAAM;IAI/B,+DAA+D;IAC/D,IAAW,WAAW,IAAI,MAAM,CAE/B;IAED,uFAAuF;IAChF,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI;IAMxC,kGAAkG;IAClG,OAAO,CAAC,UAAU;IAsBlB,gGAAgG;IAChG,OAAO,CAAC,IAAI;IAYZ;;;OAGG;IACI,SAAS,IAAI,MAAM,EAAE;CAgB/B;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,EAAE,aAAa,SAAM,GAAG,MAAM,EAAE,CA4BpF"}
|