@sdata/web-vue 3.22.2 → 3.23.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/es/index.d.ts +2 -2
- package/es/index.js +2 -1
- package/es/sender/index.d.ts +3 -1
- package/es/sender/index.js +2 -1
- package/es/sender/recorder-core.d.ts +25 -0
- package/es/sender/recorder-core.js +26 -0
- package/es/sender/sender.vue_vue_type_script_setup_true_lang.js +17 -19
- package/es/sender/types.d.ts +7 -60
- package/es/sender/use-recorder.d.ts +16 -0
- package/es/sender/use-recorder.js +117 -0
- package/json/vetur-attributes.json +1 -1
- package/json/web-types.json +2 -2
- package/package.json +2 -1
- package/es/sender/use-speech.d.ts +0 -19
- package/es/sender/use-speech.js +0 -365
package/es/index.d.ts
CHANGED
|
@@ -128,8 +128,8 @@ export { default as Scrollbar } from './scrollbar';
|
|
|
128
128
|
export type { ScrollbarElements, ScrollbarEventListener, ScrollbarEventListenerArgs, ScrollbarEventListeners, ScrollbarExpose, ScrollbarInstance, ScrollbarOptions, ScrollbarOptionsResolved, ScrollbarPlugin, ScrollbarProps, ScrollbarReadonlyOptions, ScrollbarState, ScrollbarType, ScrollbarUpdatedEvent, } from './scrollbar';
|
|
129
129
|
export { default as Select } from './select';
|
|
130
130
|
export type { FilterOption, SelectFallbackOption, SelectFieldNames, SelectInstance, SelectOption, SelectOptionData, SelectOptionGroup, SelectProps, } from './select';
|
|
131
|
-
export { default as Sender, SenderHeader, SenderSwitch } from './sender';
|
|
132
|
-
export type { SenderActionContext, SenderAllowSpeech, SenderComponents, SenderContentSlotConfig,
|
|
131
|
+
export { default as Sender, RecorderCore, SenderHeader, SenderSwitch } from './sender';
|
|
132
|
+
export type { RecorderCoreConstructor, RecorderCoreInstance, RecorderCoreOptions, SenderActionContext, SenderAllowSpeech, SenderComponents, SenderContentSlotConfig, SenderCustomSlotConfig, SenderCustomSlotRender, SenderCustomSlotState, SenderEmits, SenderFocusOptions, SenderHeaderInstance, SenderHeaderProps, SenderHeaderSemanticType, SenderInsertPosition, SenderInputSlotConfig, SenderInstance, SenderProps, SenderRef, SenderResolvedSlotConfig, SenderSelectSlotConfig, SenderSemanticType, SenderSkill, SenderSkillClosableConfig, SenderSlotConfig, SenderSlotConfigBase, SenderSubmitType, SenderSuffixPlacement, SenderSwitchInstance, SenderSwitchProps, SenderTagSlotConfig, SenderTextSlotConfig, SenderValue, } from './sender';
|
|
133
133
|
export { default as Secret } from './secret';
|
|
134
134
|
export type { SecretInstance } from './secret';
|
|
135
135
|
export { default as Skeleton, SkeletonLine, SkeletonShape } from './skeleton';
|
package/es/index.js
CHANGED
|
@@ -128,6 +128,7 @@ import { RICH_TEXT_EDITOR_BUILT_IN_NODE_NAMES, RICH_TEXT_EDITOR_JSON_FORM_NODE_N
|
|
|
128
128
|
import RichTextEditor from "./rich-text-editor/index.js";
|
|
129
129
|
import Secret from "./secret/index.js";
|
|
130
130
|
import SelectableCard from "./selectable-card/index.js";
|
|
131
|
+
import recorder_core_default from "./sender/recorder-core.js";
|
|
131
132
|
import Sender, { SenderHeader, SenderSwitch } from "./sender/index.js";
|
|
132
133
|
import line_default from "./skeleton/line.js";
|
|
133
134
|
import shape_default from "./skeleton/shape.js";
|
|
@@ -151,4 +152,4 @@ import Typography from "./typography/index.js";
|
|
|
151
152
|
import Upload from "./upload/index.js";
|
|
152
153
|
import Watermark from "./watermark/index.js";
|
|
153
154
|
import SDVue from "./sd-vue.js";
|
|
154
|
-
export { $createInlineComponentNode, $isInlineComponentNode, A2UI_0_9_1, Affix, Alert, Anchor, anchor_link_default as AnchorLink, AutoComplete, Avatar, AvatarGroup, BackTop, Badge, BasicCrudTable, BorderBeam, Breadcrumb, breadcrumb_item_default as BreadcrumbItem, Button, button_group_default as ButtonGroup, Calendar, Card, card_grid_default as CardGrid, card_meta_default as CardMeta, Carousel, carousel_item_default as CarouselItem, Cascader, cascader_panel_default as CascaderPanel, Checkbox, checkbox_group_default as CheckboxGroup, grid_col_default as Col, Collapse, collapse_item_default as CollapseItem, ColorPicker, Comment, ConfigProvider, Copy, countdown_default as Countdown, Cropper, DatePicker, Descriptions, descriptions_item_default as DescriptionsItem, dropdown_group_default as Dgroup, Divider, dropdown_option_default as Doption, Drawer, Dropdown, dropdown_button_default as DropdownButton, dropdown_submenu_default as Dsubmenu, Ellipsis, Empty, FilePreviewer, Form, form_item_default as FormItem, Grid, grid_item_default as GridItem, Icon, Image, preview_default as ImagePreview, preview_action_default as ImagePreviewAction, preview_group_default as ImagePreviewGroup, InlineComponentNode, Input, input_group_default as InputGroup, InputNumber, input_password_default as InputPassword, input_search_default as InputSearch, InputTag, JsonForm, KvList, Layout, content_default as LayoutContent, footer_default as LayoutFooter, header_default as LayoutHeader, sider_default as LayoutSider, Link, List, list_item_default as ListItem, list_item_meta_default as ListItemMeta, Mention, Menu, item_default as MenuItem, item_group_default as MenuItemGroup, Message, Modal, month_picker_default as MonthPicker, Notification, OverflowList, PageHeader, Pagination, PerformantEllipsis, Popconfirm, Popover, Progress, QrCode, quarter_picker_default as QuarterPicker, RICH_TEXT_EDITOR_BUILT_IN_NODE_NAMES, RICH_TEXT_EDITOR_JSON_FORM_NODE_NAMES, Radio, radio_group_default as RadioGroup, range_picker_default as RangePicker, Rate, ResizeBox, Result, RichTextEditor, grid_row_default as Row, Scrollbar, Secret, Select, SelectableCard, Sender, SenderHeader, SenderSwitch, Skeleton, line_default as SkeletonLine, shape_default as SkeletonShape, Slider, Space, Spin, Split, Statistic, step_default as Step, Steps, sub_menu_default as SubMenu, Switch, tab_pane_default as TabPane, Table, table_column_default as TableColumn, Tabs, Tag, TagGroup, table_tbody_default as Tbody, table_td_default as Td, Textarea, table_th_default as Th, table_thead_default as Thead, ThemeProvider, TimePicker, Timeline, item_default$1 as TimelineItem, Toolbar, Tooltip, Tour, table_tr_default as Tr, Transfer, Tree, TreeSelect, Trigger, Typography, paragraph_default as TypographyParagraph, text_default as TypographyText, title_default as TypographyTitle, Upload, VerificationCode, Watermark, week_picker_default as WeekPicker, year_picker_default as YearPicker, addI18nMessages, SDVue as default, defineJsonFormComponents, defineJsonFormSchemas, getCssVarToken, getLocale, useFormItem, useLocale };
|
|
155
|
+
export { $createInlineComponentNode, $isInlineComponentNode, A2UI_0_9_1, Affix, Alert, Anchor, anchor_link_default as AnchorLink, AutoComplete, Avatar, AvatarGroup, BackTop, Badge, BasicCrudTable, BorderBeam, Breadcrumb, breadcrumb_item_default as BreadcrumbItem, Button, button_group_default as ButtonGroup, Calendar, Card, card_grid_default as CardGrid, card_meta_default as CardMeta, Carousel, carousel_item_default as CarouselItem, Cascader, cascader_panel_default as CascaderPanel, Checkbox, checkbox_group_default as CheckboxGroup, grid_col_default as Col, Collapse, collapse_item_default as CollapseItem, ColorPicker, Comment, ConfigProvider, Copy, countdown_default as Countdown, Cropper, DatePicker, Descriptions, descriptions_item_default as DescriptionsItem, dropdown_group_default as Dgroup, Divider, dropdown_option_default as Doption, Drawer, Dropdown, dropdown_button_default as DropdownButton, dropdown_submenu_default as Dsubmenu, Ellipsis, Empty, FilePreviewer, Form, form_item_default as FormItem, Grid, grid_item_default as GridItem, Icon, Image, preview_default as ImagePreview, preview_action_default as ImagePreviewAction, preview_group_default as ImagePreviewGroup, InlineComponentNode, Input, input_group_default as InputGroup, InputNumber, input_password_default as InputPassword, input_search_default as InputSearch, InputTag, JsonForm, KvList, Layout, content_default as LayoutContent, footer_default as LayoutFooter, header_default as LayoutHeader, sider_default as LayoutSider, Link, List, list_item_default as ListItem, list_item_meta_default as ListItemMeta, Mention, Menu, item_default as MenuItem, item_group_default as MenuItemGroup, Message, Modal, month_picker_default as MonthPicker, Notification, OverflowList, PageHeader, Pagination, PerformantEllipsis, Popconfirm, Popover, Progress, QrCode, quarter_picker_default as QuarterPicker, RICH_TEXT_EDITOR_BUILT_IN_NODE_NAMES, RICH_TEXT_EDITOR_JSON_FORM_NODE_NAMES, Radio, radio_group_default as RadioGroup, range_picker_default as RangePicker, Rate, recorder_core_default as RecorderCore, ResizeBox, Result, RichTextEditor, grid_row_default as Row, Scrollbar, Secret, Select, SelectableCard, Sender, SenderHeader, SenderSwitch, Skeleton, line_default as SkeletonLine, shape_default as SkeletonShape, Slider, Space, Spin, Split, Statistic, step_default as Step, Steps, sub_menu_default as SubMenu, Switch, tab_pane_default as TabPane, Table, table_column_default as TableColumn, Tabs, Tag, TagGroup, table_tbody_default as Tbody, table_td_default as Td, Textarea, table_th_default as Th, table_thead_default as Thead, ThemeProvider, TimePicker, Timeline, item_default$1 as TimelineItem, Toolbar, Tooltip, Tour, table_tr_default as Tr, Transfer, Tree, TreeSelect, Trigger, Typography, paragraph_default as TypographyParagraph, text_default as TypographyText, title_default as TypographyTitle, Upload, VerificationCode, Watermark, week_picker_default as WeekPicker, year_picker_default as YearPicker, addI18nMessages, SDVue as default, defineJsonFormComponents, defineJsonFormSchemas, getCssVarToken, getLocale, useFormItem, useLocale };
|
package/es/sender/index.d.ts
CHANGED
|
@@ -112,8 +112,10 @@ export declare const SenderSwitch: {
|
|
|
112
112
|
unchecked?: (props: {}) => any;
|
|
113
113
|
};
|
|
114
114
|
});
|
|
115
|
+
export { default as RecorderCore } from './recorder-core';
|
|
116
|
+
export type { RecorderCore as RecorderCoreConstructor, RecorderCoreInstance, RecorderCoreOptions, } from './recorder-core';
|
|
115
117
|
export type SenderInstance = InstanceType<typeof _Sender>;
|
|
116
118
|
export type SenderHeaderInstance = InstanceType<typeof _SenderHeader>;
|
|
117
119
|
export type SenderSwitchInstance = InstanceType<typeof _SenderSwitch>;
|
|
118
|
-
export type { SenderActionContext, SenderAllowSpeech, SenderComponents, SenderContentSlotConfig,
|
|
120
|
+
export type { SenderActionContext, SenderAllowSpeech, SenderComponents, SenderContentSlotConfig, SenderCustomSlotConfig, SenderCustomSlotRender, SenderCustomSlotState, SenderEmits, SenderFocusOptions, SenderHeaderSemanticType, SenderHeaderProps, SenderInsertPosition, SenderInputSlotConfig, SenderProps, SenderRef, SenderResolvedSlotConfig, SenderSelectSlotConfig, SenderSemanticType, SenderSkill, SenderSkillClosableConfig, SenderSlotConfig, SenderSlotConfigBase, SenderSubmitType, SenderSuffixPlacement, SenderSwitchProps, SenderTagSlotConfig, SenderTextSlotConfig, SenderValue, } from './types';
|
|
119
121
|
export default Sender;
|
package/es/sender/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getComponentPrefix, setGlobalConfig } from "../_utils/global-config.js";
|
|
2
2
|
import sender_header_default from "./sender-header.js";
|
|
3
3
|
import sender_switch_default from "./sender-switch.js";
|
|
4
|
+
import recorder_core_default from "./recorder-core.js";
|
|
4
5
|
import sender_default from "./sender.js";
|
|
5
6
|
//#region components/sender/index.ts
|
|
6
7
|
var Sender = Object.assign(sender_default, {
|
|
@@ -17,4 +18,4 @@ var Sender = Object.assign(sender_default, {
|
|
|
17
18
|
var SenderHeader = sender_header_default;
|
|
18
19
|
var SenderSwitch = sender_switch_default;
|
|
19
20
|
//#endregion
|
|
20
|
-
export { SenderHeader, SenderSwitch, Sender as default };
|
|
21
|
+
export { recorder_core_default as RecorderCore, SenderHeader, SenderSwitch, Sender as default };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import Recorder from 'recorder-core';
|
|
2
|
+
import 'recorder-core/src/engine/beta-amr';
|
|
3
|
+
import 'recorder-core/src/engine/beta-amr-engine';
|
|
4
|
+
import 'recorder-core/src/engine/beta-ogg';
|
|
5
|
+
import 'recorder-core/src/engine/beta-ogg-engine';
|
|
6
|
+
import 'recorder-core/src/engine/beta-webm';
|
|
7
|
+
import 'recorder-core/src/engine/g711x';
|
|
8
|
+
import 'recorder-core/src/engine/mp3';
|
|
9
|
+
import 'recorder-core/src/engine/mp3-engine';
|
|
10
|
+
import 'recorder-core/src/engine/pcm';
|
|
11
|
+
import 'recorder-core/src/engine/wav';
|
|
12
|
+
import 'recorder-core/src/extensions/asr.aliyun.short';
|
|
13
|
+
import 'recorder-core/src/extensions/buffer_stream.player';
|
|
14
|
+
import 'recorder-core/src/extensions/create-audio.nmn2pcm';
|
|
15
|
+
import 'recorder-core/src/extensions/lib.fft';
|
|
16
|
+
import 'recorder-core/src/extensions/dtmf.decode';
|
|
17
|
+
import 'recorder-core/src/extensions/dtmf.encode';
|
|
18
|
+
import 'recorder-core/src/extensions/frequency.histogram.view';
|
|
19
|
+
import 'recorder-core/src/extensions/sonic';
|
|
20
|
+
import 'recorder-core/src/extensions/wavesurfer.view';
|
|
21
|
+
import 'recorder-core/src/extensions/waveview';
|
|
22
|
+
export type RecorderCore = typeof Recorder;
|
|
23
|
+
export type RecorderCoreInstance = ReturnType<RecorderCore>;
|
|
24
|
+
export type RecorderCoreOptions = Parameters<RecorderCore>[0];
|
|
25
|
+
export default Recorder;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import Recorder from "recorder-core";
|
|
2
|
+
import "recorder-core/src/engine/beta-amr";
|
|
3
|
+
import "recorder-core/src/engine/beta-amr-engine";
|
|
4
|
+
import "recorder-core/src/engine/beta-ogg";
|
|
5
|
+
import "recorder-core/src/engine/beta-ogg-engine";
|
|
6
|
+
import "recorder-core/src/engine/beta-webm";
|
|
7
|
+
import "recorder-core/src/engine/g711x";
|
|
8
|
+
import "recorder-core/src/engine/mp3";
|
|
9
|
+
import "recorder-core/src/engine/mp3-engine";
|
|
10
|
+
import "recorder-core/src/engine/pcm";
|
|
11
|
+
import "recorder-core/src/engine/wav";
|
|
12
|
+
import "recorder-core/src/extensions/asr.aliyun.short";
|
|
13
|
+
import "recorder-core/src/extensions/buffer_stream.player";
|
|
14
|
+
import "recorder-core/src/extensions/create-audio.nmn2pcm";
|
|
15
|
+
import "recorder-core/src/extensions/lib.fft";
|
|
16
|
+
import "recorder-core/src/extensions/dtmf.decode";
|
|
17
|
+
import "recorder-core/src/extensions/dtmf.encode";
|
|
18
|
+
import "recorder-core/src/extensions/frequency.histogram.view";
|
|
19
|
+
import "recorder-core/src/extensions/sonic";
|
|
20
|
+
import "recorder-core/src/extensions/wavesurfer.view";
|
|
21
|
+
import "recorder-core/src/extensions/waveview";
|
|
22
|
+
//#region components/sender/recorder-core.ts
|
|
23
|
+
Recorder.TrafficImgUrl = "";
|
|
24
|
+
var recorder_core_default = Recorder;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { recorder_core_default as default };
|
|
@@ -11,7 +11,7 @@ import { senderInjectionKey } from "./context.js";
|
|
|
11
11
|
import IconArrowUp from "../icon/icon-arrow-up/index.js";
|
|
12
12
|
import IconMute from "../icon/icon-mute/index.js";
|
|
13
13
|
import IconVoice from "../icon/icon-voice/index.js";
|
|
14
|
-
import {
|
|
14
|
+
import { useRecorder } from "./use-recorder.js";
|
|
15
15
|
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, getCurrentInstance, h, mergeProps, nextTick, normalizeClass, normalizeStyle, openBlock, provide, renderSlot, resolveDynamicComponent, shallowRef, toRef, unref, useAttrs, watch, withCtx } from "vue";
|
|
16
16
|
import { createReusableTemplate } from "@vueuse/core";
|
|
17
17
|
import { INSERT_LINE_BREAK_COMMAND } from "lexical";
|
|
@@ -29,7 +29,7 @@ var sender_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCom
|
|
|
29
29
|
submitType: { default: "enter" },
|
|
30
30
|
disabled: { type: Boolean },
|
|
31
31
|
slotConfig: {},
|
|
32
|
-
allowSpeech: { type:
|
|
32
|
+
allowSpeech: { type: Boolean },
|
|
33
33
|
autoSize: {
|
|
34
34
|
type: [Boolean, Object],
|
|
35
35
|
default: () => ({ maxRows: 8 })
|
|
@@ -56,13 +56,8 @@ var sender_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCom
|
|
|
56
56
|
"pasteFile",
|
|
57
57
|
"focus",
|
|
58
58
|
"blur",
|
|
59
|
-
"speechStart",
|
|
60
|
-
"speechData",
|
|
61
59
|
"speechEnd",
|
|
62
|
-
"speechError"
|
|
63
|
-
"speechTransportOpen",
|
|
64
|
-
"speechTransportMessage",
|
|
65
|
-
"speechTransportClose"
|
|
60
|
+
"speechError"
|
|
66
61
|
],
|
|
67
62
|
setup(__props, { expose: __expose, emit: __emit }) {
|
|
68
63
|
const props = __props;
|
|
@@ -404,24 +399,19 @@ var sender_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCom
|
|
|
404
399
|
if (cancelDisabled.value || props.disabled) return;
|
|
405
400
|
emit("cancel");
|
|
406
401
|
};
|
|
407
|
-
const { available: speechAvailable, recording, requesting: speechRequesting, statusText: speechStatusText, trigger: triggerSpeech } =
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
onEnd: (event) => emit("speechEnd", event),
|
|
411
|
-
onError: (event) => emit("speechError", event),
|
|
412
|
-
onTransportOpen: (event) => emit("speechTransportOpen", event),
|
|
413
|
-
onTransportMessage: (event) => emit("speechTransportMessage", event),
|
|
414
|
-
onTransportClose: (event) => emit("speechTransportClose", event)
|
|
402
|
+
const { Recorder, available: speechAvailable, recorder, recording, requesting: speechRequesting, stopping: speechStopping, statusText: speechStatusText, trigger: triggerSpeech } = useRecorder(toRef(props, "allowSpeech"), {
|
|
403
|
+
onStop: (blob, duration, mime) => emit("speechEnd", blob, duration, mime),
|
|
404
|
+
onError: (message, isUserNotAllow) => emit("speechError", message, isUserNotAllow)
|
|
415
405
|
});
|
|
416
406
|
const actionContext = computed(() => ({
|
|
417
407
|
send: triggerSend,
|
|
418
408
|
clear: triggerClear,
|
|
419
409
|
cancel: triggerCancel,
|
|
420
|
-
speech:
|
|
410
|
+
speech: triggerSpeech,
|
|
421
411
|
submitDisabled: Boolean(props.disabled || submitDisabled.value),
|
|
422
412
|
clearDisabled: Boolean(props.disabled || clearDisabled.value),
|
|
423
413
|
cancelDisabled: Boolean(props.disabled || cancelDisabled.value),
|
|
424
|
-
speechDisabled: Boolean(props.disabled || !speechAvailable.value || speechRequesting.value),
|
|
414
|
+
speechDisabled: Boolean(props.disabled || !speechAvailable.value || speechRequesting.value || speechStopping.value),
|
|
425
415
|
recording: recording.value,
|
|
426
416
|
loading: Boolean(props.loading)
|
|
427
417
|
}));
|
|
@@ -657,6 +647,14 @@ var sender_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCom
|
|
|
657
647
|
}
|
|
658
648
|
});
|
|
659
649
|
__expose({
|
|
650
|
+
Recorder,
|
|
651
|
+
get recorder() {
|
|
652
|
+
var _recorder$value;
|
|
653
|
+
return (_recorder$value = recorder.value) !== null && _recorder$value !== void 0 ? _recorder$value : null;
|
|
654
|
+
},
|
|
655
|
+
get recording() {
|
|
656
|
+
return recording.value;
|
|
657
|
+
},
|
|
660
658
|
get inputElement() {
|
|
661
659
|
var _slotInputRef$value$r, _slotInputRef$value9;
|
|
662
660
|
return isSlotMode.value ? (_slotInputRef$value$r = (_slotInputRef$value9 = slotInputRef.value) === null || _slotInputRef$value9 === void 0 ? void 0 : _slotInputRef$value9.rootElement) !== null && _slotInputRef$value$r !== void 0 ? _slotInputRef$value$r : null : getNativeTextarea();
|
|
@@ -681,7 +679,7 @@ var sender_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCom
|
|
|
681
679
|
}, [renderSlot(_ctx.$slots, "suffix", { actions: actionContext.value }, () => [createElementVNode("div", { class: normalizeClass(`${unref(prefixCls)}-actions-list-presets`) }, [__props.allowSpeech ? (openBlock(), createBlock(unref(Button), {
|
|
682
680
|
key: 0,
|
|
683
681
|
type: "text",
|
|
684
|
-
loading: unref(speechRequesting),
|
|
682
|
+
loading: unref(speechRequesting) || unref(speechStopping),
|
|
685
683
|
disabled: actionContext.value.speechDisabled,
|
|
686
684
|
title: unref(speechStatusText),
|
|
687
685
|
class: normalizeClass([`${unref(prefixCls)}-actions-btn`, { [`${unref(prefixCls)}-actions-btn-disabled`]: actionContext.value.speechDisabled }]),
|
package/es/sender/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CSSProperties, Component, VNodeChild } from 'vue';
|
|
2
2
|
import type { TooltipProps } from '../tooltip';
|
|
3
|
+
import type { RecorderCore, RecorderCoreInstance, RecorderCoreOptions } from './recorder-core';
|
|
3
4
|
export type SenderSubmitType = 'enter' | 'shiftEnter';
|
|
4
5
|
export type SenderInsertPosition = 'start' | 'end' | 'cursor';
|
|
5
6
|
export type SenderSuffixPlacement = 'content' | 'footer';
|
|
@@ -7,59 +8,7 @@ export type SenderSemanticType = 'root' | 'prefix' | 'input' | 'suffix' | 'foote
|
|
|
7
8
|
export interface SenderComponents {
|
|
8
9
|
input?: Component;
|
|
9
10
|
}
|
|
10
|
-
export
|
|
11
|
-
recording?: boolean;
|
|
12
|
-
onRecordingChange?: (recording: boolean) => void;
|
|
13
|
-
audioConstraints?: MediaTrackConstraints;
|
|
14
|
-
bufferSize?: number;
|
|
15
|
-
workletUrl?: string;
|
|
16
|
-
processorName?: string;
|
|
17
|
-
processorOptions?: Record<string, unknown>;
|
|
18
|
-
url?: string;
|
|
19
|
-
protocols?: string | string[];
|
|
20
|
-
sendMetadata?: boolean;
|
|
21
|
-
}
|
|
22
|
-
export type SenderControlledSpeechConfig = SenderSpeechConfig;
|
|
23
|
-
export type SenderAllowSpeech = boolean | SenderSpeechConfig;
|
|
24
|
-
export interface SenderSpeechStartEvent {
|
|
25
|
-
source: 'capture' | 'controlled';
|
|
26
|
-
startedAt: number;
|
|
27
|
-
stream?: MediaStream;
|
|
28
|
-
audioContext?: AudioContext;
|
|
29
|
-
sampleRate?: number;
|
|
30
|
-
}
|
|
31
|
-
export interface SenderSpeechDataEvent {
|
|
32
|
-
buffer: ArrayBuffer;
|
|
33
|
-
sampleRate: number;
|
|
34
|
-
sequence: number;
|
|
35
|
-
timestamp: number;
|
|
36
|
-
}
|
|
37
|
-
export type SenderSpeechEndReason = 'manual' | 'controlled' | 'error' | 'unmount';
|
|
38
|
-
export interface SenderSpeechEndEvent {
|
|
39
|
-
source: 'capture' | 'controlled';
|
|
40
|
-
reason: SenderSpeechEndReason;
|
|
41
|
-
startedAt: number;
|
|
42
|
-
endedAt: number;
|
|
43
|
-
duration: number;
|
|
44
|
-
chunks: number;
|
|
45
|
-
}
|
|
46
|
-
export type SenderSpeechErrorPhase = 'permission' | 'audioContext' | 'audioWorklet' | 'transport';
|
|
47
|
-
export interface SenderSpeechErrorEvent {
|
|
48
|
-
error: Error;
|
|
49
|
-
phase: SenderSpeechErrorPhase;
|
|
50
|
-
}
|
|
51
|
-
export interface SenderSpeechTransportOpenEvent {
|
|
52
|
-
event: Event;
|
|
53
|
-
socket: WebSocket;
|
|
54
|
-
}
|
|
55
|
-
export interface SenderSpeechTransportMessageEvent {
|
|
56
|
-
event: MessageEvent;
|
|
57
|
-
socket: WebSocket;
|
|
58
|
-
}
|
|
59
|
-
export interface SenderSpeechTransportCloseEvent {
|
|
60
|
-
event: CloseEvent;
|
|
61
|
-
socket: WebSocket;
|
|
62
|
-
}
|
|
11
|
+
export type SenderAllowSpeech = boolean | RecorderCoreOptions;
|
|
63
12
|
export interface SenderSkillClosableConfig {
|
|
64
13
|
closeIcon?: VNodeChild;
|
|
65
14
|
onClose?: (event: MouseEvent) => void;
|
|
@@ -144,6 +93,9 @@ export interface SenderFocusOptions {
|
|
|
144
93
|
key?: string;
|
|
145
94
|
}
|
|
146
95
|
export interface SenderRef {
|
|
96
|
+
readonly Recorder: RecorderCore;
|
|
97
|
+
readonly recorder: RecorderCoreInstance | null;
|
|
98
|
+
readonly recording: boolean;
|
|
147
99
|
readonly inputElement: HTMLTextAreaElement | HTMLDivElement | null;
|
|
148
100
|
readonly nativeElement: HTMLDivElement | null;
|
|
149
101
|
focus: (options?: SenderFocusOptions) => void;
|
|
@@ -201,13 +153,8 @@ export interface SenderEmits {
|
|
|
201
153
|
'pasteFile': [files: FileList];
|
|
202
154
|
'focus': [event: FocusEvent];
|
|
203
155
|
'blur': [event: FocusEvent];
|
|
204
|
-
'
|
|
205
|
-
'
|
|
206
|
-
'speechEnd': [event: SenderSpeechEndEvent];
|
|
207
|
-
'speechError': [event: SenderSpeechErrorEvent];
|
|
208
|
-
'speechTransportOpen': [event: SenderSpeechTransportOpenEvent];
|
|
209
|
-
'speechTransportMessage': [event: SenderSpeechTransportMessageEvent];
|
|
210
|
-
'speechTransportClose': [event: SenderSpeechTransportCloseEvent];
|
|
156
|
+
'speechEnd': [blob: Blob, duration: number, mime: string];
|
|
157
|
+
'speechError': [message: string, isUserNotAllow: boolean];
|
|
211
158
|
}
|
|
212
159
|
export type SenderHeaderSemanticType = 'header' | 'content';
|
|
213
160
|
export interface SenderHeaderProps {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type MaybeRefOrGetter } from 'vue';
|
|
2
|
+
import type { SenderAllowSpeech } from './types';
|
|
3
|
+
export interface UseRecorderOptions {
|
|
4
|
+
onStop?: (blob: Blob, duration: number, mime: string) => void;
|
|
5
|
+
onError?: (message: string, isUserNotAllow: boolean) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function useRecorder(allowSpeech: MaybeRefOrGetter<SenderAllowSpeech | undefined>, options?: UseRecorderOptions): {
|
|
8
|
+
Recorder: any;
|
|
9
|
+
available: import("vue").ComputedRef<boolean>;
|
|
10
|
+
recorder: import("vue").ShallowRef<any, any>;
|
|
11
|
+
recording: import("vue").ShallowRef<boolean, boolean>;
|
|
12
|
+
requesting: import("vue").ShallowRef<boolean, boolean>;
|
|
13
|
+
stopping: import("vue").ShallowRef<boolean, boolean>;
|
|
14
|
+
statusText: import("vue").ComputedRef<string>;
|
|
15
|
+
trigger: () => void;
|
|
16
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { useI18n } from "../locale/index.js";
|
|
2
|
+
import recorder_core_default from "./recorder-core.js";
|
|
3
|
+
import { computed, onBeforeUnmount, onMounted, shallowRef, toValue, watch } from "vue";
|
|
4
|
+
//#region components/sender/use-recorder.ts
|
|
5
|
+
function useRecorder(allowSpeech, options = {}) {
|
|
6
|
+
const { t } = useI18n();
|
|
7
|
+
const config = computed(() => toValue(allowSpeech));
|
|
8
|
+
const supported = shallowRef(false);
|
|
9
|
+
const recording = shallowRef(false);
|
|
10
|
+
const requesting = shallowRef(false);
|
|
11
|
+
const stopping = shallowRef(false);
|
|
12
|
+
const permissionDenied = shallowRef(false);
|
|
13
|
+
const captureError = shallowRef();
|
|
14
|
+
const recorder = shallowRef();
|
|
15
|
+
let mounted = false;
|
|
16
|
+
let supportChecked = false;
|
|
17
|
+
let disposed = false;
|
|
18
|
+
const available = computed(() => Boolean(config.value) && supported.value && !permissionDenied.value);
|
|
19
|
+
const statusText = computed(() => {
|
|
20
|
+
if (requesting.value) return t("sender.speech.requestingPermission");
|
|
21
|
+
if (permissionDenied.value) return t("sender.speech.permissionDenied");
|
|
22
|
+
if (captureError.value) return t("sender.speech.microphoneUnavailable");
|
|
23
|
+
if (!supported.value) return t("sender.speech.unsupported");
|
|
24
|
+
if (recording.value) return t("sender.speech.stop");
|
|
25
|
+
return t("sender.speech.start");
|
|
26
|
+
});
|
|
27
|
+
const close = () => {
|
|
28
|
+
var _recorder$value;
|
|
29
|
+
(_recorder$value = recorder.value) === null || _recorder$value === void 0 || _recorder$value.close();
|
|
30
|
+
recorder.value = void 0;
|
|
31
|
+
recording.value = false;
|
|
32
|
+
requesting.value = false;
|
|
33
|
+
stopping.value = false;
|
|
34
|
+
};
|
|
35
|
+
const stop = () => {
|
|
36
|
+
const current = recorder.value;
|
|
37
|
+
if (!current || !recording.value || requesting.value || stopping.value) return;
|
|
38
|
+
stopping.value = true;
|
|
39
|
+
const finish = () => {
|
|
40
|
+
recording.value = false;
|
|
41
|
+
requesting.value = false;
|
|
42
|
+
stopping.value = false;
|
|
43
|
+
recorder.value = void 0;
|
|
44
|
+
};
|
|
45
|
+
current.stop((blob, duration, mime) => {
|
|
46
|
+
var _options$onStop;
|
|
47
|
+
if (disposed) return;
|
|
48
|
+
(_options$onStop = options.onStop) === null || _options$onStop === void 0 || _options$onStop.call(options, blob, duration, mime);
|
|
49
|
+
finish();
|
|
50
|
+
}, (message) => {
|
|
51
|
+
var _options$onError;
|
|
52
|
+
if (disposed) return;
|
|
53
|
+
(_options$onError = options.onError) === null || _options$onError === void 0 || _options$onError.call(options, message, false);
|
|
54
|
+
finish();
|
|
55
|
+
}, true);
|
|
56
|
+
};
|
|
57
|
+
const start = () => {
|
|
58
|
+
if (!available.value || requesting.value || recording.value || stopping.value) return;
|
|
59
|
+
requesting.value = true;
|
|
60
|
+
permissionDenied.value = false;
|
|
61
|
+
captureError.value = void 0;
|
|
62
|
+
const current = recorder_core_default(typeof config.value === "object" ? config.value : {});
|
|
63
|
+
recorder.value = current;
|
|
64
|
+
current.open(() => {
|
|
65
|
+
if (disposed || recorder.value !== current) {
|
|
66
|
+
current.close();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
current.start();
|
|
70
|
+
recording.value = true;
|
|
71
|
+
requesting.value = false;
|
|
72
|
+
}, (message, isUserNotAllow) => {
|
|
73
|
+
var _options$onError2;
|
|
74
|
+
current.close();
|
|
75
|
+
if (disposed || recorder.value !== current) return;
|
|
76
|
+
recorder.value = void 0;
|
|
77
|
+
permissionDenied.value = isUserNotAllow;
|
|
78
|
+
if (!isUserNotAllow) captureError.value = message;
|
|
79
|
+
recording.value = false;
|
|
80
|
+
requesting.value = false;
|
|
81
|
+
(_options$onError2 = options.onError) === null || _options$onError2 === void 0 || _options$onError2.call(options, message, isUserNotAllow);
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
const trigger = () => {
|
|
85
|
+
if (recording.value) stop();
|
|
86
|
+
else start();
|
|
87
|
+
};
|
|
88
|
+
const checkSupport = () => {
|
|
89
|
+
if (supportChecked) return;
|
|
90
|
+
supportChecked = true;
|
|
91
|
+
supported.value = recorder_core_default.Support();
|
|
92
|
+
};
|
|
93
|
+
onMounted(() => {
|
|
94
|
+
mounted = true;
|
|
95
|
+
if (config.value) checkSupport();
|
|
96
|
+
});
|
|
97
|
+
watch(() => Boolean(config.value), (enabled) => {
|
|
98
|
+
if (enabled && mounted) checkSupport();
|
|
99
|
+
else if (!enabled) close();
|
|
100
|
+
});
|
|
101
|
+
onBeforeUnmount(() => {
|
|
102
|
+
disposed = true;
|
|
103
|
+
close();
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
Recorder: recorder_core_default,
|
|
107
|
+
available,
|
|
108
|
+
recorder,
|
|
109
|
+
recording,
|
|
110
|
+
requesting,
|
|
111
|
+
stopping,
|
|
112
|
+
statusText,
|
|
113
|
+
trigger
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
export { useRecorder };
|
|
@@ -3038,7 +3038,7 @@
|
|
|
3038
3038
|
"description": "Structured slot configuration"
|
|
3039
3039
|
},
|
|
3040
3040
|
"sd-sender/allow-speech": {
|
|
3041
|
-
"description": "
|
|
3041
|
+
"description": "Whether recording is enabled; an object value is passed to recorder-core as-is"
|
|
3042
3042
|
},
|
|
3043
3043
|
"sd-sender/auto-size": {
|
|
3044
3044
|
"description": "Textarea auto-size configuration"
|
package/json/web-types.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json",
|
|
3
3
|
"framework": "vue",
|
|
4
4
|
"name": "@sdata/web-vue",
|
|
5
|
-
"version": "3.22.
|
|
5
|
+
"version": "3.22.3",
|
|
6
6
|
"contributions": {
|
|
7
7
|
"html": {
|
|
8
8
|
"types-syntax": "typescript",
|
|
@@ -7419,7 +7419,7 @@
|
|
|
7419
7419
|
},
|
|
7420
7420
|
{
|
|
7421
7421
|
"name": "allow-speech",
|
|
7422
|
-
"description": "
|
|
7422
|
+
"description": "Whether recording is enabled; an object value is passed to recorder-core as-is",
|
|
7423
7423
|
"value": {
|
|
7424
7424
|
"type": "SenderAllowSpeech",
|
|
7425
7425
|
"kind": "expression"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdata/web-vue",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.23.0",
|
|
4
4
|
"description": "SD Design Vue: A Vue.js 3 UI Library",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sd",
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
"overlayscrollbars": "^2.16.0",
|
|
77
77
|
"pdfjs-dist": "^6.1.200",
|
|
78
78
|
"qrcode": "^1.5.4",
|
|
79
|
+
"recorder-core": "^1.3.26070800",
|
|
79
80
|
"resize-observer-polyfill": "^1.5.1",
|
|
80
81
|
"scroll-into-view-if-needed": "^3.1.0",
|
|
81
82
|
"virtua": "^0.49.3",
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { type MaybeRefOrGetter } from 'vue';
|
|
2
|
-
import type { SenderAllowSpeech, SenderSpeechDataEvent, SenderSpeechEndEvent, SenderSpeechErrorEvent, SenderSpeechStartEvent, SenderSpeechTransportCloseEvent, SenderSpeechTransportMessageEvent, SenderSpeechTransportOpenEvent } from './types';
|
|
3
|
-
interface UseSpeechOptions {
|
|
4
|
-
onStart: (event: SenderSpeechStartEvent) => void;
|
|
5
|
-
onData: (event: SenderSpeechDataEvent) => void;
|
|
6
|
-
onEnd: (event: SenderSpeechEndEvent) => void;
|
|
7
|
-
onError: (event: SenderSpeechErrorEvent) => void;
|
|
8
|
-
onTransportOpen: (event: SenderSpeechTransportOpenEvent) => void;
|
|
9
|
-
onTransportMessage: (event: SenderSpeechTransportMessageEvent) => void;
|
|
10
|
-
onTransportClose: (event: SenderSpeechTransportCloseEvent) => void;
|
|
11
|
-
}
|
|
12
|
-
export declare function useSpeech(allowSpeech: MaybeRefOrGetter<SenderAllowSpeech | undefined>, options: UseSpeechOptions): {
|
|
13
|
-
available: import("vue").ComputedRef<boolean>;
|
|
14
|
-
recording: import("vue").ComputedRef<boolean>;
|
|
15
|
-
requesting: import("vue").ComputedRef<boolean>;
|
|
16
|
-
statusText: import("vue").ComputedRef<string>;
|
|
17
|
-
trigger: (breakRecording?: boolean) => Promise<void>;
|
|
18
|
-
};
|
|
19
|
-
export {};
|
package/es/sender/use-speech.js
DELETED
|
@@ -1,365 +0,0 @@
|
|
|
1
|
-
import _objectSpread2 from "../_virtual/_@oxc-project_runtime@0.139.0/helpers/esm/objectSpread2.js";
|
|
2
|
-
import { useI18n } from "../locale/index.js";
|
|
3
|
-
import _asyncToGenerator from "../_virtual/_@oxc-project_runtime@0.139.0/helpers/esm/asyncToGenerator.js";
|
|
4
|
-
import { computed, onBeforeUnmount, onMounted, shallowRef, toValue, watch } from "vue";
|
|
5
|
-
//#region components/sender/use-speech.ts
|
|
6
|
-
var DEFAULT_PROCESSOR_NAME = "sd-sender-audio-processor";
|
|
7
|
-
var DEFAULT_BUFFER_SIZE = 4096;
|
|
8
|
-
var resolveBufferSize = (value) => typeof value === "number" && Number.isFinite(value) ? Math.max(128, Math.floor(value)) : DEFAULT_BUFFER_SIZE;
|
|
9
|
-
var WORKLET_SOURCE = `
|
|
10
|
-
class SdSenderAudioProcessor extends AudioWorkletProcessor {
|
|
11
|
-
constructor(options) {
|
|
12
|
-
super();
|
|
13
|
-
this.bufferSize = Math.max(128, options.processorOptions?.bufferSize || ${DEFAULT_BUFFER_SIZE});
|
|
14
|
-
this.buffer = new Float32Array(this.bufferSize);
|
|
15
|
-
this.offset = 0;
|
|
16
|
-
this.port.onmessage = (event) => {
|
|
17
|
-
if (event.data?.type !== 'flush') return;
|
|
18
|
-
if (this.offset > 0) {
|
|
19
|
-
const chunk = this.buffer.slice(0, this.offset);
|
|
20
|
-
this.port.postMessage(chunk.buffer, [chunk.buffer]);
|
|
21
|
-
this.offset = 0;
|
|
22
|
-
}
|
|
23
|
-
this.port.postMessage({ type: 'flushed' });
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
process(inputs) {
|
|
28
|
-
const channels = inputs[0];
|
|
29
|
-
if (!channels?.length) return true;
|
|
30
|
-
const frames = channels[0].length;
|
|
31
|
-
for (let frame = 0; frame < frames; frame += 1) {
|
|
32
|
-
let sample = 0;
|
|
33
|
-
for (let channel = 0; channel < channels.length; channel += 1) {
|
|
34
|
-
sample += channels[channel][frame] || 0;
|
|
35
|
-
}
|
|
36
|
-
this.buffer[this.offset] = sample / channels.length;
|
|
37
|
-
this.offset += 1;
|
|
38
|
-
if (this.offset === this.bufferSize) {
|
|
39
|
-
const chunk = this.buffer;
|
|
40
|
-
this.port.postMessage(chunk.buffer, [chunk.buffer]);
|
|
41
|
-
this.buffer = new Float32Array(this.bufferSize);
|
|
42
|
-
this.offset = 0;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
registerProcessor('${DEFAULT_PROCESSOR_NAME}', SdSenderAudioProcessor);
|
|
49
|
-
`;
|
|
50
|
-
var isPermissionDeniedError = (error) => error instanceof DOMException ? error.name === "NotAllowedError" || error.name === "SecurityError" : error instanceof Error && (error.name === "NotAllowedError" || error.name === "SecurityError");
|
|
51
|
-
function useSpeech(allowSpeech, options) {
|
|
52
|
-
const { t } = useI18n();
|
|
53
|
-
const config = computed(() => toValue(allowSpeech));
|
|
54
|
-
const controlled = computed(() => typeof config.value === "object" && typeof config.value.recording === "boolean" && typeof config.value.onRecordingChange === "function");
|
|
55
|
-
const supported = shallowRef(false);
|
|
56
|
-
const internalRecording = shallowRef(false);
|
|
57
|
-
const requesting = shallowRef(false);
|
|
58
|
-
const stopping = shallowRef(false);
|
|
59
|
-
const captureError = shallowRef();
|
|
60
|
-
const stream = shallowRef();
|
|
61
|
-
const audioContext = shallowRef();
|
|
62
|
-
const sourceNode = shallowRef();
|
|
63
|
-
const workletNode = shallowRef();
|
|
64
|
-
const socket = shallowRef();
|
|
65
|
-
const startedAt = shallowRef(0);
|
|
66
|
-
const chunks = shallowRef(0);
|
|
67
|
-
const sequence = shallowRef(0);
|
|
68
|
-
let generatedWorkletUrl;
|
|
69
|
-
let resolveWorkletFlush;
|
|
70
|
-
let workletReady = false;
|
|
71
|
-
let disposed = false;
|
|
72
|
-
const recording = computed(() => controlled.value && typeof config.value === "object" ? Boolean(config.value.recording) : internalRecording.value);
|
|
73
|
-
const available = computed(() => Boolean(config.value) && (controlled.value || supported.value) && !(captureError.value && isPermissionDeniedError(captureError.value.error)));
|
|
74
|
-
const statusText = computed(() => {
|
|
75
|
-
var _captureError$value;
|
|
76
|
-
if (requesting.value) return t("sender.speech.requestingPermission");
|
|
77
|
-
if (captureError.value && isPermissionDeniedError(captureError.value.error)) return t("sender.speech.permissionDenied");
|
|
78
|
-
if (!controlled.value && !supported.value) return t("sender.speech.unsupported");
|
|
79
|
-
if (recording.value) return t("sender.speech.stop");
|
|
80
|
-
if (((_captureError$value = captureError.value) === null || _captureError$value === void 0 ? void 0 : _captureError$value.error.name) === "NotFoundError") return t("sender.speech.noMicrophone");
|
|
81
|
-
if (captureError.value) return t("sender.speech.microphoneUnavailable");
|
|
82
|
-
return t("sender.speech.start");
|
|
83
|
-
});
|
|
84
|
-
const resolveAudioContext = () => {
|
|
85
|
-
var _window$AudioContext;
|
|
86
|
-
const AudioContextConstructor = (_window$AudioContext = window.AudioContext) !== null && _window$AudioContext !== void 0 ? _window$AudioContext : window.webkitAudioContext;
|
|
87
|
-
if (!AudioContextConstructor) throw new Error("AudioContext is not supported");
|
|
88
|
-
return new AudioContextConstructor();
|
|
89
|
-
};
|
|
90
|
-
const resolveWorkletUrl = () => {
|
|
91
|
-
var _generatedWorkletUrl;
|
|
92
|
-
if (typeof config.value === "object" && config.value.workletUrl) return config.value.workletUrl;
|
|
93
|
-
(_generatedWorkletUrl = generatedWorkletUrl) !== null && _generatedWorkletUrl !== void 0 || (generatedWorkletUrl = URL.createObjectURL(new Blob([WORKLET_SOURCE], { type: "text/javascript" })));
|
|
94
|
-
return generatedWorkletUrl;
|
|
95
|
-
};
|
|
96
|
-
const connectTransport = (url, protocols) => new Promise((resolve, reject) => {
|
|
97
|
-
const nextSocket = protocols ? new WebSocket(url, protocols) : new WebSocket(url);
|
|
98
|
-
let opened = false;
|
|
99
|
-
socket.value = nextSocket;
|
|
100
|
-
nextSocket.binaryType = "arraybuffer";
|
|
101
|
-
nextSocket.onopen = (event) => {
|
|
102
|
-
opened = true;
|
|
103
|
-
options.onTransportOpen({
|
|
104
|
-
event,
|
|
105
|
-
socket: nextSocket
|
|
106
|
-
});
|
|
107
|
-
resolve(nextSocket);
|
|
108
|
-
};
|
|
109
|
-
nextSocket.onmessage = (event) => options.onTransportMessage({
|
|
110
|
-
event,
|
|
111
|
-
socket: nextSocket
|
|
112
|
-
});
|
|
113
|
-
nextSocket.onclose = (event) => options.onTransportClose({
|
|
114
|
-
event,
|
|
115
|
-
socket: nextSocket
|
|
116
|
-
});
|
|
117
|
-
nextSocket.onerror = () => {
|
|
118
|
-
const errorEvent = {
|
|
119
|
-
error: /* @__PURE__ */ new Error(`Unable to connect to speech URL: ${url}`),
|
|
120
|
-
phase: "transport"
|
|
121
|
-
};
|
|
122
|
-
if (opened) options.onError(errorEvent);
|
|
123
|
-
else reject(errorEvent.error);
|
|
124
|
-
};
|
|
125
|
-
});
|
|
126
|
-
const flushWorklet = () => new Promise((resolve) => {
|
|
127
|
-
var _workletNode$value;
|
|
128
|
-
const port = (_workletNode$value = workletNode.value) === null || _workletNode$value === void 0 ? void 0 : _workletNode$value.port;
|
|
129
|
-
if (!port) {
|
|
130
|
-
resolve();
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
const timeout = window.setTimeout(resolve, 100);
|
|
134
|
-
resolveWorkletFlush = () => {
|
|
135
|
-
window.clearTimeout(timeout);
|
|
136
|
-
resolveWorkletFlush = void 0;
|
|
137
|
-
resolve();
|
|
138
|
-
};
|
|
139
|
-
port.postMessage({ type: "flush" });
|
|
140
|
-
});
|
|
141
|
-
const releaseResources = function() {
|
|
142
|
-
var _ref = _asyncToGenerator(function* () {
|
|
143
|
-
var _workletNode$value2, _sourceNode$value, _stream$value, _audioContext$value;
|
|
144
|
-
(_workletNode$value2 = workletNode.value) === null || _workletNode$value2 === void 0 || _workletNode$value2.disconnect();
|
|
145
|
-
(_sourceNode$value = sourceNode.value) === null || _sourceNode$value === void 0 || _sourceNode$value.disconnect();
|
|
146
|
-
(_stream$value = stream.value) === null || _stream$value === void 0 || _stream$value.getTracks().forEach((track) => track.stop());
|
|
147
|
-
if (((_audioContext$value = audioContext.value) === null || _audioContext$value === void 0 ? void 0 : _audioContext$value.state) === "running") yield audioContext.value.suspend();
|
|
148
|
-
if (socket.value && (socket.value.readyState === WebSocket.OPEN || socket.value.readyState === WebSocket.CONNECTING)) socket.value.close(1e3, "speech-ended");
|
|
149
|
-
workletNode.value = void 0;
|
|
150
|
-
sourceNode.value = void 0;
|
|
151
|
-
stream.value = void 0;
|
|
152
|
-
socket.value = void 0;
|
|
153
|
-
});
|
|
154
|
-
return function releaseResources() {
|
|
155
|
-
return _ref.apply(this, arguments);
|
|
156
|
-
};
|
|
157
|
-
}();
|
|
158
|
-
const closeAudioContext = function() {
|
|
159
|
-
var _ref2 = _asyncToGenerator(function* () {
|
|
160
|
-
if (audioContext.value && audioContext.value.state !== "closed") yield audioContext.value.close();
|
|
161
|
-
audioContext.value = void 0;
|
|
162
|
-
workletReady = false;
|
|
163
|
-
});
|
|
164
|
-
return function closeAudioContext() {
|
|
165
|
-
return _ref2.apply(this, arguments);
|
|
166
|
-
};
|
|
167
|
-
}();
|
|
168
|
-
const stopCapture = function() {
|
|
169
|
-
var _ref3 = _asyncToGenerator(function* (reason = "manual") {
|
|
170
|
-
if (!internalRecording.value || stopping.value) return;
|
|
171
|
-
stopping.value = true;
|
|
172
|
-
try {
|
|
173
|
-
var _socket$value, _config$value$onRecor, _config$value;
|
|
174
|
-
yield flushWorklet();
|
|
175
|
-
const endedAt = performance.now();
|
|
176
|
-
internalRecording.value = false;
|
|
177
|
-
if (((_socket$value = socket.value) === null || _socket$value === void 0 ? void 0 : _socket$value.readyState) === WebSocket.OPEN && typeof config.value === "object" && config.value.sendMetadata !== false) socket.value.send(JSON.stringify({
|
|
178
|
-
type: "end",
|
|
179
|
-
reason
|
|
180
|
-
}));
|
|
181
|
-
yield releaseResources();
|
|
182
|
-
if (typeof config.value === "object") (_config$value$onRecor = (_config$value = config.value).onRecordingChange) === null || _config$value$onRecor === void 0 || _config$value$onRecor.call(_config$value, false);
|
|
183
|
-
options.onEnd({
|
|
184
|
-
source: "capture",
|
|
185
|
-
reason,
|
|
186
|
-
startedAt: startedAt.value,
|
|
187
|
-
endedAt,
|
|
188
|
-
duration: endedAt - startedAt.value,
|
|
189
|
-
chunks: chunks.value
|
|
190
|
-
});
|
|
191
|
-
} finally {
|
|
192
|
-
stopping.value = false;
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
return function stopCapture() {
|
|
196
|
-
return _ref3.apply(this, arguments);
|
|
197
|
-
};
|
|
198
|
-
}();
|
|
199
|
-
const startCapture = function() {
|
|
200
|
-
var _ref4 = _asyncToGenerator(function* () {
|
|
201
|
-
if (requesting.value || stopping.value || internalRecording.value || !available.value) return;
|
|
202
|
-
requesting.value = true;
|
|
203
|
-
captureError.value = void 0;
|
|
204
|
-
let phase = "permission";
|
|
205
|
-
try {
|
|
206
|
-
var _speechConfig$audioCo, _speechConfig$process, _socket$value3, _speechConfig$onRecor;
|
|
207
|
-
const speechConfig = typeof config.value === "object" ? config.value : void 0;
|
|
208
|
-
const bufferSize = resolveBufferSize(speechConfig === null || speechConfig === void 0 ? void 0 : speechConfig.bufferSize);
|
|
209
|
-
stream.value = yield navigator.mediaDevices.getUserMedia({
|
|
210
|
-
audio: (_speechConfig$audioCo = speechConfig === null || speechConfig === void 0 ? void 0 : speechConfig.audioConstraints) !== null && _speechConfig$audioCo !== void 0 ? _speechConfig$audioCo : true,
|
|
211
|
-
video: false
|
|
212
|
-
});
|
|
213
|
-
if (disposed) {
|
|
214
|
-
yield releaseResources();
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
if (speechConfig === null || speechConfig === void 0 ? void 0 : speechConfig.url) {
|
|
218
|
-
phase = "transport";
|
|
219
|
-
yield connectTransport(speechConfig.url, speechConfig.protocols);
|
|
220
|
-
if (disposed) {
|
|
221
|
-
yield releaseResources();
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
phase = "audioContext";
|
|
226
|
-
let nextAudioContext = audioContext.value;
|
|
227
|
-
if (!nextAudioContext) {
|
|
228
|
-
nextAudioContext = resolveAudioContext();
|
|
229
|
-
audioContext.value = nextAudioContext;
|
|
230
|
-
workletReady = false;
|
|
231
|
-
}
|
|
232
|
-
if (nextAudioContext.state === "suspended") yield nextAudioContext.resume();
|
|
233
|
-
phase = "audioWorklet";
|
|
234
|
-
if (!workletReady) {
|
|
235
|
-
yield nextAudioContext.audioWorklet.addModule(resolveWorkletUrl());
|
|
236
|
-
workletReady = true;
|
|
237
|
-
}
|
|
238
|
-
if (disposed) {
|
|
239
|
-
yield releaseResources();
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
const processorName = (_speechConfig$process = speechConfig === null || speechConfig === void 0 ? void 0 : speechConfig.processorName) !== null && _speechConfig$process !== void 0 ? _speechConfig$process : DEFAULT_PROCESSOR_NAME;
|
|
243
|
-
const nextWorkletNode = new AudioWorkletNode(nextAudioContext, processorName, {
|
|
244
|
-
numberOfInputs: 1,
|
|
245
|
-
numberOfOutputs: 0,
|
|
246
|
-
channelCount: 1,
|
|
247
|
-
processorOptions: _objectSpread2(_objectSpread2({}, speechConfig === null || speechConfig === void 0 ? void 0 : speechConfig.processorOptions), {}, { bufferSize })
|
|
248
|
-
});
|
|
249
|
-
workletNode.value = nextWorkletNode;
|
|
250
|
-
sourceNode.value = nextAudioContext.createMediaStreamSource(stream.value);
|
|
251
|
-
nextWorkletNode.port.onmessage = (event) => {
|
|
252
|
-
var _socket$value2;
|
|
253
|
-
if (!(event.data instanceof ArrayBuffer) && "type" in event.data) {
|
|
254
|
-
if (event.data.type === "flushed") resolveWorkletFlush === null || resolveWorkletFlush === void 0 || resolveWorkletFlush();
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
if (!internalRecording.value) return;
|
|
258
|
-
const buffer = event.data;
|
|
259
|
-
const dataEvent = {
|
|
260
|
-
buffer,
|
|
261
|
-
sampleRate: nextAudioContext.sampleRate,
|
|
262
|
-
sequence: sequence.value,
|
|
263
|
-
timestamp: performance.now()
|
|
264
|
-
};
|
|
265
|
-
sequence.value += 1;
|
|
266
|
-
chunks.value += 1;
|
|
267
|
-
options.onData(dataEvent);
|
|
268
|
-
if (((_socket$value2 = socket.value) === null || _socket$value2 === void 0 ? void 0 : _socket$value2.readyState) === WebSocket.OPEN) socket.value.send(buffer);
|
|
269
|
-
};
|
|
270
|
-
sourceNode.value.connect(nextWorkletNode);
|
|
271
|
-
startedAt.value = performance.now();
|
|
272
|
-
chunks.value = 0;
|
|
273
|
-
sequence.value = 0;
|
|
274
|
-
internalRecording.value = true;
|
|
275
|
-
if (((_socket$value3 = socket.value) === null || _socket$value3 === void 0 ? void 0 : _socket$value3.readyState) === WebSocket.OPEN && (speechConfig === null || speechConfig === void 0 ? void 0 : speechConfig.sendMetadata) !== false) socket.value.send(JSON.stringify({
|
|
276
|
-
type: "start",
|
|
277
|
-
format: "pcm-f32",
|
|
278
|
-
channels: 1,
|
|
279
|
-
sampleRate: nextAudioContext.sampleRate,
|
|
280
|
-
bufferSize
|
|
281
|
-
}));
|
|
282
|
-
speechConfig === null || speechConfig === void 0 || (_speechConfig$onRecor = speechConfig.onRecordingChange) === null || _speechConfig$onRecor === void 0 || _speechConfig$onRecor.call(speechConfig, true);
|
|
283
|
-
options.onStart({
|
|
284
|
-
source: "capture",
|
|
285
|
-
startedAt: startedAt.value,
|
|
286
|
-
stream: stream.value,
|
|
287
|
-
audioContext: nextAudioContext,
|
|
288
|
-
sampleRate: nextAudioContext.sampleRate
|
|
289
|
-
});
|
|
290
|
-
} catch (error) {
|
|
291
|
-
const errorEvent = {
|
|
292
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
293
|
-
phase
|
|
294
|
-
};
|
|
295
|
-
captureError.value = errorEvent;
|
|
296
|
-
options.onError(errorEvent);
|
|
297
|
-
yield releaseResources();
|
|
298
|
-
yield closeAudioContext();
|
|
299
|
-
} finally {
|
|
300
|
-
requesting.value = false;
|
|
301
|
-
}
|
|
302
|
-
});
|
|
303
|
-
return function startCapture() {
|
|
304
|
-
return _ref4.apply(this, arguments);
|
|
305
|
-
};
|
|
306
|
-
}();
|
|
307
|
-
const trigger = function() {
|
|
308
|
-
var _ref5 = _asyncToGenerator(function* (breakRecording = false) {
|
|
309
|
-
if (requesting.value || stopping.value) return;
|
|
310
|
-
if (breakRecording && !recording.value) return;
|
|
311
|
-
if (controlled.value && typeof config.value === "object") {
|
|
312
|
-
var _config$value$onRecor2, _config$value2;
|
|
313
|
-
const nextRecording = !recording.value;
|
|
314
|
-
(_config$value$onRecor2 = (_config$value2 = config.value).onRecordingChange) === null || _config$value$onRecor2 === void 0 || _config$value$onRecor2.call(_config$value2, nextRecording);
|
|
315
|
-
const now = performance.now();
|
|
316
|
-
if (nextRecording) {
|
|
317
|
-
startedAt.value = now;
|
|
318
|
-
options.onStart({
|
|
319
|
-
source: "controlled",
|
|
320
|
-
startedAt: now
|
|
321
|
-
});
|
|
322
|
-
} else options.onEnd({
|
|
323
|
-
source: "controlled",
|
|
324
|
-
reason: "controlled",
|
|
325
|
-
startedAt: startedAt.value,
|
|
326
|
-
endedAt: now,
|
|
327
|
-
duration: now - startedAt.value,
|
|
328
|
-
chunks: 0
|
|
329
|
-
});
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
if (internalRecording.value) yield stopCapture();
|
|
333
|
-
else yield startCapture();
|
|
334
|
-
});
|
|
335
|
-
return function trigger() {
|
|
336
|
-
return _ref5.apply(this, arguments);
|
|
337
|
-
};
|
|
338
|
-
}();
|
|
339
|
-
onMounted(() => {
|
|
340
|
-
var _navigator$mediaDevic;
|
|
341
|
-
supported.value = Boolean(typeof ((_navigator$mediaDevic = navigator.mediaDevices) === null || _navigator$mediaDevic === void 0 ? void 0 : _navigator$mediaDevic.getUserMedia) === "function" && typeof AudioContext !== "undefined" && typeof AudioWorkletNode !== "undefined");
|
|
342
|
-
});
|
|
343
|
-
watch(() => Boolean(config.value), (enabled) => {
|
|
344
|
-
if (!enabled) stopCapture();
|
|
345
|
-
});
|
|
346
|
-
watch(controlled, (nextControlled) => {
|
|
347
|
-
if (nextControlled) stopCapture();
|
|
348
|
-
});
|
|
349
|
-
onBeforeUnmount(_asyncToGenerator(function* () {
|
|
350
|
-
disposed = true;
|
|
351
|
-
if (internalRecording.value) yield stopCapture("unmount");
|
|
352
|
-
else yield releaseResources();
|
|
353
|
-
yield closeAudioContext();
|
|
354
|
-
if (generatedWorkletUrl) URL.revokeObjectURL(generatedWorkletUrl);
|
|
355
|
-
}));
|
|
356
|
-
return {
|
|
357
|
-
available,
|
|
358
|
-
recording,
|
|
359
|
-
requesting: computed(() => requesting.value || stopping.value),
|
|
360
|
-
statusText,
|
|
361
|
-
trigger
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
//#endregion
|
|
365
|
-
export { useSpeech };
|